@coda-console/protocol 2.0.0 → 2.1.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 CHANGED
@@ -96,6 +96,15 @@ 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
+ 首版请求仅支持 `{slots: 1}`,可独占 slots;不支持多单位或多资源。两种类型均无补充栏,execution 绑定是由调度办事员 CAS 更新的单值。
105
+ executor 无 target,status 由持有端写;execution 绑定后由执行方完整回传,开跑前须经本机账本准入。
106
+ 对象协议 v1 与 npm 包版本、插件清单 v2 独立;旧 `contributes.executors` 不变。
107
+
99
108
  ## 谁在用
100
109
 
101
110
  - **Coda**:读插件时用它校验清单,再整理成自己的结构;只另看插件身份(ID、设置名在 ID 之下),以及装到一台机器上才知道的(来源、已装插件的冲突)。读流程时用 `readWorkflow` 同时校验方言、值与结构,再编译成引擎需要的数据。
@@ -0,0 +1,223 @@
1
+ # 运行器与执行请求协议 v1
2
+
3
+ `executor`(运行器)与 `execution`(一次执行尝试)是 Coda 预定的公共协议,唯一形状定义在
4
+ `@coda-console/protocol`。依据是 [coda#2078 已验收草案](https://github.com/coda-console/coda/issues/2078#issuecomment-6554881338),
5
+ 首版按验收决定收窄为 **单资源 `slots`,每请求 `slots: 1`,可独占 `slots`**。多单位、多资源须等本机账本支持后再开放。
6
+
7
+ 协议对象版本 `protocolVersion: 1`、本目录的 v1、npm 包版本 2.1.0 和插件清单的 v2 分开演进。
8
+ 本次只增加契约;不改变 `contributes.executors`,也不宣称 host、scheduler、空间引用解析或账本接口已经实现。
9
+
10
+ ## 定义与登记
11
+
12
+ `schemas/executor/v1/{executor,execution}.json` 分别描述 `{spec,status}`,公共定义在 `common.json`。
13
+ 原始 JSON 采用 draft-07,读取原文件时先向 Ajv 登记 `common.json`。模块导出把所需定义包含在每份 spec/status 中,
14
+ 只留本地 `$ref`;四份 schema 均可由一个全新的默认 Ajv 直接编译,不需自定义关键字、format 或外部 schema 注册。
15
+ `spec`、`status` 顶层均为 object。不支持的 `protocolVersion`、未知字段与未知资源键明确拒绝。
16
+
17
+ ```js
18
+ import { EXECUTOR_PROTOCOL, EXECUTION_PROTOCOL } from '@coda-console/protocol/executor';
19
+
20
+ // 现行 API 的装配示例:由 host 在提交端和执行端登记,提交端可关闭接活。
21
+ await coda.protocols.define(EXECUTOR_PROTOCOL);
22
+ await coda.protocols.define({ ...EXECUTION_PROTOCOL, controllers: ['scheduler'] });
23
+ ```
24
+
25
+ `EXECUTOR_PROTOCOL` 没有 `target`;`EXECUTION_PROTOCOL.target` 为 `executor`。
26
+ 两种类型均**没有补充栏**,不声明 append/or 合并;任务、标签、取消和绑定均不经补充者修改。
27
+ `controllers: ['scheduler']` 是现行接口的插件 ID 装配,不是协议对 host/scheduler 实现路径的依赖。
28
+ 其他生产者通过 provider 的本机创建/取消入口操作,不能冒用 `put`。
29
+
30
+ 当前 `protocols.define` 只允许一个插件登记同 kind,定义持久化后不能改变;尚不支持按协议依赖、多实现登记或同 kind 版本迁移。
31
+ 后续替换 provider 或演进声明须先解决 Coda 通用登记机制,不能仅更换 schema 文件接管既存类型。
32
+
33
+ 信封沿用 `{kind,id,instance,spec,status,revision}`。基座维护 `instance` 与不透明 `revision`;插件原样传回观察到的 revision,
34
+ 不能解析或自行递增。删除后同 id 重建是新 instance,旧回答不能复用。这里的 schema 校验 spec/status,不校验或重定义信封。
35
+ 所有 status 都允许 `{}`,表示镜像状态尚未到达,**不能据此判定未开始**;非空 status 必须是一份完整的已知状态。
36
+
37
+ ## executor:配置与本机事实
38
+
39
+ 一个对象对应一个后端实例的执行环境,身份不随显示名或插件版本改变。
40
+
41
+ | 区域 | 字段 |
42
+ | --- | --- |
43
+ | spec 必填 | `protocolVersion: 1`、`enabled`、`capacity: {slots: 非负整数}`、`labels: string[]` |
44
+ | spec 可选 | `title`(不超过 200 字符) |
45
+ | status 能力 | `detectedLabels`、`capabilities: {os,arch,nodeVersion,graphicalSession}`;可选 `xcode: {version,build}`、`simulatorRuntimes: [{id,version,available}]` |
46
+ | status 资源 | `capacity`、`occupied`、`available`,各为 `{slots: 非负整数}`;`accepting` |
47
+ | status 观察 | `observedAt`、`observedRevision`;可选 `reason: {code,message}` |
48
+ | status 支持 | `executionFeatures: {multiResource: false,multiUnit: false,exclusive: boolean}` |
49
+
50
+ 非空 status 除明确可选项外全部必填。`graphicalSession` 是 boolean 或 null;null 表示无法确定。
51
+ 探测失败写 `reason`,不将失败变成用户标签或伪造能力;可选探测项未知时省略。
52
+ 探测只读,不启动模拟器。`observedRevision` 对应探测时采用的配置版本,而非要求它等于之后每次 status 写入产生的 revision。
53
+
54
+ 标签使用小写字母、数字、`.`、`_`、`-`,以字母或数字起始,每项最多 40 字符、每列表最多 64 项、不重复。
55
+ `requirements.labels` 沿用 runsOn 的**全部满足**语义,匹配用户标签与探测标签的并集。
56
+ 保留 OS 标签 `darwin/linux/win32/aix/android/freebsd/openbsd/sunos/cygwin/netbsd/haiku`、架构标签
57
+ `arm/arm64/ia32/x64/loong64/mips/mipsel/ppc/ppc64/riscv64/s390/s390x`、`gui`,以及 `xcode-`、`ios-`、`node-` 前缀给探测结果。
58
+ spec.labels 只能加自定义标签,不能写这些保留值。精确版本和 simulator runtime ID 另外保存在结构化能力中。
59
+
60
+ `slots` 的单位是执行名额,host 映射到同一本机 `host` 账本;scheduler 比较资源键与数量,不硬编码 `group === 'host'`。
61
+ 容量可大于 1;**每次请求量仍只能为 1**。其他资源键拒绝,不能按 0 算。有能力不代表有名额。
62
+ 缩容量允许 occupied 暂时大于 capacity,此时 available 归零并停止新准入,不强杀已有任务。
63
+ 占用含旧流程与 run-held;借用或独占按账本事实计算,不能简单数 host 自己启动的进程,也不能要求独占时 available 等于 capacity − occupied。
64
+ 这些跨栏计算和准入状态由实现检查,不加动态 schema 关键字。
65
+
66
+ 对象无 target,配置经运行器持有端更新;**status 只由持有后端的实现者写**,空间层单向复制。
67
+ accepting、available、observedAt 都只是最近观察事实,不保证在线,也不是预留凭证;新鲜度阈值与选择策略属于 scheduler。
68
+
69
+ ```json
70
+ {
71
+ "spec": {
72
+ "protocolVersion": 1,
73
+ "enabled": true,
74
+ "capacity": {"slots": 2},
75
+ "labels": ["build-lab"],
76
+ "title": "Mac mini"
77
+ },
78
+ "status": {
79
+ "detectedLabels": ["darwin", "arm64", "xcode-26.1", "node-26"],
80
+ "capabilities": {"os": "darwin", "arch": "arm64", "nodeVersion": "26.8.2", "graphicalSession": null, "xcode": {"version": "26.1", "build": "17B55"}},
81
+ "capacity": {"slots": 2},
82
+ "occupied": {"slots": 1},
83
+ "available": {"slots": 1},
84
+ "accepting": true,
85
+ "observedAt": "2026-11-01T07:00:00Z",
86
+ "observedRevision": "opaque-config-revision",
87
+ "executionFeatures": {"multiResource": false, "multiUnit": false, "exclusive": true}
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## execution:一次尝试
93
+
94
+ 正本在发起后端。`run: ResourceRef` 关联已有运行对象,`step?` 关联步骤,`attempt` 是正整数。
95
+ 重新执行创建新 execution ID 和新 attempt,保留上一尝试,不另造 Run 或会话命令队列。取消不是新尝试。
96
+
97
+ | spec 区域 | 形状与约束 |
98
+ | --- | --- |
99
+ | 身份 | `protocolVersion: 1`、`run`、`attempt` 必填;`step` 可选 |
100
+ | 源码 | `source` 必填:null、`{snapshot: ResourceRef<sourceSnapshot>}`、`{repository: ResourceRef<gitSource>,commit: 完整 SHA}` 三选一;SHA 为 40 或 64 位十六进制 |
101
+ | 能力 | `requirements: {labels: string[]}`,可为空列表 |
102
+ | 资源 | `resources: {slots: 1}`;`exclusiveResources` 默认 `[]`,仅可为 `[]` 或 `["slots"]` |
103
+ | 任务 | `{script,shell}` 或 `{command,args}`,严格二选一;程序名与参数不隐含 shell |
104
+ | 环境 | `workingDirectory` 默认 `.`;`env: Record<string,string>` 必填,只含声明变量,可为空对象 |
105
+ | 时限 | `timeoutMs?` 正整数;`cancelGraceMs?` 非负整数;缺省策略由执行实现决定 |
106
+ | 取消 | `cancel` 默认 false,是单向意图 |
107
+ | 绑定 | `executor: ResourceRef<executor>\|null`、`assignmentId: string\|null` 必填,两者同为 null 或同时有值 |
108
+
109
+ 独占是 `exclusiveResources: ["slots"]`,而非把“全部名额”换成某次观察到的容量数字。
110
+ 生产端必须先看 `executionFeatures.exclusive`;不支持独占的执行方明确拒绝,不降级为普通名额。
111
+ 原 coda:group 由生产端/资源 provider 声明映射;runsOn 放入 labels,两者独立。
112
+
113
+ workingDirectory 是源码根下的相对路径;没有源码时相对执行方隔离目录。
114
+ schema 拒绝绝对路径、盘符、反斜线、控制字符和 `..` 路径段;执行方仍须检查真实落点、符号链接和来源权限。
115
+ 工作树先由提交端制成 ready 快照,执行端读取快照的精确 commit/ref,不重新抓取“最新工作树”。
116
+ env 不等于提交机进程环境;沿用 hook 的声明变量、密文与鉴权边界,不把本机凭据或绝对路径放进跨机 spec。
117
+
118
+ JSON Schema 的 default 只声明缺省值。单独使用默认 Ajv 校验不会填值;现行 Provider 用 `useDefaults` 补齐
119
+ `workingDirectory`、`exclusiveResources`、`cancel`。不使用 default 填造任何 status 事实。
120
+
121
+ | 非空 status | 约束 |
122
+ | --- | --- |
123
+ | 基本 | `phase: queued\|preparing\|running\|finished` 与 `assignmentId` 必填;可选 `reason` |
124
+ | 进度 | 可选 `startedAt`、`executionRun: ResourceRef`、`admission: {lease,slots}`;running 三者必填 |
125
+ | 准入 | lease 是不透明本机凭证,slots 是不重复的非负名额序号数组;独占可记录多个序号,仅表示事实 |
126
+ | 完成 | finished 必须给 `finishedAt`、`outcome: succeeded\|failed\|cancelled\|rejected`;其他 phase 不接受 finishedAt、outcome、exitCode |
127
+ | 结果 | 可选 `exitCode: integer\|null`、`outputs`、`summary`、`outputTail`、`logs`、`artifacts` |
128
+
129
+ preparing/running 必须有 assignmentId;succeeded/failed 还必须有 startedAt 与 assignmentId。
130
+ 取消可在未绑定、未启动时完成;rejected 表示本次 assignment 不会启动,不接受 startedAt 或非 null exitCode。
131
+ 没启动或无法确定退出码时省略或写 null,不能造 0。这些字段的静态组合不能代替实际进程、收尾和状态迁移检查。
132
+
133
+ 文本限制使用 JSON Schema 的 Unicode 字符数,不是 UTF-8 字节或 UTF-16 长度:summary 最多 4096、outputTail 最多 16384;
134
+ outputs 最多 64 项,键 1–128 字符、值最多 4096。logs/artifacts 各最多 64 项。
135
+ logs 项为 `{ref: ResourceRef,cursor?}`;artifacts 项为 `{ref: ResourceRef,name,sha256?,size?,mediaType?}`。
136
+ 引用不接受本机绝对路径代替对象;sha256 为 64 位十六进制,size 为非负安全整数。
137
+ 任务脚本最多 65536 字符,command/shell 最多 4096,args 最多 256 项、每项最多 16384;
138
+ env 最多 128 项,变量名使用字母/下划线起始的常规环境变量名、最多 128 字符,值最多 16384。
139
+ 数组、键和值都有上限,不能把无限日志复制进 status;完整限制见 schema。
140
+ 时间统一使用 UTC `...Z`,schema 仅检查日期时间文字的形状,写入方保证日期真实、时序合理。
141
+
142
+ ```json
143
+ {
144
+ "spec": {
145
+ "protocolVersion": 1,
146
+ "run": {"kind": "runs", "id": "opaque-run-ref"},
147
+ "step": "check",
148
+ "attempt": 1,
149
+ "source": {"snapshot": {"kind": "sourceSnapshot", "id": "opaque-snapshot-ref"}},
150
+ "requirements": {"labels": ["darwin", "arm64"]},
151
+ "resources": {"slots": 1},
152
+ "exclusiveResources": ["slots"],
153
+ "task": {"command": "npm", "args": ["test"]},
154
+ "workingDirectory": ".",
155
+ "env": {"CI": "true"},
156
+ "cancel": false,
157
+ "executor": {"kind": "executor", "id": "opaque-executor-ref"},
158
+ "assignmentId": "assignment-1"
159
+ },
160
+ "status": {
161
+ "phase": "finished",
162
+ "assignmentId": "assignment-1",
163
+ "startedAt": "2026-11-01T07:00:00Z",
164
+ "finishedAt": "2026-11-01T07:01:00Z",
165
+ "executionRun": {"kind": "runs", "id": "opaque-execution-run-ref"},
166
+ "admission": {"lease": "opaque-lease", "slots": [0, 1]},
167
+ "outcome": "succeeded",
168
+ "exitCode": 0,
169
+ "summary": "检查通过",
170
+ "logs": [{"ref": {"kind": "logs", "id": "opaque-log-ref"}}]
171
+ }
172
+ }
173
+ ```
174
+
175
+ ResourceRef 的插件可见形状严格为 `{kind,id}`,id 是适配层提供的不透明引用。
176
+ 示例里的 `runs`、`logs` 是引用示意,不在本协议登记新类型;实际使用现有运行和日志对象的 kind。
177
+ 适配层必须保持源码、运行、日志和产物的持有者归属,不能把本机 `remote:…` 字符串原样传给另一台便认为能解析。
178
+ 日志衔接现有运行日志读面;产物这里只预留引用、大小与摘要,字节传输和 sha256 核验仍属后续 artifacts 实施步骤。
179
+
180
+ ## 写权限、绑定与改派
181
+
182
+ 1. executor 无 target,status 权威是持有端。execution 未绑定时,本机 provider/获准 scheduler 可写 queued 与原因;
183
+ 创建时可给 queued,缺失状态是未知。scheduler 只处理本机正本,按观察到的 revision 同次 CAS 写入 executor 与新的 assignmentId。
184
+ 绑定是顶层单值,**不走 append/or 补充合并**,不将多个候选合并成绑定。
185
+ 2. 绑定后任务、源码、身份、资源与需求冻结,只可单向设置 cancel;安全改派仅更换绑定两栏。
186
+ 空间桥将 executor 翻译成 `coda.pub/target`。执行方只观察 assigned,并核对具体 executor ID 是自己实现的;
187
+ 指向后端不表示该后端所有执行环境都可领单。**绑定后只有执行方完整回传 status**,包括字段删除;
188
+ 其他节点无权写,持有者不能伪造远端完成。旧 status 按 assignmentId 区分,不能拿旧绑定结果解释新绑定。
189
+ 3. 未绑定请求可直接选择。已绑定请求只有当前 assignment 明确拒绝,或撤销已确认且证明不会再启动,才可改派。
190
+ 对齐 assignmentId、验证拒绝/撤销事实和权限是机制责任,单凭符合 schema 的对象不能授权改派。
191
+ 旧 queued、缺 startedAt、空 status、租约超时或节点掉线均不能证明未开始;已开始或是否开始不明,保留原绑定与运行引用并恢复观察。
192
+ 4. 持有端将不可达作为观察状态展示,不代写执行结果。取消或撤下镜像也不是原进程已停证明。
193
+ 自动重试依任务幂等性和重试政策,创建新 attempt;协议不承诺网络分区中的 exactly-once。
194
+
195
+ ## 开跑前的本机账本准入与恢复
196
+
197
+ 执行方须在 prepare/启动前,持久登记 **execution 的 instance + assignmentId**,经同一本机账本原子获得资源与固定名额序号。
198
+ 调度看到 available 或 binding 成功均不等于准入。普通请求只占一个 slots;独占须走账本独占动作,不能连续读 available 或分批占额模拟。
199
+ 容量冲突可排队或明确拒绝,scheduler 决定是否在满足改派条件后换目标。
200
+ 多键/多数量原子准入尚未开放,schema 和 executionFeatures 均不宣称支持。
201
+
202
+ 进程和 cleanup 确认结束后才释放;故障期间保留待恢复占用。重启 list-watch 的 ADDED 只能对账,不得再次启动同一 instance + assignment。
203
+ 旧镜像指纹/revision 防回写不能代替防重复启动。被拒绝的旧结果不能简单换成最新 revision 重交。
204
+
205
+ `control` 对同对象串行回调;不能 await 整段长任务堵住取消。
206
+ 办事员登记并启动/跟随持久运行后返回,后续观察更新取消进程组。取消只改 spec.cancel,执行方重读同 assignment 的当前意图,
207
+ 完成取消与收尾后报告 cancelled;取消不能回退为 false,也不能回写旧请求的结果。
208
+
209
+ ## 与现有执行链的衔接边界
210
+
211
+ 执行保留 `prepare_job / run_script_step / cleanup_job` 三段 hook,以及 `{command,responseFile,args,state}`、准备 state、
212
+ 日志、退出码、取消进程组、失败也收尾的语义。清单 `command` 是 hook 入口,与 `task.command` 不同。
213
+ 源码 provider 给出精确 commit/ref 与本机鉴权代理入口;hook 本机路径在执行方生成,不进入跨机 spec。
214
+
215
+ 缓存目录建议 `$CODA_PLUGIN_STATE/workspaces/<source-key>/<slot-index>/`:source-key 来自来源工作副本身份及对象 instance,
216
+ 不能用 origin URL、提交号或快照 ID 代替;同仓库不同 linked worktree 也隔离。
217
+ slot-index 来自本机账本,scheduler 不猜序号;借用也不得使目录重入。日志/响应文件另放尝试目录。
218
+ 准入后 fetch 精确 ref/commit、核对实际 commit,再 reset --hard 与 clean -fd,保留忽略缓存;cleanup 清理本次临时资源并放开资源,保留缓存副本。
219
+ 发布/部署仍由对应流程和插件检查已合并提交准入,不因支持快照删除 origin/发布检查。
220
+
221
+ 旧 Executor 发布与 steps/Step 跨机请求由后续消费方切换撤除,不永久保留两套调度真相。
222
+ 本包只交付标准 JSON Schema 和规范;引用可移植解析、本机鉴权入口、账本准入/取消/释放及稳定序号、恢复日志跟随、
223
+ 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.0.0",
3
+ "version": "2.1.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,84 @@
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
+ "resourceAmounts": {
53
+ "type": "object",
54
+ "properties": {"slots": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}},
55
+ "required": ["slots"],
56
+ "additionalProperties": false,
57
+ "description": "首版只有 slots;容量与占用可大于 1,请求量另限为 1"
58
+ },
59
+ "reason": {
60
+ "type": "object",
61
+ "properties": {
62
+ "code": {"type": "string", "minLength": 1, "maxLength": 128},
63
+ "message": {"type": "string", "minLength": 1, "maxLength": 4096}
64
+ },
65
+ "required": ["code", "message"],
66
+ "additionalProperties": false
67
+ },
68
+ "timestamp": {
69
+ "type": "string",
70
+ "minLength": 1,
71
+ "maxLength": 40,
72
+ "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])",
73
+ "description": "UTC 时间文字;schema 检查形状,真实日历时间由写入方保证"
74
+ },
75
+ "assignmentId": {"type": "string", "minLength": 1, "maxLength": 256, "description": "每次绑定生成的新 token,与 executor 同次 CAS 更新"},
76
+ "workingDirectory": {
77
+ "type": "string",
78
+ "minLength": 1,
79
+ "maxLength": 4096,
80
+ "pattern": "^(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])(?!.*(?:^|/)\\.\\.(?:/|$)).+$",
81
+ "description": "工作根下的相对路径;拒绝绝对路径、反斜线和 .. 段,真实落点与符号链接仍须执行方检查"
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,199 @@
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
+ "resources": {
41
+ "type": "object",
42
+ "properties": {"slots": {"const": 1}},
43
+ "required": ["slots"],
44
+ "additionalProperties": false,
45
+ "description": "首版仅 slots: 1;不接受多单位或其他资源"
46
+ },
47
+ "exclusiveResources": {
48
+ "type": "array",
49
+ "items": {"const": "slots"},
50
+ "maxItems": 1,
51
+ "uniqueItems": true,
52
+ "default": [],
53
+ "description": "空数组或独占 slots;不换算成观察到的容量数字"
54
+ },
55
+ "task": {
56
+ "oneOf": [
57
+ {
58
+ "type": "object",
59
+ "properties": {
60
+ "script": {"type": "string", "minLength": 1, "maxLength": 65536},
61
+ "shell": {"type": "string", "minLength": 1, "maxLength": 4096}
62
+ },
63
+ "required": ["script", "shell"],
64
+ "additionalProperties": false
65
+ },
66
+ {
67
+ "type": "object",
68
+ "properties": {
69
+ "command": {"type": "string", "minLength": 1, "maxLength": 4096},
70
+ "args": {"type": "array", "items": {"type": "string", "minLength": 0, "maxLength": 16384}, "maxItems": 256}
71
+ },
72
+ "required": ["command", "args"],
73
+ "additionalProperties": false
74
+ }
75
+ ]
76
+ },
77
+ "workingDirectory": {"$ref": "common.json#/definitions/workingDirectory", "default": "."},
78
+ "env": {
79
+ "type": "object",
80
+ "propertyNames": {"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$(?![\\s\\S])"},
81
+ "maxProperties": 128,
82
+ "additionalProperties": {"type": "string", "minLength": 0, "maxLength": 16384}
83
+ },
84
+ "timeoutMs": {"type": "integer", "minimum": 1, "maximum": 9007199254740991},
85
+ "cancelGraceMs": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
86
+ "cancel": {"type": "boolean", "default": false},
87
+ "executor": {
88
+ "oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/executorRef"}],
89
+ "description": "scheduler 写入的顶层单值绑定,与 assignmentId 同次 CAS 更新,不参与补充合并"
90
+ },
91
+ "assignmentId": {"oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/assignmentId"}]}
92
+ },
93
+ "required": ["protocolVersion", "run", "attempt", "source", "requirements", "resources", "task", "env", "executor", "assignmentId"],
94
+ "additionalProperties": false,
95
+ "allOf": [
96
+ {
97
+ "if": {"properties": {"executor": {"type": "null"}}},
98
+ "then": {"properties": {"assignmentId": {"type": "null"}}},
99
+ "else": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}}
100
+ }
101
+ ],
102
+ "description": "一次执行尝试;绑定后任务、源码和需求冻结,取消为单向意图"
103
+ },
104
+ "status": {
105
+ "type": "object",
106
+ "properties": {
107
+ "phase": {"enum": ["queued", "preparing", "running", "finished"]},
108
+ "reason": {"$ref": "common.json#/definitions/reason"},
109
+ "assignmentId": {"oneOf": [{"type": "null"}, {"$ref": "common.json#/definitions/assignmentId"}]},
110
+ "startedAt": {"$ref": "common.json#/definitions/timestamp"},
111
+ "finishedAt": {"$ref": "common.json#/definitions/timestamp"},
112
+ "executionRun": {"$ref": "common.json#/definitions/resourceRef"},
113
+ "admission": {
114
+ "type": "object",
115
+ "properties": {
116
+ "lease": {"$ref": "common.json#/definitions/opaqueId"},
117
+ "slots": {
118
+ "type": "array",
119
+ "items": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
120
+ "maxItems": 4096,
121
+ "minItems": 1,
122
+ "uniqueItems": true
123
+ }
124
+ },
125
+ "required": ["lease", "slots"],
126
+ "additionalProperties": false
127
+ },
128
+ "outcome": {"enum": ["succeeded", "failed", "cancelled", "rejected"]},
129
+ "exitCode": {"type": ["integer", "null"]},
130
+ "outputs": {
131
+ "type": "object",
132
+ "maxProperties": 64,
133
+ "propertyNames": {"type": "string", "minLength": 1, "maxLength": 128},
134
+ "additionalProperties": {"type": "string", "minLength": 0, "maxLength": 4096}
135
+ },
136
+ "summary": {"type": "string", "minLength": 0, "maxLength": 4096},
137
+ "outputTail": {"type": "string", "minLength": 0, "maxLength": 16384},
138
+ "logs": {
139
+ "type": "array",
140
+ "items": {
141
+ "type": "object",
142
+ "properties": {
143
+ "ref": {"$ref": "common.json#/definitions/resourceRef"},
144
+ "cursor": {"type": "string", "minLength": 1, "maxLength": 2048}
145
+ },
146
+ "required": ["ref"],
147
+ "additionalProperties": false
148
+ },
149
+ "maxItems": 64
150
+ },
151
+ "artifacts": {
152
+ "type": "array",
153
+ "items": {
154
+ "type": "object",
155
+ "properties": {
156
+ "ref": {"$ref": "common.json#/definitions/resourceRef"},
157
+ "name": {"type": "string", "minLength": 1, "maxLength": 256},
158
+ "sha256": {"type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[0-9a-fA-F]{64}$"},
159
+ "size": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
160
+ "mediaType": {"type": "string", "minLength": 1, "maxLength": 256}
161
+ },
162
+ "required": ["ref", "name"],
163
+ "additionalProperties": false
164
+ },
165
+ "maxItems": 64
166
+ }
167
+ },
168
+ "required": [],
169
+ "additionalProperties": false,
170
+ "anyOf": [{"maxProperties": 0}, {"required": ["phase", "assignmentId"]}],
171
+ "allOf": [
172
+ {
173
+ "if": {"properties": {"phase": {"const": "finished"}}, "required": ["phase"]},
174
+ "then": {"required": ["outcome", "finishedAt"]},
175
+ "else": {"not": {"anyOf": [{"required": ["outcome"]}, {"required": ["exitCode"]}, {"required": ["finishedAt"]}]}}
176
+ },
177
+ {
178
+ "if": {"properties": {"phase": {"enum": ["preparing", "running"]}}, "required": ["phase"]},
179
+ "then": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}}
180
+ },
181
+ {
182
+ "if": {"properties": {"phase": {"const": "running"}}, "required": ["phase"]},
183
+ "then": {"required": ["startedAt", "executionRun", "admission"]}
184
+ },
185
+ {
186
+ "if": {"properties": {"outcome": {"enum": ["succeeded", "failed"]}}, "required": ["outcome"]},
187
+ "then": {"properties": {"assignmentId": {"$ref": "common.json#/definitions/assignmentId"}}, "required": ["startedAt"]}
188
+ },
189
+ {
190
+ "if": {"properties": {"outcome": {"const": "rejected"}}, "required": ["outcome"]},
191
+ "then": {"not": {"required": ["startedAt"]}, "properties": {"exitCode": {"type": "null"}}}
192
+ }
193
+ ],
194
+ "description": "空对象表示未知;绑定后由执行方完整回传,按 assignmentId 区分旧回答"
195
+ }
196
+ },
197
+ "required": ["spec", "status"],
198
+ "additionalProperties": false
199
+ }
@@ -0,0 +1,95 @@
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": {"multiResource": {"const": false}, "multiUnit": {"const": false}, "exclusive": {"type": "boolean"}},
68
+ "required": ["multiResource", "multiUnit", "exclusive"],
69
+ "additionalProperties": false
70
+ }
71
+ },
72
+ "required": [],
73
+ "additionalProperties": false,
74
+ "anyOf": [
75
+ {"maxProperties": 0},
76
+ {
77
+ "required": [
78
+ "detectedLabels",
79
+ "capabilities",
80
+ "capacity",
81
+ "occupied",
82
+ "available",
83
+ "accepting",
84
+ "observedAt",
85
+ "observedRevision",
86
+ "executionFeatures"
87
+ ]
88
+ }
89
+ ],
90
+ "description": "空对象表示未知;非空为持有端完整事实,不是在线或预留保证"
91
+ }
92
+ },
93
+ "required": ["spec", "status"],
94
+ "additionalProperties": false
95
+ }