@zhuoyuezs/ml-platform 0.1.16 → 0.1.17

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:168e376d3eec34da4cb7966aac44b5c87239ca432d9921588f499713c6aeb24b",
6
+ "size_bytes": 6122
7
7
  },
8
8
  {
9
9
  "path": "runtime/business-client/package-lock.json",
10
- "sha256": "sha256:a8da8027de475825949a661bc0d5674903e686d72ac945dcd964febfc5cf4667",
10
+ "sha256": "sha256:b234d20b44cbfd166efef2a9ec6d91e00488952c9a6c7ccb1fdfa50374c4b986",
11
11
  "size_bytes": 381
12
12
  },
13
13
  {
14
14
  "path": "runtime/business-client/package.json",
15
- "sha256": "sha256:e1f916820a4bb19929a76f2690970068d17bb12bf640b89aae007eef50ef7b95",
15
+ "sha256": "sha256:95551c271e726f4d69bf81b9f234b11781dc3b9a2e4b9f86db90ba8ca827ce42",
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:3739b93b0bf42bf8c0521005cfe73b080e14ec88e0fe46feb0f375575a98b7ec",
26
+ "size_bytes": 53174
27
27
  },
28
28
  {
29
29
  "path": "runtime/business-client/src/config.js",
@@ -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:5dfeab163f2c11bd4554da23e898d388a7883a2501e6a9655ee45fcada7659bb",
121
+ "size_bytes": 2608
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:3b040a97dcbf3b22b194bef2bb8c1af744d434ee88a6f47deed9ee4b83cf3147",
131
+ "size_bytes": 1926
132
+ },
133
+ {
134
+ "path": "skills/model-deployment-management/references/operations.md",
135
+ "sha256": "sha256:410111f01b972832bd48290d280f5364cacf2e63e7d586d3b670fad9b997f695",
136
+ "size_bytes": 2333
137
+ },
138
+ {
139
+ "path": "skills/model-deployment-management/references/troubleshooting.md",
140
+ "sha256": "sha256:2354dfad1493c361f534a7d02dbd8ccafa4662a6ebc8e6d1b0bf8dce447dd527",
141
+ "size_bytes": 1410
142
+ },
118
143
  {
119
144
  "path": "skills/model-lifecycle-management/SKILL.md",
120
- "sha256": "sha256:faa4f8c0dbae7d1c1e2ca94499ce9b13cc8b52e8229154c05ac6282ea5e47788",
121
- "size_bytes": 3610
145
+ "sha256": "sha256:74ca6bad54ddb1dc30d3307335d045a70d2d383279011657cc49c1e549eda5a6",
146
+ "size_bytes": 3801
122
147
  },
123
148
  {
124
149
  "path": "skills/model-lifecycle-management/agents/openai.yaml",
@@ -137,8 +162,8 @@
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:f46a5f9c326049a0c2411fdb9d392bea2f4d667062289ee0322a5d9d328c8363",
166
+ "size_bytes": 4888
142
167
  },
143
168
  {
144
169
  "path": "skills/model-lifecycle-management/references/training-contracts.md",
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.17",
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:864a3c32efc728bda13b4e2364174e63b9054368be9062ce4f67fe1aac034720",
17
+ "version": "0.7.7"
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.17",
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",
40
+ "requires_cli": ">=0.7.7 <0.8.0",
41
+ "revision": "0.1.17",
42
42
  "sha256": "sha256:182b6f2df4fe58c946e482dfaf416c7d5b78c6fde9c8c8d7293103246618cb4e"
43
43
  },
44
+ "model-deployment-management": {
45
+ "path": "skills/model-deployment-management",
46
+ "requires_cli": ">=0.7.7 <0.8.0",
47
+ "revision": "0.1.17",
48
+ "sha256": "sha256:8a4c71ec8bde6c8b37c038f327797286ffa5a3ec10501278bd3d0abbab1b7509"
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.7 <0.8.0",
53
+ "revision": "0.1.17",
54
+ "sha256": "sha256:a00a50de7b7a5244a08d9865db10c812a1c6291c94ca01a8562f1b2d1ff72f2b"
49
55
  }
50
56
  },
51
- "source_commit": "49bc1482ab3a58874e40e308640db26e13e6ef12"
57
+ "source_commit": "2f547af208a94ebaf2a4c61ff6ca93f11bdbf43e"
52
58
  }
@@ -48,7 +48,28 @@ ml-platform get-parameter-lifecycle <name> <version> --project <project>
48
48
  ml-platform fetch-inference-context /absolute/path/to/request.json
49
49
  ```
50
50
 
51
- 已部署模型的标准推理使用 Serving URL,不使用控制面 `--api-url`。Chronos-2 v2 的
51
+ 受治理部署通过同一业务 CLI 完成预检、创建、状态等待、晋级、回滚和下线。正常业务操作
52
+ 不使用 `kubectl`,也不创建、修改或删除 NetworkPolicy:
53
+
54
+ ```bash
55
+ ml-platform validate-deployment /absolute/path/to/deployment.json \
56
+ --evaluation-id <pass-evaluation-id>
57
+ ml-platform create-deployment /absolute/path/to/deployment.json \
58
+ --evaluation-id <pass-evaluation-id>
59
+ ml-platform wait-deployment <deployment-id> --wait-timeout-seconds 1800
60
+ ml-platform get-deployment <deployment-id>
61
+ ml-platform get-deployment-events <deployment-id> --limit 100 --offset 0
62
+ ```
63
+
64
+ `READY` 必须同时包含 `ReplicasReady=true`、`ArtifactsVerified=true` 和
65
+ `SmokePredictionPassed=true`。`get-deployment` 返回控制器实际观察到的 endpoint 元数据;
66
+ 本地 CLI 不推测 Kubernetes Service 名称。部署后验证优先使用控制面代理:
67
+
68
+ ```bash
69
+ ml-platform predict-deployment <deployment-id> /absolute/path/to/request.json
70
+ ```
71
+
72
+ 直接访问已知外部 Serving URL 时仍可使用 `predict-model`。Chronos-2 v2 的
52
73
  `known_future` 协变量放在 inline 请求的 `inputs.future_covariates` 中;每个预测步一行,
53
74
  行数必须等于模型的 `prediction_length`。下例为 15 分钟频率、预测 30 分钟;预测明天时
54
75
  同样提供整天的所有未来步:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
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.7",
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.7",
4
4
  "private": true,
5
5
  "description": "JavaScript implementation of the ML Platform business client",
6
6
  "bin": {
@@ -35,6 +35,10 @@ const BUSINESS_COMMANDS = new Set([
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
37
  "create-model-package", "get-model-package", "cancel-model-package", "validate-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,21 @@ 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
+ "list-deployment-targets": "list-deployment-targets",
146
+ "validate-deployment": "validate-deployment SPEC_JSON --evaluation-id ID",
147
+ "create-deployment": "create-deployment SPEC_JSON --evaluation-id ID",
148
+ "list-deployments": "list-deployments [--service SERVICE] [--environment ENV] [--state STATE] [-q QUERY] [--limit N] [--offset N]",
149
+ "get-deployment": "get-deployment DEPLOYMENT_ID",
150
+ "wait-deployment": "wait-deployment DEPLOYMENT_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
151
+ "reconcile-deployment": "reconcile-deployment DEPLOYMENT_ID",
152
+ "approve-deployment": "approve-deployment DEPLOYMENT_ID --revision N --approver NAME --decision approved|rejected [--reason TEXT]",
153
+ "promote-deployment": "promote-deployment DEPLOYMENT_ID --stage canary|production --actor NAME --reason TEXT [--approval-id ID]",
154
+ "rollback-deployment": "rollback-deployment DEPLOYMENT_ID --actor NAME --reason TEXT [--to-revision N] [--to-deployment-id ID]",
155
+ "suspend-deployment": "suspend-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
156
+ "resume-deployment": "resume-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
157
+ "undeploy-deployment": "undeploy-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
158
+ "get-deployment-events": "get-deployment-events DEPLOYMENT_ID [--limit N] [--offset N]",
159
+ "predict-deployment": "predict-deployment DEPLOYMENT_ID REQUEST_JSON",
141
160
  apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
142
161
  };
143
162
 
@@ -291,6 +310,28 @@ async function waitForJob(api, jobId, pollInterval, timeoutSeconds) {
291
310
  }
292
311
  }
293
312
 
313
+ function observedDeploymentState(payload) {
314
+ const state = payload?.observed_state?.observed_state ?? payload?.observed_state;
315
+ if (typeof state !== "string") throw new Error("deployment status response has no observed state");
316
+ return state;
317
+ }
318
+
319
+ async function waitForDeployment(api, deploymentId, pollInterval, timeoutSeconds) {
320
+ if (!(pollInterval > 0)) throw new Error("deployment poll interval must be positive");
321
+ if (timeoutSeconds !== undefined && !(timeoutSeconds > 0)) throw new Error("deployment wait timeout must be positive");
322
+ const deadline = timeoutSeconds === undefined ? Infinity : Date.now() + timeoutSeconds * 1000;
323
+ let previous;
324
+ while (true) {
325
+ const payload = await api.get(`/deployments/${encodeURIComponent(deploymentId)}`);
326
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("deployment status response must be a JSON object");
327
+ const state = observedDeploymentState(payload);
328
+ if (state !== previous) { process.stderr.write(`deployment ${deploymentId}: ${state}\n`); previous = state; }
329
+ if (["READY", "FAILED", "DEGRADED", "SUPERSEDED", "SUSPENDED", "UNDEPLOYED", "ROLLED_BACK"].includes(state)) return payload;
330
+ if (Date.now() >= deadline) throw new Error(`timed out waiting for deployment ${deploymentId}`);
331
+ await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
332
+ }
333
+ }
334
+
294
335
  async function runBusinessCli(argv) {
295
336
  if (argv.length === 0) throw new Error("a business command is required");
296
337
  if (argv.includes("--help") || argv.includes("-h")) {
@@ -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))) {
@@ -529,7 +655,7 @@ async function listCollection(api, endpoint, params, rest) {
529
655
  return api.get(endpoint, { ...params, limit, offset });
530
656
  }
531
657
 
532
- module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForJob };
658
+ module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForDeployment, waitForJob };
533
659
 
534
660
  if (require.main === module) {
535
661
  runBusinessCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error) => {
@@ -0,0 +1,47 @@
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
+ - Run `validate-deployment` before `create-deployment`. Validation is read-only
32
+ and does not authorize creation.
33
+ - Obtain explicit authorization for the exact deployment and operation before
34
+ create, reconcile, approval, promotion, rollback, suspend, resume, or
35
+ undeploy. Retry requires renewed authorization when the previous mutation
36
+ returned an ambiguous result.
37
+ - Treat `READY` as valid only when `ReplicasReady`, `ArtifactsVerified`, and
38
+ `SmokePredictionPassed` are all true for the current revision.
39
+ - Use `predict-deployment`; it routes through the control plane to the recorded
40
+ deployment endpoint. Do not infer Kubernetes Service names or require the
41
+ user to reach a cluster-local URL.
42
+ - Never create, modify, or delete NetworkPolicy. Cluster operators own that
43
+ boundary.
44
+
45
+ Report the deployment ID, revision, immutable image digest, desired and
46
+ observed state, failed conditions, endpoint reachability, and event evidence.
47
+ 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,55 @@
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
+ "replicas": 1,
21
+ "resources": {},
22
+ "rollout": {"strategy": "canary", "traffic_percent": 10},
23
+ "stage": "staging"
24
+ }
25
+ ```
26
+
27
+ Do not reconstruct `feature_retrieval` or `feature_lookup_test` from memory.
28
+ They must match the registered artifact lineage and the tested ModelPackage.
29
+ The server normalizes an omitted lookup test from the package during
30
+ validation and creation.
31
+
32
+ The first deployment starts at `staging`. A replacement for an existing
33
+ service/environment declares the active leaf in `previous_deployment_id`.
34
+ Production is reached only by staging to canary to production promotion.
35
+
36
+ ## Revision State
37
+
38
+ `DeploymentSpec` is immutable. Promotion, rollback, suspension, resumption,
39
+ and undeploy append immutable revisions. Reconcile writes mutable observed
40
+ state separately.
41
+
42
+ `READY` requires all three current-revision conditions:
43
+
44
+ - `ReplicasReady=true`
45
+ - `ArtifactsVerified=true`
46
+ - `SmokePredictionPassed=true`
47
+
48
+ The detail response includes `endpoints`. Cluster-local endpoints have
49
+ `externally_reachable=false`; a Gateway endpoint is externally reachable only
50
+ when rollout hostname metadata exists. `available=true` means the current
51
+ revision passed all READY gates.
52
+
53
+ Successful smoke verification is cached durably by revision, image digest,
54
+ contract hash, and frozen lookup-test hash. A new revision or changed identity
55
+ must run smoke again.
@@ -0,0 +1,78 @@
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
+ ```bash
72
+ ml-platform --profile server predict-deployment <deployment-id> \
73
+ /absolute/path/request.json
74
+ ```
75
+
76
+ Prediction is allowed only for a current `READY` revision with all readiness
77
+ conditions true. This command uses the control-plane proxy and does not require
78
+ a cluster-local Serving URL.
@@ -0,0 +1,31 @@
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
+ - `SUPERSEDED`: operate on the active deployment leaf.
20
+ - `SUSPENDED`: resume only with explicit authorization and a reason.
21
+ - `UNDEPLOYED`: create a governed deployment or rollback as supported; resume
22
+ is invalid.
23
+
24
+ `reconcile-deployment` is a mutation because it may update Kubernetes
25
+ resources and execute verification. Obtain authorization for the exact
26
+ deployment before invoking it. Repeated successful reconcile should not rerun
27
+ the same expensive smoke or append identical events.
28
+
29
+ Use Kubernetes commands only when the user explicitly requests platform
30
+ diagnostics or administration. Do not use them as an alternate deployment
31
+ workflow, and never create, modify, or delete NetworkPolicy.
@@ -36,3 +36,7 @@ to discover fields. Preserve release/error evidence when deployment differs.
36
36
  - Keep credentials, registry secrets, Kubernetes details, and model binaries out of request JSON and reports.
37
37
 
38
38
  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.
39
+
40
+ Route deployment validation, creation, rollout state, approval, promotion,
41
+ rollback, offline operations, and deployment-bound prediction to the separate
42
+ `model-deployment-management` Skill.
@@ -31,6 +31,53 @@ 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
+ For `feature_lookup`, the package request must bind both the exact retrieval
35
+ contract and a sanitized real lookup test. They are immutable package identity,
36
+ not namespace Secret/ConfigMap settings:
37
+
38
+ ```json
39
+ {
40
+ "model_artifact_id": "replace_registered_model_artifact_id",
41
+ "package_version": "1.0.0",
42
+ "input_modes": ["inline", "feature_lookup"],
43
+ "feature_retrieval": {
44
+ "type": "data_platform",
45
+ "manifest": {
46
+ "project": "replace_project",
47
+ "dataset_id": "replace_inference_manifest",
48
+ "dataset_version": "v1",
49
+ "manifest_hash": "sha256:<registered-manifest-hash>"
50
+ },
51
+ "feature_set": {
52
+ "project": "replace_project",
53
+ "name": "replace_feature_set",
54
+ "version": "v1"
55
+ },
56
+ "contract_signature": "sha256:<artifact-contract-signature>",
57
+ "request": {
58
+ "mode": "row",
59
+ "input_adapter_hash": "sha256:<artifact-input-adapter-hash>",
60
+ "temporal_binding_hash": "sha256:<training-temporal-binding-hash>",
61
+ "entity_keys": {}
62
+ },
63
+ "freshness_policy": "strict"
64
+ },
65
+ "feature_lookup_test": {
66
+ "input_mode": "feature_lookup",
67
+ "cutoff_time": "2026-09-11T00:00:00+08:00",
68
+ "horizon": [{"duration": "PT10M"}],
69
+ "entity": {}
70
+ }
71
+ }
72
+ ```
73
+
74
+ The inference DatasetManifest must exist with the supplied hash and use
75
+ `mode=inference`. Its FeatureSet, stable contract signature, adapter/temporal
76
+ hashes, entity keys, and row/context parameters must match the ModelArtifact.
77
+ The test cutoff must include a timezone. A legacy feature-lookup package missing
78
+ either package-bound field remains readable but cannot be rerun or deployed;
79
+ create a new package version.
80
+
34
81
  Always query the exact package ID and version; states are `PACKAGING`, `READY`,
35
82
  `FAILED`, `CANCELLED`. A client timeout does not prove packaging failed. Retain the
36
83
  known identity and query it before attempting another creation.