@zhuoyuezs/ml-platform 0.2.6 → 0.2.8

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/checksums.json CHANGED
@@ -2,17 +2,17 @@
2
2
  "files": [
3
3
  {
4
4
  "path": "runtime/business-client/README.md",
5
- "sha256": "sha256:e0bf5707e13eb8bc36b530741a476827eef8263f23ff796f064dc0bfae9a8bfb",
6
- "size_bytes": 14154
5
+ "sha256": "sha256:ef7aabd0a1434dbf5d60018e357b018eeb08099de32a9b6a01a89d1f6764bc20",
6
+ "size_bytes": 17026
7
7
  },
8
8
  {
9
9
  "path": "runtime/business-client/package-lock.json",
10
- "sha256": "sha256:744e49f72a879478a18899c6eb64cf3bbdc22700e9d8a4b61d45ad3a5927deb9",
10
+ "sha256": "sha256:706e7515b082fe20d4ad482347e222e41e34cd153c50bb947dc14f795cd5efb6",
11
11
  "size_bytes": 1628
12
12
  },
13
13
  {
14
14
  "path": "runtime/business-client/package.json",
15
- "sha256": "sha256:97145119a4080392bbd2242b6a5a67046aa45e720c38977686fa0f4346c41bf0",
15
+ "sha256": "sha256:6462d955b53f556056493bb6ee7acb506ced249e58d44532b9395cd8e1be684f",
16
16
  "size_bytes": 671
17
17
  },
18
18
  {
@@ -20,10 +20,15 @@
20
20
  "sha256": "sha256:369259ccfef1b079acf616eca1dc0040dda61cfb4b69941d0fcca30a658cd549",
21
21
  "size_bytes": 58749
22
22
  },
23
+ {
24
+ "path": "runtime/business-client/src/cli-arguments.js",
25
+ "sha256": "sha256:7e18df77a96a8a88035573406e26dc70f803211699021933a28c8e922ee38712",
26
+ "size_bytes": 2251
27
+ },
23
28
  {
24
29
  "path": "runtime/business-client/src/cli.js",
25
- "sha256": "sha256:6ad8fb06e410cb5a197a696f3cb9819e9e3b06919d443d303a3e9d285cf4c0e2",
26
- "size_bytes": 68793
30
+ "sha256": "sha256:cfabcb75a2e2b4e694c9c36390589299abcca3c0fd87e7171ea40b4a29d4e4eb",
31
+ "size_bytes": 73361
27
32
  },
28
33
  {
29
34
  "path": "runtime/business-client/src/config.js",
@@ -47,8 +52,8 @@
47
52
  },
48
53
  {
49
54
  "path": "skills/feature-management/SKILL.md",
50
- "sha256": "sha256:1118aef3734fe14fd8f7d1074ff730340663e42565155bde984ab5b397c2da77",
51
- "size_bytes": 33777
55
+ "sha256": "sha256:6b16afd7fde7949879a9277e29268c203fd42ac1bd630108d3c0101a56db1558",
56
+ "size_bytes": 34392
52
57
  },
53
58
  {
54
59
  "path": "skills/feature-management/agents/openai.yaml",
@@ -102,8 +107,8 @@
102
107
  },
103
108
  {
104
109
  "path": "skills/feature-management/references/commands.md",
105
- "sha256": "sha256:ffd106cd785fe0d07ee20f51b7f55ca520da2fc6367bc5bc1ec0218600146d6e",
106
- "size_bytes": 13905
110
+ "sha256": "sha256:bb6d8508bad62db6987df7d97802a679d5ccfc1fd7eb00f47b61b28396452be6",
111
+ "size_bytes": 15872
107
112
  },
108
113
  {
109
114
  "path": "skills/feature-management/references/contracts.md",
@@ -122,8 +127,8 @@
122
127
  },
123
128
  {
124
129
  "path": "skills/feature-management/references/supervised-datasets.md",
125
- "sha256": "sha256:2c7a894330a649248e7f52c944422bcd7a3aba4558f7984d129dfd21c8736cf3",
126
- "size_bytes": 4540
130
+ "sha256": "sha256:2c23ed96c49af1954864660bf8d13f3114bb03a6598e20df6abbc191c5986e2b",
131
+ "size_bytes": 10024
127
132
  },
128
133
  {
129
134
  "path": "skills/model-deployment-management/SKILL.md",
@@ -152,8 +157,8 @@
152
157
  },
153
158
  {
154
159
  "path": "skills/model-lifecycle-management/SKILL.md",
155
- "sha256": "sha256:785219483f917d83d9a371772ea107dc03ddf024a0c4bd098711c4689c5c2ce4",
156
- "size_bytes": 5718
160
+ "sha256": "sha256:5880a2f7dc5077767c05f3c2e34be30699d63ea620a08b7ed6944c6b5b29f801",
161
+ "size_bytes": 6482
157
162
  },
158
163
  {
159
164
  "path": "skills/model-lifecycle-management/agents/openai.yaml",
@@ -167,8 +172,13 @@
167
172
  },
168
173
  {
169
174
  "path": "skills/model-lifecycle-management/references/evaluation.md",
170
- "sha256": "sha256:4314e65b162f0a72c5c5e6411186340661c853551dc8b2ebce3597680acef117",
171
- "size_bytes": 9477
175
+ "sha256": "sha256:e5c82007aa19632565ac3b9862b2c12e896c08f21171985795d342992324a683",
176
+ "size_bytes": 10310
177
+ },
178
+ {
179
+ "path": "skills/model-lifecycle-management/references/model-comparison.md",
180
+ "sha256": "sha256:cb97b16476e28d3ca6f984ef2a964f936227bbb28a11570bbcd86f6ea51bccba",
181
+ "size_bytes": 9359
172
182
  },
173
183
  {
174
184
  "path": "skills/model-lifecycle-management/references/packaging.md",
@@ -177,13 +187,13 @@
177
187
  },
178
188
  {
179
189
  "path": "skills/model-lifecycle-management/references/training-contracts.md",
180
- "sha256": "sha256:26ca0f0b8bc212bd30ea69b0a0afc169554681a16317ebb054d9147ae9c0587a",
181
- "size_bytes": 14994
190
+ "sha256": "sha256:eb774709bc94e07a51a539e1a871202bd7569ae2c4a3d0fa4ebd2b1784ee08a6",
191
+ "size_bytes": 17803
182
192
  },
183
193
  {
184
194
  "path": "skills/model-lifecycle-management/references/training.md",
185
- "sha256": "sha256:ae87837e5c315aa0fc9c02575213e1fd293f192ed43a1c68e9f8d08407becc48",
186
- "size_bytes": 8235
195
+ "sha256": "sha256:6732128fd6e0ef1fd1d40340bccd4ef555936c9050a7ccbf5ccbfc6c5854a8c3",
196
+ "size_bytes": 8369
187
197
  },
188
198
  {
189
199
  "path": "skills/model-lifecycle-management/references/zero-shot-inference.md",
@@ -192,8 +202,8 @@
192
202
  },
193
203
  {
194
204
  "path": "skills/prediction-report-management/SKILL.md",
195
- "sha256": "sha256:35242893137502ece779eb3b66f0d5a2e36d10dd944af5e2fead20547c7866e3",
196
- "size_bytes": 5923
205
+ "sha256": "sha256:7c751b1c6176a807f287095ece6463747007f9277afa82abad0c6035f7cbdca5",
206
+ "size_bytes": 6489
197
207
  },
198
208
  {
199
209
  "path": "skills/prediction-report-management/agents/openai.yaml",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "ML 数据平台 business CLI 与 Agent Skills 管理工具",
5
5
  "bin": {
6
6
  "ml-platform": "scripts/main.js"
package/release.json CHANGED
@@ -13,8 +13,8 @@
13
13
  "entrypoint": "src/cli.js",
14
14
  "name": "ml-platform",
15
15
  "path": "runtime/business-client",
16
- "sha256": "sha256:3cf1b6b441fc99bfc8e1615f7c0949a3234da0e4ccaa70fca2ddb4ed7b96cae4",
17
- "version": "0.8.5"
16
+ "sha256": "sha256:eec6e0e65cf300d6fbfbddcc13bdee6af324409567222454c9ba7abc812a79f3",
17
+ "version": "0.8.6"
18
18
  },
19
19
  "policy_sha256": "sha256:02fbde0134696b78c0c365d6e0b76c9814227208da5b2b3d4e5dbd82dc4a3606",
20
20
  "public_schema_versions": [
@@ -25,7 +25,7 @@
25
25
  "ml_data_platform.feature_set/v1",
26
26
  "ml_data_platform.dataset_manifest/v1"
27
27
  ],
28
- "release_version": "0.2.6",
28
+ "release_version": "0.2.8",
29
29
  "runtime_requirements": {
30
30
  "node": ">=18",
31
31
  "os": [
@@ -37,34 +37,34 @@
37
37
  "skills": {
38
38
  "data-source-exploration": {
39
39
  "path": "skills/data-source-exploration",
40
- "requires_cli": ">=0.8.5 <0.9.0",
41
- "revision": "0.2.6",
40
+ "requires_cli": ">=0.8.6 <0.9.0",
41
+ "revision": "0.2.8",
42
42
  "sha256": "sha256:6cb1cad3e9531e5098582c532288edd9838c2acab2b324794ec99d2550418f32"
43
43
  },
44
44
  "feature-management": {
45
45
  "path": "skills/feature-management",
46
- "requires_cli": ">=0.8.5 <0.9.0",
47
- "revision": "0.2.6",
48
- "sha256": "sha256:6dfa708172c3b5b23af75997c82a6d43bc2eb8ca67cc2275c8b95022455b0c42"
46
+ "requires_cli": ">=0.8.6 <0.9.0",
47
+ "revision": "0.2.8",
48
+ "sha256": "sha256:91c59ad073fc6d13ae024ba3f2f5652742ebb7dbee8a0ad4573332e8f009a580"
49
49
  },
50
50
  "model-deployment-management": {
51
51
  "path": "skills/model-deployment-management",
52
- "requires_cli": ">=0.8.5 <0.9.0",
53
- "revision": "0.2.6",
52
+ "requires_cli": ">=0.8.6 <0.9.0",
53
+ "revision": "0.2.8",
54
54
  "sha256": "sha256:a83471a2914210065b4354c50b9ddb8f6e4be582739636a116b9098524948a11"
55
55
  },
56
56
  "model-lifecycle-management": {
57
57
  "path": "skills/model-lifecycle-management",
58
- "requires_cli": ">=0.8.5 <0.9.0",
59
- "revision": "0.2.6",
60
- "sha256": "sha256:0f63e9122b55a72a11258d517b7eb378d84ef7e64ea9b613491c910e6ed8efc8"
58
+ "requires_cli": ">=0.8.6 <0.9.0",
59
+ "revision": "0.2.8",
60
+ "sha256": "sha256:113c673d383cdfb191d3c0efbc91aa0f950e2db0ca03a163209680c6dc1683a0"
61
61
  },
62
62
  "prediction-report-management": {
63
63
  "path": "skills/prediction-report-management",
64
- "requires_cli": ">=0.8.5 <0.9.0",
65
- "revision": "0.2.6",
66
- "sha256": "sha256:3a6991eec5cfa6bf30eaf43295f0c7d5c1e70e4f638904dfde1be2a39965d64f"
64
+ "requires_cli": ">=0.8.6 <0.9.0",
65
+ "revision": "0.2.8",
66
+ "sha256": "sha256:671f65e54bf464c7fa46398d4fae31a6317290cf0987fc5c886e56a89309fafc"
67
67
  }
68
68
  },
69
- "source_commit": "working-tree-request-time-mixed-availability"
69
+ "source_commit": "f05d2c98eddd9a4d2cdf47a1d7a3133fc421ffc9"
70
70
  }
@@ -20,6 +20,8 @@ node business-client-js/src/cli.js --help
20
20
  ml-platform list-datasets --project <project>
21
21
  ml-platform get-dataset <dataset_id> <dataset_version> --project <project>
22
22
  ml-platform resolve-dataset <dataset_id> <dataset_version> --project <project>
23
+ ml-platform build-registered-dataset <dataset_id> <dataset_version> \
24
+ --project <project> --plan
23
25
  ml-platform build-registered-dataset <dataset_id> <dataset_version> \
24
26
  --project <project> --wait
25
27
  ml-platform register-model-artifact <artifact_id> --evaluation-id <evaluation_result_or_summary_id>
@@ -34,6 +36,15 @@ ml-platform register-model-artifact <artifact_id> --evaluation-id <evaluation_re
34
36
  `resolve-manifest` 和 `build-dataset <manifest.json>` 仍用于尚未注册的本地契约。Catalog
35
37
  normalizer 保留并校验非默认 `project`;`apply --project` 与 Catalog 内项目不一致时拒绝执行。
36
38
 
39
+ 两个 build 命令默认 `--partition-duration auto`,按目标行数、输出宽度和源读取包络选择
40
+ 分区。`--plan` 只读预览,不创建 Job,与 `--wait` 互斥。返回估算、原因及限制;
41
+ 提交时固定具体时长,重试不重新选择。`--partition-duration 7d` 可显式覆盖,
42
+ `--partition-duration none` 保留不分区。软目标可通过 `--target-partition-rows`、
43
+ `--target-partition-source-rows`、`--target-partition-cells` 调整,默认分别为
44
+ 50000、500000、1000000;并非内存或耗时承诺,并行度当前仍为 1。
45
+ 此流程需要 API 支持 `/datasets/build-plan` 和 `partition_duration=auto`;应配套升级,
46
+ 不自动回退为未经预览的提交。API 省略时长仍保留原兼容语义。
47
+
37
48
  同一 Parameter 需要从历史库训练、从实时库取数时,在 Parameter JSON 同时填写 `source` 与
38
49
  可选的 `online_source`,然后照常使用 `add-parameter` 或 Catalog `apply`;CLI 原样透传该
39
50
  字段,不需要额外参数。离线构建读取 `source`,实时因果读取自动选择 `online_source`。
@@ -73,6 +84,22 @@ ml-platform get-deployment-events <deployment-id> --limit 100 --offset 0
73
84
  `list-deployments` 也支持按 `service`、`environment`、`state` 和自由文本 `q` 过滤,返回平台
74
85
  Deployment Controller 管理的部署,不把模型中心回写的 delivery 状态当作平台部署。
75
86
 
87
+ 部署业务分类由平台控制面管理。领域固定为 `power`(电力)、`blast_furnace`(高炉)和
88
+ `other`(其他),场景可按领域自定义:
89
+
90
+ ```bash
91
+ ml-platform list-business-domains
92
+ ml-platform list-scenarios --business-domain-id power
93
+ ml-platform create-scenario /absolute/path/to/scenario.json
94
+ ml-platform put-deployment-metadata <deployment-id> /absolute/path/to/metadata.json
95
+ ml-platform list-deployments --business-domain-id power --scenario-id energy_storage
96
+ ```
97
+
98
+ 场景 JSON 包含 `scenario_id`、`business_domain_id`、`name`。元数据 JSON 包含
99
+ `display_name`、`description`、`scenario_id`、可选 `updated_by`;保存采用完整替换,
100
+ 省略场景或传 `null` 清除归属,操作不会创建部署 revision 或触发模型重启。
101
+ 完整示例见[部署业务分类](../docs/designs/model-deployment-management.md#7-部署业务分类)。
102
+
76
103
  ## Prediction reporting
77
104
 
78
105
  定时预测上报通过业务 CLI 管理版本化推送目标、计划与运行记录。下面的 JSON 是字段示例,
@@ -99,7 +126,8 @@ Deployment Controller 管理的部署,不把模型中心回写的 delivery 状
99
126
  "values": {
100
127
  "source": "predictions", "type": "array",
101
128
  "items": {"fields": {
102
- "time": {"source": "target_time", "type": "string"},
129
+ "time": {"source": "target_time", "format": "%H:%M", "timezone": "Asia/Shanghai",
130
+ "midnight_as_24": true, "type": "string"},
103
131
  "value": {"source": "value", "type": "number", "scale": 1.0, "offset": 0.0}
104
132
  }}
105
133
  }
@@ -129,7 +157,10 @@ Deployment Controller 管理的部署,不把模型中心回写的 delivery 状
129
157
  `cutoff_time`、`entity`、`horizon`、`output_window`、`prediction_hash` 和标准化的
130
158
  `predictions`。每个 prediction point 至少包含 `series_index`、`value`,序列结果通常还包含
131
159
  `target_time`。`source` 引用平台标准预测结果中的字段;`fields` 生成嵌套对象,`items` 将数组逐项转换,
132
- `constant` 写入固定值,`template` 可拼接已存在的字符串字段。`format` 可格式化时间,
160
+ `constant` 写入固定值,`template` 可拼接已存在的字符串字段。`format` 可格式化时间;需要按业务时区格式化时,
161
+ 同时设置 `timezone`(IANA 名称),例如 `2026-09-10T16:00:00Z` 配置 `Asia/Shanghai` 后得到 `2026-09-11`;
162
+ 如果目标接口把本地日界的午夜表达为 `24:00`,在包含 `%H` 的时间格式上设置 `midnight_as_24: true`,例如
163
+ `%H:%M` 会将本地 `00:00` 映射为 `24:00`,其他时刻保持不变;
133
164
  `scale` 和 `offset` 可执行受控数值换算,`type` 校验最终类型。`token_path`、业务成功码和
134
165
  `/forecast/report` 只是示例,必须按目标接口的真实响应和路径配置。
135
166
  映射字段缺失或类型不匹配时不会发送请求,Run 会记录 `payload_mapping_failed`。若响应提供有效期,可设置 `expires_in_path` 与 `expires_in_unit`;否则服务端默认按 7200 秒缓存,
@@ -276,3 +307,13 @@ the complete mapping delivered to the Worker after defaults are resolved.
276
307
  Maintainers can export the current API, including uncommitted schema changes,
277
308
  with `uv run python scripts/generate_openapi_schema.py --working-tree` from the
278
309
  repository root; use `--check` with it to verify the committed snapshot.
310
+
311
+ ## Agent 宿主接入
312
+
313
+ Coding agent 加载已安装的 Skills 后,通过原生 shell/exec 直接运行普通 `ml-platform` 命令。
314
+ 使用 `ml-platform --help` 和 `ml-platform <command> --help` 发现命令及参数;业务结果沿用
315
+ CLI 原有 JSON 输出和退出码。CLI 不要求专用 Agent 调用协议。
316
+
317
+ 方法、预检顺序、幂等键使用及结果核验由 Skills 指导,命令参数和平台校验由 CLI/API 负责。
318
+ 宿主复用自己的权限、超时、Stop、输出截断及工具历史;进程中断不代表服务端任务已取消。
319
+ 提交结果不明时,先查询平台真实状态,再决定是否重试。
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zhuoyuezs/ml-platform-business-client",
9
- "version": "0.8.5",
9
+ "version": "0.8.6",
10
10
  "license": "UNLICENSED",
11
11
  "bin": {
12
12
  "ml-platform": "src/cli.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "private": true,
5
5
  "description": "JavaScript implementation of the ML Platform business client",
6
6
  "bin": {
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Validate ordinary CLI arguments from the command usage maintained by the CLI.
4
+ function grammar(command, usages) {
5
+ const signature = usages[command];
6
+ if (!signature)
7
+ throw new Error(`unknown business command: ${command}`);
8
+ const tokens = signature.replace(/[\[\]]/g, "").split(/\s+/).slice(1);
9
+ const flags = new Map();
10
+ const positionals = [];
11
+ for (let i = 0; i < tokens.length; i++) {
12
+ if (tokens[i].startsWith("-")) {
13
+ const value = tokens[i + 1] && !tokens[i + 1].startsWith("-") ? tokens[++i] : null;
14
+ flags.set(tokens[value ? i - 1 : i], value);
15
+ }
16
+ else
17
+ positionals.push(tokens[i]);
18
+ }
19
+ return { flags, positionals };
20
+ }
21
+ function parseCommand(argv, usages, allowMissing = false) {
22
+ if (!Array.isArray(argv) || !argv.length || !argv[0] || argv.length > 200 || argv.some(arg => typeof arg !== "string" || arg.includes("\0") || arg.length > 65536))
23
+ throw new Error("argv must be a bounded string array with a non-empty command");
24
+ const command = argv[0];
25
+ const spec = grammar(command, usages);
26
+ if (argv.length === 2 && ["--help", "-h"].includes(argv[1]))
27
+ return;
28
+ const flags = {};
29
+ let count = 0;
30
+ for (let i = 1; i < argv.length; i++) {
31
+ const arg = argv[i];
32
+ let label, value;
33
+ if (arg.startsWith("-")) {
34
+ if (!spec.flags.has(arg))
35
+ throw new Error(`unknown argument: ${arg}`);
36
+ if (arg in flags && arg !== "--file")
37
+ throw new Error(`duplicate argument: ${arg}`);
38
+ label = spec.flags.get(arg);
39
+ if (label && (i + 1 >= argv.length || argv[i + 1].startsWith("--")))
40
+ throw new Error(`${arg} requires a value`);
41
+ value = label ? argv[++i] : true;
42
+ flags[arg] = value;
43
+ }
44
+ else {
45
+ label = spec.positionals[count++];
46
+ if (!label)
47
+ throw new Error(`unknown argument: ${arg}`);
48
+ }
49
+ }
50
+ if (!allowMissing && count !== spec.positionals.length)
51
+ throw new Error(`${spec.positionals[count]} is required`);
52
+ }
53
+ module.exports = { parseCommand };
@@ -13,6 +13,7 @@ const BUSINESS_COMMANDS = new Set([
13
13
  "get-model-runtime-release", "get-training-run", "get-model-artifact", "get-executable-package",
14
14
  "validate-training-job", "validate-model-package",
15
15
  "version", "create-project", "list-projects", "get-project", "delete-project",
16
+ "get-dataset-contract-file", "validate-evaluation-comparison",
16
17
  "configure", "show-config", "health",
17
18
  "list-datasource-connections", "list-datasource-namespaces", "list-datasource-objects",
18
19
  "list-datasource-fields", "query-datasource-object", "create-datasource-connection",
@@ -38,6 +39,7 @@ const BUSINESS_COMMANDS = new Set([
38
39
  "validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
39
40
  "get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
40
41
  "create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact", "register-model-artifact",
42
+ "list-business-domains", "list-scenarios", "create-scenario", "put-deployment-metadata",
41
43
  "list-deployment-targets", "validate-deployment", "create-deployment", "list-deployments",
42
44
  "get-deployment", "wait-deployment", "reconcile-deployment", "approve-deployment",
43
45
  "promote-deployment", "rollback-deployment", "suspend-deployment", "resume-deployment",
@@ -69,7 +71,7 @@ const COMMAND_USAGE = {
69
71
  "update-datasource-connection": "update-datasource-connection CONNECTION_ID SPEC_JSON",
70
72
  "delete-datasource-connection": "delete-datasource-connection CONNECTION_ID",
71
73
  "test-datasource-connection": "test-datasource-connection CONNECTION_ID",
72
- "create-project": "create-project NAME [--display-name TEXT] [--description TEXT] [--owner OWNER]",
74
+ "create-project": "create-project NAME [--display-name TEXT] [--description TEXT] [--owner OWNER] [--idempotency-key KEY]",
73
75
  "list-projects": "list-projects",
74
76
  "get-project": "get-project NAME",
75
77
  "delete-project": "delete-project NAME",
@@ -108,11 +110,11 @@ const COMMAND_USAGE = {
108
110
  "delete-dataset": "delete-dataset DATASET_ID DATASET_VERSION [--project PROJECT]",
109
111
  "list-dataset-artifacts": "list-dataset-artifacts [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [--include-deleted] [--limit N] [--offset N]",
110
112
  "get-dataset": "get-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--include-deleted]",
111
- "list-jobs": "list-jobs [filters] [-q QUERY] [--limit N] [--offset N] [--all]",
113
+ "list-jobs": "list-jobs [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [--manifest-hash HASH] [--status STATUS] [--parent-job-id ID] [--created-from TIMESTAMP] [--created-to TIMESTAMP] [--updated-from TIMESTAMP] [--updated-to TIMESTAMP] [-q QUERY] [--limit N] [--offset N] [--all]",
112
114
  "resolve-manifest": "resolve-manifest MANIFEST [--out PATH]",
113
115
  "resolve-dataset": "resolve-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--out PATH]",
114
- "build-dataset": "build-dataset MANIFEST [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
115
- "build-registered-dataset": "build-registered-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
116
+ "build-dataset": "build-dataset MANIFEST [--plan] [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration auto|none|DURATION (default: auto)] [--target-partition-rows N] [--target-partition-source-rows N] [--target-partition-cells N] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
117
+ "build-registered-dataset": "build-registered-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--plan] [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration auto|none|DURATION (default: auto)] [--target-partition-rows N] [--target-partition-source-rows N] [--target-partition-cells N] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
116
118
  "get-job": "get-job JOB_ID",
117
119
  "wait-job": "wait-job JOB_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
118
120
  "retry-job": "retry-job JOB_ID",
@@ -122,6 +124,7 @@ const COMMAND_USAGE = {
122
124
  "delete-operator": "delete-operator NAME VERSION [--project PROJECT]",
123
125
  "run-operator": "run-operator NAME VERSION [--project PROJECT] [--config JSON]",
124
126
  "get-dataset-artifact": "get-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT]",
127
+ "get-dataset-contract-file": "get-dataset-contract-file DATASET_ID MANIFEST_HASH FILE [--project PROJECT]",
125
128
  "download-dataset-artifact": "download-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT] --out-dir PATH [--file PATH] [--force]",
126
129
  "fetch-inference-data": "fetch-inference-data MANIFEST --cutoff-time TIMESTAMP [--max-workers N] [--max-source-lag-hours HOURS] [--no-validate-freshness] [--allow-missing]",
127
130
  "fetch-inference-context": "fetch-inference-context REQUEST_JSON",
@@ -132,13 +135,13 @@ const COMMAND_USAGE = {
132
135
  "predict-foundation-csv": "predict-foundation-csv NAME CSV_FILE --schema SCHEMA_JSON --forecast FORECAST_JSON [--release-id ID]",
133
136
  "list-system-models": "list-system-models",
134
137
  "get-system-model": "get-system-model NAME VERSION [--project PROJECT]",
135
- "register-training-run": "register-training-run RUN_JSON",
138
+ "register-training-run": "register-training-run RUN_JSON [--idempotency-key KEY]",
136
139
  "validate-training-run": "validate-training-run RUN_JSON",
137
- "submit-training-job": "submit-training-job REQUEST_JSON",
140
+ "submit-training-job": "submit-training-job REQUEST_JSON [--idempotency-key KEY]",
138
141
  "get-training-job": "get-training-job JOB_ID",
139
142
  "retry-training-job": "retry-training-job JOB_ID",
140
143
  "cancel-training-job": "cancel-training-job JOB_ID",
141
- "register-evaluation-config": "register-evaluation-config CONFIG_JSON",
144
+ "register-evaluation-config": "register-evaluation-config CONFIG_JSON [--idempotency-key KEY]",
142
145
  "get-evaluation-config": "get-evaluation-config NAME VERSION",
143
146
  "register-metric-definition": "register-metric-definition DEFINITION_JSON",
144
147
  "get-metric-definition": "get-metric-definition NAME VERSION",
@@ -146,7 +149,8 @@ const COMMAND_USAGE = {
146
149
  "verify-executable-package": "verify-executable-package NAME VERSION",
147
150
  "publish-executable-package": "publish-executable-package NAME VERSION",
148
151
  "validate-evaluation-run": "validate-evaluation-run REQUEST_JSON",
149
- "submit-evaluation": "submit-evaluation REQUEST_JSON",
152
+ "validate-evaluation-comparison": "validate-evaluation-comparison REQUEST_JSON",
153
+ "submit-evaluation": "submit-evaluation REQUEST_JSON [--idempotency-key KEY]",
150
154
  "get-evaluation-run": "get-evaluation-run RUN_ID",
151
155
  "get-evaluation-job": "get-evaluation-job JOB_ID",
152
156
  "retry-evaluation-job": "retry-evaluation-job JOB_ID",
@@ -157,15 +161,19 @@ const COMMAND_USAGE = {
157
161
  "get-evaluation-result-artifacts": "get-evaluation-result-artifacts RESULT_ID",
158
162
  "summarize-evaluation-run": "summarize-evaluation-run RUN_ID",
159
163
  "get-evaluation-summary": "get-evaluation-summary SUMMARY_ID",
160
- "create-model-package": "create-model-package REQUEST_JSON",
164
+ "create-model-package": "create-model-package REQUEST_JSON [--idempotency-key KEY]",
161
165
  "get-model-package": "get-model-package PACKAGE_ID [--package-version VERSION]",
162
166
  "cancel-model-package": "cancel-model-package PACKAGE_ID [--package-version VERSION]",
163
167
  "validate-model-artifact": "validate-model-artifact ARTIFACT_ID",
164
168
  "register-model-artifact": "register-model-artifact ARTIFACT_ID --evaluation-id EVALUATION_ID",
169
+ "list-business-domains": "list-business-domains",
170
+ "list-scenarios": "list-scenarios [--business-domain-id ID]",
171
+ "create-scenario": "create-scenario SPEC_JSON",
172
+ "put-deployment-metadata": "put-deployment-metadata DEPLOYMENT_ID SPEC_JSON",
165
173
  "list-deployment-targets": "list-deployment-targets",
166
174
  "validate-deployment": "validate-deployment SPEC_JSON --evaluation-id ID",
167
175
  "create-deployment": "create-deployment SPEC_JSON --evaluation-id ID",
168
- "list-deployments": "list-deployments [--service SERVICE] [--environment ENV] [--package-id ID] [--package-version VERSION] [--model-artifact-id ID] [--state STATE] [-q QUERY] [--limit N] [--offset N]",
176
+ "list-deployments": "list-deployments [--service SERVICE] [--environment ENV] [--package-id ID] [--package-version VERSION] [--model-artifact-id ID] [--state STATE] [--business-domain-id ID] [--scenario-id ID] [-q QUERY] [--limit N] [--offset N]",
169
177
  "get-deployment": "get-deployment DEPLOYMENT_ID",
170
178
  "wait-deployment": "wait-deployment DEPLOYMENT_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
171
179
  "reconcile-deployment": "reconcile-deployment DEPLOYMENT_ID",
@@ -314,6 +322,19 @@ function buildOptions(rest) {
314
322
  if (maxParallelism < 1)
315
323
  throw new Error("--max-parallelism must be at least 1");
316
324
  const wait = boolean(rest, "--wait");
325
+ const plan = boolean(rest, "--plan");
326
+ if (plan && wait)
327
+ throw new Error("--plan is read-only and cannot be combined with --wait");
328
+ const sizing = {};
329
+ for (const [flag, key] of [["--target-partition-rows", "target_partition_rows"], ["--target-partition-source-rows", "target_partition_source_rows"], ["--target-partition-cells", "target_partition_cells"]]) {
330
+ const raw = take(rest, flag);
331
+ if (raw !== undefined) {
332
+ const value = integer(raw, flag);
333
+ if (value < 1)
334
+ throw new Error(`${flag} must be at least 1`);
335
+ sizing[key] = value;
336
+ }
337
+ }
317
338
  const pollInterval = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
318
339
  if (!(pollInterval > 0))
319
340
  throw new Error("--poll-interval must be positive");
@@ -325,7 +346,9 @@ function buildOptions(rest) {
325
346
  engine,
326
347
  sourceMode,
327
348
  upload: boolean(rest, "--upload-chronon-metadata"),
328
- partitionDuration: take(rest, "--partition-duration"),
349
+ partitionDuration: take(rest, "--partition-duration", "auto"),
350
+ plan,
351
+ sizing,
329
352
  maxParallelism,
330
353
  resume: !boolean(rest, "--no-resume"),
331
354
  wait,
@@ -334,7 +357,8 @@ function buildOptions(rest) {
334
357
  };
335
358
  }
336
359
  async function submitBuild(api, manifest, build) {
337
- let result = await api.post("/datasets/build", manifest, { engine: build.engine, source_mode: build.sourceMode, upload_chronon_metadata: build.upload, partition_duration: build.partitionDuration, max_parallelism: build.maxParallelism, resume: build.resume });
360
+ const controls = { partition_duration: build.partitionDuration, max_parallelism: build.maxParallelism, resume: build.resume, ...build.sizing };
361
+ let result = await api.post(build.plan ? "/datasets/build-plan" : "/datasets/build", manifest, build.plan ? controls : { engine: build.engine, source_mode: build.sourceMode, upload_chronon_metadata: build.upload, ...controls });
338
362
  let exitCode = 0;
339
363
  if (build.wait) {
340
364
  if (!result || typeof result !== "object" || !result.job_id)
@@ -397,18 +421,21 @@ async function waitForDeployment(api, deploymentId, pollInterval, timeoutSeconds
397
421
  await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
398
422
  }
399
423
  }
400
- async function runBusinessCli(argv) {
424
+ async function runBusinessCli(argv, output = { write: text => process.stdout.write(text) }) {
401
425
  if (argv.length === 0)
402
426
  throw new Error("a business command is required");
403
427
  if (argv.includes("--help") || argv.includes("-h")) {
404
428
  const command = argv.find((item) => BUSINESS_COMMANDS.has(item));
405
429
  if (command || argv[0] === "--help" || argv[0] === "-h") {
406
- process.stdout.write(usage(command));
430
+ output.write(usage(command));
407
431
  return 0;
408
432
  }
409
433
  }
410
434
  const options = parse(argv);
411
435
  const rest = [...options.rest];
436
+ const { parseCommand } = require("./cli-arguments");
437
+ // Reject malformed argv before a handler can mutate the platform.
438
+ parseCommand([options.command, ...rest], COMMAND_USAGE, true);
412
439
  let result;
413
440
  let exitCode = 0;
414
441
  if (options.command === "version")
@@ -473,7 +500,9 @@ async function runBusinessCli(argv) {
473
500
  }
474
501
  else if (options.command === "create-project") {
475
502
  const name = positional(rest, "project name");
476
- result = await client(options).post("/projects", { schema_version: "ml_data_platform.project/v1", name, display_name: take(rest, "--display-name", name), description: take(rest, "--description", ""), owner: take(rest, "--owner", "demo") });
503
+ const spec = { schema_version: "ml_data_platform.project/v1", name, display_name: take(rest, "--display-name", name), description: take(rest, "--description", ""), owner: take(rest, "--owner", "demo") };
504
+ const key = take(rest, "--idempotency-key");
505
+ result = key ? await client(options).postWithHeaders("/projects", spec, { "Idempotency-Key": key }) : await client(options).post("/projects", spec);
477
506
  }
478
507
  else if (options.command === "list-projects")
479
508
  result = await client(options).get("/projects");
@@ -717,6 +746,15 @@ async function runBusinessCli(argv) {
717
746
  const project = take(rest, "--project", "default");
718
747
  result = await client(options).get(`/dataset-artifacts/${encodeURIComponent(project)}/${encodeURIComponent(dataset)}/${encodeURIComponent(hash)}`);
719
748
  }
749
+ else if (options.command === "get-dataset-contract-file") {
750
+ const dataset = positional(rest, "dataset id");
751
+ const hash = positional(rest, "manifest hash");
752
+ const file = positional(rest, "contract file");
753
+ const project = take(rest, "--project", "default");
754
+ if (!["data_schema.json", "resolved_manifest.json"].includes(file))
755
+ throw new Error("unsupported contract file; use data_schema.json or resolved_manifest.json");
756
+ result = await client(options).get(`/dataset-artifacts/${encodeURIComponent(project)}/${encodeURIComponent(dataset)}/${encodeURIComponent(hash)}/files/${file}`);
757
+ }
720
758
  else if (options.command === "download-dataset-artifact") {
721
759
  const dataset = positional(rest, "dataset id");
722
760
  const hash = positional(rest, "manifest hash");
@@ -795,6 +833,19 @@ async function runBusinessCli(argv) {
795
833
  const headers = releaseId ? { "X-ITSMP-Foundation-Release": releaseId } : {};
796
834
  result = await client(options).uploadFoundationCsv(`/foundation-models/${encodeURIComponent(name)}/predict-file`, readSpec(schemaFile), readSpec(forecastFile), csvFile, headers);
797
835
  }
836
+ else if (options.command === "list-business-domains") {
837
+ result = await client(options).get("/business-domains");
838
+ }
839
+ else if (options.command === "list-scenarios") {
840
+ result = await client(options).get("/scenarios", { business_domain_id: take(rest, "--business-domain-id") });
841
+ }
842
+ else if (options.command === "create-scenario") {
843
+ result = await client(options).post("/scenarios", readSpec(positional(rest, "scenario spec JSON")));
844
+ }
845
+ else if (options.command === "put-deployment-metadata") {
846
+ const deploymentId = positional(rest, "deployment id");
847
+ result = await client(options).put("/deployments/" + encodeURIComponent(deploymentId) + "/metadata", readSpec(positional(rest, "metadata spec JSON")));
848
+ }
798
849
  else if (options.command === "list-deployment-targets") {
799
850
  result = await client(options).get("/deployment-targets");
800
851
  }
@@ -814,6 +865,8 @@ async function runBusinessCli(argv) {
814
865
  package_version: take(rest, "--package-version"),
815
866
  model_artifact_id: take(rest, "--model-artifact-id"),
816
867
  state: take(rest, "--state"),
868
+ business_domain_id: take(rest, "--business-domain-id"),
869
+ scenario_id: take(rest, "--scenario-id"),
817
870
  q: take(rest, "--q", take(rest, "-q")),
818
871
  };
819
872
  boolean(rest, "--all");
@@ -984,11 +1037,16 @@ async function runBusinessCli(argv) {
984
1037
  else if ({
985
1038
  "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate",
986
1039
  "validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions",
987
- "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs",
1040
+ "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "validate-evaluation-comparison": "/evaluation-comparisons/validate", "submit-evaluation": "/evaluation-runs",
988
1041
  "create-model-package": "/model-packages",
989
1042
  }[options.command]) {
990
- const endpoint = { "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate", "validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions", "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs", "create-model-package": "/model-packages" }[options.command];
991
- result = await client(options).post(endpoint, readSpec(positional(rest, "request JSON")));
1043
+ const endpoint = { "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate", "validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions", "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "validate-evaluation-comparison": "/evaluation-comparisons/validate", "submit-evaluation": "/evaluation-runs", "create-model-package": "/model-packages" }[options.command];
1044
+ const spec = readSpec(positional(rest, "request JSON"));
1045
+ const operationCommands = new Set(["register-training-run", "submit-training-job", "register-evaluation-config", "submit-evaluation", "create-model-package"]);
1046
+ const key = operationCommands.has(options.command) ? take(rest, "--idempotency-key") : null;
1047
+ result = key
1048
+ ? await client(options).postWithHeaders(endpoint, spec, { "Idempotency-Key": key })
1049
+ : await client(options).post(endpoint, spec);
992
1050
  }
993
1051
  else if (/^(get|retry|cancel)-training-job$/.test(options.command)) {
994
1052
  const jobId = positional(rest, "training job id");
@@ -1053,7 +1111,7 @@ async function runBusinessCli(argv) {
1053
1111
  throw new Error(`unknown business command: ${options.command}`);
1054
1112
  if (rest.length)
1055
1113
  throw new Error(`unknown arguments: ${rest.join(" ")}`);
1056
- process.stdout.write(typeof result === "string" ? `${result}\n` : `${JSON.stringify(result, null, 2)}\n`);
1114
+ output.write(typeof result === "string" ? `${result}\n` : `${JSON.stringify(result, null, 2)}\n`);
1057
1115
  return exitCode;
1058
1116
  }
1059
1117
  async function fetchAll(api, endpoint, params = {}) { const items = []; for (let offset = 0;; offset += 500) {
@@ -48,10 +48,11 @@ Keep these evidence rules active in every workflow:
48
48
 
49
49
  1. Resolve the Skill root as the directory containing this `SKILL.md`.
50
50
  2. Read [references/contracts.md](references/contracts.md) before creating or changing JSON assets.
51
- 3. Read [references/operator-authoring.md](references/operator-authoring.md) whenever creating or changing Operator code or a wheel.
52
- 4. Read [references/commands.md](references/commands.md) immediately before a platform CLI action: discovery, validation, publication, build, realtime fetch, or artifact download. A local-only draft that is forbidden to call the CLI does not need this reference.
53
- 5. Use [assets/catalog-template](assets/catalog-template) as a copyable starting point for a new end-to-end catalog. Rename every `example_*` identifier and update every referenced path before validation.
54
- 6. Read [references/platform-capability-guide.md](references/platform-capability-guide.md) only when selecting an asset type or explaining a platform capability. Do not load it merely to write a confirmed local draft; it is never evidence for unresolved business semantics.
51
+ 3. Read [references/supervised-datasets.md](references/supervised-datasets.md) when preparing time-series supervision, labels, fixed grids, or training/validation/test splits. It distinguishes DatasetArtifact data semantics from model-specific sequence adapters.
52
+ 4. Read [references/operator-authoring.md](references/operator-authoring.md) whenever creating or changing Operator code or a wheel.
53
+ 5. Read [references/commands.md](references/commands.md) immediately before a platform CLI action: discovery, validation, publication, build, realtime fetch, or artifact download. A local-only draft that is forbidden to call the CLI does not need this reference.
54
+ 6. Use [assets/catalog-template](assets/catalog-template) as a copyable starting point for a new end-to-end catalog. Rename every `example_*` identifier and update every referenced path before validation.
55
+ 7. Read [references/platform-capability-guide.md](references/platform-capability-guide.md) only when selecting an asset type or explaining a platform capability. Do not load it merely to write a confirmed local draft; it is never evidence for unresolved business semantics.
55
56
 
56
57
  ## Initialize The Client
57
58
 
@@ -404,7 +405,7 @@ Stop on any error. Do not weaken schema validation, fabricate a missing dependen
404
405
 
405
406
  Publish only after the user explicitly approves Registry and wheel changes. Use catalog `apply` so publication follows Parameter -> Operator -> Feature -> FeatureSet -> Dataset order.
406
407
 
407
- After publication, resolve every new DatasetManifest and compare its exact Parameter, Operator, Feature, and FeatureSet versions with the pre-publication mapping. Stop if an affected old key or any unexpected version remains. Resolve the manifest before building. Submit a build only when requested. For the current partitioned materialization runtime, use `--max-parallelism 1` unless a separately verified platform capability says otherwise; do not retry or resubmit a failed Job until its error and already-completed partitions are understood. For server builds, wait for the terminal Job state and download the exact artifact by `dataset_id + manifest_hash`.
408
+ After publication, resolve every new DatasetManifest and compare its exact Parameter, Operator, Feature, and FeatureSet versions with the pre-publication mapping. Stop if an affected old key or any unexpected version remains. Resolve the manifest before building. Submit a build only when requested. Before choosing partition controls, use the build command's read-only `--plan` capability described in [commands.md](references/commands.md), inspect its estimates and warnings, and prefer platform automatic sizing over a copied daily example. Explicit overrides need source/resource evidence; preserve the submitted Job's concrete plan when resuming. For the current partitioned materialization runtime, use `--max-parallelism 1` unless a separately verified platform capability says otherwise; do not retry or resubmit a failed Job until its error and already-completed partitions are understood. For server builds, wait for the terminal Job state and download the exact artifact by `dataset_id + manifest_hash`.
408
409
 
409
410
  Do not delete versioned Registry assets, cancel Jobs, rebuild images, modify Kubernetes, or change service configuration as part of this workflow unless the user separately and explicitly requests that action. An empty Project may be soft-deleted only on an explicit request; rely on the server to reject deletion when resources still exist.
410
411
 
@@ -177,13 +177,28 @@ ml-platform --profile server \
177
177
  resolve-dataset <dataset_id> <dataset_version> --project <project>
178
178
  ```
179
179
 
180
+ Preview execution sizing without submitting a build:
181
+
182
+ ```bash
183
+ ml-platform --profile server \
184
+ build-registered-dataset <dataset_id> <dataset_version> \
185
+ --project <project> --plan
186
+ ```
187
+
188
+ Inspect `execution_spec`, estimated target/source rows, partition count, reasons
189
+ and warnings. This reads registry metadata only. It does not query source data,
190
+ start Operators or submit a Job. The defaults are soft sizing targets, not
191
+ measured memory or runtime limits. The installed CLI and API must both support
192
+ this capability; inspect `--help` and report version drift instead of silently
193
+ submitting when a preview is unavailable.
194
+
180
195
  After the user confirms the exact triple and authorizes one build:
181
196
 
182
197
  ```bash
183
198
  ml-platform --profile server \
184
199
  build-registered-dataset <dataset_id> <dataset_version> \
185
200
  --project <project> \
186
- --partition-duration 1d \
201
+ --partition-duration auto \
187
202
  --max-parallelism 1 \
188
203
  --wait
189
204
  ```
@@ -210,15 +225,35 @@ Submit and wait for a server build:
210
225
  ```bash
211
226
  ml-platform --profile server \
212
227
  build-dataset <catalog>/datasets/<dataset>.json \
213
- --partition-duration 1d \
228
+ --partition-duration auto \
214
229
  --max-parallelism 1 \
215
230
  --wait
216
231
  ```
217
232
 
233
+ Both build commands default to `--partition-duration auto`. Add `--plan` to
234
+ either command for read-only preview; it cannot be combined with `--wait`.
235
+ The platform sizes windows from target grids, output width, source frequency
236
+ and history/label read overlap. Do not copy `1d` as a performance default.
237
+ Use the same execution options for preview and submission, then record the
238
+ actual Job's persisted `build_plan` and concrete `execution_spec`.
239
+
240
+ When resource evidence justifies a change, the soft sizing controls are
241
+ `--target-partition-rows`, `--target-partition-source-rows` and
242
+ `--target-partition-cells` (defaults 50000, 500000 and 1000000). Explicit
243
+ `--partition-duration 7d` overrides selection; `--partition-duration none`
244
+ preserves full-window execution. Unknown source frequencies, event-driven
245
+ rowsets, source fan-out and Operator allocations require inspection; automatic
246
+ sizing is not a resource guarantee. Final merge memory is not bounded by a
247
+ partition's estimated rows. A long prediction horizon is not itself a reason to
248
+ use daily partitions: inspect the reported source read amplification.
249
+
218
250
  `--partition-duration` is an execution control and does not change the
219
- DatasetManifest hash. The initial implementation is sequential, so keep
251
+ DatasetManifest hash. The current implementation is sequential, so keep
220
252
  `--max-parallelism 1`. Completed partitions are checkpointed and resumed by
221
- default; use `--no-resume` only to force a clean execution.
253
+ default; use `--no-resume` only to force a clean execution. Retries preserve the
254
+ resolved partition duration. Changing it requires a new execution, rather than
255
+ reusing old checkpoint boundaries. Changing partition size does not authorize
256
+ cancelling or resubmitting an already-running Job.
222
257
 
223
258
  Record the returned `job_id`, `dataset_id`, and resolved `manifest_hash`; for a registered Dataset
224
259
  also retain `registered_manifest_hash` for FeatureRetrievalSpec binding. If a separate wait is needed:
@@ -36,6 +36,72 @@ names need source-frame evidence. The schema lists `dataset_column` and
36
36
  resolve before building rather than treating schema acceptance as runtime support.
37
37
  Choose `report`, `reject` or `drop` according to the intended label population.
38
38
 
39
+ ## Four different time-series concepts
40
+
41
+ Do not use *target*, *label*, *history* and *grid* interchangeably. They describe
42
+ different data and are checked at different stages:
43
+
44
+ | Concept | Meaning | Contract location |
45
+ | --- | --- | --- |
46
+ | Continuous target series | Observed target-channel values at successive `prediction_time` rows, used as a trajectory for a forecasting Trainer. It is not the future label column. | `forecast_target_adapter.target_input_column` (v1) or `targets[].input_column` (v2); the selected column must be a numeric Feature with availability at prediction. |
47
+ | Supervised label | The ground-truth value for one sample's future `label_time`; it is generated by `label_materializations` and listed in `data.labels`. | DatasetArtifact label column plus `TemporalBinding.label_time_column`; for a fixed horizon, `label_time = prediction_time + horizon`. |
48
+ | Historical input window | The model's past covariate/feature rows visible to one prediction. It is assembled around the sample's prediction time and is independent of the label horizon. | Sequence `input_adapter.context_length`, `frequency`, `stride`, `context_end`, `gap_policy` and `padding_policy`. |
49
+ | Grid break | An absent expected timestamp between rows for a series at the declared frequency. It is not a present row whose value is null or non-finite. | `input_adapter.gap_policy` for historical windows; `forecast_target_adapter.target_gap_policy` for continuous target trajectories. |
50
+
51
+ For example, with a 5-minute grid, a cutoff at 10:00, and a 15-minute horizon:
52
+ the historical window ends at 10:00 when `context_end` is inclusive; the
53
+ supervised label is at 10:15; and a continuous target trajectory may contain
54
+ the target Feature's observed values at 10:00, 10:05, 10:10, and later times
55
+ within its split. The future label is materialized per sample. It must not be
56
+ copied backward into the target trajectory or treated as a prediction-time
57
+ input.
58
+
59
+ The platform assembles continuous target trajectories per split before the
60
+ forecast Trainer samples its own context/future windows. A trajectory starts a
61
+ new segment at a gap when `target_gap_policy="split"`; `"reject"` fails on the
62
+ gap. Segments shorter than the effective minimum history plus prediction
63
+ length are dropped. This policy does not fill missing values: a null or
64
+ non-finite value in a target or required covariate is an error. Likewise,
65
+ `input_adapter.gap_policy` governs sequence history construction and does not
66
+ change target-trajectory segmentation. Do not select `fill` or `split` merely
67
+ to hide missing values; identify whether the row itself is absent or its value
68
+ is invalid and apply an explicitly supported data-quality method.
69
+
70
+ Splits are applied before continuous target-trajectory assembly. A
71
+ train/validation or validation/test boundary therefore ends one trajectory and
72
+ starts another; never concatenate split values into one target trajectory.
73
+ Sequence input windows are assembled for each prediction row from timestamped
74
+ rows in the artifact, using only the adapter's expected history timestamps at
75
+ or before that row. In a chronological holdout, validation/test windows may
76
+ therefore use earlier training-period observations as history; this does not
77
+ move those rows into the validation/test target population. Verify the split's
78
+ temporal ordering and preserve the platform's sample membership. Do not use
79
+ rows after a prediction time to complete its history, and do not infer that
80
+ target-trajectory split behavior also applies to sequence windows. An early
81
+ row may lack enough history; padding or skipping follows the declared adapter,
82
+ not the label policy.
83
+
84
+ When diagnosing a built artifact, inspect timestamps and values separately:
85
+
86
+ 1. Group by `series_keys` and sort by `prediction_time_column`; compare adjacent
87
+ timestamp deltas with `temporal.frequency` to find grid breaks and duplicate
88
+ times.
89
+ 2. At existing grid rows, inspect numeric target/input values for null and
90
+ non-finite cells. A row with a null value is not a grid break.
91
+ 3. Check label null rates independently using the final artifact's label column,
92
+ `missing_stats.labels`, and the chosen `missing_policy`; inspect Parquet rows
93
+ when exact row identities or counts are needed.
94
+ 4. Check history sufficiency against `context_length`, `context_end`, `stride`
95
+ and split boundaries; check forecast-trajectory sufficiency against
96
+ `min_past + prediction_length` within each uninterrupted segment.
97
+
98
+ Do not infer final label null rates or counts from an earlier materialization's
99
+ `missing_before_drop`: that statistic describes rows before the configured
100
+ drop stage, while the final artifact may contain only retained rows. Use the
101
+ final `missing_stats.labels` for rates and sampled/final Parquet rows for exact
102
+ counts and row identities in the published artifact. Do not infer grid
103
+ completeness from null counts alone.
104
+
39
105
  - `prediction_time = event_time` is the causal cutoff.
40
106
  - `label_time = prediction_time + prediction.horizon` is the forecast target.
41
107
  - `event_time.offset` shifts the source observation clock before alignment. At
@@ -87,6 +153,15 @@ split counts and binding together.
87
153
 
88
154
  ## Evidence checkpoints
89
155
 
156
+ SQL-source `verifiable=false` means the automatic clock analysis could not
157
+ establish the claim; `availability_guarantee=not_enforced` means availability
158
+ is not enforced. Neither alone proves leakage or a platform-wide training ban.
159
+ Check the actual feature cutoff, label clock and split evidence, preserve
160
+ remaining uncertainty, and obtain the platform's read-only validation result.
161
+ Future labels are supervision and need not be available at prediction time;
162
+ feature inputs still obey their declared causal/availability contract.
163
+ Passing schema or training preflight is not proof of leakage-free data.
164
+
90
165
  Before publication, review schema and Operator limits and run catalog dry-run.
91
166
  After publication, resolve labels, source fields, horizon and dependencies before
92
167
  building. After build, inspect DataSchema and sampled Parquet rows for the clock
@@ -19,10 +19,23 @@ For late-arriving context observations, read the package-bound
19
19
  The caller supplies one cutoff; the package chooses strict cutoff visibility or
20
20
  visibility at the actual retrieval attempt.
21
21
 
22
+ When authoring time-series data or comparing sequence and tabular models over
23
+ the same artifact, follow
24
+ [feature-management/references/supervised-datasets.md](../feature-management/references/supervised-datasets.md)
25
+ for the distinction between continuous target trajectories, future labels,
26
+ historical windows and grid breaks. Do not replace that data method with a
27
+ Runtime or TUI prompt rule.
28
+
22
29
  Read [references/discovery.md](references/discovery.md) before locating existing
23
30
  resources or selecting runtime bindings. It also defines Job/package preflight
24
31
  and version-skew handling.
25
32
 
33
+ For multiple-model experiments or iterative optimization, read
34
+ [references/model-comparison.md](references/model-comparison.md). This method
35
+ uses platform CLI contracts and works without an ITSMP TUI or a specific Agent
36
+ Runtime. An execution host may additionally persist plans, method hashes,
37
+ attempts and checkpoints; those records do not replace platform evidence.
38
+
26
39
  Read [references/zero-shot-inference.md](references/zero-shot-inference.md) when
27
40
  the user wants synchronous zero-shot forecasting from JSON or CSV. This path
28
41
  uses the platform-owned `chronos2_forecaster` model name, not a public project
@@ -1,5 +1,29 @@
1
1
  # Evaluation Commands
2
2
 
3
+ ## Comparing completed results
4
+
5
+ Use a read-only comparison preflight when the installed CLI/deployed API supports it:
6
+
7
+ ```bash
8
+ ml-platform validate-evaluation-comparison comparison.json
9
+ ```
10
+
11
+ ```json
12
+ {
13
+ "schema_version": "ml_data_platform.evaluation_result_comparison_request/v1",
14
+ "comparison_type": "model",
15
+ "result_ids": ["replace_result_a", "replace_result_b"],
16
+ "metrics": ["regression.mae"],
17
+ "sort_direction": "ascending"
18
+ }
19
+ ```
20
+
21
+ Use actual distinct, immutable complete results. Check the returned status,
22
+ `ranking_allowed` and incompatibility reasons before naming a winner; matching
23
+ metric names alone is insufficient. Direction comes from MetricDefinition;
24
+ neutral metrics and ties do not establish a unique best model. A missing CLI
25
+ command is a version/capability gap; do not bypass it with caller-authored results.
26
+
3
27
  ## Request bodies and prerequisites
4
28
 
5
29
  Evaluation reuses the model's original TrainingRun, replaces its artifact
@@ -0,0 +1,148 @@
1
+ # Comparing models within one experiment
2
+
3
+ Use one documented goal and user-authorized scope: metric, target, candidate
4
+ constraints and any maximum number of training submissions. Preserve that goal
5
+ when a follow-up changes a constraint or asks for verification. Reuse existing
6
+ authorization when it covers the next action; ask only for missing decisions.
7
+ No TUI, session service, or Runtime-specific tool is required for this method.
8
+
9
+ ## Shared scope and candidate execution
10
+
11
+ Freeze a common comparison scope before training: DatasetArtifact identity,
12
+ DataSchema, label/temporal semantics, train/validation/test assignment values,
13
+ evaluation configuration, metric definition/version, target, slice and
14
+ calculation space. Use the training/evaluation references for exact JSON.
15
+ When data needs authoring or rebuilding, use `feature-management` first; a
16
+ registered manifest alone is not training data.
17
+
18
+ Discover actual SystemModels and Runtime Release bindings through the CLI.
19
+ Validate each complete TrainingRun before registration/submission. Candidate
20
+ models may differ in estimator, hyperparameters or ordered input subsets that
21
+ the platform accepts; changing labels, frozen rowsets or evaluation semantics
22
+ starts a different comparison scope. Read-only preflight does not consume a
23
+ training submission, and passing validation is not proof of leakage-free data.
24
+
25
+ The common scope freezes the data and evaluation population; it does not imply
26
+ that every candidate must consume an identical model-input tensor. For each
27
+ candidate, bind a registered exact-version FeatureSet that is an allowed
28
+ subset/order projection of the same DatasetArtifact and validate the model's
29
+ own `input_adapter` and (only when required by its Trainer capability)
30
+ `forecast_target_adapter`. A tabular tree model and a sequence model can share
31
+ the artifact, labels, temporal binding, split assignments and EvaluationConfig
32
+ while using different ordered FeatureSet projections or adapters. Keep the
33
+ target definition, rowset membership, metric, evaluation slice and calculation
34
+ space fixed. If a candidate requires data that is absent from the frozen
35
+ artifact, it is not comparable under this scope; do not rebuild only for that
36
+ candidate and call it the same comparison.
37
+
38
+ For forecast comparisons, distinguish future supervised labels from a
39
+ continuous target trajectory and from a sequence model's historical context.
40
+ Check the final artifact for label alignment, grid breaks, null/non-finite
41
+ values, and per-split history/trajectory sufficiency using
42
+ [`feature-management/references/supervised-datasets.md`](../../feature-management/references/supervised-datasets.md).
43
+ Do not treat a candidate-specific context length as a change to the shared
44
+ forecast horizon, and do not let trajectory construction cross split
45
+ boundaries. Compare complete platform-produced EvaluationResults only after
46
+ each candidate passes its own TrainingRun and evaluation preflight.
47
+
48
+ For each candidate, record its hypothesis, exact changed inputs, parent attempt
49
+ if any, request JSON and returned immutable IDs in the agent's working
50
+ directory. Submit within the user's authorization, observe the TrainingJob,
51
+ validate the resulting ModelArtifact, then submit and inspect governed
52
+ evaluation. Explain the observed result before choosing another candidate.
53
+ Do not resubmit after an unknown outcome: reconcile the existing Job or retry
54
+ only the exact supported idempotent request. Infrastructure retries require
55
+ the authorization described in the training reference.
56
+
57
+ ## Rounds, failure evidence and stopping
58
+
59
+ A round groups candidates that test a related hypothesis under the frozen
60
+ comparison scope. Keep a small round record in the agent's working directory
61
+ (outside a platform source checkout), using normal file tools. Include the
62
+ scope/request paths, candidate identities and changes, submission IDs, observed
63
+ status, result IDs, per-candidate reflection and the round's next decision.
64
+ This is an agent work record, not a new platform resource or required tool.
65
+ Update it after submissions and observations so another coding agent can resume.
66
+
67
+ When a candidate fails, inspect the actual Job status, attempts and diagnostic
68
+ evidence. Distinguish a model execution failure from a runtime/infrastructure
69
+ failure, a rejected contract and an unexplained error. Preserve the original
70
+ error and its source; a keyword in an error message is insufficient to establish
71
+ the cause. A failed Job has no quality score and cannot be ranked as a bad model.
72
+
73
+ A known terminal failure of one candidate need not block independent candidates
74
+ whose data, contracts and runtime remain valid. Record what failed and continue
75
+ within the existing authorization. A shared data/contract/runtime defect blocks
76
+ affected candidates until corrected. Unknown submission outcomes require
77
+ reconciliation before further submissions. A timeout while polling does not
78
+ prove a Job failed or stopped, and does not authorize a duplicate or cancellation.
79
+
80
+ At the round boundary, account for all candidates: complete, failed, still
81
+ running, outcome unknown or skipped with reason. Compare only complete,
82
+ comparable validation evidence. State whether it supports the hypothesis and
83
+ what would change next. Keep global-best and model-family-best references only
84
+ within the same comparison scope and metric direction; ties remain ties.
85
+
86
+ Training-count constraints are optional user instructions. Omitted constraints
87
+ do not mean zero permission, and shell hosts need not implement a submission
88
+ counter. Count actual submissions from retained CLI/platform evidence, including
89
+ failures; distinguish preflight, reused Jobs and newly submitted retries. Resolve
90
+ uncertainty before claiming remaining capacity. If useful, agree on a
91
+ no-improvement window, minimum meaningful metric change, failure threshold or
92
+ observation deadline for a long optimization task. Do not invent default hard
93
+ quotas. Apply improvement criteria only to comparable complete validation
94
+ results; infrastructure failures are not evidence of no improvement.
95
+
96
+ ## Validation selection and held-out test
97
+
98
+ Compare only complete platform-produced EvaluationResults. When supported,
99
+ use `ml-platform validate-evaluation-comparison REQUEST_JSON`; the request and
100
+ evidence rules are in [evaluation.md](evaluation.md). Rank only when the
101
+ platform confirms comparability and the MetricDefinition gives a direction.
102
+ Report ties and inconclusive results as such. Never compute or register a
103
+ replacement platform result to manufacture a winner.
104
+
105
+ Before reading held-out test metrics, save the selected validation candidate's
106
+ exact ModelArtifact ID, validation Result IDs, selection rationale and frozen
107
+ evaluation request. For conflicting metrics or ties, report the tradeoff or
108
+ obtain the missing selection criterion rather than inventing a unique winner.
109
+
110
+ Evaluate that same immutable ModelArtifact on the declared test rowset using
111
+ the normal `validate-evaluation-run`, `submit-evaluation`, Job reads and Result
112
+ reads described in [evaluation.md](evaluation.md). Evaluation JSON uses
113
+ `rowset: "test"` (or the actual declared role); it has no `purpose` field.
114
+ Test and validation may be disjoint splits of the **same DatasetArtifact**.
115
+ Inspect the original TrainingRun split assignment values and temporal coverage
116
+ to establish isolation. Different artifact hashes alone do not prove that the
117
+ rows are independent. A separate artifact also needs the model's full schema
118
+ compatibility; do not rebuild a dataset merely to give test a different hash.
119
+
120
+ Test results assess the locked candidate, not the next tuning decision. Retain
121
+ validation selection and test evidence separately; never mix their Result IDs
122
+ in a validation ranking. Do not retrain or choose another candidate based on
123
+ test results while claiming an unseen final test. If the test was already used
124
+ for selection, disclose that limitation and define a new holdout only within
125
+ the user's authorized scope. A task without a valid test split may deliver its
126
+ validation results with that limitation; it must not invent test evidence.
127
+
128
+ ## Resume and delivery
129
+
130
+ On resume, read the saved requests/round record and query the actual platform
131
+ Jobs, ModelArtifacts and EvaluationResults before continuing. A stopped TUI or
132
+ shell process does not cancel remote Jobs. Preserve a running or unknown Job ID;
133
+ do not rerun its submission command just because the prior tool result is missing.
134
+
135
+ Stop when the requested target is reached, the authorized training count is
136
+ used, the user stops, or evidence establishes a concrete blocker. Deliver the
137
+ candidate/input changes, Run/Job/ModelArtifact/EvaluationResult IDs, metrics,
138
+ comparison limitations and explanation. Distinguish a finished conversation
139
+ from a completed modeling task. A report is complete only when it accounts
140
+ for every submitted candidate and its actual outcome; no model is best when
141
+ there are no comparable results. Publishing or deploying remains a separate
142
+ authorized action.
143
+
144
+ Deliver the stop reason, each round's hypothesis and outcome, validation
145
+ comparison, locked candidate and any test evidence, plus failed/skipped/pending
146
+ candidates and concrete blockers. When all candidates fail, deliver a diagnostic
147
+ report without naming a winner. An unfinished remote Job or unresolved submission
148
+ must remain explicitly pending; ending the conversation is not completion of it.
@@ -68,6 +68,50 @@ Non-temporal regression can omit it. Do not change task kind just to evade check
68
68
  `series_keys` must identify entity-key columns when multiple series coexist;
69
69
  an empty list treats the entire artifact as one series.
70
70
 
71
+ ### Target trajectory, labels and historical context
72
+
73
+ Keep these contracts separate:
74
+
75
+ - `data.labels` and `TemporalBinding.label_time_column` identify supervised
76
+ future truth, normally materialized at `prediction_time + horizon`.
77
+ - `forecast_target_adapter.target_input_column` (v1) or
78
+ `targets[].input_column` (v2) identifies a numeric prediction-time Feature
79
+ whose successive observations form a continuous target trajectory for a
80
+ trajectory-capable Trainer. It is not a substitute for `data.labels`.
81
+ - `input_adapter` defines tabular rows or the model's historical input window.
82
+ Sequence `context_length`, `context_end`, `frequency`, `stride` and
83
+ `gap_policy` concern that window, independently of forecast horizon and
84
+ label alignment.
85
+ - `temporal.frequency` defines expected adjacent prediction-time grid points.
86
+ A grid break is an absent timestamp; a null/non-finite value at an existing
87
+ timestamp is invalid data, not a grid break.
88
+
89
+ The platform applies split assignment before constructing continuous target
90
+ trajectories. `target_gap_policy="reject"` fails if adjacent trajectory rows
91
+ are not one frequency apart; `"split"` begins a new segment at each break.
92
+ Neither setting imputes null/non-finite target or covariate values. A segment
93
+ must meet the effective minimum history plus prediction length or it is
94
+ excluded. `input_adapter.gap_policy` separately controls sequence-window
95
+ behavior (`reject`, `skip` or supported `fill` with a padding policy). Do not
96
+ use either setting to conceal nulls. Target trajectories are split-isolated.
97
+ Sequence history is assembled per prediction row from artifact timestamps at
98
+ or before that row; with chronological holdouts, an evaluation window may use
99
+ earlier training-period history while its target sample remains in the
100
+ evaluation split. Preserve the declared sample membership and never use rows
101
+ after the prediction cutoff. Do not assume target-trajectory split handling
102
+ and sequence-window handling are interchangeable.
103
+
104
+ Before creating a forecast TrainingRun, establish from the final DatasetArtifact
105
+ that the target-input Feature is present, numeric, prediction-time available,
106
+ and sufficiently continuous in each split; labels are separately aligned to
107
+ the declared horizon; and each candidate's history requirements are met. A
108
+ tabular model may consume one row per sample without a sequence history window,
109
+ while a sequence model may need a model-specific window over the same artifact.
110
+ The target-trajectory adapter is required only when the selected Trainer
111
+ capability requires it; do not attach it just because the task is a forecast.
112
+ For the platform's detailed diagnostic and label-statistics procedure, read
113
+ [`feature-management/references/supervised-datasets.md`](../../feature-management/references/supervised-datasets.md).
114
+
71
115
  ## Chronos-2 LoRA parameters
72
116
 
73
117
  Chronos-2 fine-tuning settings belong under `trainer.parameters`; they are
@@ -13,6 +13,8 @@ FeatureSet order and evaluation runtime before the first Job. See
13
13
  for device`, preserve the rejected identity, device and release ID. Use `get-model-runtime-release` to find the
14
14
  approved binding; if the deployed release lacks discovery, request its contract;
15
15
  do not try similar names or re-register trainers to bypass approval.
16
+ - ARIMA supports the v1 single-target adapter for single-channel CPU
17
+ forecasting; it does not support covariates or the v2 adapter.
16
18
  - Chronos-2 supports the legacy v1 single-target adapter and the v2
17
19
  covariate-aware adapter. v2 jointly forecasts ordered target channels and
18
20
  accepts numeric `past_only` and `known_future` covariates. Do not silently
@@ -75,9 +75,17 @@ business configuration submitted through this API, not Kubernetes Secrets.
75
75
  Mapping sources use standard report fields such as `run_id`, `schedule_id`,
76
76
  `deployment_id`, `cutoff_time`, `entity`, and `predictions`. Use `fields` for
77
77
  nested objects, `items` for array conversion, `constant` for fixed values,
78
- `template` for string composition, and `format`, `scale`, `offset`, and `type`
79
- for bounded transformations and validation. Arbitrary code and expressions are
80
- not supported. A missing field or type failure produces
78
+ `template` for string composition, and `format`, `timezone`, `scale`, `offset`,
79
+ and `type` for bounded transformations and validation. `timezone` is an IANA
80
+ timezone and must be used together with `format`; it converts an aware ISO-8601
81
+ timestamp before formatting. For a business endpoint that represents local
82
+ midnight as the end of the previous business day, set `midnight_as_24: true`
83
+ on a time field whose format contains `%H` (for example, `%H:%M`). This maps
84
+ local `00:00` to `24:00` after timezone conversion and leaves other times
85
+ unchanged. For example, use
86
+ `{"source":"cutoff_time","format":"%Y-%m-%d","timezone":"Asia/Shanghai"}`
87
+ to map `2026-09-10T16:00:00Z` to `2026-09-11`. Arbitrary code and expressions
88
+ are not supported. A missing field or type failure produces
81
89
  `payload_mapping_failed` before any HTTP request is made.
82
90
 
83
91
  Use `preview-destination-profile` with a standard prediction payload to inspect