打地基:让 Action 有东西可改
写两个 Value Type 和两个对象类型、一条链接,dry-run 检查、define 落库、publish 出版本;拼错的字段会被当场拒收。

本课目标
读完这一课,你将能够
- 写出两个 Value Type、两个对象类型和一条链接的定义
- 用 dry-run 检查、define 落库、publish 出版本
- 说出定义写错时平台怎么拦,以及智能体作者要走哪条路
Action 改的是对象,先有对象类型
Action 不凭空改数据,它改的是对象。所以第一步不是写 Action,而是让你的 Semantic 里有它要改的东西。这一课打好整个座位申请的地基:两个 Value Type,两个对象类型,一条链接。地基一次打好,后面的 Action 一个个往上加。
建一个目录 seat-lab,里面再建 starter 和 ontology 两个子目录(后面几课的命令都在 seat-lab 里运行;第 6、7 课生成的应用也会放在这里,和定义文件分开各占一个子目录)。定义是 JSON 文件,kind 用官方的名字:valueType、objectType、linkType、actionType。
第一份定义:客户公司
把下面的文件存成 starter/01-customer.json。它有两个定义:客户阶段是一个带约束的类型(只能是这四个值之一,写入时检查),客户公司是一个对象类型。
[
{
"kind": "valueType",
"apiName": "customerStage",
"title": "客户阶段",
"schema": {
"baseType": "string",
"constraints": [{ "type": "enum", "options": ["试点", "付费", "暂停", "内部"] }],
"version": "1.0.0"
}
},
{
"kind": "objectType",
"apiName": "customer",
"title": "客户公司",
"description": "一家客户公司。教程里的起点:先有它,座位申请才有可改的东西。",
"schema": {
"pluralDisplayName": "客户公司",
"visibility": "PROMINENT",
"titleColumn": "name",
"columns": [
{ "name": "customerId", "type": "string", "primaryKey": true, "title": "公司 ID" },
{ "name": "name", "type": "string", "title": "名称", "notNull": true },
{ "name": "stage", "type": "string", "title": "阶段", "valueType": "customerStage" },
{ "name": "seats", "type": "integer", "title": "座位数", "unit": "个" },
{ "name": "members", "type": "integer", "title": "成员数", "unit": "人" }
]
}
}
]
- apiName:Object Type 和 Value Type 用 camelCase,Action 用 kebab-case。一旦有人在用,就别改它。
- primaryKey:一个对象类型只有一个主键属性;
titleColumn是这个对象在界面和日志里显示的名字。 - valueType:属性引用 Value Type,就带上了它的约束。
stage填「不存在」会被拒,Action 的参数引用它也一样。
座位申请:另一个对象类型和一条链接
在 ontology 目录里再存三个文件:01-seat-request-status.json(申请的状态)、02-seat-request.json(申请本身),以及 03-links.json(「一家公司有多张申请」这条链接)。
{
"kind": "valueType",
"apiName": "seatRequestStatus",
"title": "座位申请状态",
"schema": {
"baseType": "string",
"constraints": [{ "type": "enum", "options": ["submitted", "approved", "rejected"] }],
"version": "1.0.0"
}
}
{
"kind": "objectType",
"apiName": "seatRequest",
"title": "座位申请",
"description": "客户公司申请把座位数提到新的数目的一张单。由 Action 新建、批准或拒绝,不来自任何数据源。",
"schema": {
"pluralDisplayName": "座位申请",
"icon": { "blueprint": { "name": "inbox", "color": "#E5620C" } },
"visibility": "PROMINENT",
"synonyms": ["加座申请", "申请"],
"titleColumn": "customerName",
"columns": [
{ "name": "requestId", "type": "string", "primaryKey": true, "title": "申请编号" },
{ "name": "customerId", "type": "string", "title": "公司", "notNull": true },
{ "name": "customerName", "type": "string", "title": "公司名称" },
{ "name": "currentSeats", "type": "integer", "title": "申请时的座位数", "unit": "个" },
{ "name": "seats", "type": "integer", "title": "申请的座位数", "unit": "个" },
{ "name": "reason", "type": "string", "title": "理由" },
{ "name": "status", "type": "string", "title": "状态", "valueType": "seatRequestStatus" },
{ "name": "requestedBy", "type": "string", "title": "申请人(账号 ID)" },
{ "name": "requestedAt", "type": "timestamp", "title": "申请时间" },
{ "name": "decidedBy", "type": "string", "title": "决定人(账号 ID)" },
{ "name": "decidedAt", "type": "timestamp", "title": "决定时间" },
{ "name": "decisionNote", "type": "string", "title": "批复意见" }
]
}
}
{
"kind": "linkType",
"apiName": "customerSeatRequests",
"title": "公司的座位申请",
"schema": {
"from": "customer",
"to": "seatRequest",
"cardinality": "ONE_TO_MANY",
"apiNameAtoB": "seatRequests",
"apiNameBtoA": "customer",
"displayNameAtoB": "座位申请",
"displayNameBtoA": "所属公司",
"foreignKey": { "side": "to", "property": "customerId" }
}
}
申请里存了公司名和申请时的座位数,而不是每次去查公司:申请是一张单据,要记住的是提出那一刻的情况。customerId 是外键,链接 customerSeatRequests 靠它把两端接起来;这些属性都由 Action 写入,不来自任何数据源。
dry-run、define、publish
- dry-run:只检查
整批定义一起校验,批内互相引用算数;不通过一条都不写。
- define:落库
有则改,没有就建。改动如果是破坏性的(删属性、改类型…),会在返回里列出来。
- publish:出版本
发一个语义版本 vN,只增不减;定义没变就不占版本号。
aidc semantic define starter --dry-run --json # 检查
aidc semantic define starter # 落库
aidc semantic publish --notes "客户"
{
"dryRun": true,
"defined": [
{ "apiName": "customerStage", "kind": "valueType", "created": true, "breaking": [] },
{ "apiName": "customer", "kind": "object", "created": true, "breaking": [] }
],
"warnings": []
}
上面是 starter,里面现在只有 01-customer.json(02-register-customer.json 下一课再放)。ontology 也照这个顺序做,里面现在只有前面那三个文件(04 到 07 是四个 Action,后面几课再放):
aidc semantic define ontology --dry-run && aidc semantic define ontology
aidc semantic publish --notes "座位申请的对象类型"
aidc semantic ontology --json # 全貌:两个对象类型,一条链接
要点
- 先有对象类型,Action 才有东西可改;地基一次打好,Action 一个个往上加。
- 定义是 JSON:
kind用官方名字,Object Type 用 camelCase,Action 用 kebab-case。 - dry-run 只检查,define 落库,publish 出版本;整批一起校验,要么都成,要么都不写。
- 拼错的字段会被拒收并指出路径;智能体作者要走分支和提案,不能直接写 main。
练一练
把地基放进你的公司
在你自己的公司里做;定义可以反复 define,不会写坏数据。
存好四个定义文件,按上面的顺序做:先 starter,再 ontology(ontology 里的链接引用 starter 里的 customer,顺序不能反)。第一遍 --dry-run 里,defined 每一项的 created 是不是 true?全部落库之后再各跑一次 --dry-run:这次呢?
把 primaryKey 拼成 primarykey,再 dry-run:提示指到哪一层?改回来。
define 并 publish 之后,打开 /semantic/<你的公司>/object-types:两个对象类型、它们的属性和链接都在吗?
小测
选一个答案,马上看解析。
Q1define --dry-run 会做什么?
dry-run 做的是完整的服务端校验(含批内引用和破坏性变更),只是不落库;不通过一条都不写。
Q2为什么座位申请里要存 currentSeats(申请时的座位数)?
申请里的快照不随公司的座位数变化,事后回看一张申请,还能知道当时是从几座提到几座。
Q3发布之后再 define 一份没有任何变化的定义,再 publish,会怎样?
语义版本只在定义真的变了才增加;重复 define / publish 是安全的,可以放心重试。