@coda-console/protocol 2.0.0 → 2.2.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.
- package/README.md +11 -0
- package/docs/agent-protocol.md +1 -1
- package/docs/executor-protocol.md +261 -0
- package/executor.mjs +21 -0
- package/package.json +5 -3
- package/schemas/executor/v1/common.json +89 -0
- package/schemas/executor/v1/execution.json +246 -0
- package/schemas/executor/v1/executor.json +98 -0
- package/workflow/moddle.json +7 -0
- package/workflow/values.json +6 -0
package/README.md
CHANGED
|
@@ -96,6 +96,17 @@ agentValueProblem('review.json', review, '工具的审核');
|
|
|
96
96
|
深层/大型 JSON 用显式栈检查,非有限数字与循环引用不会通过。会话归属、活动状态迁移、调用起因、协商、超时、重试和授权仍由宿主判断;
|
|
97
97
|
丢弃未知风险值、压缩答复的裁剪与兜底等整理也留在 Coda。详见正文的兼容性与接入说明。
|
|
98
98
|
|
|
99
|
+
## 运行器与执行请求
|
|
100
|
+
|
|
101
|
+
从 2.1.0 起,`executor` 与 `execution` 是 Coda 预定公共对象协议,见[运行器协议正文](docs/executor-protocol.md)。
|
|
102
|
+
`schemas/executor/v1/` 使用默认 Ajv 可读的 draft-07;`@coda-console/protocol/executor` 导出
|
|
103
|
+
`EXECUTOR_PROTOCOL`、`EXECUTION_PROTOCOL`,其 spec/status 都自包含,可直接传给 `protocols.define`。
|
|
104
|
+
2.2.0 支持无资源请求或一个资源的一单位(slots 或本机 `group:<原名>`),可独占申请资源;仍不支持多单位或多资源原子申请。
|
|
105
|
+
可选 placement 是生产者的位置/指定环境硬约束,context 是受控运行上下文与输出声明;执行器须显式声明支持新能力。
|
|
106
|
+
流程可在 process、subProcess、scriptTask 写 `coda:placement="local|any"`,祖先 local 不可被子节点放宽。两种类型均无补充栏,execution 绑定是由调度办事员 CAS 更新的单值。
|
|
107
|
+
executor 无 target,status 由持有端写;execution 绑定后由执行方完整回传,开跑前须经本机账本准入。
|
|
108
|
+
对象协议 v1 与 npm 包版本、插件清单 v2 独立;旧 `contributes.executors` 不变。
|
|
109
|
+
|
|
99
110
|
## 谁在用
|
|
100
111
|
|
|
101
112
|
- **Coda**:读插件时用它校验清单,再整理成自己的结构;只另看插件身份(ID、设置名在 ID 之下),以及装到一台机器上才知道的(来源、已装插件的冲突)。读流程时用 `readWorkflow` 同时校验方言、值与结构,再编译成引擎需要的数据。
|
package/docs/agent-protocol.md
CHANGED
|
@@ -460,7 +460,7 @@ coda-console/coda-plugins 的 `plugins/<插件>/`、`test/plugins/<插件>/`,
|
|
|
460
460
|
|
|
461
461
|
| Agent | 入口 | 它在协议之外替 Agent 做的 |
|
|
462
462
|
| --- | --- | --- |
|
|
463
|
-
| Codex(插件 `codex`) | `agent/entry.mjs`:在同一进程里运行锁定版本的 codex-acp,加载 Coda 的源码补丁(适配器一变就启动失败) | 向 codex-acp 声明 JetBrains AIR 的 `nativeSubagentSessions` 与 `asyncTasks`,把 AIR 报的子 Agent 与转入后台的命令翻成 `_coda/wait`:命令带 `toolCallId`,codex-acp 说能停的经 AIR 的停止(发给根会话)停;子 Agent 在 codex-acp 说能取消时经取消它的会话停。codex-acp 把继续的子 Agent 叫 `X:generation:N`,对 Coda 一律是 `X`,每次运行一个新的 `waitId`。会话默认以只读模式开始(`INITIAL_AGENT_MODE`,运行机设了就用它的);Coda
|
|
463
|
+
| Codex(插件 `codex`) | `agent/entry.mjs`:在同一进程里运行锁定版本的 codex-acp,加载 Coda 的源码补丁(适配器一变就启动失败) | 向 codex-acp 声明 JetBrains AIR 的 `nativeSubagentSessions` 与 `asyncTasks`,把 AIR 报的子 Agent 与转入后台的命令翻成 `_coda/wait`:命令带 `toolCallId`,codex-acp 说能停的经 AIR 的停止(发给根会话)停;子 Agent 在 codex-acp 说能取消时经取消它的会话停。codex-acp 把继续的子 Agent 叫 `X:generation:N`,对 Coda 一律是 `X`,每次运行一个新的 `waitId`。会话默认以只读模式开始(`INITIAL_AGENT_MODE`,运行机设了就用它的);Coda 选的账号经账号桥接上。原生 `clock.sleep` 通过同一会话的 `_coda/wait` 报为 `task`(标题带等待时长),开始报 `waiting`,结束报 `done`;它仍在当前轮内,取消或失败的轮次及子 Agent 会了结尚未结束的 sleep。等待项不能单独停止,立即插话或停止会话沿用原生行为。Codex 没有自主轮次,不报 `handling`。会话指令经 codex-acp 的 `thread/start`、`thread/resume` 作为 `developerInstructions` 交给 Codex,接在运行机配置自己的 `developer_instructions` 后面;声明插话,一轮已经结束时收到的插话回 `promptRequired`(Coda 的补丁,codex-acp 原本会自己另开一轮) |
|
|
464
464
|
| Claude(插件 `claude`,Agent `claude-sdk`) | `agent/entry.mjs`:Coda 基于 Claude Agent SDK 写的 ACP Agent,一个进程一个 SDK 会话;SDK 是插件依赖的 `@anthropic-ai/claude-agent-sdk` | 会话 ID 由入口生成;模型一律是具体 ID(SDK 列出的 `resolvedModel` 加上下文变体,不接受别名)。后台任务、子 Agent 与 Stop hook 列出的定时唤醒报成 `_coda/wait`(定时的计划不是五段 cron、算不出下次时间时不带 `at`):子 Agent 的会话是启动它的工具调用 ID,SendMessage 续聊是同一会话的新一次运行;子 Agent 起的后台任务与子 Agent 在它自己的会话里报;没有轮次时结束的任务,等一轮接手或会话空闲才了结。Claude 自己开始的一轮,先看开场消息指名的:到点的定时、结束通知所属的任务、交回报告的子 Agent(它已结束时才算;还在跑的子 Agent 发来进展不算)。指名的已处理过(Claude Code 为子 Agent 的结束通知另开的、不调模型的一轮)或还在跑,这一轮不处理任何一项;开场什么都没指名,才依次找已结束的任务、唯一到点的定时。认定的项在任何输出与请求之前报 `handling`;什么都没认定的,等这一轮第一次说话、调用工具、审批或提问时才报自造的「Claude 自主继续」,什么都不说的一轮不报。审批的选项是仅允许本次、每条建议规则一个 `allow_always`、拒绝;SDK 不再等回答时发 `$/cancel_request`。MCP 服务器的表单原样转来,链接用 `mode: url`。额度写进插件状态目录的 `limits.json`(内置插件:Claude(Coda 的 docs/plugins.md「内置插件claude」)),标题经 `session_info_update` 报。会话指令追加在 `claude_code` 预设的系统提示词后面 |
|
|
465
465
|
| Kimi(插件 `kimi`) | `agent/entry.mjs`:把 `kimi acp` 作为子进程,在两者之间逐条转发 | 读 Kimi 的会话文件,把入口启动之后开始的后台命令与子 Agent 报成 `_coda/wait`(子 Agent 的会话是它的 agentId,过程报成那个会话的 `session/update`;按运行先后交付登记、过程与了结,同一子 Agent 的下一次运行等上一次了结才报,从而接着用同一张卡片);在 Coda 发起的一轮里结束的任务直接了结,因为主 Agent 已在该轮处理结果;轮外结束且结果要交给主 Agent 的才从结束起报 `handling`,主 Agent 在那之后结束一轮时了结。不能停,不带 `toolCallId`。只认 Kimi CLI 2.1.1 的文件布局,见会话行为契约(Coda 的 docs/session-contracts.md「停止恢复与进程回收」)。会话指令放在原生会话第一轮的输入前面 |
|
|
466
466
|
| Grok、OpenCode(插件 `grok`、`opencode`) | `agent/entry.mjs`:把运行机上的 `grok agent --no-leader stdio`(先找 `~/.grok/bin/grok`)、`opencode acp` 作为子进程逐条转发 | 只做会话指令:声明它,并放在原生会话第一轮的输入前面;不报告活动 |
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# 运行器与执行请求协议 v1
|
|
2
|
+
|
|
3
|
+
`executor`(运行器)与 `execution`(一次执行尝试)是 Coda 预定的公共协议,唯一形状定义在
|
|
4
|
+
`@coda-console/protocol`。依据是 [coda#2078 已验收草案](https://github.com/coda-console/coda/issues/2078#issuecomment-6554881338),
|
|
5
|
+
2.2.0 按[第 7 步方案](https://github.com/coda-console/coda/issues/2078#issuecomment-6559423071)补齐流程接入:
|
|
6
|
+
**无资源或一个资源的一单位**,可独占所申请资源;多单位、多资源原子申请仍未开放。
|
|
7
|
+
|
|
8
|
+
协议对象版本 `protocolVersion: 1`、本目录的 v1、npm 包版本 2.2.0 和插件清单的 v2 分开演进。
|
|
9
|
+
本次只增加契约;不改变 `contributes.executors`,也不宣称 host、scheduler、空间引用解析或账本接口已经实现。
|
|
10
|
+
|
|
11
|
+
## 定义与登记
|
|
12
|
+
|
|
13
|
+
`schemas/executor/v1/{executor,execution}.json` 分别描述 `{spec,status}`,公共定义在 `common.json`。
|
|
14
|
+
原始 JSON 采用 draft-07,读取原文件时先向 Ajv 登记 `common.json`。模块导出把所需定义包含在每份 spec/status 中,
|
|
15
|
+
只留本地 `$ref`;四份 schema 均可由一个全新的默认 Ajv 直接编译,不需自定义关键字、format 或外部 schema 注册。
|
|
16
|
+
`spec`、`status` 顶层均为 object。不支持的 `protocolVersion`、未知字段与不合资源命名约定的键明确拒绝。
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { EXECUTOR_PROTOCOL, EXECUTION_PROTOCOL } from '@coda-console/protocol/executor';
|
|
20
|
+
|
|
21
|
+
// 现行 API 的装配示例:由 host 在提交端和执行端登记,提交端可关闭接活。
|
|
22
|
+
await coda.protocols.define(EXECUTOR_PROTOCOL);
|
|
23
|
+
await coda.protocols.define({ ...EXECUTION_PROTOCOL, controllers: ['scheduler'] });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`EXECUTOR_PROTOCOL` 没有 `target`;`EXECUTION_PROTOCOL.target` 为 `executor`。
|
|
27
|
+
两种类型均**没有补充栏**,不声明 append/or 合并;任务、标签、取消和绑定均不经补充者修改。
|
|
28
|
+
`controllers: ['scheduler']` 是现行接口的插件 ID 装配,不是协议对 host/scheduler 实现路径的依赖。
|
|
29
|
+
其他生产者通过 provider 的本机创建/取消入口操作,不能冒用 `put`。
|
|
30
|
+
|
|
31
|
+
当前 `protocols.define` 只允许一个插件登记同 kind,定义持久化后不能改变;尚不支持按协议依赖、多实现登记或同 kind 版本迁移。
|
|
32
|
+
后续替换 provider 或演进声明须先解决 Coda 通用登记机制,不能仅更换 schema 文件接管既存类型。
|
|
33
|
+
|
|
34
|
+
信封沿用 `{kind,id,instance,spec,status,revision}`。基座维护 `instance` 与不透明 `revision`;插件原样传回观察到的 revision,
|
|
35
|
+
不能解析或自行递增。删除后同 id 重建是新 instance,旧回答不能复用。这里的 schema 校验 spec/status,不校验或重定义信封。
|
|
36
|
+
所有 status 都允许 `{}`,表示镜像状态尚未到达,**不能据此判定未开始**;非空 status 必须是一份完整的已知状态。
|
|
37
|
+
|
|
38
|
+
## executor:配置与本机事实
|
|
39
|
+
|
|
40
|
+
一个对象对应一个后端实例的执行环境,身份不随显示名或插件版本改变。
|
|
41
|
+
|
|
42
|
+
| 区域 | 字段 |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| spec 必填 | `protocolVersion: 1`、`enabled`、`capacity: Record<resource, 非负整数>`、`labels: string[]` |
|
|
45
|
+
| spec 可选 | `title`(不超过 200 字符) |
|
|
46
|
+
| status 能力 | `detectedLabels`、`capabilities: {os,arch,nodeVersion,graphicalSession}`;可选 `xcode: {version,build}`、`simulatorRuntimes: [{id,version,available}]` |
|
|
47
|
+
| status 资源 | `capacity`、`occupied`、`available`,各为 `Record<resource, 非负整数>`;`accepting` |
|
|
48
|
+
| status 观察 | `observedAt`、`observedRevision`;可选 `reason: {code,message}` |
|
|
49
|
+
| status 支持 | `executionFeatures: {multiResource: false,multiUnit: false,exclusive: boolean}`;可选 `noResources`、`namedResources`、`context` 布尔值 |
|
|
50
|
+
|
|
51
|
+
非空 status 除明确可选项外全部必填。`graphicalSession` 是 boolean 或 null;null 表示无法确定。
|
|
52
|
+
探测失败写 `reason`,不将失败变成用户标签或伪造能力;可选探测项未知时省略。
|
|
53
|
+
探测只读,不启动模拟器。`observedRevision` 对应探测时采用的配置版本,而非要求它等于之后每次 status 写入产生的 revision。
|
|
54
|
+
|
|
55
|
+
标签使用小写字母、数字、`.`、`_`、`-`,以字母或数字起始,每项最多 40 字符、每列表最多 64 项、不重复。
|
|
56
|
+
`requirements.labels` 沿用 runsOn 的**全部满足**语义,匹配用户标签与探测标签的并集。
|
|
57
|
+
保留 OS 标签 `darwin/linux/win32/aix/android/freebsd/openbsd/sunos/cygwin/netbsd/haiku`、架构标签
|
|
58
|
+
`arm/arm64/ia32/x64/loong64/mips/mipsel/ppc/ppc64/riscv64/s390/s390x`、`gui`,以及 `xcode-`、`ios-`、`node-` 前缀给探测结果。
|
|
59
|
+
spec.labels 只能加自定义标签,不能写这些保留值。精确版本和 simulator runtime ID 另外保存在结构化能力中。
|
|
60
|
+
|
|
61
|
+
`slots` 的单位是执行名额,host 映射到同一本机 `host` 账本;scheduler 比较资源键与数量,不硬编码 `group === 'host'`。
|
|
62
|
+
容量可大于 1;**每次请求量仍只能为 1**。其他本机命名组用 `group:<原名>`,原名为 1–100 个非空白、非控制字符;
|
|
63
|
+
`group:host` 拒绝,host 唯一映射是 slots。容量 map 可以为空或含至多 128 个独立资源,这不表示支持一个请求同时申请多个资源。
|
|
64
|
+
未报告的资源不能按无限容量或 0 占用处理。有能力不代表有名额。
|
|
65
|
+
|
|
66
|
+
`noResources`、`namedResources`、`context` 只有显式 true 才表示支持无资源请求、命名组请求、受控运行上下文;
|
|
67
|
+
省略表示未声明支持。scheduler 必须按请求用到的能力筛选,不能将旧执行器当作支持新形状。
|
|
68
|
+
命名组在本版本仅允许本机执行,由适配层判断归属;不凭带 `group:` 的字符串授权远端资源映射。
|
|
69
|
+
缩容量允许 occupied 暂时大于 capacity,此时 available 归零并停止新准入,不强杀已有任务。
|
|
70
|
+
占用含旧流程与 run-held;借用或独占按账本事实计算,不能简单数 host 自己启动的进程,也不能要求独占时 available 等于 capacity − occupied。
|
|
71
|
+
这些跨栏计算和准入状态由实现检查,不加动态 schema 关键字。
|
|
72
|
+
|
|
73
|
+
对象无 target,配置经运行器持有端更新;**status 只由持有后端的实现者写**,空间层单向复制。
|
|
74
|
+
accepting、available、observedAt 都只是最近观察事实,不保证在线,也不是预留凭证;新鲜度阈值与选择策略属于 scheduler。
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"spec": {
|
|
79
|
+
"protocolVersion": 1,
|
|
80
|
+
"enabled": true,
|
|
81
|
+
"capacity": {"slots": 2},
|
|
82
|
+
"labels": ["build-lab"],
|
|
83
|
+
"title": "Mac mini"
|
|
84
|
+
},
|
|
85
|
+
"status": {
|
|
86
|
+
"detectedLabels": ["darwin", "arm64", "xcode-26.1", "node-26"],
|
|
87
|
+
"capabilities": {"os": "darwin", "arch": "arm64", "nodeVersion": "26.8.2", "graphicalSession": null, "xcode": {"version": "26.1", "build": "17B55"}},
|
|
88
|
+
"capacity": {"slots": 2},
|
|
89
|
+
"occupied": {"slots": 1},
|
|
90
|
+
"available": {"slots": 1},
|
|
91
|
+
"accepting": true,
|
|
92
|
+
"observedAt": "2026-11-01T07:00:00Z",
|
|
93
|
+
"observedRevision": "opaque-config-revision",
|
|
94
|
+
"executionFeatures": {"multiResource": false, "multiUnit": false, "exclusive": true}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## execution:一次尝试
|
|
100
|
+
|
|
101
|
+
正本在发起后端。`run: ResourceRef` 关联已有运行对象,`step?` 关联步骤,`attempt` 是正整数。
|
|
102
|
+
重新执行创建新 execution ID 和新 attempt,保留上一尝试,不另造 Run 或会话命令队列。取消不是新尝试。
|
|
103
|
+
|
|
104
|
+
| spec 区域 | 形状与约束 |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| 身份 | `protocolVersion: 1`、`run`、`attempt` 必填;`step` 可选 |
|
|
107
|
+
| 源码 | `source` 必填:null、`{snapshot: ResourceRef<sourceSnapshot>}`、`{repository: ResourceRef<gitSource>,commit: 完整 SHA}` 三选一;SHA 为 40 或 64 位十六进制 |
|
|
108
|
+
| 能力 | `requirements: {labels: string[]}`,可为空列表 |
|
|
109
|
+
| 资源 | `resources: {}`、`{slots: 1}` 或 `{ "group:<原名>": 1 }`;`exclusiveResources` 默认 `[]`,至多独占申请的那一个资源 |
|
|
110
|
+
| 定位(可选) | `placement: {locality?: "local"或"any", executor?: ResourceRef<executor>}`;省略 locality 等同 any |
|
|
111
|
+
| 上下文(可选) | `context: {workflow?,plugin?,audience?,outputNames?}`,见下文 |
|
|
112
|
+
| 任务 | `{script,shell}` 或 `{command,args}`,严格二选一;程序名与参数不隐含 shell |
|
|
113
|
+
| 环境 | `workingDirectory` 默认 `.`;`env: Record<string,string>` 必填,只含声明变量,可为空对象 |
|
|
114
|
+
| 时限 | `timeoutMs?` 正整数;`cancelGraceMs?` 非负整数;缺省策略由执行实现决定 |
|
|
115
|
+
| 取消 | `cancel` 默认 false,是单向意图 |
|
|
116
|
+
| 绑定 | `executor: ResourceRef<executor>\|null`、`assignmentId: string\|null` 必填,两者同为 null 或同时有值 |
|
|
117
|
+
|
|
118
|
+
独占是 `exclusiveResources: [资源键]`,而非把“全部名额”换成某次观察到的容量数字。
|
|
119
|
+
空 resources 只允许空 exclusiveResources;非空时独占键必须与请求键一致,schema 检查数量和命名,执行方核对二者关系。
|
|
120
|
+
生产端必须先看 `executionFeatures.exclusive`;不支持独占的执行方明确拒绝,不降级为普通名额。
|
|
121
|
+
原 coda:group 由生产端/资源 provider 声明映射;runsOn 放入 labels,两者独立。
|
|
122
|
+
|
|
123
|
+
workingDirectory 是源码根下的相对路径;没有源码时相对执行方隔离目录。
|
|
124
|
+
schema 拒绝绝对路径、盘符、反斜线、控制字符和 `..` 路径段;执行方仍须检查真实落点、符号链接和来源权限。
|
|
125
|
+
工作树先由提交端制成 ready 快照,执行端读取快照的精确 commit/ref,不重新抓取“最新工作树”。
|
|
126
|
+
env 不等于提交机进程环境;沿用 hook 的声明变量、密文与鉴权边界,不把本机凭据或绝对路径放进跨机 spec。
|
|
127
|
+
|
|
128
|
+
JSON Schema 的 default 只声明缺省值。单独使用默认 Ajv 校验不会填值;现行 Provider 用 `useDefaults` 补齐
|
|
129
|
+
`workingDirectory`、`exclusiveResources`、`cancel`。不使用 default 填造任何 status 事实。
|
|
130
|
+
|
|
131
|
+
| 非空 status | 约束 |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| 基本 | `phase: queued\|preparing\|running\|finished` 与 `assignmentId` 必填;可选 `reason` |
|
|
134
|
+
| 进度 | 可选 `startedAt`、`executionRun: ResourceRef`、`admission: {lease,slots}`;running 三者必填 |
|
|
135
|
+
| 准入 | 无资源请求 admission 为 null;占资源时为 `{lease,slots,resource?}`,resource 省略表示 slots。slots 是非空且不重复的非负名额序号数组,独占可记录多个序号 |
|
|
136
|
+
| 完成 | finished 必须给 `finishedAt`、`outcome: succeeded\|failed\|cancelled\|rejected`;其他 phase 不接受 finishedAt、outcome、exitCode |
|
|
137
|
+
| 结果 | 可选 `exitCode: integer\|null`、`outputs`、`summary`、`outputTail`、`logs`、`artifacts` |
|
|
138
|
+
|
|
139
|
+
preparing/running 必须有 assignmentId;succeeded/failed 还必须有 startedAt 与 assignmentId。
|
|
140
|
+
取消可在未绑定、未启动时完成;rejected 表示本次 assignment 不会启动,不接受 startedAt 或非 null exitCode。
|
|
141
|
+
没启动或无法确定退出码时省略或写 null,不能造 0。这些字段的静态组合不能代替实际进程、收尾和状态迁移检查。
|
|
142
|
+
独立 spec/status schema 不比较请求与结果:执行方必须保证 admission 为 null 当且仅当请求 resources 为空;
|
|
143
|
+
非空准入的 resource 必须等于请求资源,不能用 null、别的组的租约或虚构 lease 绕过占额。
|
|
144
|
+
|
|
145
|
+
文本限制使用 JSON Schema 的 Unicode 字符数,不是 UTF-8 字节或 UTF-16 长度:summary 最多 4096、outputTail 最多 16384;
|
|
146
|
+
outputs 最多 64 项,键 1–128 字符、值最多 4096。logs/artifacts 各最多 64 项。
|
|
147
|
+
logs 项为 `{ref: ResourceRef,cursor?}`;artifacts 项为 `{ref: ResourceRef,name,sha256?,size?,mediaType?}`。
|
|
148
|
+
引用不接受本机绝对路径代替对象;sha256 为 64 位十六进制,size 为非负安全整数。
|
|
149
|
+
任务脚本最多 65536 字符,command/shell 最多 4096,args 最多 256 项、每项最多 16384;
|
|
150
|
+
env 最多 128 项,变量名使用字母/下划线起始的常规环境变量名、最多 128 字符,值最多 16384。
|
|
151
|
+
数组、键和值都有上限,不能把无限日志复制进 status;完整限制见 schema。
|
|
152
|
+
时间统一使用 UTC `...Z`,schema 仅检查日期时间文字的形状,写入方保证日期真实、时序合理。
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"spec": {
|
|
157
|
+
"protocolVersion": 1,
|
|
158
|
+
"run": {"kind": "runs", "id": "opaque-run-ref"},
|
|
159
|
+
"step": "check",
|
|
160
|
+
"attempt": 1,
|
|
161
|
+
"source": {"snapshot": {"kind": "sourceSnapshot", "id": "opaque-snapshot-ref"}},
|
|
162
|
+
"requirements": {"labels": ["darwin", "arm64"]},
|
|
163
|
+
"resources": {"slots": 1},
|
|
164
|
+
"exclusiveResources": ["slots"],
|
|
165
|
+
"task": {"command": "npm", "args": ["test"]},
|
|
166
|
+
"workingDirectory": ".",
|
|
167
|
+
"env": {"CI": "true"},
|
|
168
|
+
"cancel": false,
|
|
169
|
+
"executor": {"kind": "executor", "id": "opaque-executor-ref"},
|
|
170
|
+
"assignmentId": "assignment-1"
|
|
171
|
+
},
|
|
172
|
+
"status": {
|
|
173
|
+
"phase": "finished",
|
|
174
|
+
"assignmentId": "assignment-1",
|
|
175
|
+
"startedAt": "2026-11-01T07:00:00Z",
|
|
176
|
+
"finishedAt": "2026-11-01T07:01:00Z",
|
|
177
|
+
"executionRun": {"kind": "runs", "id": "opaque-execution-run-ref"},
|
|
178
|
+
"admission": {"lease": "opaque-lease", "slots": [0, 1]},
|
|
179
|
+
"outcome": "succeeded",
|
|
180
|
+
"exitCode": 0,
|
|
181
|
+
"summary": "检查通过",
|
|
182
|
+
"logs": [{"ref": {"kind": "logs", "id": "opaque-log-ref"}}]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
ResourceRef 的插件可见形状严格为 `{kind,id}`,id 是适配层提供的不透明引用。
|
|
188
|
+
示例里的 `runs`、`logs` 是引用示意,不在本协议登记新类型;实际使用现有运行和日志对象的 kind。
|
|
189
|
+
适配层必须保持源码、运行、日志和产物的持有者归属,不能把本机 `remote:…` 字符串原样传给另一台便认为能解析。
|
|
190
|
+
日志衔接现有运行日志读面;产物这里只预留引用、大小与摘要,字节传输和 sha256 核验仍属后续 artifacts 实施步骤。
|
|
191
|
+
|
|
192
|
+
## 流程定位与受控上下文
|
|
193
|
+
|
|
194
|
+
placement 是生产者约束,顶层 executor/assignmentId 才是 scheduler 的实际绑定,两者不可混用。
|
|
195
|
+
`placement.executor` 指定具体环境,`locality=local` 限定请求持有端,二者同时满足;约束冲突或目标不可用时排队并报告原因,
|
|
196
|
+
不能丢弃绑定后自动选本机。`locality=any` 只放开位置,标签、能力、资源与准入仍须满足。归属由适配层判断,不解析 ID、拼机器参数。
|
|
197
|
+
绑定前后执行方都应检查 placement;schema 不检查引用的实际归属或两个 executor 引用相等。
|
|
198
|
+
|
|
199
|
+
流程方言可在 process、subProcess、scriptTask 写 `coda:placement="local|any"`。
|
|
200
|
+
任何祖先的 local 都是硬限制,子节点 any 不能放宽。未写 placement 且无有效 runsOn 的脚本由流程生产者设置 local;
|
|
201
|
+
有 runsOn 才默认 any。显式项目绑定与此约束相交,不覆盖本机限制。需要 CODA_ARTIFACTS、桌面或本机安装状态的流程显式写 local。
|
|
202
|
+
用户任务和 Coda 动作仍由流程持有端处理,不接受 placement。这些继承与准入规则由宿主实现,解析器只校验和保留声明。
|
|
203
|
+
|
|
204
|
+
context 是可信流程生产者冻结的事实,不是权限证明,也不是任意环境变量 map:
|
|
205
|
+
|
|
206
|
+
- `workflow` 若存在,必须完整给出 `name`、`job`、`runId`、`runAttempt`、`sha`、`ref`、`repository`。
|
|
207
|
+
sha 为完整 SHA 或空字符串(不检出);ref/repository 可为空。执行方生成 GITHUB_WORKFLOW/JOB/RUN_ID/RUN_ATTEMPT/SHA/REF/REPOSITORY,
|
|
208
|
+
并根据本机工作区生成 GITHUB_WORKSPACE、输出与临时路径。生产者必须核对这些值与 run/step/源码相符。
|
|
209
|
+
- `plugin: {id,version}` 标记插件脚本来源,不给绝对路径。实际执行还须满足本机安装、版本和项目授权;本阶段插件流程仍由生产者限定本机。
|
|
210
|
+
- `audience: "user"` 沿用用户专属结果边界。持有端与执行端必须在对象、日志及结果读面实施可见性,不能仅靠此字符串保护私有内容。
|
|
211
|
+
- `outputNames` 是最多 64 个不重复的输出名;明确给空数组表示不返回脚本变量。给出时只回填声明键,未写出的声明输出为空字符串。
|
|
212
|
+
省略沿用普通执行请求的输出收集;不能凭未声明变量泄露私有数据。
|
|
213
|
+
|
|
214
|
+
所有本机路径、凭据、插件设置、完整进程环境和事件密文均不进入 context。实际组准入后才由执行方生成 CODA_GROUPS,不能信任提交者自报已占额。
|
|
215
|
+
context 字段的文本边界见 schema;执行方仅在显式声明 executionFeatures.context 时接收它。
|
|
216
|
+
|
|
217
|
+
## 写权限、绑定与改派
|
|
218
|
+
|
|
219
|
+
1. executor 无 target,status 权威是持有端。execution 未绑定时,本机 provider/获准 scheduler 可写 queued 与原因;
|
|
220
|
+
创建时可给 queued,缺失状态是未知。scheduler 只处理本机正本,按观察到的 revision 同次 CAS 写入 executor 与新的 assignmentId。
|
|
221
|
+
绑定是顶层单值,**不走 append/or 补充合并**,不将多个候选合并成绑定。
|
|
222
|
+
2. 绑定后任务、源码、身份、资源、需求、placement 与 context 冻结,只可单向设置 cancel;安全改派仅更换绑定两栏。
|
|
223
|
+
空间桥将 executor 翻译成 `coda.pub/target`。执行方只观察 assigned,并核对具体 executor ID 是自己实现的;
|
|
224
|
+
指向后端不表示该后端所有执行环境都可领单。**绑定后只有执行方完整回传 status**,包括字段删除;
|
|
225
|
+
其他节点无权写,持有者不能伪造远端完成。旧 status 按 assignmentId 区分,不能拿旧绑定结果解释新绑定。
|
|
226
|
+
3. 未绑定请求可直接选择。已绑定请求只有当前 assignment 明确拒绝,或撤销已确认且证明不会再启动,才可改派。
|
|
227
|
+
对齐 assignmentId、验证拒绝/撤销事实和权限是机制责任,单凭符合 schema 的对象不能授权改派。
|
|
228
|
+
旧 queued、缺 startedAt、空 status、租约超时或节点掉线均不能证明未开始;已开始或是否开始不明,保留原绑定与运行引用并恢复观察。
|
|
229
|
+
4. 持有端将不可达作为观察状态展示,不代写执行结果。取消或撤下镜像也不是原进程已停证明。
|
|
230
|
+
自动重试依任务幂等性和重试政策,创建新 attempt;协议不承诺网络分区中的 exactly-once。
|
|
231
|
+
|
|
232
|
+
## 开跑前的本机账本准入与恢复
|
|
233
|
+
|
|
234
|
+
执行方须在 prepare/启动前,持久登记 **execution 的 instance + assignmentId**,经同一本机账本原子获得资源与固定名额序号。
|
|
235
|
+
调度看到 available 或 binding 成功均不等于准入。普通占资源请求只占指定资源的一单位;独占须走账本独占动作,不能连续读 available 或分批占额模拟。
|
|
236
|
+
无资源请求仍须持久登记并确认执行实现,只是不申请租约;不得给无 group 的步骤暗加 slots 限流。
|
|
237
|
+
容量冲突可排队或明确拒绝,scheduler 决定是否在满足改派条件后换目标。
|
|
238
|
+
多键/多数量原子准入尚未开放,schema 和 executionFeatures 均不宣称支持。
|
|
239
|
+
|
|
240
|
+
进程和 cleanup 确认结束后才释放;故障期间保留待恢复占用。重启 list-watch 的 ADDED 只能对账,不得再次启动同一 instance + assignment。
|
|
241
|
+
旧镜像指纹/revision 防回写不能代替防重复启动。被拒绝的旧结果不能简单换成最新 revision 重交。
|
|
242
|
+
|
|
243
|
+
`control` 对同对象串行回调;不能 await 整段长任务堵住取消。
|
|
244
|
+
办事员登记并启动/跟随持久运行后返回,后续观察更新取消进程组。取消只改 spec.cancel,执行方重读同 assignment 的当前意图,
|
|
245
|
+
完成取消与收尾后报告 cancelled;取消不能回退为 false,也不能回写旧请求的结果。
|
|
246
|
+
|
|
247
|
+
## 与现有执行链的衔接边界
|
|
248
|
+
|
|
249
|
+
执行保留 `prepare_job / run_script_step / cleanup_job` 三段 hook,以及 `{command,responseFile,args,state}`、准备 state、
|
|
250
|
+
日志、退出码、取消进程组、失败也收尾的语义。清单 `command` 是 hook 入口,与 `task.command` 不同。
|
|
251
|
+
源码 provider 给出精确 commit/ref 与本机鉴权代理入口;hook 本机路径在执行方生成,不进入跨机 spec。
|
|
252
|
+
|
|
253
|
+
缓存目录建议 `$CODA_PLUGIN_STATE/workspaces/<source-key>/<slot-index>/`:source-key 来自来源工作副本身份及对象 instance,
|
|
254
|
+
不能用 origin URL、提交号或快照 ID 代替;同仓库不同 linked worktree 也隔离。
|
|
255
|
+
slot-index 来自本机账本,scheduler 不猜序号;借用也不得使目录重入。日志/响应文件另放尝试目录。
|
|
256
|
+
准入后 fetch 精确 ref/commit、核对实际 commit,再 reset --hard 与 clean -fd,保留忽略缓存;cleanup 清理本次临时资源并放开资源,保留缓存副本。
|
|
257
|
+
发布/部署仍由对应流程和插件检查已合并提交准入,不因支持快照删除 origin/发布检查。
|
|
258
|
+
|
|
259
|
+
旧 Executor 发布与 steps/Step 跨机请求由后续消费方切换撤除,不永久保留两套调度真相。
|
|
260
|
+
本包只交付标准 JSON Schema 和规范;引用可移植解析、本机鉴权入口、账本准入/取消/释放及稳定序号、恢复日志跟随、
|
|
261
|
+
host/scheduler 实现与流程切换均属于各自仓库后续工作。来源权限、时间先后、容量计算、状态迁移和真实路径不伪装成 schema 动态审核器。
|
package/executor.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Public object contracts. Each spec/status can be compiled by a fresh, default Ajv (draft-07).
|
|
2
|
+
// Only local references remain; no validator keywords, provider identity or scheduling policy are added here.
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const base = 'https://coda.pub/schemas/executor/v1/';
|
|
6
|
+
const read = name => JSON.parse(readFileSync(new URL(`./schemas/executor/v1/${name}.json`, import.meta.url), 'utf8'));
|
|
7
|
+
const common = read('common');
|
|
8
|
+
const local = value => JSON.parse(JSON.stringify(value).replaceAll('common.json#/definitions/', '#/definitions/'));
|
|
9
|
+
function protocol(kind) {
|
|
10
|
+
const schema = read(kind);
|
|
11
|
+
const part = name => ({
|
|
12
|
+
$schema: schema.$schema,
|
|
13
|
+
$id: `${base}${kind}.${name}.json`,
|
|
14
|
+
...local(schema.properties[name]),
|
|
15
|
+
definitions: local(common.definitions),
|
|
16
|
+
});
|
|
17
|
+
return { kind, spec: part('spec'), status: part('status') };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const EXECUTOR_PROTOCOL = protocol('executor');
|
|
21
|
+
export const EXECUTION_PROTOCOL = { ...protocol('execution'), target: 'executor' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coda-console/protocol",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Coda 的协议:插件清单的类型(JSON Schema)、通用校验器、流程方言与 Agent 接入协议",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"agent.mjs",
|
|
18
18
|
"workflow",
|
|
19
19
|
"workflow.mjs",
|
|
20
|
-
"docs"
|
|
20
|
+
"docs",
|
|
21
|
+
"executor.mjs"
|
|
21
22
|
],
|
|
22
23
|
"exports": {
|
|
23
24
|
"./validate": "./validate.mjs",
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
"./workflow/*": "./workflow/*",
|
|
27
28
|
"./docs/*": "./docs/*",
|
|
28
29
|
"./package.json": "./package.json",
|
|
29
|
-
"./agent": "./agent.mjs"
|
|
30
|
+
"./agent": "./agent.mjs",
|
|
31
|
+
"./executor": "./executor.mjs"
|
|
30
32
|
},
|
|
31
33
|
"engines": {
|
|
32
34
|
"node": ">=24"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://coda.pub/schemas/executor/v1/common.json",
|
|
4
|
+
"title": "运行器协议共享静态定义",
|
|
5
|
+
"definitions": {
|
|
6
|
+
"opaqueId": {"type": "string", "minLength": 1, "maxLength": 2048, "description": "适配层给出的不透明标识;不解析或拼接机器路由"},
|
|
7
|
+
"resourceRef": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {"kind": {"type": "string", "minLength": 1, "maxLength": 128}, "id": {"$ref": "common.json#/definitions/opaqueId"}},
|
|
10
|
+
"required": ["kind", "id"],
|
|
11
|
+
"additionalProperties": false
|
|
12
|
+
},
|
|
13
|
+
"executorRef": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {"kind": {"const": "executor"}, "id": {"$ref": "common.json#/definitions/opaqueId"}},
|
|
16
|
+
"required": ["kind", "id"],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
"snapshotRef": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {"kind": {"const": "sourceSnapshot"}, "id": {"$ref": "common.json#/definitions/opaqueId"}},
|
|
22
|
+
"required": ["kind", "id"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
},
|
|
25
|
+
"repositoryRef": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {"kind": {"const": "gitSource"}, "id": {"$ref": "common.json#/definitions/opaqueId"}},
|
|
28
|
+
"required": ["kind", "id"],
|
|
29
|
+
"additionalProperties": false
|
|
30
|
+
},
|
|
31
|
+
"labels": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {"type": "string", "minLength": 1, "maxLength": 40, "pattern": "^[a-z0-9][a-z0-9._-]*$(?![\\s\\S])"},
|
|
34
|
+
"maxItems": 64,
|
|
35
|
+
"uniqueItems": true
|
|
36
|
+
},
|
|
37
|
+
"userLabels": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1,
|
|
42
|
+
"maxLength": 40,
|
|
43
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$(?![\\s\\S])",
|
|
44
|
+
"not": {
|
|
45
|
+
"pattern": "^(darwin|linux|win32|aix|android|freebsd|openbsd|sunos|cygwin|netbsd|haiku|arm|arm64|ia32|x64|loong64|mips|mipsel|ppc|ppc64|riscv64|s390|s390x|gui)$|^(xcode|ios|node)-"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"maxItems": 64,
|
|
49
|
+
"uniqueItems": true,
|
|
50
|
+
"description": "用户自定义标签,不占用 OS、架构、gui 与 xcode-/ios-/node- 探测命名空间"
|
|
51
|
+
},
|
|
52
|
+
"resourceName": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"pattern": "^(?:slots|group:(?!host$(?![\\s\\S]))[^\\s\\u0000-\\u001f\\u007f]{1,100})$(?![\\s\\S])",
|
|
55
|
+
"description": "slots 对应 host;其他本机命名组用 group:<原名>,不将不同组折叠为 slots"
|
|
56
|
+
},
|
|
57
|
+
"resourceAmounts": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"propertyNames": {"$ref": "common.json#/definitions/resourceName"},
|
|
60
|
+
"maxProperties": 128,
|
|
61
|
+
"additionalProperties": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
|
62
|
+
"description": "可报告多个独立资源的容量;每个请求仍至多申请一个资源的一单位"
|
|
63
|
+
},
|
|
64
|
+
"reason": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"code": {"type": "string", "minLength": 1, "maxLength": 128},
|
|
68
|
+
"message": {"type": "string", "minLength": 1, "maxLength": 4096}
|
|
69
|
+
},
|
|
70
|
+
"required": ["code", "message"],
|
|
71
|
+
"additionalProperties": false
|
|
72
|
+
},
|
|
73
|
+
"timestamp": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"minLength": 1,
|
|
76
|
+
"maxLength": 40,
|
|
77
|
+
"pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.\\d{1,9})?Z$(?![\\s\\S])",
|
|
78
|
+
"description": "UTC 时间文字;schema 检查形状,真实日历时间由写入方保证"
|
|
79
|
+
},
|
|
80
|
+
"assignmentId": {"type": "string", "minLength": 1, "maxLength": 256, "description": "每次绑定生成的新 token,与 executor 同次 CAS 更新"},
|
|
81
|
+
"workingDirectory": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"minLength": 1,
|
|
84
|
+
"maxLength": 4096,
|
|
85
|
+
"pattern": "^(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])(?!.*(?:^|/)\\.\\.(?:/|$)).+$",
|
|
86
|
+
"description": "工作根下的相对路径;拒绝绝对路径、反斜线和 .. 段,真实落点与符号链接仍须执行方检查"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://coda.pub/schemas/executor/v1/execution.json",
|
|
4
|
+
"title": "execution:一次执行尝试的请求与完整结果",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"spec": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"protocolVersion": {"const": 1},
|
|
11
|
+
"run": {"$ref": "common.json#/definitions/resourceRef"},
|
|
12
|
+
"step": {"type": "string", "minLength": 1, "maxLength": 256},
|
|
13
|
+
"attempt": {"type": "integer", "minimum": 1, "maximum": 9007199254740991},
|
|
14
|
+
"source": {
|
|
15
|
+
"oneOf": [
|
|
16
|
+
{"type": "null"},
|
|
17
|
+
{
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {"snapshot": {"$ref": "common.json#/definitions/snapshotRef"}},
|
|
20
|
+
"required": ["snapshot"],
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"repository": {"$ref": "common.json#/definitions/repositoryRef"},
|
|
27
|
+
"commit": {"type": "string", "minLength": 1, "maxLength": 64, "pattern": "^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$(?![\\s\\S])"}
|
|
28
|
+
},
|
|
29
|
+
"required": ["repository", "commit"],
|
|
30
|
+
"additionalProperties": false
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"requirements": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {"labels": {"$ref": "common.json#/definitions/labels"}},
|
|
37
|
+
"required": ["labels"],
|
|
38
|
+
"additionalProperties": false
|
|
39
|
+
},
|
|
40
|
+
"placement": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"locality": {"enum": ["local", "any"], "default": "any"},
|
|
44
|
+
"executor": {"$ref": "common.json#/definitions/executorRef"}
|
|
45
|
+
},
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"description": "生产者的硬约束,不是调度绑定;省略等同 any。指定 executor 与 locality 必须同时满足,不能隐式回退"
|
|
48
|
+
},
|
|
49
|
+
"resources": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"propertyNames": {"$ref": "common.json#/definitions/resourceName"},
|
|
52
|
+
"maxProperties": 1,
|
|
53
|
+
"additionalProperties": {"const": 1},
|
|
54
|
+
"description": "空对象表示不占资源;否则只申请一个资源的一单位,不接受多单位或资源向量"
|
|
55
|
+
},
|
|
56
|
+
"exclusiveResources": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"items": {"$ref": "common.json#/definitions/resourceName"},
|
|
59
|
+
"maxItems": 1,
|
|
60
|
+
"uniqueItems": true,
|
|
61
|
+
"default": [],
|
|
62
|
+
"description": "空数组或独占申请的那一个资源;执行方核对与 resources 的键一致,不换算成容量数字"
|
|
63
|
+
},
|
|
64
|
+
"context": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"workflow": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"name": {"type": "string", "minLength": 1, "maxLength": 256},
|
|
71
|
+
"job": {"type": "string", "minLength": 1, "maxLength": 256},
|
|
72
|
+
"runId": {"$ref": "common.json#/definitions/opaqueId"},
|
|
73
|
+
"runAttempt": {"type": "integer", "minimum": 1, "maximum": 9007199254740991},
|
|
74
|
+
"sha": {"type": "string", "pattern": "^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})?$(?![\\s\\S])"},
|
|
75
|
+
"ref": {"type": "string", "maxLength": 1024},
|
|
76
|
+
"repository": {"type": "string", "maxLength": 2048}
|
|
77
|
+
},
|
|
78
|
+
"required": ["name", "job", "runId", "runAttempt", "sha", "ref", "repository"],
|
|
79
|
+
"additionalProperties": false
|
|
80
|
+
},
|
|
81
|
+
"plugin": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"id": {"type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$(?![\\s\\S])"},
|
|
85
|
+
"version": {"type": "string", "minLength": 1, "maxLength": 128}
|
|
86
|
+
},
|
|
87
|
+
"required": ["id", "version"],
|
|
88
|
+
"additionalProperties": false
|
|
89
|
+
},
|
|
90
|
+
"audience": {"const": "user"},
|
|
91
|
+
"outputNames": {"type": "array", "maxItems": 64, "uniqueItems": true,
|
|
92
|
+
"items": {"type": "string", "minLength": 1, "maxLength": 128}}
|
|
93
|
+
},
|
|
94
|
+
"additionalProperties": false,
|
|
95
|
+
"description": "可信生产者给出的运行上下文与输出声明;不是环境快照或授权凭证,本机路径和密文不进入此字段"
|
|
96
|
+
},
|
|
97
|
+
"task": {
|
|
98
|
+
"oneOf": [
|
|
99
|
+
{
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": {
|
|
102
|
+
"script": {"type": "string", "minLength": 1, "maxLength": 65536},
|
|
103
|
+
"shell": {"type": "string", "minLength": 1, "maxLength": 4096}
|
|
104
|
+
},
|
|
105
|
+
"required": ["script", "shell"],
|
|
106
|
+
"additionalProperties": false
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"type": "object",
|
|
110
|
+
"properties": {
|
|
111
|
+
"command": {"type": "string", "minLength": 1, "maxLength": 4096},
|
|
112
|
+
"args": {"type": "array", "items": {"type": "string", "minLength": 0, "maxLength": 16384}, "maxItems": 256}
|
|
113
|
+
},
|
|
114
|
+
"required": ["command", "args"],
|
|
115
|
+
"additionalProperties": false
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"workingDirectory": {"$ref": "common.json#/definitions/workingDirectory", "default": "."},
|
|
120
|
+
"env": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"propertyNames": {"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$(?![\\s\\S])"},
|
|
123
|
+
"maxProperties": 128,
|
|
124
|
+
"additionalProperties": {"type": "string", "minLength": 0, "maxLength": 16384}
|
|
125
|
+
},
|
|
126
|
+
"timeoutMs": {"type": "integer", "minimum": 1, "maximum": 9007199254740991},
|
|
127
|
+
"cancelGraceMs": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
|
128
|
+
"cancel": {"type": "boolean", "default": false},
|
|
129
|
+
"executor": {
|
|
130
|
+
"oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/executorRef"}],
|
|
131
|
+
"description": "scheduler 写入的顶层单值绑定,与 assignmentId 同次 CAS 更新,不参与补充合并"
|
|
132
|
+
},
|
|
133
|
+
"assignmentId": {"oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/assignmentId"}]}
|
|
134
|
+
},
|
|
135
|
+
"required": ["protocolVersion", "run", "attempt", "source", "requirements", "resources", "task", "env", "executor", "assignmentId"],
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"allOf": [
|
|
138
|
+
{
|
|
139
|
+
"if": {"properties": {"resources": {"maxProperties": 0}}},
|
|
140
|
+
"then": {"properties": {"exclusiveResources": {"maxItems": 0}}}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"if": {"properties": {"executor": {"type": "null"}}},
|
|
144
|
+
"then": {"properties": {"assignmentId": {"type": "null"}}},
|
|
145
|
+
"else": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}}
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"description": "一次执行尝试;绑定后任务、源码和需求冻结,取消为单向意图"
|
|
149
|
+
},
|
|
150
|
+
"status": {
|
|
151
|
+
"type": "object",
|
|
152
|
+
"properties": {
|
|
153
|
+
"phase": {"enum": ["queued", "preparing", "running", "finished"]},
|
|
154
|
+
"reason": {"$ref": "common.json#/definitions/reason"},
|
|
155
|
+
"assignmentId": {"oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/assignmentId"}]},
|
|
156
|
+
"startedAt": {"$ref": "common.json#/definitions/timestamp"},
|
|
157
|
+
"finishedAt": {"$ref": "common.json#/definitions/timestamp"},
|
|
158
|
+
"executionRun": {"$ref": "common.json#/definitions/resourceRef"},
|
|
159
|
+
"admission": {"oneOf": [{"type": "null"}, {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"properties": {
|
|
162
|
+
"resource": {"$ref": "common.json#/definitions/resourceName"},
|
|
163
|
+
"lease": {"$ref": "common.json#/definitions/opaqueId"},
|
|
164
|
+
"slots": {
|
|
165
|
+
"type": "array",
|
|
166
|
+
"items": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
|
167
|
+
"maxItems": 4096,
|
|
168
|
+
"minItems": 1,
|
|
169
|
+
"uniqueItems": true
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"required": ["lease", "slots"],
|
|
173
|
+
"additionalProperties": false
|
|
174
|
+
}], "description": "无资源请求为 null;占资源须有真实 lease 与名额,resource 省略表示 slots。执行方按请求核对,不用 null 绕过准入"},
|
|
175
|
+
"outcome": {"enum": ["succeeded", "failed", "cancelled", "rejected"]},
|
|
176
|
+
"exitCode": {"type": ["integer", "null"]},
|
|
177
|
+
"outputs": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"maxProperties": 64,
|
|
180
|
+
"propertyNames": {"type": "string", "minLength": 1, "maxLength": 128},
|
|
181
|
+
"additionalProperties": {"type": "string", "minLength": 0, "maxLength": 4096}
|
|
182
|
+
},
|
|
183
|
+
"summary": {"type": "string", "minLength": 0, "maxLength": 4096},
|
|
184
|
+
"outputTail": {"type": "string", "minLength": 0, "maxLength": 16384},
|
|
185
|
+
"logs": {
|
|
186
|
+
"type": "array",
|
|
187
|
+
"items": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"properties": {
|
|
190
|
+
"ref": {"$ref": "common.json#/definitions/resourceRef"},
|
|
191
|
+
"cursor": {"type": "string", "minLength": 1, "maxLength": 2048}
|
|
192
|
+
},
|
|
193
|
+
"required": ["ref"],
|
|
194
|
+
"additionalProperties": false
|
|
195
|
+
},
|
|
196
|
+
"maxItems": 64
|
|
197
|
+
},
|
|
198
|
+
"artifacts": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"ref": {"$ref": "common.json#/definitions/resourceRef"},
|
|
204
|
+
"name": {"type": "string", "minLength": 1, "maxLength": 256},
|
|
205
|
+
"sha256": {"type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[0-9a-fA-F]{64}$"},
|
|
206
|
+
"size": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
|
207
|
+
"mediaType": {"type": "string", "minLength": 1, "maxLength": 256}
|
|
208
|
+
},
|
|
209
|
+
"required": ["ref", "name"],
|
|
210
|
+
"additionalProperties": false
|
|
211
|
+
},
|
|
212
|
+
"maxItems": 64
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"required": [],
|
|
216
|
+
"additionalProperties": false,
|
|
217
|
+
"anyOf": [{"maxProperties": 0}, {"required": ["phase", "assignmentId"]}],
|
|
218
|
+
"allOf": [
|
|
219
|
+
{
|
|
220
|
+
"if": {"properties": {"phase": {"const": "finished"}}, "required": ["phase"]},
|
|
221
|
+
"then": {"required": ["outcome", "finishedAt"]},
|
|
222
|
+
"else": {"not": {"anyOf": [{"required": ["outcome"]}, {"required": ["exitCode"]}, {"required": ["finishedAt"]}]}}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"if": {"properties": {"phase": {"enum": ["preparing", "running"]}}, "required": ["phase"]},
|
|
226
|
+
"then": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"if": {"properties": {"phase": {"const": "running"}}, "required": ["phase"]},
|
|
230
|
+
"then": {"required": ["startedAt", "executionRun", "admission"]}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"if": {"properties": {"outcome": {"enum": ["succeeded", "failed"]}}, "required": ["outcome"]},
|
|
234
|
+
"then": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}, "required": ["startedAt"]}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"if": {"properties": {"outcome": {"const": "rejected"}}, "required": ["outcome"]},
|
|
238
|
+
"then": {"not": {"required": ["startedAt"]}, "properties": {"exitCode": {"type": "null"}}}
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
"description": "空对象表示未知;绑定后由执行方完整回传,按 assignmentId 区分旧回答"
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
"required": ["spec", "status"],
|
|
245
|
+
"additionalProperties": false
|
|
246
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://coda.pub/schemas/executor/v1/executor.json",
|
|
4
|
+
"title": "executor:运行器配置意图与持有端事实",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"spec": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"protocolVersion": {"const": 1},
|
|
11
|
+
"enabled": {"type": "boolean"},
|
|
12
|
+
"capacity": {"$ref": "common.json#/definitions/resourceAmounts"},
|
|
13
|
+
"labels": {"$ref": "common.json#/definitions/userLabels"},
|
|
14
|
+
"title": {"type": "string", "minLength": 1, "maxLength": 200}
|
|
15
|
+
},
|
|
16
|
+
"required": ["protocolVersion", "enabled", "capacity", "labels"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"description": "运行器配置意图;无 target,由持有端维护"
|
|
19
|
+
},
|
|
20
|
+
"status": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"detectedLabels": {"$ref": "common.json#/definitions/labels"},
|
|
24
|
+
"capabilities": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"os": {"type": "string", "minLength": 1, "maxLength": 40},
|
|
28
|
+
"arch": {"type": "string", "minLength": 1, "maxLength": 40},
|
|
29
|
+
"nodeVersion": {"type": "string", "minLength": 1, "maxLength": 128},
|
|
30
|
+
"xcode": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"version": {"type": "string", "minLength": 1, "maxLength": 128},
|
|
34
|
+
"build": {"type": "string", "minLength": 1, "maxLength": 128}
|
|
35
|
+
},
|
|
36
|
+
"required": ["version", "build"],
|
|
37
|
+
"additionalProperties": false
|
|
38
|
+
},
|
|
39
|
+
"simulatorRuntimes": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"id": {"type": "string", "minLength": 1, "maxLength": 256},
|
|
45
|
+
"version": {"type": "string", "minLength": 1, "maxLength": 128},
|
|
46
|
+
"available": {"type": "boolean"}
|
|
47
|
+
},
|
|
48
|
+
"required": ["id", "version", "available"],
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
},
|
|
51
|
+
"maxItems": 256
|
|
52
|
+
},
|
|
53
|
+
"graphicalSession": {"type": ["boolean", "null"]}
|
|
54
|
+
},
|
|
55
|
+
"required": ["os", "arch", "nodeVersion", "graphicalSession"],
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
},
|
|
58
|
+
"capacity": {"$ref": "common.json#/definitions/resourceAmounts"},
|
|
59
|
+
"occupied": {"$ref": "common.json#/definitions/resourceAmounts"},
|
|
60
|
+
"available": {"$ref": "common.json#/definitions/resourceAmounts"},
|
|
61
|
+
"accepting": {"type": "boolean"},
|
|
62
|
+
"reason": {"$ref": "common.json#/definitions/reason"},
|
|
63
|
+
"observedAt": {"$ref": "common.json#/definitions/timestamp"},
|
|
64
|
+
"observedRevision": {"$ref": "common.json#/definitions/opaqueId"},
|
|
65
|
+
"executionFeatures": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"multiResource": {"const": false}, "multiUnit": {"const": false}, "exclusive": {"type": "boolean"},
|
|
69
|
+
"noResources": {"type": "boolean"}, "namedResources": {"type": "boolean"}, "context": {"type": "boolean"}
|
|
70
|
+
},
|
|
71
|
+
"required": ["multiResource", "multiUnit", "exclusive"],
|
|
72
|
+
"additionalProperties": false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": [],
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"anyOf": [
|
|
78
|
+
{"maxProperties": 0},
|
|
79
|
+
{
|
|
80
|
+
"required": [
|
|
81
|
+
"detectedLabels",
|
|
82
|
+
"capabilities",
|
|
83
|
+
"capacity",
|
|
84
|
+
"occupied",
|
|
85
|
+
"available",
|
|
86
|
+
"accepting",
|
|
87
|
+
"observedAt",
|
|
88
|
+
"observedRevision",
|
|
89
|
+
"executionFeatures"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"description": "空对象表示未知;非空为持有端完整事实,不是在线或预留保证"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": ["spec", "status"],
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
}
|
package/workflow/moddle.json
CHANGED
|
@@ -47,6 +47,13 @@
|
|
|
47
47
|
{"name": "action", "isAttr": true, "type": "String"}
|
|
48
48
|
]
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
"name": "PlacementProperties",
|
|
52
|
+
"isAbstract": true,
|
|
53
|
+
"extends": ["bpmn:Process", "bpmn:ScriptTask", "bpmn:SubProcess"],
|
|
54
|
+
"description": "local 将脚本限制在发起端;any 允许符合要求的执行器。上层 local 不可被子节点放宽;显式执行器绑定仍须满足此约束",
|
|
55
|
+
"properties": [{"name": "placement", "isAttr": true, "type": "String"}]
|
|
56
|
+
},
|
|
50
57
|
{
|
|
51
58
|
"name": "RunsOnProperties",
|
|
52
59
|
"isAbstract": true,
|
package/workflow/values.json
CHANGED
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
"retry": {"type": "string", "pattern": "^[1-5]$", "description": "coda:retry 是失败后最多再试几次,须为 1 到 5 的整数"}
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
+
"PlacementProperties": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"placement": {"enum": ["local", "any"], "description": "coda:placement 只能是 local 或 any;local 限制为发起端,不能隐式回退或被子节点放宽"}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
29
35
|
"RunsOnProperties": {
|
|
30
36
|
"type": "object",
|
|
31
37
|
"properties": {
|