@coda-console/protocol 2.1.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 CHANGED
@@ -101,7 +101,9 @@ agentValueProblem('review.json', review, '工具的审核');
101
101
  从 2.1.0 起,`executor` 与 `execution` 是 Coda 预定公共对象协议,见[运行器协议正文](docs/executor-protocol.md)。
102
102
  `schemas/executor/v1/` 使用默认 Ajv 可读的 draft-07;`@coda-console/protocol/executor` 导出
103
103
  `EXECUTOR_PROTOCOL`、`EXECUTION_PROTOCOL`,其 spec/status 都自包含,可直接传给 `protocols.define`。
104
- 首版请求仅支持 `{slots: 1}`,可独占 slots;不支持多单位或多资源。两种类型均无补充栏,execution 绑定是由调度办事员 CAS 更新的单值。
104
+ 2.2.0 支持无资源请求或一个资源的一单位(slots 或本机 `group:<原名>`),可独占申请资源;仍不支持多单位或多资源原子申请。
105
+ 可选 placement 是生产者的位置/指定环境硬约束,context 是受控运行上下文与输出声明;执行器须显式声明支持新能力。
106
+ 流程可在 process、subProcess、scriptTask 写 `coda:placement="local|any"`,祖先 local 不可被子节点放宽。两种类型均无补充栏,execution 绑定是由调度办事员 CAS 更新的单值。
105
107
  executor 无 target,status 由持有端写;execution 绑定后由执行方完整回传,开跑前须经本机账本准入。
106
108
  对象协议 v1 与 npm 包版本、插件清单 v2 独立;旧 `contributes.executors` 不变。
107
109
 
@@ -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 选的账号经账号桥接上。Codex 没有自主轮次,不报 `handling`。会话指令经 codex-acp 的 `thread/start`、`thread/resume` 作为 `developerInstructions` 交给 Codex,接在运行机配置自己的 `developer_instructions` 后面;声明插话,一轮已经结束时收到的插话回 `promptRequired`(Coda 的补丁,codex-acp 原本会自己另开一轮) |
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` 作为子进程逐条转发 | 只做会话指令:声明它,并放在原生会话第一轮的输入前面;不报告活动 |
@@ -2,9 +2,10 @@
2
2
 
3
3
  `executor`(运行器)与 `execution`(一次执行尝试)是 Coda 预定的公共协议,唯一形状定义在
4
4
  `@coda-console/protocol`。依据是 [coda#2078 已验收草案](https://github.com/coda-console/coda/issues/2078#issuecomment-6554881338),
5
- 首版按验收决定收窄为 **单资源 `slots`,每请求 `slots: 1`,可独占 `slots`**。多单位、多资源须等本机账本支持后再开放。
5
+ 2.2.0 按[第 7 步方案](https://github.com/coda-console/coda/issues/2078#issuecomment-6559423071)补齐流程接入:
6
+ **无资源或一个资源的一单位**,可独占所申请资源;多单位、多资源原子申请仍未开放。
6
7
 
7
- 协议对象版本 `protocolVersion: 1`、本目录的 v1、npm 包版本 2.1.0 和插件清单的 v2 分开演进。
8
+ 协议对象版本 `protocolVersion: 1`、本目录的 v1、npm 包版本 2.2.0 和插件清单的 v2 分开演进。
8
9
  本次只增加契约;不改变 `contributes.executors`,也不宣称 host、scheduler、空间引用解析或账本接口已经实现。
9
10
 
10
11
  ## 定义与登记
@@ -12,7 +13,7 @@
12
13
  `schemas/executor/v1/{executor,execution}.json` 分别描述 `{spec,status}`,公共定义在 `common.json`。
13
14
  原始 JSON 采用 draft-07,读取原文件时先向 Ajv 登记 `common.json`。模块导出把所需定义包含在每份 spec/status 中,
14
15
  只留本地 `$ref`;四份 schema 均可由一个全新的默认 Ajv 直接编译,不需自定义关键字、format 或外部 schema 注册。
15
- `spec`、`status` 顶层均为 object。不支持的 `protocolVersion`、未知字段与未知资源键明确拒绝。
16
+ `spec`、`status` 顶层均为 object。不支持的 `protocolVersion`、未知字段与不合资源命名约定的键明确拒绝。
16
17
 
17
18
  ```js
18
19
  import { EXECUTOR_PROTOCOL, EXECUTION_PROTOCOL } from '@coda-console/protocol/executor';
@@ -40,12 +41,12 @@ await coda.protocols.define({ ...EXECUTION_PROTOCOL, controllers: ['scheduler']
40
41
 
41
42
  | 区域 | 字段 |
42
43
  | --- | --- |
43
- | spec 必填 | `protocolVersion: 1`、`enabled`、`capacity: {slots: 非负整数}`、`labels: string[]` |
44
+ | spec 必填 | `protocolVersion: 1`、`enabled`、`capacity: Record<resource, 非负整数>`、`labels: string[]` |
44
45
  | spec 可选 | `title`(不超过 200 字符) |
45
46
  | status 能力 | `detectedLabels`、`capabilities: {os,arch,nodeVersion,graphicalSession}`;可选 `xcode: {version,build}`、`simulatorRuntimes: [{id,version,available}]` |
46
- | status 资源 | `capacity`、`occupied`、`available`,各为 `{slots: 非负整数}`;`accepting` |
47
+ | status 资源 | `capacity`、`occupied`、`available`,各为 `Record<resource, 非负整数>`;`accepting` |
47
48
  | status 观察 | `observedAt`、`observedRevision`;可选 `reason: {code,message}` |
48
- | status 支持 | `executionFeatures: {multiResource: false,multiUnit: false,exclusive: boolean}` |
49
+ | status 支持 | `executionFeatures: {multiResource: false,multiUnit: false,exclusive: boolean}`;可选 `noResources`、`namedResources`、`context` 布尔值 |
49
50
 
50
51
  非空 status 除明确可选项外全部必填。`graphicalSession` 是 boolean 或 null;null 表示无法确定。
51
52
  探测失败写 `reason`,不将失败变成用户标签或伪造能力;可选探测项未知时省略。
@@ -58,7 +59,13 @@ await coda.protocols.define({ ...EXECUTION_PROTOCOL, controllers: ['scheduler']
58
59
  spec.labels 只能加自定义标签,不能写这些保留值。精确版本和 simulator runtime ID 另外保存在结构化能力中。
59
60
 
60
61
  `slots` 的单位是执行名额,host 映射到同一本机 `host` 账本;scheduler 比较资源键与数量,不硬编码 `group === 'host'`。
61
- 容量可大于 1;**每次请求量仍只能为 1**。其他资源键拒绝,不能按 0 算。有能力不代表有名额。
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:` 的字符串授权远端资源映射。
62
69
  缩容量允许 occupied 暂时大于 capacity,此时 available 归零并停止新准入,不强杀已有任务。
63
70
  占用含旧流程与 run-held;借用或独占按账本事实计算,不能简单数 host 自己启动的进程,也不能要求独占时 available 等于 capacity − occupied。
64
71
  这些跨栏计算和准入状态由实现检查,不加动态 schema 关键字。
@@ -99,14 +106,17 @@ accepting、available、observedAt 都只是最近观察事实,不保证在线
99
106
  | 身份 | `protocolVersion: 1`、`run`、`attempt` 必填;`step` 可选 |
100
107
  | 源码 | `source` 必填:null、`{snapshot: ResourceRef<sourceSnapshot>}`、`{repository: ResourceRef<gitSource>,commit: 完整 SHA}` 三选一;SHA 为 40 或 64 位十六进制 |
101
108
  | 能力 | `requirements: {labels: string[]}`,可为空列表 |
102
- | 资源 | `resources: {slots: 1}`;`exclusiveResources` 默认 `[]`,仅可为 `[]` 或 `["slots"]` |
109
+ | 资源 | `resources: {}`、`{slots: 1}` 或 `{ "group:<原名>": 1 }`;`exclusiveResources` 默认 `[]`,至多独占申请的那一个资源 |
110
+ | 定位(可选) | `placement: {locality?: "local"或"any", executor?: ResourceRef<executor>}`;省略 locality 等同 any |
111
+ | 上下文(可选) | `context: {workflow?,plugin?,audience?,outputNames?}`,见下文 |
103
112
  | 任务 | `{script,shell}` 或 `{command,args}`,严格二选一;程序名与参数不隐含 shell |
104
113
  | 环境 | `workingDirectory` 默认 `.`;`env: Record<string,string>` 必填,只含声明变量,可为空对象 |
105
114
  | 时限 | `timeoutMs?` 正整数;`cancelGraceMs?` 非负整数;缺省策略由执行实现决定 |
106
115
  | 取消 | `cancel` 默认 false,是单向意图 |
107
116
  | 绑定 | `executor: ResourceRef<executor>\|null`、`assignmentId: string\|null` 必填,两者同为 null 或同时有值 |
108
117
 
109
- 独占是 `exclusiveResources: ["slots"]`,而非把“全部名额”换成某次观察到的容量数字。
118
+ 独占是 `exclusiveResources: [资源键]`,而非把“全部名额”换成某次观察到的容量数字。
119
+ 空 resources 只允许空 exclusiveResources;非空时独占键必须与请求键一致,schema 检查数量和命名,执行方核对二者关系。
110
120
  生产端必须先看 `executionFeatures.exclusive`;不支持独占的执行方明确拒绝,不降级为普通名额。
111
121
  原 coda:group 由生产端/资源 provider 声明映射;runsOn 放入 labels,两者独立。
112
122
 
@@ -122,13 +132,15 @@ JSON Schema 的 default 只声明缺省值。单独使用默认 Ajv 校验不会
122
132
  | --- | --- |
123
133
  | 基本 | `phase: queued\|preparing\|running\|finished` 与 `assignmentId` 必填;可选 `reason` |
124
134
  | 进度 | 可选 `startedAt`、`executionRun: ResourceRef`、`admission: {lease,slots}`;running 三者必填 |
125
- | 准入 | lease 是不透明本机凭证,slots 是不重复的非负名额序号数组;独占可记录多个序号,仅表示事实 |
135
+ | 准入 | 无资源请求 admission 为 null;占资源时为 `{lease,slots,resource?}`,resource 省略表示 slots。slots 是非空且不重复的非负名额序号数组,独占可记录多个序号 |
126
136
  | 完成 | finished 必须给 `finishedAt`、`outcome: succeeded\|failed\|cancelled\|rejected`;其他 phase 不接受 finishedAt、outcome、exitCode |
127
137
  | 结果 | 可选 `exitCode: integer\|null`、`outputs`、`summary`、`outputTail`、`logs`、`artifacts` |
128
138
 
129
139
  preparing/running 必须有 assignmentId;succeeded/failed 还必须有 startedAt 与 assignmentId。
130
140
  取消可在未绑定、未启动时完成;rejected 表示本次 assignment 不会启动,不接受 startedAt 或非 null exitCode。
131
141
  没启动或无法确定退出码时省略或写 null,不能造 0。这些字段的静态组合不能代替实际进程、收尾和状态迁移检查。
142
+ 独立 spec/status schema 不比较请求与结果:执行方必须保证 admission 为 null 当且仅当请求 resources 为空;
143
+ 非空准入的 resource 必须等于请求资源,不能用 null、别的组的租约或虚构 lease 绕过占额。
132
144
 
133
145
  文本限制使用 JSON Schema 的 Unicode 字符数,不是 UTF-8 字节或 UTF-16 长度:summary 最多 4096、outputTail 最多 16384;
134
146
  outputs 最多 64 项,键 1–128 字符、值最多 4096。logs/artifacts 各最多 64 项。
@@ -177,12 +189,37 @@ ResourceRef 的插件可见形状严格为 `{kind,id}`,id 是适配层提供
177
189
  适配层必须保持源码、运行、日志和产物的持有者归属,不能把本机 `remote:…` 字符串原样传给另一台便认为能解析。
178
190
  日志衔接现有运行日志读面;产物这里只预留引用、大小与摘要,字节传输和 sha256 核验仍属后续 artifacts 实施步骤。
179
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
+
180
217
  ## 写权限、绑定与改派
181
218
 
182
219
  1. executor 无 target,status 权威是持有端。execution 未绑定时,本机 provider/获准 scheduler 可写 queued 与原因;
183
220
  创建时可给 queued,缺失状态是未知。scheduler 只处理本机正本,按观察到的 revision 同次 CAS 写入 executor 与新的 assignmentId。
184
221
  绑定是顶层单值,**不走 append/or 补充合并**,不将多个候选合并成绑定。
185
- 2. 绑定后任务、源码、身份、资源与需求冻结,只可单向设置 cancel;安全改派仅更换绑定两栏。
222
+ 2. 绑定后任务、源码、身份、资源、需求、placement 与 context 冻结,只可单向设置 cancel;安全改派仅更换绑定两栏。
186
223
  空间桥将 executor 翻译成 `coda.pub/target`。执行方只观察 assigned,并核对具体 executor ID 是自己实现的;
187
224
  指向后端不表示该后端所有执行环境都可领单。**绑定后只有执行方完整回传 status**,包括字段删除;
188
225
  其他节点无权写,持有者不能伪造远端完成。旧 status 按 assignmentId 区分,不能拿旧绑定结果解释新绑定。
@@ -195,7 +232,8 @@ ResourceRef 的插件可见形状严格为 `{kind,id}`,id 是适配层提供
195
232
  ## 开跑前的本机账本准入与恢复
196
233
 
197
234
  执行方须在 prepare/启动前,持久登记 **execution 的 instance + assignmentId**,经同一本机账本原子获得资源与固定名额序号。
198
- 调度看到 available 或 binding 成功均不等于准入。普通请求只占一个 slots;独占须走账本独占动作,不能连续读 available 或分批占额模拟。
235
+ 调度看到 available 或 binding 成功均不等于准入。普通占资源请求只占指定资源的一单位;独占须走账本独占动作,不能连续读 available 或分批占额模拟。
236
+ 无资源请求仍须持久登记并确认执行实现,只是不申请租约;不得给无 group 的步骤暗加 slots 限流。
199
237
  容量冲突可排队或明确拒绝,scheduler 决定是否在满足改派条件后换目标。
200
238
  多键/多数量原子准入尚未开放,schema 和 executionFeatures 均不宣称支持。
201
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coda-console/protocol",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Coda 的协议:插件清单的类型(JSON Schema)、通用校验器、流程方言与 Agent 接入协议",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,12 +49,17 @@
49
49
  "uniqueItems": true,
50
50
  "description": "用户自定义标签,不占用 OS、架构、gui 与 xcode-/ios-/node- 探测命名空间"
51
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
+ },
52
57
  "resourceAmounts": {
53
58
  "type": "object",
54
- "properties": {"slots": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}},
55
- "required": ["slots"],
56
- "additionalProperties": false,
57
- "description": "首版只有 slots;容量与占用可大于 1,请求量另限为 1"
59
+ "propertyNames": {"$ref": "common.json#/definitions/resourceName"},
60
+ "maxProperties": 128,
61
+ "additionalProperties": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
62
+ "description": "可报告多个独立资源的容量;每个请求仍至多申请一个资源的一单位"
58
63
  },
59
64
  "reason": {
60
65
  "type": "object",
@@ -37,20 +37,62 @@
37
37
  "required": ["labels"],
38
38
  "additionalProperties": false
39
39
  },
40
- "resources": {
40
+ "placement": {
41
41
  "type": "object",
42
- "properties": {"slots": {"const": 1}},
43
- "required": ["slots"],
42
+ "properties": {
43
+ "locality": {"enum": ["local", "any"], "default": "any"},
44
+ "executor": {"$ref": "common.json#/definitions/executorRef"}
45
+ },
44
46
  "additionalProperties": false,
45
- "description": "首版仅 slots: 1;不接受多单位或其他资源"
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": "空对象表示不占资源;否则只申请一个资源的一单位,不接受多单位或资源向量"
46
55
  },
47
56
  "exclusiveResources": {
48
57
  "type": "array",
49
- "items": {"const": "slots"},
58
+ "items": {"$ref": "common.json#/definitions/resourceName"},
50
59
  "maxItems": 1,
51
60
  "uniqueItems": true,
52
61
  "default": [],
53
- "description": "空数组或独占 slots;不换算成观察到的容量数字"
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": "可信生产者给出的运行上下文与输出声明;不是环境快照或授权凭证,本机路径和密文不进入此字段"
54
96
  },
55
97
  "task": {
56
98
  "oneOf": [
@@ -93,6 +135,10 @@
93
135
  "required": ["protocolVersion", "run", "attempt", "source", "requirements", "resources", "task", "env", "executor", "assignmentId"],
94
136
  "additionalProperties": false,
95
137
  "allOf": [
138
+ {
139
+ "if": {"properties": {"resources": {"maxProperties": 0}}},
140
+ "then": {"properties": {"exclusiveResources": {"maxItems": 0}}}
141
+ },
96
142
  {
97
143
  "if": {"properties": {"executor": {"type": "null"}}},
98
144
  "then": {"properties": {"assignmentId": {"type": "null"}}},
@@ -110,9 +156,10 @@
110
156
  "startedAt": {"$ref": "common.json#/definitions/timestamp"},
111
157
  "finishedAt": {"$ref": "common.json#/definitions/timestamp"},
112
158
  "executionRun": {"$ref": "common.json#/definitions/resourceRef"},
113
- "admission": {
159
+ "admission": {"oneOf": [{"type": "null"}, {
114
160
  "type": "object",
115
161
  "properties": {
162
+ "resource": {"$ref": "common.json#/definitions/resourceName"},
116
163
  "lease": {"$ref": "common.json#/definitions/opaqueId"},
117
164
  "slots": {
118
165
  "type": "array",
@@ -124,7 +171,7 @@
124
171
  },
125
172
  "required": ["lease", "slots"],
126
173
  "additionalProperties": false
127
- },
174
+ }], "description": "无资源请求为 null;占资源须有真实 lease 与名额,resource 省略表示 slots。执行方按请求核对,不用 null 绕过准入"},
128
175
  "outcome": {"enum": ["succeeded", "failed", "cancelled", "rejected"]},
129
176
  "exitCode": {"type": ["integer", "null"]},
130
177
  "outputs": {
@@ -64,7 +64,10 @@
64
64
  "observedRevision": {"$ref": "common.json#/definitions/opaqueId"},
65
65
  "executionFeatures": {
66
66
  "type": "object",
67
- "properties": {"multiResource": {"const": false}, "multiUnit": {"const": false}, "exclusive": {"type": "boolean"}},
67
+ "properties": {
68
+ "multiResource": {"const": false}, "multiUnit": {"const": false}, "exclusive": {"type": "boolean"},
69
+ "noResources": {"type": "boolean"}, "namedResources": {"type": "boolean"}, "context": {"type": "boolean"}
70
+ },
68
71
  "required": ["multiResource", "multiUnit", "exclusive"],
69
72
  "additionalProperties": false
70
73
  }
@@ -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,
@@ -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": {