# 语义 SDK（Semantic）

**Semantic 是 AIDC 的数据平台**（照 Palantir Foundry：Data Connection + Ontology + Automate + Security）。企业数据的一切都在这里：数据从哪来、存成什么对象、谁能看、谁改了什么、变了之后自动做什么、花了多少钱。ERP / MES / OA 这些数据源永远只读；应用和智能体读对象集、用 **Action** 改（参数校验、谁能做、留痕）。

```
ERP / MES / OA ──(发布端，只读)──▶ 数据流 ──(Object Type 定义里的 datasources)──▶ Semantic（对象 · 链接 · Action）──▶ 应用 · 智能体 · 自动化 · SQL
```

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

## Semantic 是数据平台

| 要做什么 | 用什么 | Palantir 里叫 |
| --- | --- | --- |
| 读对象、沿链接走、聚合 | `semantic.ontology().objects(t).where(…).fetchPage() / aggregate() / pivotTo()` | Ontology SDK（OSDK） |
| 实时 | `objectSet.subscribe({ onChange, onOutOfDate })` | Object set subscription |
| 改数据 | `client.action(a).applyAction(…)`（只有这一条写路径） | Actions |
| 数据从哪来 | `semantic.streams` / `semantic.stream()` + Object Type 定义里的 `datasources` | Data Connection · Streams · backing datasource |
| SQL | `semantic.sql()` / `semantic.database()` | Ontology SQL |
| 谁能看 | `semantic.filesystem`（Private / Group / Public / Open to Internet）· `semantic.admin`（当前账号、成员） | Filesystem ResourceRoles · Admin |
| 变了做什么 | `semantic.automate`（数据变化 / 定时 → Action、模型分析、通知） | Automate |
| 留痕与用量 | Action Log、`editsHistory`、`semantic.observability`、`semantic.usage`，平台对象类型 `aidcModelUsage` / `aidcAppEvent` / `aidcAutomationRun` | Action log · Observability · Resource Management |
| 建本体 | 分支与提案（智能体作者，人在网页上审核合并） | Global Branching · Ontology proposals |

原来的连接、数据、权限、工作流、日志、账单六个 SDK 就是上面这些部分，1.20.0 起都在 `semantic` 里。旧名照样能用、和新家是同一份实现：`connect.stream === semantic.stream`、`auth.me === semantic.admin.getCurrentUser`、`workflow.run === semantic.automate.run`、`log.feedback === semantic.observability.feedback`、`billing.summary === semantic.usage.summary`，`connect.agent` 搬到了 `model.agent`；应用清单 `sdk` 里写旧名也照收（归一成 `semantic`）。

## 数据从哪来：数据流与数据源

数据在源头一变，源头旁边的**发布端**（`aidc semantic streams pipe` + 适配器，只读）就把变化推上来，平台按序落账（[数据流](connect.md)）。**数据流接到哪个 Object Type、哪一列对哪个属性，写在 Object Type 的定义里**（照 Palantir：Ontology Manager 的 Datasources 页选 backing datasource）——改数据源就是改本体：

```json
{ "kind": "objectType", "apiName": "production.order_line", "title": "工单线体进度",
  "schema": {
    "columns": [ { "name": "order_no", "type": "string", "primaryKey": true }, { "name": "line_code", "type": "string", "primaryKey": true }, … ],
    "datasources": [
      { "type": "stream", "stream": "production-progress", "propertyMapping": { "order_no": "aufnr", "line_code": "line", "ok": "gmnga" }, "mode": "mirror" }
    ] } }
```

- **规则**（定义时就校验）：流要在本组织存在；映射到的列要在流里；主键属性必须映射；只在语义层维护的属性（`writeback`，Palantir 的 editOnly）和派生属性不映射；同一条流在一个类型里只出现一次。还是数据源的流删不掉（先从定义里去掉）。
- **mode**：`mirror` = 流里没了的行在对象上标记「源头已消失」；`upsert` = 只增改（多行汇成一个对象、保留历史）。
- **谁能改**：开发者直接定义（`aidc semantic define`，或下面的快捷命令）；智能体在分支上改、开提案，人审核合并（提案的 merge checks 多一项 `datasources`）。
- **定义落了之后**：平台按定义建好同步，用流的当前全量同步一次；之后流每落账一批就增量同步。定义里去掉数据源（或归档类型），同步就停，已有对象保留。
- **没写 = 沿用**：重新定义时没写 `datasources`，沿用现有的数据源（返回一条警告）；要去掉全部数据源，明确写 `"datasources": []`。

```bash
aidc semantic streams create production-progress --title "生产进度" --key key --field key --field aufnr --field line --field gmnga:number
aidc semantic streams key production-progress --label "SAP 旁边的发布端"      # aidc-pk-…，只显示一次
aidc semantic datasource set production.order_line --stream production-progress --map order_no=aufnr --map line_code=line --map ok=gmnga
aidc semantic datasource list                                                    # 每个类型的数据源与同步进度
aidc semantic datasource sync                                                    # 平台数据源立即同步（= /semantic 的「立即同步」）
```

```js
await semantic.streams.create({ name: "orders", title: "订单", key: "order_no", fields: [{ name: "order_no" }, { name: "qty", type: "number" }] });
const pub = semantic.stream("orders").publisher();   // 发布端：只发与上次相比的差
await pub.rows(await readErpOrders());
```

## 实时：订阅对象集

```js
const client = semantic.ontology();
const issues = client.objects("production.order_line").where({ status: "异常" });
const sub = issues.subscribe({
  onChange({ object, state }) { state === "REMOVED" ? drop(object.__primaryKey) : upsert(object); },
  onOutOfDate() { reload(); },            // 整个对象集要重读：刚订阅时来一次、断得太久、依赖的类型变了
  onError({ subscriptionClosed, error }) { if (subscriptionClosed) showOffline(error); },
}, { properties: ["line_code", "gap", "status"] });
// sub.unsubscribe()
```

写法照 Palantir OSDK 的 `objectSet.subscribe`：对象进来或变了 → `ADDED_OR_UPDATED`（带属性），离开对象集（被过滤掉、删了、源头消失）→ `REMOVED`（只带主键）。同步、Action、别人的修改都会推过来；断线自动带着序号续传，不丢不重；页面隐藏超过 1 分钟断开、切回续上；应用里的连接时长计入应用的计算分钟。CLI：`aidc semantic subscribe <类型> [--where '{…}']`。官方走 WebSocket，AIDC 在同一路径（`/api/v1/ontologySubscriptions/ontologies/{ns}/streamSubscriptions`）走 SSE，消息一样。

## 访问与账号

```js
const me = await semantic.admin.getCurrentUser();      // 我是谁、什么角色、能读哪些类型、能执行哪些 Action
if (!me.signedIn) semantic.admin.signIn();
await semantic.filesystem.setResourceAccess(rid, "public");                        // Private / Group / Public / Open to Internet
await semantic.filesystem.shareResource(rid, { group: "cell-acme:dept:ops", role: "viewer" });
await semantic.filesystem.shareApplication({ audience: "company" });                // 应用的分享
```

开放程度四档、Owner / Editor / Viewer 三种角色、只有 Owner 改分享；资源是 Ontology、Object Type、数字员工、文件。详见[访问与账号](auth.md)。

## 数据安全：Markings 与安全策略

财务、薪资、采购价、技术配方这类数据也进 Semantic，照 Palantir 用两种控制管住。上面的开放程度和角色决定「能不能打开这个资源」，下面这些再往里管到每一行、每一列：

| 控制 | 挂在哪 | 怎么判 |
| --- | --- | --- |
| **Markings**（强制控制） | Ontology、Object Type、数据流、TableImport；或者写在行上（`marking` 类型的属性） | 挂着的 Marking 全都满足才看得见（同一个 DISJUNCTIVE 类别里满足任一即可）。不看角色，Owner、开发者也一样；只限制、不授予；沿层级（Object Type ← Ontology）和数据依赖（数据流 → 用它的类型的对象）传播 |
| **对象安全策略**（行） | Object Type 定义里的 `objectSecurityPolicy` | 按对象的属性和读的人（组、账号、用户属性、行上的 Marking）判断每个对象看不看得见 |
| **属性安全策略**（列） | Object Type 定义里的 `propertySecurityGroups` | 一组属性要另外满足的条件或 Marking；不通过的人读到 null，也不能拿它筛选、排序、求和 |

先建 Marking，再挂到资源上或写进定义：

```bash
aidc semantic admin marking-categories create --name 数据分级                  # 类别：本组织开发者建
aidc semantic admin markings create --name 薪资 --category <类别 id> --member cell-demo:dept:hr
aidc semantic admin markings grant <薪资 id> USE <HR 负责人账号 id>             # USE = 可以把它挂到资源上
aidc semantic filesystem mark <配方类型的 rid> <技术配方 id>                    # 整个类型挂 Marking（要 USE、是类型的 Owner）
aidc semantic filesystem markings <rid>                                         # 直接挂的 + 继承来的
```

Marking 和类别建了不能删。角色：ADMINISTER 管成员与角色，USE 挂上，DECLASSIFY 去掉或停止继承；角色和成员互相独立（管 Marking 的人不一定看得见它保护的数据）。

行和列写在定义里（Demo Company 的员工薪酬；Marking 一律用 id 引用，`aidc semantic admin markings list` 查）：

```json
{
  "kind": "objectType", "apiName": "employeeCompensation", "title": "员工薪酬",
  "schema": {
    "columns": [
      { "name": "employeeId", "type": "string", "primaryKey": true },
      { "name": "accountId", "type": "string" },
      { "name": "baseSalary", "type": "decimal" },
      { "name": "rowMarkings", "type": "array", "arraySubType": "marking", "notNull": true }
    ],
    "objectSecurityPolicy": {
      "name": "compensation-rows",
      "granularPolicy": { "type": "and", "conditions": [
        { "type": "markingProperty", "property": "rowMarkings" },
        { "type": "or", "conditions": [
          { "type": "group", "name": "cell-demo:dept:hr" },
          { "type": "comparison", "comparison": { "operator": "EQUAL",
            "left": { "type": "userProperty", "userProperty": { "type": "userId", "userId": {} } },
            "right": { "type": "property", "property": "accountId" } } }
        ] }
      ] }
    },
    "propertySecurityGroups": [{ "name": "pay", "properties": ["baseSalary"], "appliedMarkings": { "<薪资 id>": "MANDATORY" } }],
    "dataSecurity": { "markingConstraint": { "markingIds": ["<高管薪酬 id>"] } }
  }
}
```

意思是：HR 部门或本人看得见这一行，行上带的 Marking（比如高管的行带「高管薪酬」）也要满足；金额另外要「薪资」。其他三类数据的常见配法：

| 数据 | 配法 |
| --- | --- |
| 财务 | Marking「财务」挂在 ERP 财务数据流上，用这条流的类型自动带上 |
| 采购价 | 采购订单行对采购员可见，`unitPrice` 放进属性安全策略，要「采购价」 |
| 技术配方 | 配方类型整个挂「技术配方」，成员只给研发负责人 |

规则（照 Palantir）：

- **读**：所有读路径都按读的人判定——Ontology API、对象集订阅、聚合、编辑历史、旧数据 API、/semantic 页面、自动化。Agent Key 是智能体身份，不是任何 Marking 的成员；Ontology SQL（Semantic 数据库）不含受保护的类型。
- **Action**：可以新建自己看不见的对象；改一个属性要看得见它现在的值；删要看得见整个对象；链接两端都要看得见；开了 Action Log 的，执行人要满足被改类型的全部 Marking。
- **自动化**：条件和效果按拥有者（最后改条件或效果的人）的权限算；邮件通知按每个收件人的权限过滤。
- **只在读的时候判定**：函数的结果、Action 写进去的值、导出的文件不带原数据的安全。
- **改安全策略**：它是本体定义的一部分。改的人要满足改前改后涉及的全部 Marking，新加 Marking 要 USE，去掉或停止继承要 DECLASSIFY + USE。智能体在分支上改、开提案，由满足条件的人批准合并；`--dry-run` 自测时权限不够照样给出结果，「要谁来批」写在 warnings 里。分支预览（`--branch`）按 main 与分支里更严的那份判定。
- **上限**：每个策略最多 10 个比较；权重是常量 1、集合 1,000、Marking 条件 3,000，总和小于 10,000（属性安全策略连同对象安全策略一起算）；不能对组或 Marking 成员取反；策略用到的属性为空的行谁都看不见；`marking` 属性必须 `notNull`，值要在 `dataSecurity.markingConstraint` 列出的 Marking 里。

试策略（照 Ontology Manager 的 Test security policies；只给开发者，结果不带属性值）：

```bash
aidc semantic security test employeeCompensation --user <账号 id> --pk e1,e2
aidc semantic security test employeeCompensation --user <账号 id> --object '{"employeeId":"x","rowMarkings":[]}' --policy 新策略.json
```

SDK：`semantic.admin.createMarking / addMarkingMembers / addMarkingRoleAssignments…`、`semantic.filesystem.addMarkings / removeMarkings / resourceMarkings`、`client.testSecurity(类型, {…})`；读对象时带 `$loadPropertySecurityMetadata: true`（照 OSDK），受属性安全策略保护的值会带上它的安全标记。

## 自动化（Automate）

数据一变（或到了时间）就做事：写在工作流应用的清单 `workflow` 里，照 Palantir Automate——**条件**（`trigger.change` = 对象新建 / 修改，`when` 过滤；`trigger.schedule` = 时间；手动）加**效果**（Action、模型分析、通知）。

```js
const { run } = await semantic.automate.run({ rfq_no: "RFQ-001" }, { slug: "quote-workflow", mode: "preview" });
semantic.automate.watch(run.id, { onRun: draw, onDone: finish }, { slug: "quote-workflow" });
```

触发读的是 Semantic 的变化账（和对象集订阅同一条），不轮询、不写 cron。详见[自动化](workflow.md)。

## 留痕与用量

- **改数据的留痕**：开了 `actionLog` 的 Action 每次成功提交写一个 Action Log 对象；`client.editsHistory(类型, { primaryKey })` 看一个对象的每次修改。
- **应用日志**：`semantic.observability.track / feedback / error` 记使用、反馈、错误；`summary / events` 看（[应用日志](log.md)）。
- **用量与账单**：`semantic.usage.summary / records / check / prices / estimate`（[用量与账单](billing.md)）。
- **AIDC 平台对象类型**：平台替你的组织记下的应用、模型用量、应用日志、自动化运行，可以作为只读的 Object Type 进你的 Ontology——`aidcApplication`、`aidcModelUsage`（按天 × 应用 / Key × 模型）、`aidcAppEvent`、`aidcAutomationRun`。本组织开发者打开 /semantic 时，智能体（数据库小艾）会开一个提案，你审核合并后才出现（不需要就关掉提案，同一批定义不会再提）；之后按需同步（合并后、点「立即同步」、超过 1 小时有人看时）。用量、日志、自动化运行只给本组织开发者（可以在 Share 里开给别人），应用列表和 Nexus 里看到的一样。有了它们，看账单、查反馈就是查对象集，超支提醒就是一个自动化：

```js
const byDay = await client.objects("aidcModelUsage").aggregate({ $select: { "costUsd:sum": "desc" }, $groupBy: { day: "exact" } });
const openFeedback = await client.objects("aidcAppEvent").where({ kind: "feedback", status: "open" }).fetchPage();
```

## Ontology（照 Palantir）

语义层的建模语言、名字和 API 形状全部照 Palantir Ontology（设计：`docs/semantic-ontology.md`）。上面的写法（`describe`、`objects`、`action().apply`）继续可用；新写法是 `semantic.ontology()`，与 Palantir 的 OSDK 同形。示例取自 Demo Company 的示例本体（[打开示例](https://www.ai-dc.ai/nexus/s/cQjivrjhrGGGbOMurJ-4HR3OSiAW0wln)，要先登录 AIDC 账号；命名空间 `cell-demo`，全部是虚构数据；定义与数据在 `developer/apps/ontology-demo-data/`，应用在 `developer/apps/ontology-demo/`）。

### 语义部分（名词）

| Palantir 构件 | 是什么 | Demo 里的例子 |
| --- | --- | --- |
| **Object Type** | 一类对象：主键、title、属性 | `customer` 客户公司、`agent` 智能体、`application` 应用、`modelUsage` 模型用量、`cloudCost` 云费用 |
| **Property** | 属性；base type 照官方（string、integer、long、double、decimal、boolean、date、timestamp、array、struct、geopoint、vector、timeseries…） | `customer.location`（geopoint）、`customer.contact`（struct）、`modelUsage.dailyTokens`（timeseries） |
| **Derived Property** | 沿链接读时计算（最多 3 跳；count、sum、avg、min、max、collectList、collectSet） | `customer.agentCount` = 智能体个数；`customer.modelCostUsd` = 模型费用合计 |
| **Link Type** | 两个 Object Type 的关系，两端各有名字；1:N 用外键，N:M 另存每一条链接 | `customerAgents`：`customer.agents` ↔ `agent.customer`；`applicationAgents`（N:M） |
| **Interface** | 多个 Object Type 共有的形状，可以继承 | `Billable`（继承 `Monthly`）：`modelUsage` 和 `cloudCost` 都实现它，可以一起按月汇总成本 |
| **Shared Property** | 跨类型复用的属性定义 | `costUsd`、`month` |
| **Value Type** | 带约束的类型（enum、range、length、regex…），写入时校验 | `cellId`（`cell-` 开头）、`yearMonth`、`customerStage`（试点 / 付费 / 暂停） |

### 动力部分（动词、函数）

| Palantir 构件 | 是什么 | Demo 里的例子 |
| --- | --- | --- |
| **Action Type** | 受控的写操作：参数 + 规则（createObject、modifyObject、createOrModifyObject、deleteObject、createLink、deleteLink）+ 提交条件（submission criteria） | `adjust-seats`：座位数不能少于成员数；`assign-agent`：给应用接入一个智能体（建一条 N:M 链接） |
| **Action Log** | 开了 `actionLog` 的 Action，每次成功执行都写一个 `log.<action>` 对象 | `log.adjust-seats`：谁在什么时候把哪家的座位数改成多少 |
| **Object Set** | 对象的集合：过滤、并 / 交 / 差、沿链接走（searchAround）、按接口取、派生属性 | 付费客户的全部智能体 |
| **Function** | 代码写的逻辑，在隔离运行时里执行 | 要补（还没有隔离运行时）；`queryTypes` 现在返回空列表 |

### 定义

定义还是 JSON，`kind` 用 Palantir 的名字（`objectType`、`linkType`、`actionType`、`interfaceType`、`sharedPropertyType`、`valueType`；旧的 `object`、`link`、`action`、`enum` 照旧可用）。

```json
{
  "kind": "linkType",
  "apiName": "customerAgents",
  "title": "公司的智能体",
  "schema": {
    "from": "customer", "to": "agent", "cardinality": "ONE_TO_MANY",
    "apiNameAtoB": "agents", "apiNameBtoA": "customer",
    "foreignKey": { "side": "to", "property": "customerId" }
  }
}
```

```json
{
  "kind": "actionType",
  "apiName": "adjust-seats",
  "title": "调整座位数",
  "schema": {
    "parameters": [
      { "name": "customer", "type": "object", "objectType": "customer", "required": true },
      { "name": "seats", "type": "integer", "required": true, "min": 1, "max": 10000 }
    ],
    "rules": [{ "type": "modifyObject", "objectType": "customer", "object": "$customer", "values": { "seats": "$seats" } }],
    "submissionCriteria": [{
      "condition": { "type": "comparison", "left": { "param": "seats" }, "operator": "gte", "right": { "param": "customer", "property": "members" } },
      "failureMessage": "座位数不能少于成员数"
    }],
    "actionLog": true
  }
}
```

名字照官方大小写：Object Type、Property、Link Type 两端、Value Type、Shared Property 用 camelCase，Action Type 用 kebab-case，Interface 用 UpperCamelCase。

从零做一个会执行的 Action（定义、只校验、执行、提交条件、命令行 / SDK / REST、做成应用、交给自动化）：Academy 课程[《动手：做一个会执行的 Action》](https://www.ai-dc.ai/academy/semantic-action)，配套样板见[样板应用](samples.md)的「座位申请」。新的 Object Type 只能有一个主键属性（旧的复合主键类型照旧可用，定义时给警告）。

定义里写了不认识的字段（多半是拼错了，比如把 `submissionCriteria` 写成 `submissionCriterias`）会直接被拒，错误信息给出路径——以前这类字段会被悄悄丢掉，Action 就少了提交条件。

### Action 改源系统：writeback webhook

照 Palantir（Action 的 webhook）：Action 可以配一个 **writeback webhook**——校验通过之后、规则之前调用源系统；源系统拒绝（或超时）就整个 Action 不生效，语义层一条都不改，原因原样返回给执行人（`webhook_failed`）。webhook 的输出在规则里用 `$writeback.<输出名>`。

```json
{
  "kind": "actionType",
  "apiName": "stop-ec2-instance",
  "title": "关机",
  "schema": {
    "parameters": [{ "name": "instance", "type": "object", "objectType": "ec2Instance", "required": true }, { "name": "reason", "type": "string" }],
    "submissionCriteria": [{ "condition": { "type": "comparison", "left": { "param": "instance", "property": "state" }, "operator": "is", "right": { "literal": "running" } }, "failureMessage": "只有运行中的实例能关机" }],
    "webhooks": { "writeback": { "webhook": "aws/ec2-stop-instances", "inputs": { "region": "$instance.region", "instanceId": "$instance.instanceId", "expectedLaunchTime": "$instance.launchTime" } } },
    "rules": [{ "type": "modifyObject", "objectType": "ec2Instance", "object": "$instance", "values": { "lastAction": "stop", "lastActionAt": "$now", "lastActionResult": "$writeback.currentState" } }],
    "roles": ["developer"],
    "actionLog": true
  }
}
```

- webhook 写成 `<数据源>/<名字>`，挂在 Data Connection 的数据源（Source）上，凭证在数据源里、不经过调用方。定义时校验：webhook 存在、必填输入都给了、没有多余的输入、`$writeback.<输出>` 是它的输出、这个组织能用那个数据源。
- 规则只写只在语义层维护的属性（`writeback: true`）；状态这类从数据源来的属性等数据源同步回来——规则改它会一直盖住之后同步来的值（定义时会提醒）。
- 只校验（`$validateOnly`）与预演不调用源系统；有 writeback 的 Action 一次只能提交一个请求（不收 applyBatch）。
- 现在的数据源只有 AIDC 平台提供的 `aws`（AIDC 自己的 AWS 账号：`aws/ec2-stop-instances`、`aws/ec2-start-instances`、`aws/ec2-set-tag`），客户自己的数据源在做。样板：`developer/apps/aws-servers`（EC2 实例、EBS 卷、账号费用的本体，开关机与自动关机）。

### 读与写（OSDK 写法）

```js
const client = semantic.ontology();                        // 应用里自动取应用所在公司；CLI / 智能体用 Key 所属公司

const paying = client.objects("customer").where({ stage: "付费", seats: { $gt: 30 } });
const page = await paying.fetchPage({ $orderBy: { seats: "desc" }, $pageSize: 20 });
// page.data[i]：{ __primaryKey, __apiName, __title, name, seats, location: { type: "Point", coordinates: [...] }, … }

const agents = await paying.pivotTo("agents").fetchPage();  // 沿链接走（searchAround）
const one = await client.objects("customer").fetchOne("cell-demo-a");

// 聚合（结果形状同 OSDK）
const byMonth = await client.interface("Billable").aggregate({ $select: { "costUsd:sum": "desc" }, $groupBy: { month: "exact" } });

// 派生属性
const withCost = client.objects("customer").withProperties({ apps: (b) => b.pivotTo("applications").aggregate("$count") });

// Action：先只校验，再执行
await client.action("adjust-seats").applyAction({ customer: "cell-demo-a", seats: 60 }, { $validateOnly: true });
const r = await client.action("adjust-seats").applyAction({ customer: "cell-demo-a", seats: 60 }, { $returnEdits: true });
// r.validation.result = VALID / INVALID（参数逐项、提交条件逐条）；r.edits：新建 / 修改 / 删除了哪些对象和链接
```

where 写法：`$eq $ne $gt $gte $lt $lte $isNull $in $contains $startsWith $containsAnyTerm $containsAllTerms $containsAllTermsInOrder $matchesRegex $within $and $or $not`；聚合：`$count`、`属性:sum|avg|min|max|exactDistinct|approximateDistinct`，分组 `exact`、`$fixedWidth`、`$ranges`、`$duration`。

### 本体由智能体构建（唯一和 Palantir 不同的地方）

智能体不能直接改 main 上的本体。它在分支上改，开提案，由人在网页上审核、合并：

1. `aidc semantic branch create add-department`：开分支（基线 = 当前语义版本）。
2. `aidc semantic branch modify add-department ontology/`：把定义放到分支上（一整份清单，`--dry-run` 试跑，`--expected-version` 防止并发覆盖）。
3. `aidc semantic objects agent --branch add-department`：在分支上预览（读接口都收 `?branch=`）；`branch validate` 看 merge checks，`branch conflicts` 看和 main 冲突的地方，`branch rebase` 跟上 main。
4. `aidc semantic branch propose add-department --title … --trigger "为什么要改"`：开提案。平台自动附上按资源的改动、校验结果、影响面（30 天的写入与活跃用户、依赖的应用）、破坏性改动。
5. 人在 `/developer/<公司>/ontology/proposals/<id>` 逐项批准（破坏性改动要输入资源名确认），全部批准、merge checks 通过后合并：落到 main，发一个语义版本。

作者由凭证决定：Agent Key 一律是智能体；开发者 Key 在环境变量 `AIDC_AGENT_ID` 存在时，CLI 自动带 `x-aidc-author: agent:<id>`，作者也记为智能体。智能体作者直接定义 main 会得到 409 `branch_required`。批准、拒绝、合并只接受人的网页登录，任何 Key 都不行。

### 和 ERP、OA 的关系

ERP、MES、OA 缺省只读：发布端把数据推进数据流，按 Object Type 定义里的 `datasources` 同步进来。人和智能体的改动只走 Action，落在语义层（对象的 edits），数据源下一次同步不会冲掉它们。要真的改源系统（关一台服务器、回写一张单据），给 Action 配 writeback webhook（见上一节）：源系统先接受，语义层才记一笔。

## Semantic 平台（www.ai-dc.ai/semantic）

登录 [ai-dc.ai/semantic/app](/semantic/app) 就进到自己组织的 Semantic；不属于任何组织的账号落到 Public。界面照 Palantir 的应用分工，只调这一页写的同一套 API：

| 页面 | 地址 | 做什么 |
| --- | --- | --- |
| 概览 | `/semantic/<cell>` | Object Type 一览（对象数、属性、链接、Public 标记）、最近的变化、待审核的提案、数据库状态 |
| Ontology 图 | `/semantic/<cell>/graph` | 画布：Object Type 是节点、Link Type 是边（多对多虚线）；拖动、缩放、点开看属性与关系 |
| Object Types | `/semantic/<cell>/object-types[/<apiName>]` | Ontology Manager：属性（base type、SQL 列、主键 / 标题 / Value Type / 派生 / 只在语义层）、链接、用到它的 Action、数据源、Share |
| Object Explorer | `/semantic/<cell>/objects/<apiName>[/<主键>]` | 表格：搜索、筛选、排序、按属性看分布；Object View：属性、每条链接上的对象、编辑历史、能执行的 Action |
| Actions | `/semantic/<cell>/actions[/<apiName>]` | 参数表单：先「只校验」（VALIDATE_ONLY）再提交；Action Log |
| SQL Console | `/semantic/<cell>/sql` | Ontology SQL（见下一节）；开发者在「连接」里轮换口令拿只读直连串 |
| Proposals | `/semantic/<cell>/proposals` | 智能体开的 Ontology proposal：逐项批准（可一次批准全部非破坏性的）、合并 |
| Access | `/semantic/<cell>/access` | 开放程度（Private / Group / Public / Open to Internet）、分享给谁、角色，见 [访问与账号](/developer/docs/auth) |
| Public | `/semantic/public` | 所有登录的 AIDC 账号都看得见的 Ontology、Object Type、数字员工、文件，以及「分享给我」 |

网页会话调 `/api/v1/ontologies/**` 时，角色由统一的访问判定决定（Private / Group / Public）：本组织开发者 = developer（与开发者 Key 同权）；本组织成员（Ontology 对组织可见时）= member；别的组织的人按授予——Editor = editor、Viewer = viewer（只读）；Ontology 与所有 Object Type 都没有角色 = 403。

## Semantic 数据库（Ontology SQL）

每个组织一个 Postgres schema（`ont_<组织>`），第一次用时按需建好，定义变了下一次查询时自动重建。照 Palantir Ontology SQL：

- **表**：每个 Object Type 一个视图，表名 = API name、列名 = 属性 API name（列类型：string → text、integer / long → bigint、double → double precision、decimal → numeric、date、timestamp → timestamptz，其余 jsonb）。每个多对多 Link Type 一个视图，两端的列名 `<objectTypeApiName>_<relationApiName>`（值是走这条链接到达的对象的主键）；每个 Interface 一个视图（实现它的类型 UNION ALL，另有 `__objectType`、`__primaryKey`）。一对多直接 join。派生属性是读时算的，不进 SQL。
- **数据**：视图直接读当前值（数据源 ⊕ 语义层改动），不复制；删除的、源头已消失的不出现。
- **上限**：只能一条 SELECT（可以 WITH / VALUES / TABLE 开头），最多 10,000 行、20 秒；位置参数 `$1、$2…`；`dryRun` = EXPLAIN。写入永远走 Action。
- **两个只读角色**：`reader`（本组织看得见的全部类型：本组织成员、开发者 Key、Agent Key、在 Ontology 上有 Viewer 以上授予的人）与 `public`（只有授予了 Everyone 的类型：只看得见 Public 的外部账号）。应用票据不走 SQL——在应用里用 `semantic.ontology()`。
- **直连**：本组织开发者可以轮换口令，拿到 reader 的连接串，用 psql、BI 工具、Notebook 只读直连（只读事务、20 秒超时、最多 10 个连接、只看得见本组织的视图）。口令由服务端派生、不落库，库里只有 SCRAM 校验值；再轮换一次旧口令立即失效。

```js
const r = await semantic.sql(`
  SELECT o.name, count(a.*) AS agents
  FROM organization o LEFT JOIN agent a ON a."organizationId" = o."organizationId"
  GROUP BY 1 ORDER BY 2 DESC`);
const cost = await semantic.sql('SELECT month, sum("costUsd") FROM "modelUsage" WHERE "organizationId" = $1 GROUP BY 1', { parameters: ["cell-aidc"] });
const db = await semantic.database();                       // 表与列、我用哪个角色
const conn = await semantic.rotateDatabaseCredentials();    // 开发者：psql "<conn.uri>"
```

```bash
aidc semantic sql 'SELECT stage, count(*) FROM organization GROUP BY 1' [--csv]
aidc semantic database --rotate
```

| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/api/v1/sqlQueries/executeOntology` | 执行（照官方 ExecuteOntologySqlQueryRequest：`query`、`parameters`、`rowLimit`、`dryRun`、`ontologyIdentifier`）；响应是 JSON（官方是 Arrow） |
| GET | `/api/v1/ontologies/{ns}/database` | 数据库信息；开发者另有直连信息（不含口令） |
| POST | `/api/v1/ontologies/{ns}/database/sync` | 按当前定义重建视图（开发者） |
| POST | `/api/v1/ontologies/{ns}/database/credentials` | 轮换直连口令，新连接串只返回这一次（开发者） |

## 旧写法（1.2.x：describe / objects / action / define / publish）

1.2.x 起的写法照样能用（和上面同一份数据、同一套 Action 引擎），新代码请用上面的 `semantic.ontology()`。

### 三个概念

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

改数据只有两条路：**成员用 Action**（校验、权限、留痕）；**开发者用旧数据层直接改**（管理用）。两条路的改动都落在语义层（对象的 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("100000012345|L01-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()
```

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

### 写：执行 Action

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

- 参数按定义校验（不合法 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 生产进度报表）",
  "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 "100000012345|L01-05" --param issue=缺料 --param severity=异常
```

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

## 权限一览

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

应用清单里这样登记：

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

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

上表之外，[数据安全](#数据安全markings-与安全策略)（Markings、对象 / 属性安全策略）对所有人生效，包括开发者 Key：挂了 Marking 的数据，不是成员就读不到。

## 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` | 语义版本列表 / 发布 |

旧的数据层（`/api/v1/developer/data/…`：查询、聚合、订阅、直接写）照样能用；新代码用下面的 Ontology API 与对象集订阅。命名空间 = 公司 cellId（`cell-…`）；应用里自动取应用所在公司。

Ontology API 照 Palantir REST API v2，前缀是 `/api/v1/ontologies/{命名空间}`，响应体放在信封的 `data` 里、字段照官方：

| 方法 | 路径 | 官方操作 |
| --- | --- | --- |
| GET | `/ontologies`、`/ontologies/{ns}`、`/{ns}/fullMetadata` | List / Get Ontology、Full Metadata |
| GET | `/{ns}/objectTypes[/{type}[/fullMetadata \| /outgoingLinkTypes[/{link}]]]` | Object Type、Outgoing Link Types |
| GET | `/{ns}/actionTypes`、`/interfaceTypes`、`/sharedPropertyTypes`、`/valueTypes`、`/queryTypes`（各带 `/{apiName}`） | 各类型的 List / Get |
| GET | `/{ns}/objects/{type}`、`/{type}/{pk}`、`/{type}/{pk}/links/{link}[/{pk2}]` | List Objects、Get Object、Linked Objects |
| POST | `/{ns}/objects/{type}/search`、`/aggregate`、`/count` | Search、Aggregate、Count |
| GET / POST | `/{ns}/objects/{type}/{pk}/timeseries/{property}/firstPoint`、`lastPoint`、`streamPoints` | Time Series |
| POST | `/{ns}/objectSets/loadObjects`、`/aggregate`、`/createTemporary`；GET `/{ns}/objectSets[/{rid}]` | Object Set |
| POST | `/{ns}/objectTypes/{type}/editsHistory` | Edits History |
| POST | `/{ns}/actions/{action}/apply`、`/applyBatch`（最多 20 个，一个事务） | Apply Action |
| POST | `/api/v1/ontologySubscriptions/ontologies/{ns}/streamSubscriptions`（SSE） | Object set subscription（官方走 WebSocket） |
| POST | `/{ns}/datasources/platform/sync` | 同步 AIDC 平台数据（平台对象类型；开发者） |
| GET / POST | `/{ns}/branches[/{name}[/modify \| validate \| conflicts \| discard \| rebase \| lock \| fullMetadata \| proposals]]` | Global Branching |
| GET / POST | `/{ns}/proposals[/{id}[/tasks/{apiName}/review \| merge \| close]]` | Ontology proposal（review、merge 只收网页登录） |
| POST | `/{ns}/objectTypes/{type}/security/test` | AIDC：Test security policies（Ontology Manager 的同名功能，官方没有公开 API） |

Markings 的管理照 Palantir AdminV2 / FilesystemV2：`/api/v1/admin/markingCategories[/{id}]`、`/api/v1/admin/markings[/{id}[/markingMembers[/add \| /remove] \| /roleAssignments[/add \| /remove]]]`、`/api/v1/admin/markings/getBatch`、`/api/v1/admin/users/{userId}/getMarkings`、`/api/v1/filesystem/resources/{rid}/markings \| addMarkings \| removeMarkings`。

完整字段见 [OpenAPI](/developer/openapi.json)。
