@zhuoyuezs/ml-platform 0.1.16 → 0.1.18

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/DEVELOPMENT.md CHANGED
@@ -9,7 +9,7 @@
9
9
  - Business CLI 与 Skill 是并列组件;CLI 不属于 Skill。
10
10
  - Skill 直接调用正式命令 `ml-platform`,不嵌入 CLI runtime。
11
11
  - 正式 npm 包只有 `@zhuoyuezs/ml-platform`,正式 bin 只有 `ml-platform`。
12
- - `business-client-js/` 是 CLI 唯一源码,`skills/feature-management/` Skill 唯一源码。
12
+ - `business-client-js/` 是 CLI 唯一源码,仓库 `skills/` 下三个受支持 Skill 是各自唯一源码。
13
13
  - `npx @zhuoyuezs/ml-platform@<version> install` 是面向用户的统一安装入口;默认目标是 Codex
14
14
  用户级 Skill,不要求额外安装参数。
15
15
  - 管理命令默认输出简洁结果;Agent 和 CI 显式使用 `--json` 获取稳定结构化输出。
@@ -27,6 +27,8 @@
27
27
  ```text
28
28
  business-client-js/
29
29
  skills/feature-management/
30
+ skills/model-lifecycle-management/
31
+ skills/model-deployment-management/
30
32
  npm/ml-platform/
31
33
  npm/scripts/build_ml_platform_release.py
32
34
  agent_poc/codex-host/
@@ -41,7 +43,7 @@ package/
41
43
  │ ├── main.js
42
44
  │ └── lib.js
43
45
  ├── runtime/business-client/
44
- ├── skills/feature-management/
46
+ ├── skills/<skill-name>/
45
47
  ├── release-policy.json
46
48
  ├── release.json
47
49
  ├── checksums.json
@@ -76,7 +78,7 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
76
78
 
77
79
  完成标准:正式 CLI 不需要 Python、`uv` 或第三方 npm runtime 依赖,旧 Python client 可删除。
78
80
 
79
- ### Step 2:重建纯 feature-management Skill(已完成)
81
+ ### Step 2:重建纯 Skills(已完成)
80
82
 
81
83
  - 保留 `SKILL.md`、`agents/`、`references/` 和 `assets/catalog-template/`;
82
84
  - npm staging 时排除 `assets/wheels/`、Skill installer 和 CLI launcher;这些旧 Python
@@ -94,7 +96,7 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
94
96
  只有 Step 1 和 Step 2 都完成后,才开始 npm dispatcher/installer:
95
97
 
96
98
  - 定义 `release.json` v2 和 `checksums.json` 覆盖范围;
97
- - 确定 `runtime/business-client` 与 `skills/feature-management` 并列目录;
99
+ - 确定 `runtime/business-client` 与 `skills/<skill-name>` 并列目录;
98
100
  - 定义 `ml-platform` 安装管理命令和业务命令的无歧义分发;
99
101
  - 定义 `npx ... install` 如何建立持久 CLI,禁止引用一次性 npx cache;
100
102
  - 定义 CLI home、shim、PATH 检查和跨平台失败行为;
@@ -106,7 +108,7 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
106
108
  ### Step 4:重构 release builder(已完成)
107
109
 
108
110
  - 从 `business-client-js` 复制唯一 client tree 到 `runtime/business-client`;
109
- - canonical Skill 复制纯 Skill 到 `skills/feature-management`;
111
+ - 从仓库 canonical `skills/<skill-name>/` 复制全部纯 Skill
110
112
  - 生成 release v2 和 checksums;
111
113
  - 拒绝多份 client、Skill runtime、旧 wheel 和未知文件;
112
114
  - 执行 npm package allowlist 和可重现 digest 检查;
@@ -134,7 +136,7 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
134
136
 
135
137
  ### Step 7:Skill + CLI 端到端发布验收(已完成)
136
138
 
137
- - `git diff --check -- npm business-client-js skills/feature-management agent_poc/codex-host`;
139
+ - `git diff --check -- npm business-client-js skills agent_poc/codex-host`;
138
140
  - JS business-client tests;
139
141
  - JavaScript OpenAPI contract 与 Catalog tests;
140
142
  - Skill validator 和 forward tests;
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # ML Platform
2
2
 
3
- ML Platform 的 JavaScript CLI、`feature-management` 与 `model-lifecycle-management` Agent Skill。CLI Skill 由同一个 npm
3
+ ML Platform 的 JavaScript CLI、`feature-management`、`model-lifecycle-management` 与
4
+ `model-deployment-management` Agent Skill。CLI 与 Skill 由同一个 npm
4
5
  release 安装和升级,运行时不需要 Python 或 `uv`。
5
6
 
6
7
  ## 环境要求
@@ -17,7 +18,7 @@ release 安装和升级,运行时不需要 Python 或 `uv`。
17
18
  npx @zhuoyuezs/ml-platform@latest install
18
19
  ```
19
20
 
20
- 这条命令不需要额外参数。默认安装持久化的 `ml-platform` 命令,并将两个 Skill 安装到
21
+ 这条命令不需要额外参数。默认安装持久化的 `ml-platform` 命令,并将三个 Skill 安装到
21
22
  `~/.codex/skills/`,供当前用户的 Codex 使用。安装后重启终端和 Agent 会话。
22
23
 
23
24
  只在当前项目使用 Skill 时,在项目根目录运行:
@@ -26,7 +27,7 @@ npx @zhuoyuezs/ml-platform@latest install
26
27
  npx @zhuoyuezs/ml-platform@latest install --scope project
27
28
  ```
28
29
 
29
- 此时 Skill 安装到当前目录的 `.agents/skills/feature-management/`。如果命令不在项目根目录运行,
30
+ 此时 Skill 安装到当前目录的 `.agents/skills/<skill-name>/`。如果命令不在项目根目录运行,
30
31
  再使用 `--project-dir /absolute/path/to/project` 显式指定项目。
31
32
 
32
33
  ### 安装参数
@@ -124,7 +125,8 @@ npx --yes --registry=https://registry.npmjs.org/ \
124
125
  ## 包内容
125
126
 
126
127
  - 一份 JavaScript business CLI;
127
- - 两份纯 Skill:`feature-management` 与 `model-lifecycle-management`;
128
+ - 三份纯 Skill:`feature-management`、`model-lifecycle-management` 与
129
+ `model-deployment-management`;
128
130
  - 显式安装、升级、状态、校验和回滚逻辑。
129
131
 
130
132
  不包含 Codex CLI、Python business-client wheel、Agent Host、平台服务端、模型训练或 Kubernetes
package/checksums.json CHANGED
@@ -2,17 +2,17 @@
2
2
  "files": [
3
3
  {
4
4
  "path": "runtime/business-client/README.md",
5
- "sha256": "sha256:b823c0cafc47478fa1297c7c2e61e15b537db155586dd012b40d678d788c3ce3",
6
- "size_bytes": 5158
5
+ "sha256": "sha256:69a21686d3b27682ad9864646f59353ca188454851941e63a88175951f516450",
6
+ "size_bytes": 7326
7
7
  },
8
8
  {
9
9
  "path": "runtime/business-client/package-lock.json",
10
- "sha256": "sha256:a8da8027de475825949a661bc0d5674903e686d72ac945dcd964febfc5cf4667",
10
+ "sha256": "sha256:386e8b46ae8e6d7fd83e247b0412560710bee263c2545b593c5442b8f1363765",
11
11
  "size_bytes": 381
12
12
  },
13
13
  {
14
14
  "path": "runtime/business-client/package.json",
15
- "sha256": "sha256:e1f916820a4bb19929a76f2690970068d17bb12bf640b89aae007eef50ef7b95",
15
+ "sha256": "sha256:83e60780d91abc78420a533ba0f8ea184f31886f963ca6f1554da819530a6335",
16
16
  "size_bytes": 501
17
17
  },
18
18
  {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  {
24
24
  "path": "runtime/business-client/src/cli.js",
25
- "sha256": "sha256:4f135325b57725c614d266285013df799cdd9a2096d8fbe0a9e9608dae2bcdb7",
26
- "size_bytes": 44947
25
+ "sha256": "sha256:566ce8f224b74f03fcf168f5f2c8102505f1041b85862e8c407ce9103c96424f",
26
+ "size_bytes": 53638
27
27
  },
28
28
  {
29
29
  "path": "runtime/business-client/src/config.js",
@@ -37,8 +37,8 @@
37
37
  },
38
38
  {
39
39
  "path": "skills/feature-management/SKILL.md",
40
- "sha256": "sha256:e5b927611cd401bc5e6160a2d358369efd0c4f65d3fe5cbf21bbab9adb6bd978",
41
- "size_bytes": 31991
40
+ "sha256": "sha256:88898ea83ff339512d5512537db485a2764420abb67afceb9dd090cc780c00e0",
41
+ "size_bytes": 33021
42
42
  },
43
43
  {
44
44
  "path": "skills/feature-management/agents/openai.yaml",
@@ -92,13 +92,13 @@
92
92
  },
93
93
  {
94
94
  "path": "skills/feature-management/references/commands.md",
95
- "sha256": "sha256:c748f1877f6d2ac1f6ecc29e45cd270997cbd35b498a190decddb66c74b840ab",
96
- "size_bytes": 13751
95
+ "sha256": "sha256:ffd106cd785fe0d07ee20f51b7f55ca520da2fc6367bc5bc1ec0218600146d6e",
96
+ "size_bytes": 13905
97
97
  },
98
98
  {
99
99
  "path": "skills/feature-management/references/contracts.md",
100
- "sha256": "sha256:4e10ba13399de17561a10b1dead4e2fcc57e0962cf81eba5131d12d66c4ce013",
101
- "size_bytes": 29965
100
+ "sha256": "sha256:ef582d4ee9ade16de2a5f97b8bf9546969b6f2859eb020986bf25952993df402",
101
+ "size_bytes": 30483
102
102
  },
103
103
  {
104
104
  "path": "skills/feature-management/references/operator-authoring.md",
@@ -115,10 +115,35 @@
115
115
  "sha256": "sha256:2c7a894330a649248e7f52c944422bcd7a3aba4558f7984d129dfd21c8736cf3",
116
116
  "size_bytes": 4540
117
117
  },
118
+ {
119
+ "path": "skills/model-deployment-management/SKILL.md",
120
+ "sha256": "sha256:a78f492b95258e2111e8f410f4bf731cb2fa71997f39d236d9bf75dda3e0ad18",
121
+ "size_bytes": 3082
122
+ },
123
+ {
124
+ "path": "skills/model-deployment-management/agents/openai.yaml",
125
+ "sha256": "sha256:55c1a33d59cf08e27e7c806157576a9f8cec079a0fc6775f299794304ae8b6e1",
126
+ "size_bytes": 238
127
+ },
128
+ {
129
+ "path": "skills/model-deployment-management/references/contracts.md",
130
+ "sha256": "sha256:49a2595106b9b0cb5a305acbd285cdccad2bf4aa7627d6fb6b966783372ea724",
131
+ "size_bytes": 3543
132
+ },
133
+ {
134
+ "path": "skills/model-deployment-management/references/operations.md",
135
+ "sha256": "sha256:4407d87c1debbc865095b2857a6ece7a728d04b25fd8772d4a79a0361b0724ea",
136
+ "size_bytes": 3474
137
+ },
138
+ {
139
+ "path": "skills/model-deployment-management/references/troubleshooting.md",
140
+ "sha256": "sha256:fcdd4b244d670513fe14a3f343af2074a24cac0e3b07645ad455658564314e95",
141
+ "size_bytes": 2852
142
+ },
118
143
  {
119
144
  "path": "skills/model-lifecycle-management/SKILL.md",
120
- "sha256": "sha256:faa4f8c0dbae7d1c1e2ca94499ce9b13cc8b52e8229154c05ac6282ea5e47788",
121
- "size_bytes": 3610
145
+ "sha256": "sha256:61a89145ef33a2d58c58d8f352006590a47cf9587705568dec1c8ffe7ed8938e",
146
+ "size_bytes": 4014
122
147
  },
123
148
  {
124
149
  "path": "skills/model-lifecycle-management/agents/openai.yaml",
@@ -137,18 +162,18 @@
137
162
  },
138
163
  {
139
164
  "path": "skills/model-lifecycle-management/references/packaging.md",
140
- "sha256": "sha256:0992f2f9104cb0d65a6325756108211d3f94856e05eca12640e9792c07e1108f",
141
- "size_bytes": 3250
165
+ "sha256": "sha256:ad4e519f895d121202f99ed038a0ac6a6db3d97bdf95473e942399163d7062a3",
166
+ "size_bytes": 5640
142
167
  },
143
168
  {
144
169
  "path": "skills/model-lifecycle-management/references/training-contracts.md",
145
- "sha256": "sha256:b6d72f37adf40c7d3832dbe46645cd416485411dcd48ea7b93f6072d458922c9",
146
- "size_bytes": 11825
170
+ "sha256": "sha256:28fe32b2673db8f6cb1e02053bf5709826786e48dbe9951eac7bb4d98368d0c4",
171
+ "size_bytes": 12644
147
172
  },
148
173
  {
149
174
  "path": "skills/model-lifecycle-management/references/training.md",
150
- "sha256": "sha256:f7bb40919da78b99cad13d4f1f05fef136552409df5ba6e2838e1e6b4a2435a5",
151
- "size_bytes": 5508
175
+ "sha256": "sha256:97c20f02b1179dc7b7527cb7b5e11978778ccb8b76cdf88c229bcfdb5786f045",
176
+ "size_bytes": 5629
152
177
  }
153
178
  ],
154
179
  "schema_version": "data_platform.ml_platform_checksums/v1"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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:262508ceeef693cdd580e2952f49161d860cee257fbc87ee46c95979cf1285d1",
17
- "version": "0.7.6"
16
+ "sha256": "sha256:4304c1a26444c60408541217bc252e871a2950d1e3d6259dc0bf6b27d294bf09",
17
+ "version": "0.7.8"
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.1.16",
28
+ "release_version": "0.1.18",
29
29
  "runtime_requirements": {
30
30
  "node": ">=18",
31
31
  "os": [
@@ -37,16 +37,22 @@
37
37
  "skills": {
38
38
  "feature-management": {
39
39
  "path": "skills/feature-management",
40
- "requires_cli": ">=0.7.6 <0.8.0",
41
- "revision": "0.1.16",
42
- "sha256": "sha256:182b6f2df4fe58c946e482dfaf416c7d5b78c6fde9c8c8d7293103246618cb4e"
40
+ "requires_cli": ">=0.7.8 <0.8.0",
41
+ "revision": "0.1.18",
42
+ "sha256": "sha256:d32f0306f129d7bf76eb037fe239c84dd3c29c64b61600799d6e40320784bb10"
43
+ },
44
+ "model-deployment-management": {
45
+ "path": "skills/model-deployment-management",
46
+ "requires_cli": ">=0.7.8 <0.8.0",
47
+ "revision": "0.1.18",
48
+ "sha256": "sha256:b09f84d5270406d918cb5fe5818f84424ff41c1167c1b18f8d2b18f2e9b4a047"
43
49
  },
44
50
  "model-lifecycle-management": {
45
51
  "path": "skills/model-lifecycle-management",
46
- "requires_cli": ">=0.7.6 <0.8.0",
47
- "revision": "0.1.16",
48
- "sha256": "sha256:61ba17f955b36d1c2e8329a10454d040ee7eca5afdb90ff0914d3f85b6e2d203"
52
+ "requires_cli": ">=0.7.8 <0.8.0",
53
+ "revision": "0.1.18",
54
+ "sha256": "sha256:7c79a6393e302579e2aa48c6e20754aa992776d0f8936f897bb0feeafc84e508"
49
55
  }
50
56
  },
51
- "source_commit": "49bc1482ab3a58874e40e308640db26e13e6ef12"
57
+ "source_commit": "ec29c26"
52
58
  }
@@ -22,8 +22,15 @@ ml-platform get-dataset <dataset_id> <dataset_version> --project <project>
22
22
  ml-platform resolve-dataset <dataset_id> <dataset_version> --project <project>
23
23
  ml-platform build-registered-dataset <dataset_id> <dataset_version> \
24
24
  --project <project> --wait
25
+ ml-platform register-model-artifact <artifact_id> --evaluation-id <evaluation_result_or_summary_id>
25
26
  ```
26
27
 
28
+ `resolve-dataset` 返回两个不同的身份:`manifest_hash` 是解析后的 DatasetArtifact
29
+ 缓存/构建键,`registered_manifest_hash` 是注册 DatasetManifest 的 canonical 契约哈希。
30
+ 构造 `FeatureRetrievalSpec.manifest.manifest_hash` 时必须使用后者;客户端不需要、也不应
31
+ 自行复算 Python canonical hash。模型评估结果通过 `register-model-artifact` 交给服务端治理
32
+ 校验后注册,不要绕过 CLI 直接调用 HTTP 注册接口。
33
+
27
34
  `resolve-manifest` 和 `build-dataset <manifest.json>` 仍用于尚未注册的本地契约。Catalog
28
35
  normalizer 保留并校验非默认 `project`;`apply --project` 与 Catalog 内项目不一致时拒绝执行。
29
36
 
@@ -48,9 +55,37 @@ ml-platform get-parameter-lifecycle <name> <version> --project <project>
48
55
  ml-platform fetch-inference-context /absolute/path/to/request.json
49
56
  ```
50
57
 
51
- 已部署模型的标准推理使用 Serving URL,不使用控制面 `--api-url`。Chronos-2 v2 的
58
+ 受治理部署通过同一业务 CLI 完成预检、创建、状态等待、晋级、回滚和下线。正常业务操作
59
+ 不使用 `kubectl`,也不创建、修改或删除 NetworkPolicy:
60
+
61
+ ```bash
62
+ ml-platform validate-deployment /absolute/path/to/deployment.json \
63
+ --evaluation-id <pass-evaluation-id>
64
+ ml-platform create-deployment /absolute/path/to/deployment.json \
65
+ --evaluation-id <pass-evaluation-id>
66
+ ml-platform wait-deployment <deployment-id> --wait-timeout-seconds 1800
67
+ ml-platform get-deployment <deployment-id>
68
+ ml-platform get-deployment-events <deployment-id> --limit 100 --offset 0
69
+ ```
70
+
71
+ `READY` 必须同时包含 `ReplicasReady=true`、`ArtifactsVerified=true` 和
72
+ `SmokePredictionPassed=true`。`get-deployment` 返回控制器实际观察到的 endpoint 元数据;
73
+ 本地 CLI 不推测 Kubernetes Service 名称。部署后验证优先使用控制面代理:
74
+
75
+ ```bash
76
+ ml-platform predict-deployment <deployment-id> /absolute/path/to/request.json
77
+ ```
78
+
79
+ 部署文件可用 `inference_policy` 配置默认执行 horizon 和半开输出窗口。例如 15 分钟频率、
80
+ 当天 00:00 cutoff、只返回次日全天时,配置 `horizon=2d` 和 `[1d,2d)`;模型必须具备
81
+ 192 点输出能力,响应返回 96 点。请求可省略 `horizon` 和 `output_window` 使用部署默认值,
82
+ 也可携带其中任意一项覆盖;覆盖值仍受模型长度、频率和窗口边界校验。
83
+
84
+ 直接访问已知外部 Serving URL 时仍可使用 `predict-model`。Chronos-2 v2 的
52
85
  `known_future` 协变量放在 inline 请求的 `inputs.future_covariates` 中;每个预测步一行,
53
- 行数必须等于模型的 `prediction_length`。下例为 15 分钟频率、预测 30 分钟;预测明天时
86
+ 行数必须等于模型的 `prediction_length`。`feature_lookup` 模式不得传该字段;Serving 会从
87
+ 部署绑定的 FeatureSet 在线读取完整未来 Feature 窗口,并只使用
88
+ `available_at <= cutoff_time` 的修订。下例为 15 分钟频率、预测 30 分钟;预测明天时
54
89
  同样提供整天的所有未来步:
55
90
 
56
91
  ```json
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zhuoyuezs/ml-platform-business-client",
9
- "version": "0.7.6",
9
+ "version": "0.7.8",
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.7.6",
3
+ "version": "0.7.8",
4
4
  "private": true,
5
5
  "description": "JavaScript implementation of the ML Platform business client",
6
6
  "bin": {
@@ -34,7 +34,11 @@ const BUSINESS_COMMANDS = new Set([
34
34
  "register-executable-package", "verify-executable-package", "publish-executable-package",
35
35
  "validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
36
36
  "get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
37
- "create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact",
37
+ "create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact", "register-model-artifact",
38
+ "list-deployment-targets", "validate-deployment", "create-deployment", "list-deployments",
39
+ "get-deployment", "wait-deployment", "reconcile-deployment", "approve-deployment",
40
+ "promote-deployment", "rollback-deployment", "suspend-deployment", "resume-deployment",
41
+ "undeploy-deployment", "get-deployment-events", "predict-deployment",
38
42
  ]);
39
43
 
40
44
  const COMMAND_USAGE = {
@@ -138,6 +142,22 @@ const COMMAND_USAGE = {
138
142
  "get-model-package": "get-model-package PACKAGE_ID [--package-version VERSION]",
139
143
  "cancel-model-package": "cancel-model-package PACKAGE_ID [--package-version VERSION]",
140
144
  "validate-model-artifact": "validate-model-artifact ARTIFACT_ID",
145
+ "register-model-artifact": "register-model-artifact ARTIFACT_ID --evaluation-id EVALUATION_ID",
146
+ "list-deployment-targets": "list-deployment-targets",
147
+ "validate-deployment": "validate-deployment SPEC_JSON --evaluation-id ID",
148
+ "create-deployment": "create-deployment SPEC_JSON --evaluation-id ID",
149
+ "list-deployments": "list-deployments [--service SERVICE] [--environment ENV] [--state STATE] [-q QUERY] [--limit N] [--offset N]",
150
+ "get-deployment": "get-deployment DEPLOYMENT_ID",
151
+ "wait-deployment": "wait-deployment DEPLOYMENT_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
152
+ "reconcile-deployment": "reconcile-deployment DEPLOYMENT_ID",
153
+ "approve-deployment": "approve-deployment DEPLOYMENT_ID --revision N --approver NAME --decision approved|rejected [--reason TEXT]",
154
+ "promote-deployment": "promote-deployment DEPLOYMENT_ID --stage canary|production --actor NAME --reason TEXT [--approval-id ID]",
155
+ "rollback-deployment": "rollback-deployment DEPLOYMENT_ID --actor NAME --reason TEXT [--to-revision N] [--to-deployment-id ID]",
156
+ "suspend-deployment": "suspend-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
157
+ "resume-deployment": "resume-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
158
+ "undeploy-deployment": "undeploy-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
159
+ "get-deployment-events": "get-deployment-events DEPLOYMENT_ID [--limit N] [--offset N]",
160
+ "predict-deployment": "predict-deployment DEPLOYMENT_ID REQUEST_JSON",
141
161
  apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
142
162
  };
143
163
 
@@ -291,6 +311,28 @@ async function waitForJob(api, jobId, pollInterval, timeoutSeconds) {
291
311
  }
292
312
  }
293
313
 
314
+ function observedDeploymentState(payload) {
315
+ const state = payload?.observed_state?.observed_state ?? payload?.observed_state;
316
+ if (typeof state !== "string") throw new Error("deployment status response has no observed state");
317
+ return state;
318
+ }
319
+
320
+ async function waitForDeployment(api, deploymentId, pollInterval, timeoutSeconds) {
321
+ if (!(pollInterval > 0)) throw new Error("deployment poll interval must be positive");
322
+ if (timeoutSeconds !== undefined && !(timeoutSeconds > 0)) throw new Error("deployment wait timeout must be positive");
323
+ const deadline = timeoutSeconds === undefined ? Infinity : Date.now() + timeoutSeconds * 1000;
324
+ let previous;
325
+ while (true) {
326
+ const payload = await api.get(`/deployments/${encodeURIComponent(deploymentId)}`);
327
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("deployment status response must be a JSON object");
328
+ const state = observedDeploymentState(payload);
329
+ if (state !== previous) { process.stderr.write(`deployment ${deploymentId}: ${state}\n`); previous = state; }
330
+ if (["READY", "FAILED", "DEGRADED", "SUPERSEDED", "SUSPENDED", "UNDEPLOYED", "ROLLED_BACK"].includes(state)) return payload;
331
+ if (Date.now() >= deadline) throw new Error(`timed out waiting for deployment ${deploymentId}`);
332
+ await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
333
+ }
334
+ }
335
+
294
336
  async function runBusinessCli(argv) {
295
337
  if (argv.length === 0) throw new Error("a business command is required");
296
338
  if (argv.includes("--help") || argv.includes("-h")) {
@@ -390,9 +432,8 @@ async function runBusinessCli(argv) {
390
432
  const out = take(rest, "--out");
391
433
  if (out) { const target = resolvedPath(out); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`); }
392
434
  } else if (options.command === "resolve-dataset") {
393
- const datasetId = positional(rest, "dataset id"); const datasetVersion = positional(rest, "dataset version"); const project = take(rest, "--project", "default"); const api = client(options);
394
- const manifest = normalizeRegistryDataset(await registeredDataset(api, project, datasetId, datasetVersion));
395
- result = await api.post("/datasets/resolve", manifest);
435
+ const datasetId = positional(rest, "dataset id"); const datasetVersion = positional(rest, "dataset version"); const project = take(rest, "--project", "default");
436
+ result = await client(options).post(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(datasetId)}/${encodeURIComponent(datasetVersion)}/resolve`);
396
437
  const out = take(rest, "--out");
397
438
  if (out) { const target = resolvedPath(out); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`); }
398
439
  } else if (options.command === "build-dataset") {
@@ -457,6 +498,91 @@ async function runBusinessCli(argv) {
457
498
  const request = readSpec(positional(rest, "request JSON")); const servingUrl = take(rest, "--serving-url");
458
499
  if (!servingUrl) throw new Error("--serving-url is required");
459
500
  result = await new PlatformApiClient(servingUrl, options.timeout).post("/v1/inference", request);
501
+ } else if (options.command === "list-deployment-targets") {
502
+ result = await client(options).get("/deployment-targets");
503
+ } else if (["validate-deployment", "create-deployment"].includes(options.command)) {
504
+ const deployment = readSpec(positional(rest, "deployment spec JSON"));
505
+ const evaluationId = take(rest, "--evaluation-id");
506
+ if (!evaluationId) throw new Error("--evaluation-id is required");
507
+ const endpoint = options.command === "validate-deployment" ? "/deployments/validate" : "/deployments";
508
+ result = await client(options).post(endpoint, { deployment, evaluation_id: evaluationId });
509
+ } else if (options.command === "list-deployments") {
510
+ const params = {
511
+ service: take(rest, "--service"),
512
+ environment: take(rest, "--environment"),
513
+ state: take(rest, "--state"),
514
+ q: take(rest, "--q", take(rest, "-q")),
515
+ };
516
+ boolean(rest, "--all");
517
+ result = await listCollection(client(options), "/deployments", params, rest);
518
+ } else if (options.command === "get-deployment") {
519
+ const deploymentId = positional(rest, "deployment id");
520
+ result = await client(options).get(`/deployments/${encodeURIComponent(deploymentId)}`);
521
+ } else if (options.command === "wait-deployment") {
522
+ const deploymentId = positional(rest, "deployment id");
523
+ const poll = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
524
+ const rawTimeout = take(rest, "--wait-timeout-seconds");
525
+ result = await waitForDeployment(
526
+ client(options),
527
+ deploymentId,
528
+ poll,
529
+ rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds"),
530
+ );
531
+ if (observedDeploymentState(result) !== "READY") exitCode = 1;
532
+ } else if (options.command === "reconcile-deployment") {
533
+ const deploymentId = positional(rest, "deployment id");
534
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/reconcile`);
535
+ } else if (options.command === "approve-deployment") {
536
+ const deploymentId = positional(rest, "deployment id");
537
+ const rawRevision = take(rest, "--revision");
538
+ const approver = take(rest, "--approver");
539
+ if (rawRevision === undefined) throw new Error("--revision is required");
540
+ if (!approver) throw new Error("--approver is required");
541
+ const decision = take(rest, "--decision", "approved");
542
+ if (!["approved", "rejected"].includes(decision)) throw new Error("--decision must be approved or rejected");
543
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/approvals`, {
544
+ revision: integer(rawRevision, "--revision"), stage: "production", decision, approver,
545
+ reason: take(rest, "--reason", ""),
546
+ });
547
+ } else if (options.command === "promote-deployment") {
548
+ const deploymentId = positional(rest, "deployment id");
549
+ const stage = take(rest, "--stage"); const actor = take(rest, "--actor"); const reason = take(rest, "--reason");
550
+ if (!["canary", "production"].includes(stage)) throw new Error("--stage must be canary or production");
551
+ if (!actor) throw new Error("--actor is required");
552
+ if (!reason) throw new Error("--reason is required");
553
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/promote`, {
554
+ stage, actor, reason, approval_id: take(rest, "--approval-id"),
555
+ });
556
+ } else if (options.command === "rollback-deployment") {
557
+ const deploymentId = positional(rest, "deployment id");
558
+ const actor = take(rest, "--actor"); const reason = take(rest, "--reason");
559
+ if (!actor) throw new Error("--actor is required");
560
+ if (!reason) throw new Error("--reason is required");
561
+ const rawRevision = take(rest, "--to-revision");
562
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/rollback`, {
563
+ actor, reason,
564
+ to_revision: rawRevision === undefined ? undefined : integer(rawRevision, "--to-revision"),
565
+ to_deployment_id: take(rest, "--to-deployment-id"),
566
+ });
567
+ } else if (["suspend-deployment", "resume-deployment", "undeploy-deployment"].includes(options.command)) {
568
+ const deploymentId = positional(rest, "deployment id");
569
+ const actor = take(rest, "--actor"); const reason = take(rest, "--reason");
570
+ if (!actor) throw new Error("--actor is required");
571
+ if (!reason) throw new Error("--reason is required");
572
+ const action = options.command.split("-")[0];
573
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/${action}`, { actor, reason });
574
+ } else if (options.command === "get-deployment-events") {
575
+ const deploymentId = positional(rest, "deployment id");
576
+ result = await listCollection(
577
+ client(options),
578
+ `/deployments/${encodeURIComponent(deploymentId)}/events`,
579
+ {},
580
+ rest,
581
+ );
582
+ } else if (options.command === "predict-deployment") {
583
+ const deploymentId = positional(rest, "deployment id");
584
+ const request = readSpec(positional(rest, "request JSON"));
585
+ result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/predict`, request);
460
586
  } else if (options.command === "get-model-runtime-release") {
461
587
  result = await client(options).get("/model-runtime-release");
462
588
  } else if (options.command.startsWith("list-") && LIFECYCLE_COLLECTIONS.has(options.command.slice(5))) {
@@ -506,6 +632,11 @@ async function runBusinessCli(argv) {
506
632
  const name = positional(rest, "package name"); const version = positional(rest, "package version"); const action = options.command.split("-")[0];
507
633
  result = await client(options).post(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${action}`);
508
634
  } else if (options.command === "validate-model-artifact") result = await client(options).get(`/model-artifacts/${encodeURIComponent(positional(rest, "model artifact id"))}/validate`);
635
+ else if (options.command === "register-model-artifact") {
636
+ const artifactId = positional(rest, "model artifact id"); const evaluationId = take(rest, "--evaluation-id");
637
+ if (!evaluationId) throw new Error("--evaluation-id is required");
638
+ result = await client(options).post(`/models/${encodeURIComponent(artifactId)}/register`, { evaluation_id: evaluationId });
639
+ }
509
640
  else if (/^(get|cancel)-model-package$/.test(options.command)) {
510
641
  const packageId = positional(rest, "model package id"); const params = { package_version: take(rest, "--package-version") }; const endpoint = `/model-packages/${encodeURIComponent(packageId)}`;
511
642
  result = options.command === "get-model-package" ? await client(options).get(endpoint, params) : await client(options).post(`${endpoint}/cancel`, undefined, params);
@@ -529,7 +660,7 @@ async function listCollection(api, endpoint, params, rest) {
529
660
  return api.get(endpoint, { ...params, limit, offset });
530
661
  }
531
662
 
532
- module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForJob };
663
+ module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForDeployment, waitForJob };
533
664
 
534
665
  if (require.main === module) {
535
666
  runBusinessCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error) => {
@@ -205,11 +205,14 @@ platform source checkout or a local Catalog directory for a matching JSON file.
205
205
  target from a similar local filename.
206
206
  4. Run `get-dataset <dataset_id> <dataset_version> --project <project>` and
207
207
  inspect the returned registered contract.
208
- 5. Run `resolve-dataset` for a read-only dependency and manifest-hash check.
208
+ 5. Run `resolve-dataset` for a read-only dependency and identity check. Use its
209
+ `registered_manifest_hash` for `FeatureRetrievalSpec.manifest.manifest_hash`;
210
+ use its `manifest_hash` only as the resolved DatasetArtifact/build key.
209
211
  6. Submit exactly one `build-registered-dataset ... --wait` only after the user
210
212
  authorizes that exact registered version.
211
- 7. Use the returned Job ID with `get-job`, then use the returned manifest hash
212
- with `get-dataset-artifact` after success.
213
+ 7. Use the returned Job ID with `get-job`, then use the resolved `manifest_hash`
214
+ with `get-dataset-artifact` after success. Keep the separate
215
+ `registered_manifest_hash` for FeatureRetrievalSpec binding.
213
216
 
214
217
  Local `resolve-manifest` and `build-dataset <file>` remain authoring tools for a
215
218
  new or changed Catalog contract before it is registered. They are not required
@@ -419,6 +422,17 @@ replayable `as_of` manifest; when an online source override is active, `replay.s
419
422
  must be false because the offline manifest cannot reproduce that source. Report
420
423
  tolerated missing or gap-filled inputs as degraded freshness, not as a normal read.
421
424
 
425
+ Model-serving `feature_lookup` may additionally request declared known-future
426
+ Features from the same FeatureSet. Do not emulate that by passing model inputs
427
+ through this Skill. Author their Parameters with trustworthy availability
428
+ evidence: future event times are valid, but only revisions with
429
+ `available_at <= cutoff_time` may be used. `available_at` may therefore be
430
+ earlier than `event_time` for forecasts and schedules. The platform reads only
431
+ the requested known-future Features and their Parameter dependency closure;
432
+ unrelated historical Parameters in the same DatasetManifest do not need a
433
+ future-visibility contract. Every Parameter inside the requested closure still
434
+ fails closed when trustworthy availability is absent.
435
+
422
436
  ## Verify The Artifact
423
437
 
424
438
  Treat Registry metadata inspection and downloaded-file inspection as separate
@@ -461,7 +475,7 @@ Require all of the following before reporting success:
461
475
  windows tiling the fetch range, and any retries are accounted for;
462
476
  - missing values and warnings are reported, not silently repaired.
463
477
 
464
- The manifest hash returned by `resolve-manifest` or `resolve-dataset` is the artifact key: it stays
478
+ The `manifest_hash` returned by `resolve-manifest` or `resolve-dataset` is the artifact key: it stays
465
479
  the same after the build, so it is usable for cache reuse and polling before
466
480
  submission. Distinguish the two lookup failures rather than treating both as "not
467
481
  ready" — `422` with `invalid_artifact_key` means the key can never name an
@@ -480,7 +494,7 @@ new / unchanged assets by type
480
494
  planned versus resolved dependency versions and any explicitly retained old versions
481
495
  Operator package filename and SHA-256
482
496
  FeatureSet and ordered feature count
483
- dataset id, version, manifest hash, and Job id
497
+ dataset id, version, resolved manifest hash, registered manifest hash when applicable, and Job id
484
498
  row and column counts
485
499
  rowset candidate / policy-applied / eligible / ineligible counts when endpoint_policy is declared
486
500
  validation status, warnings, and missing-data summary
@@ -220,7 +220,8 @@ DatasetManifest hash. The initial implementation is sequential, so keep
220
220
  `--max-parallelism 1`. Completed partitions are checkpointed and resumed by
221
221
  default; use `--no-resume` only to force a clean execution.
222
222
 
223
- Record the returned `job_id`, `dataset_id`, and `manifest_hash`. If a separate wait is needed:
223
+ Record the returned `job_id`, `dataset_id`, and resolved `manifest_hash`; for a registered Dataset
224
+ also retain `registered_manifest_hash` for FeatureRetrievalSpec binding. If a separate wait is needed:
224
225
 
225
226
  ```bash
226
227
  ml-platform --profile server wait-job <job_id>
@@ -266,7 +267,8 @@ ml-platform --profile server fetch-inference-context /tmp/inference-context.json
266
267
  ```
267
268
 
268
269
  The `FeatureRetrievalSpec` must bind an existing inference-mode DatasetManifest
269
- by project, dataset id, version, and manifest hash; its FeatureSet and contract
270
+ by project, dataset id, version, and the `registered_manifest_hash` returned by
271
+ `resolve-dataset`; its FeatureSet and contract
270
272
  signature must match the resolved online contract. Context mode also fixes
271
273
  `context_length`, `frequency`, `context_end`, `stride`, input-adapter hash, and
272
274
  temporal-binding hash. Do not replace these immutable deployment bindings with
@@ -138,6 +138,12 @@ supported field.
138
138
  当前严格 `as_of` 只接受 `exact` 或有合同支撑的 `upper_bound`;修改这些语义需要发布
139
139
  新的 Parameter 业务版本。
140
140
 
141
+ 未来预报、排程、日历等 known-future 数据允许 `available_at < event_time`。这不是时钟错误:
142
+ `event_time` 是未来目标时刻,`available_at` 是该修订何时已可读取。模型
143
+ `feature_lookup` 会读取 cutoff 后的未来事件窗口,但仍严格过滤
144
+ `available_at <= cutoff_time`。因此这类 Parameter 应优先使用能表达预报签发时间或计划发布
145
+ 时间的 `source_field`,不能用事后实测值替代,也不能用调用方请求绕过 availability 合同。
146
+
141
147
  An optional `rounding` section states the fixed-point precision the platform
142
148
  enforces at the Parameter boundary:
143
149
 
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: model-deployment-management
3
+ description: Manage governed ITSMP model deployments and post-deployment predictions through the ml-platform business CLI. Use when a user asks to validate or create a DeploymentSpec, inspect targets or rollout state, wait for readiness, record approval, promote, roll back, suspend, resume, undeploy, inspect deployment events, or invoke a deployed model. Do not use for training, evaluation, packaging, image release engineering, or Kubernetes administration.
4
+ ---
5
+
6
+ # Model Deployment Management
7
+
8
+ Use the `ml-platform` executable installed with the same npm release as this
9
+ Skill. Normal deployment operations go through the ITSMP control plane; do not
10
+ use `kubectl` to create, promote, roll back, suspend, resume, or undeploy a
11
+ business model.
12
+
13
+ ```text
14
+ REGISTERED ModelArtifact + PASS EvaluationResult + READY ModelPackage
15
+ -> validated DeploymentSpec -> DeploymentRevision -> READY -> prediction
16
+ ```
17
+
18
+ Read [references/contracts.md](references/contracts.md) before authoring or
19
+ validating a DeploymentSpec. Read
20
+ [references/operations.md](references/operations.md) immediately before a
21
+ deployment mutation or prediction. Read
22
+ [references/troubleshooting.md](references/troubleshooting.md) when a
23
+ deployment does not reach `READY` or an operation is rejected.
24
+
25
+ ## Required Gates
26
+
27
+ - Verify `ml-platform version`, `ml-platform show-config`, and
28
+ `ml-platform --profile server health` before control-plane work.
29
+ - Use only a `READY` ModelPackage image with an immutable `@sha256:` digest and
30
+ the package-bound FeatureRetrievalSpec and smoke request.
31
+ - When `DeploymentSpec.inference_policy` is configured, treat its horizon and
32
+ half-open output window as request defaults. Prediction requests may omit or
33
+ override `horizon` and `output_window`; every effective combination must fit
34
+ the model capacity and sampling grid. In `feature_lookup` mode, never send
35
+ `future_covariates`; the platform retrieves the complete known-future Feature
36
+ window visible at the cutoff. Inline mode still supplies those rows in inputs.
37
+ - Run `validate-deployment` before `create-deployment`. Validation is read-only
38
+ and does not authorize creation.
39
+ - Obtain explicit authorization for the exact deployment and operation before
40
+ create, reconcile, approval, promotion, rollback, suspend, resume, or
41
+ undeploy. Retry requires renewed authorization when the previous mutation
42
+ returned an ambiguous result.
43
+ - Treat `READY` as valid only when `ReplicasReady`, `ArtifactsVerified`, and
44
+ `SmokePredictionPassed` are all true for the current revision.
45
+ - Use `predict-deployment`; it routes through the control plane to the recorded
46
+ deployment endpoint. Do not infer Kubernetes Service names or require the
47
+ user to reach a cluster-local URL.
48
+ - Never create, modify, or delete NetworkPolicy. Cluster operators own that
49
+ boundary.
50
+
51
+ Report the deployment ID, revision, immutable image digest, desired and
52
+ observed state, failed conditions, endpoint reachability, and event evidence.
53
+ Do not claim a deployment is usable merely because its Pod is running.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "模型部署管理"
3
+ short_description: "管理 ITSMP 模型部署、晋级、回滚与部署后推理"
4
+ default_prompt: "使用 $model-deployment-management 管理受治理模型部署及部署后推理。"
@@ -0,0 +1,82 @@
1
+ # Deployment Contracts
2
+
3
+ ## DeploymentSpec
4
+
5
+ Use a JSON object accepted by `ml-platform validate-deployment` with these
6
+ load-bearing fields:
7
+
8
+ ```json
9
+ {
10
+ "schema_version": "ml_data_platform.deployment/v1",
11
+ "deployment_id": "deployment_forecast_staging_01",
12
+ "service": "forecast-service",
13
+ "environment": "staging",
14
+ "model_artifact_id": "artifact_01",
15
+ "package_id": "package_01",
16
+ "package_version": "1",
17
+ "image": "registry.example/model-serving@sha256:<64-hex>",
18
+ "target": {"type": "kubernetes", "namespace": "itsmp-model-serving"},
19
+ "feature_retrieval": {},
20
+ "inference_policy": {
21
+ "horizon": {"duration": "2d"},
22
+ "output_window": {"start_offset": "1d", "end_offset": "2d"},
23
+ "allow_request_override": true
24
+ },
25
+ "replicas": 1,
26
+ "resources": {},
27
+ "rollout": {"strategy": "canary", "traffic_percent": 10},
28
+ "stage": "staging"
29
+ }
30
+ ```
31
+
32
+ Do not reconstruct `feature_retrieval` or `feature_lookup_test` from memory.
33
+ They must match the registered artifact lineage and the tested ModelPackage.
34
+ The server normalizes an omitted lookup test from the package during
35
+ validation and creation.
36
+
37
+ `inference_policy` is optional. When present, its horizon and offsets must be
38
+ whole-minute durations aligned to the model sampling frequency. The half-open
39
+ window `[cutoff+start_offset, cutoff+end_offset)` must be non-empty, end within
40
+ the configured horizon, and fit the model's maximum sequence output. A 15-minute
41
+ model using `horizon=2d` and `[1d,2d)` therefore needs at least 192 output steps
42
+ and returns 96 steps. This is a response projection, not extra model capacity.
43
+
44
+ The deployment values are defaults. Calls may omit `horizon` and
45
+ `output_window`, override either one, or override both. Serving validates the
46
+ effective values again; an output window must remain non-empty, aligned, inside
47
+ the effective horizon, and within model capacity. Set
48
+ `allow_request_override=false` only when the endpoint must reject non-equivalent
49
+ request values.
50
+
51
+ Known-future covariates still cover the model's complete prediction length,
52
+ including points outside a shorter returned window. In `feature_lookup` mode,
53
+ Serving requests those Feature columns from the deployment-bound FeatureSet;
54
+ the platform permits future event times only when `available_at <= cutoff_time`.
55
+ The prediction caller must not send top-level `future_covariates`. Inline mode
56
+ continues to carry them in `inputs.future_covariates`.
57
+
58
+ The first deployment starts at `staging`. A replacement for an existing
59
+ service/environment declares the active leaf in `previous_deployment_id`.
60
+ Production is reached only by staging to canary to production promotion.
61
+
62
+ ## Revision State
63
+
64
+ `DeploymentSpec` is immutable. Its inference policy and full deployment contract
65
+ hash are copied into each immutable revision. Promotion, rollback, suspension,
66
+ resumption, and undeploy append revisions that retain that policy. Reconcile
67
+ writes mutable observed state separately.
68
+
69
+ `READY` requires all three current-revision conditions:
70
+
71
+ - `ReplicasReady=true`
72
+ - `ArtifactsVerified=true`
73
+ - `SmokePredictionPassed=true`
74
+
75
+ The detail response includes `endpoints`. Cluster-local endpoints have
76
+ `externally_reachable=false`; a Gateway endpoint is externally reachable only
77
+ when rollout hostname metadata exists. `available=true` means the current
78
+ revision passed all READY gates.
79
+
80
+ Successful smoke verification is cached durably by revision, image digest,
81
+ FeatureRetrieval contract hash, deployment contract hash, and frozen lookup-test
82
+ hash. A new revision or changed identity must run smoke again.
@@ -0,0 +1,111 @@
1
+ # Deployment Operations
2
+
3
+ ## Initialize
4
+
5
+ ```bash
6
+ command -v ml-platform
7
+ ml-platform version
8
+ ml-platform show-config
9
+ ml-platform --profile server health
10
+ ml-platform --profile server list-deployment-targets
11
+ ```
12
+
13
+ ## Validate And Create
14
+
15
+ Validation and creation are separate authorization points:
16
+
17
+ ```bash
18
+ ml-platform --profile server validate-deployment /absolute/path/deployment.json \
19
+ --evaluation-id <pass-evaluation-id>
20
+
21
+ ml-platform --profile server create-deployment /absolute/path/deployment.json \
22
+ --evaluation-id <pass-evaluation-id>
23
+
24
+ ml-platform --profile server wait-deployment <deployment-id> \
25
+ --poll-interval 5 --wait-timeout-seconds 1800
26
+ ```
27
+
28
+ Use `get-deployment` to inspect the full revision history and endpoints. Use
29
+ `list-deployments` for discovery and `get-deployment-events` for paginated
30
+ audit evidence.
31
+
32
+ ## Promotion And Approval
33
+
34
+ ```bash
35
+ ml-platform --profile server promote-deployment <deployment-id> \
36
+ --stage canary --actor <actor> --reason <reason>
37
+
38
+ ml-platform --profile server approve-deployment <deployment-id> \
39
+ --revision <canary-revision> --approver <approver> \
40
+ --decision approved --reason <reason>
41
+
42
+ ml-platform --profile server promote-deployment <deployment-id> \
43
+ --stage production --actor <actor> --reason <reason> \
44
+ --approval-id <approval-id>
45
+ ```
46
+
47
+ Do not approve production before the canary revision is `READY` and the user
48
+ has authorized that exact revision.
49
+
50
+ ## Rollback And Offline Operations
51
+
52
+ ```bash
53
+ ml-platform --profile server rollback-deployment <deployment-id> \
54
+ --to-revision <revision> --actor <actor> --reason <reason>
55
+
56
+ ml-platform --profile server suspend-deployment <deployment-id> \
57
+ --actor <actor> --reason <reason>
58
+
59
+ ml-platform --profile server resume-deployment <deployment-id> \
60
+ --actor <actor> --reason <reason>
61
+
62
+ ml-platform --profile server undeploy-deployment <deployment-id> \
63
+ --actor <actor> --reason <reason>
64
+ ```
65
+
66
+ Suspend is recoverable. Undeploy removes runtime resources but retains revision
67
+ history; it is not equivalent to resume.
68
+
69
+ ## Prediction
70
+
71
+ With a deployment-level inference policy, the request may use both deployment
72
+ defaults by omitting `horizon` and `output_window`:
73
+
74
+ ```json
75
+ {
76
+ "input_mode": "feature_lookup",
77
+ "cutoff_time": "2026-09-14T00:00:00+08:00",
78
+ "entity": {}
79
+ }
80
+ ```
81
+
82
+ To override the revision defaults, add either or both fields. This example keeps
83
+ the deployment horizon but returns its first day:
84
+
85
+ ```json
86
+ {
87
+ "input_mode": "feature_lookup",
88
+ "cutoff_time": "2026-09-14T00:00:00+08:00",
89
+ "entity": {},
90
+ "output_window": {"start_offset": "0min", "end_offset": "1d"}
91
+ }
92
+ ```
93
+
94
+ Do not send `future_covariates` in `feature_lookup` mode. Serving derives the
95
+ declared known-future columns and full model prediction length from the
96
+ ModelArtifact, then the platform retrieves visible future Feature revisions.
97
+ Only those Features and their Parameter dependency closure are read; unrelated
98
+ historical Parameters in the same inference Manifest are not evaluated as
99
+ future inputs. Requested future Parameters must still provide trustworthy
100
+ `available_at <= cutoff_time` evidence.
101
+ For inline mode only, supply one row per model prediction step in
102
+ `inputs.future_covariates`.
103
+
104
+ ```bash
105
+ ml-platform --profile server predict-deployment <deployment-id> \
106
+ /absolute/path/request.json
107
+ ```
108
+
109
+ Prediction is allowed only for a current `READY` revision with all readiness
110
+ conditions true. This command uses the control-plane proxy and does not require
111
+ a cluster-local Serving URL.
@@ -0,0 +1,50 @@
1
+ # Deployment Troubleshooting
2
+
3
+ Start with read-only control-plane evidence:
4
+
5
+ ```bash
6
+ ml-platform --profile server get-deployment <deployment-id>
7
+ ml-platform --profile server get-deployment-events <deployment-id> \
8
+ --limit 100 --offset 0
9
+ ```
10
+
11
+ Interpret conditions before considering a reconcile:
12
+
13
+ - `ReplicasReady=false`: inspect scheduler, image pull, resource quota, and Pod
14
+ readiness through the platform/cluster diagnostic boundary.
15
+ - `ArtifactsVerified=false`: the Serving `/model` identity does not match the
16
+ revision, or the endpoint is unavailable.
17
+ - `SmokePredictionPassed=false`: preserve the frozen package smoke request and
18
+ error. Do not replace it with a current-time request to force success.
19
+ - `request horizon cannot override deployment inference_policy` or
20
+ `request output_window cannot override deployment inference_policy`: the
21
+ revision has `allow_request_override=false`; use equivalent values or create
22
+ a reviewed revision whose policy permits the required request behavior.
23
+ - `inference_policy output window must end within the configured horizon`:
24
+ override `horizon` and `output_window` together, or keep the deployment
25
+ defaults. A shorter horizon cannot retain a later default window.
26
+ - `feature_lookup obtains known future covariates from online retrieval`: remove
27
+ top-level `future_covariates`. Verify the columns belong to the bound
28
+ FeatureSet and their Parameters provide trustworthy `available_at` values.
29
+ - `availability_not_supported` or `available_at_missing` during future lookup:
30
+ publish a corrected Parameter/Feature dependency version with a source-field
31
+ or justified upper-bound availability contract; do not substitute caller data.
32
+ - `inference_policy horizon exceeds model maximum prediction length`: retrain
33
+ or select a model artifact with enough sequence steps. For `[1d,2d)` at
34
+ 15-minute frequency, the model needs 192 steps, not only the returned 96.
35
+ - An unexpected output point count usually indicates frequency/target-time
36
+ drift or an incomplete runtime trajectory. Inspect `output_window.boundary`,
37
+ UTC `start_time`/`end_time`, `point_count`, and the model sampling interval.
38
+ - `SUPERSEDED`: operate on the active deployment leaf.
39
+ - `SUSPENDED`: resume only with explicit authorization and a reason.
40
+ - `UNDEPLOYED`: create a governed deployment or rollback as supported; resume
41
+ is invalid.
42
+
43
+ `reconcile-deployment` is a mutation because it may update Kubernetes
44
+ resources and execute verification. Obtain authorization for the exact
45
+ deployment before invoking it. Repeated successful reconcile should not rerun
46
+ the same expensive smoke or append identical events.
47
+
48
+ Use Kubernetes commands only when the user explicitly requests platform
49
+ diagnostics or administration. Do not use them as an alternate deployment
50
+ workflow, and never create, modify, or delete NetworkPolicy.
@@ -32,7 +32,12 @@ to discover fields. Preserve release/error evidence when deployment differs.
32
32
  - Before submission, show the exact JSON path, immutable identities, expected workload, and target API. Obtain explicit authorization. Retry and cancel require separate authorization for the exact Job.
33
33
  - Never use `latest` image tags. During development TrainerDefinition stays at `v1`; rebinding it follows the reviewed release procedure and requires confirming no active Job references the old definition.
34
34
  - A succeeded Job is insufficient evidence. Training requires a valid ModelArtifact and signature; evaluation requires immutable Result/Summary evidence and coverage; packaging requires `READY`, an immutable image digest, manifest, and passing test report.
35
+ - Register a ModelArtifact through `register-model-artifact` only after a platform-generated PASS evaluation/result or summary has complete evidence; do not bypass the CLI with a direct registration HTTP request.
35
36
  - Do not register caller-computed evaluation results. Workers produce predictions, metrics, decisions, and summaries from frozen DatasetArtifact rowsets.
36
37
  - Keep credentials, registry secrets, Kubernetes details, and model binaries out of request JSON and reports.
37
38
 
38
39
  Report immutable input identities, returned run/job/package IDs, status, validation or gate failures, and the next authorized action. Never claim deployment; it is outside this Skill.
40
+
41
+ Route deployment validation, creation, rollout state, approval, promotion,
42
+ rollback, offline operations, and deployment-bound prediction to the separate
43
+ `model-deployment-management` Skill.
@@ -31,6 +31,69 @@ runtime. `feature_lookup` still requires artifact lineage
31
31
  signature. Do not infer either capability from training success.
32
32
  Omit rather than use an empty list to request default inline behavior.
33
33
 
34
+ Register a validated artifact only after a platform-generated evaluation result or
35
+ summary is `PASS`, `COMPLETE`, and belongs to the same artifact:
36
+
37
+ ```bash
38
+ ml-platform --profile server register-model-artifact <artifact-id> \
39
+ --evaluation-id <evaluation-result-or-summary-id>
40
+ ```
41
+
42
+ The API performs the complete registration gate. Do not replace this command with
43
+ a direct `POST /models/{artifact_id}/register` request.
44
+
45
+ For `feature_lookup`, the package request must bind both the exact retrieval
46
+ contract and a sanitized real lookup test. They are immutable package identity,
47
+ not namespace Secret/ConfigMap settings:
48
+
49
+ ```json
50
+ {
51
+ "model_artifact_id": "replace_registered_model_artifact_id",
52
+ "package_version": "1.0.0",
53
+ "input_modes": ["inline", "feature_lookup"],
54
+ "feature_retrieval": {
55
+ "type": "data_platform",
56
+ "manifest": {
57
+ "project": "replace_project",
58
+ "dataset_id": "replace_inference_manifest",
59
+ "dataset_version": "v1",
60
+ "manifest_hash": "sha256:<registered-manifest-hash>"
61
+ },
62
+ "feature_set": {
63
+ "project": "replace_project",
64
+ "name": "replace_feature_set",
65
+ "version": "v1"
66
+ },
67
+ "contract_signature": "sha256:<artifact-contract-signature>",
68
+ "request": {
69
+ "mode": "row",
70
+ "input_adapter_hash": "sha256:<artifact-input-adapter-hash>",
71
+ "temporal_binding_hash": "sha256:<training-temporal-binding-hash>",
72
+ "entity_keys": {}
73
+ },
74
+ "freshness_policy": "strict"
75
+ },
76
+ "feature_lookup_test": {
77
+ "input_mode": "feature_lookup",
78
+ "cutoff_time": "2026-09-11T00:00:00+08:00",
79
+ "horizon": [{"duration": "PT10M"}],
80
+ "entity": {}
81
+ }
82
+ }
83
+ ```
84
+
85
+ The inference DatasetManifest must exist with the supplied hash and use
86
+ `mode=inference`. Its FeatureSet, stable contract signature, adapter/temporal
87
+ hashes, entity keys, and row/context parameters must match the ModelArtifact.
88
+ The package-bound signature remains the Serving binding identity. Online fetch
89
+ responses may also report an independently resolved execution signature for
90
+ diagnostics; database principals are excluded from new stable signatures, so
91
+ credential rotation or distinct read-only workload accounts do not alter the
92
+ model data contract.
93
+ The test cutoff must include a timezone. A legacy feature-lookup package missing
94
+ either package-bound field remains readable but cannot be rerun or deployed;
95
+ create a new package version.
96
+
34
97
  Always query the exact package ID and version; states are `PACKAGING`, `READY`,
35
98
  `FAILED`, `CANCELLED`. A client timeout does not prove packaging failed. Retain the
36
99
  known identity and query it before attempting another creation.
@@ -169,10 +169,12 @@ and `data.labels` must use that same output-label order:
169
169
  Every target and covariate must be a numeric Feature. `past_only` is available
170
170
  only through the cutoff. `known_future` must be represented in training by the
171
171
  forecast or schedule available at that historic cutoff, never by a later actual
172
- observation. At runtime callers provide one finite numeric value for every
173
- `known_future` column and every prediction step. The current contract validates
174
- the names, values, and row count; retain forecast issue/availability lineage in
175
- the upstream Feature data when revisions must be audited.
172
+ observation. At runtime inline callers provide one finite numeric value for
173
+ every `known_future` column and every model prediction step. In
174
+ `feature_lookup` mode, callers provide none: Serving requests the declared
175
+ Feature columns from the deployment-bound FeatureSet, and the platform selects
176
+ future event rows whose revisions satisfy `available_at <= cutoff_time`.
177
+ Retain forecast issue/availability lineage in the upstream Feature data.
176
178
 
177
179
  ## TrainingJobRequest and identity propagation
178
180
 
@@ -243,6 +245,16 @@ aligned horizons up to that maximum and returns the corresponding trajectory
243
245
  prefix; it rejects unaligned or longer horizons. A one-step model remains a
244
246
  scalar response.
245
247
 
248
+ When a deployment must use cutoff at today's 00:00 and return only tomorrow's
249
+ 00:00 through the following 00:00, train for the complete interval from cutoff
250
+ to the output end. At 15-minute frequency, deployment horizon `2d` means
251
+ `prediction_length=192`; output window `[1d,2d)` then projects the second 96
252
+ points. Chronos-2 known-future covariates must still contain all 192 rows. In
253
+ `feature_lookup` mode the platform retrieves those rows even when the request
254
+ overrides the returned window; inline requests must still carry them. A
255
+ 150-step artifact cannot serve this contract and must be retrained rather than
256
+ having its response window stretched at deployment time.
257
+
246
258
  `gap_policy=fill` requires `padding_policy=edge` or `zero`; non-fill requires
247
259
  `padding_policy=none`. Filling changes model inputs and needs an intended
248
260
  business policy. `skip` and padding are not automatic fixes for rejected grids.
@@ -93,15 +93,18 @@ the ordered target/output-label pairs; see [training-contracts.md](training-cont
93
93
  for the complete JSON fragment.
94
94
 
95
95
  Training records must contain the forecast that was available at each historic
96
- cutoff, not actual weather observed later. At online inference, pass one numeric
97
- row per forecast step using the Serving runtime, not `fetch-inference-data`:
96
+ cutoff, not actual weather observed later. Invoke the Serving runtime, not
97
+ `fetch-inference-data`:
98
98
 
99
99
  ```bash
100
100
  ml-platform predict-model /absolute/path/to/inference-request.json \
101
101
  --serving-url https://storage-forecast.example
102
102
  ```
103
103
 
104
- For `input_mode=inline`, put those rows in `inputs.future_covariates`. For
105
- `input_mode=feature_lookup`, put them in the top-level `future_covariates`.
106
- Their column names must exactly match the adapter's `known_future` covariates,
107
- and their count must equal the trained `prediction_length`.
104
+ For `input_mode=inline`, put one row per trained prediction step in
105
+ `inputs.future_covariates`; names must exactly match the adapter's
106
+ `known_future` columns. For `input_mode=feature_lookup`, do not send future
107
+ rows. Serving asks the platform for the declared columns and full
108
+ `prediction_length`; the Features must belong to the deployment-bound
109
+ FeatureSet, and their source revisions must satisfy
110
+ `available_at <= cutoff_time` even though their event times are later.