@zhuoyuezs/ml-platform 0.1.5 → 0.1.7
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 +9 -7
- package/README.md +1 -1
- package/checksums.json +37 -12
- package/package.json +1 -1
- package/release.json +11 -5
- package/runtime/business-client/README.md +13 -1
- package/runtime/business-client/package.json +1 -1
- package/runtime/business-client/src/catalog.js +1 -2
- package/runtime/business-client/src/cli.js +123 -2
- package/scripts/lib.js +41 -9
- package/skills/feature-management/SKILL.md +11 -1
- package/skills/feature-management/references/commands.md +55 -3
- package/skills/model-lifecycle-management/SKILL.md +27 -0
- package/skills/model-lifecycle-management/agents/openai.yaml +4 -0
- package/skills/model-lifecycle-management/references/evaluation.md +33 -0
- package/skills/model-lifecycle-management/references/packaging.md +17 -0
- package/skills/model-lifecycle-management/references/training.md +33 -0
package/DEVELOPMENT.md
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
用户级 Skill,不要求额外安装参数。
|
|
15
15
|
- 管理命令默认输出简洁结果;Agent 和 CI 显式使用 `--json` 获取稳定结构化输出。
|
|
16
16
|
- npm `postinstall` 不得修改 Agent Skills 目录。
|
|
17
|
-
- Python business client
|
|
17
|
+
- Python business client 与迁移 oracle 已删除;release builder 继续防御性排除 Python
|
|
18
|
+
runtime、wheel 和旧 wrapper,npm 包只携带 JavaScript CLI 与纯 Skill payload。
|
|
18
19
|
- Codex CLI 不重新打包;当前只支持唯一自研 Host。
|
|
19
20
|
- builder 使用 Python,因此开发构建命令仍通过 `uv run ...` 启动;最终 npm 包和用户侧
|
|
20
21
|
`ml-platform` 运行时只依赖 Node.js,不依赖 Python 或 `uv`。
|
|
@@ -48,7 +49,7 @@ package/
|
|
|
48
49
|
└── DEVELOPMENT.md
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
严格 `files` allowlist 必须排除 `node_modules
|
|
52
|
+
严格 `files` allowlist 必须排除 `node_modules`、旧 wheel、Skill runtime、旧 wrapper、
|
|
52
53
|
Host 副本、日志、会话数据、attic、凭证和实验文件。
|
|
53
54
|
|
|
54
55
|
最终发布单元是 builder 生成并通过 smoke test 的 `.tgz`,不是 `dist/package/` 目录。目录只用于
|
|
@@ -61,24 +62,25 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
|
|
|
61
62
|
|
|
62
63
|
### Step 1:完成 business-client-js 迁移(已完成)
|
|
63
64
|
|
|
64
|
-
-
|
|
65
|
+
- 完成原 Python CLI 命令和参数契约到 JavaScript 的迁移;
|
|
65
66
|
- 对齐 JSON stdout、stderr 和退出码;
|
|
66
67
|
- 补齐数值参数、子命令 help、路径展开和 HTTP 错误测试;
|
|
67
68
|
- 已注册 Dataset 支持 `get-dataset`、`resolve-dataset` 和
|
|
68
69
|
`build-registered-dataset` API-first 流程,不依赖本地 Manifest;
|
|
69
70
|
- Catalog 规范化保留 `project`,Registry 只读元数据与可发布契约分离,目标 project
|
|
70
71
|
不一致时在访问 Registry 前失败;
|
|
71
|
-
- 扩大 Catalog normalization
|
|
72
|
+
- 扩大 Catalog normalization 与 OpenAPI contract tests;
|
|
72
73
|
- 完成 Job、Artifact、inference 和 Operator upload 负向测试;
|
|
73
74
|
- 删除正式运行路径中的旧产品名兼容;
|
|
74
75
|
- 独立执行 `npm pack --json --dry-run` 机器可读清单校验、clean install 和 CLI smoke test。
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
完成标准:正式 CLI 不需要 Python、`uv` 或第三方 npm runtime 依赖,旧 Python client 可删除。
|
|
77
78
|
|
|
78
79
|
### Step 2:重建纯 feature-management Skill(已完成)
|
|
79
80
|
|
|
80
81
|
- 保留 `SKILL.md`、`agents/`、`references/` 和 `assets/catalog-template/`;
|
|
81
|
-
-
|
|
82
|
+
- npm staging 时排除 `assets/wheels/`、Skill installer 和 CLI launcher;这些旧 Python
|
|
83
|
+
runtime/installer/zip 交付物随后已从仓库彻底删除,staging 排除逻辑保留为防御性边界。
|
|
82
84
|
- 所有命令示例改为直接调用 `ml-platform`;
|
|
83
85
|
- 明确 CLI 是外部 prerequisite,并提供 `ml-platform --help` 自省路径;
|
|
84
86
|
- 更新 `agents/openai.yaml`;
|
|
@@ -134,7 +136,7 @@ Host 副本、日志、会话数据、attic、凭证和实验文件。
|
|
|
134
136
|
|
|
135
137
|
- `git diff --check -- npm business-client-js skills/feature-management agent_poc/codex-host`;
|
|
136
138
|
- JS business-client tests;
|
|
137
|
-
-
|
|
139
|
+
- JavaScript OpenAPI contract 与 Catalog tests;
|
|
138
140
|
- Skill validator 和 forward tests;
|
|
139
141
|
- npm installer tests;
|
|
140
142
|
- release builder;
|
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ npx --yes --registry=https://registry.npmjs.org/ \
|
|
|
120
120
|
## 包内容
|
|
121
121
|
|
|
122
122
|
- 一份 JavaScript business CLI;
|
|
123
|
-
-
|
|
123
|
+
- 两份纯 Skill:`feature-management` 与 `model-lifecycle-management`;
|
|
124
124
|
- 显式安装、升级、状态、校验和回滚逻辑。
|
|
125
125
|
|
|
126
126
|
不包含 Codex CLI、Python business-client wheel、Agent Host、平台服务端、模型训练或 Kubernetes
|
package/checksums.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"files": [
|
|
3
3
|
{
|
|
4
4
|
"path": "runtime/business-client/README.md",
|
|
5
|
-
"sha256": "sha256:
|
|
6
|
-
"size_bytes":
|
|
5
|
+
"sha256": "sha256:b12f30609c756ac4473723df434e6b1055f8713b7acfc3278b091b52288f6347",
|
|
6
|
+
"size_bytes": 2459
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"path": "runtime/business-client/package-lock.json",
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"path": "runtime/business-client/package.json",
|
|
15
|
-
"sha256": "sha256:
|
|
16
|
-
"size_bytes":
|
|
15
|
+
"sha256": "sha256:9e815ffe0487eb216447f4a4b31d248fe37392e08f0fd4bae2aba9c4f4285547",
|
|
16
|
+
"size_bytes": 501
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "runtime/business-client/src/catalog.js",
|
|
20
|
-
"sha256": "sha256:
|
|
21
|
-
"size_bytes":
|
|
20
|
+
"sha256": "sha256:8dcc4c4baff7f292a6fb09d6f82693b5ce7da16b9f0a9e03b22e1f18e893240f",
|
|
21
|
+
"size_bytes": 53659
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "runtime/business-client/src/cli.js",
|
|
25
|
-
"sha256": "sha256:
|
|
26
|
-
"size_bytes":
|
|
25
|
+
"sha256": "sha256:ca7bbee61e83f4597c0da75fd3527f1ee43d5257fff07693b581987efeeaafd3",
|
|
26
|
+
"size_bytes": 41926
|
|
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:
|
|
41
|
-
"size_bytes":
|
|
40
|
+
"sha256": "sha256:ba725b6be2d0f8e4d741d3a6f96e64657112c15935f8e9fd2818cd0b17380816",
|
|
41
|
+
"size_bytes": 30428
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "skills/feature-management/agents/openai.yaml",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "skills/feature-management/references/commands.md",
|
|
95
|
-
"sha256": "sha256:
|
|
96
|
-
"size_bytes":
|
|
95
|
+
"sha256": "sha256:c748f1877f6d2ac1f6ecc29e45cd270997cbd35b498a190decddb66c74b840ab",
|
|
96
|
+
"size_bytes": 13751
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "skills/feature-management/references/contracts.md",
|
|
@@ -109,6 +109,31 @@
|
|
|
109
109
|
"path": "skills/feature-management/references/platform-capability-guide.md",
|
|
110
110
|
"sha256": "sha256:40d0f43a30688c5852ef09203ec35b4dc2c6b9db4bc7dd26dfae52e12d10e9ee",
|
|
111
111
|
"size_bytes": 4257
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"path": "skills/model-lifecycle-management/SKILL.md",
|
|
115
|
+
"sha256": "sha256:48c60d20ab9203cde9681fc61a0515ce3f316ad674e4f349aa21e320127a7520",
|
|
116
|
+
"size_bytes": 2837
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"path": "skills/model-lifecycle-management/agents/openai.yaml",
|
|
120
|
+
"sha256": "sha256:37857849a8baa6b06b6d5d07ae6036080239356fcccd36f77bbdc52c1dfa283f",
|
|
121
|
+
"size_bytes": 249
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"path": "skills/model-lifecycle-management/references/evaluation.md",
|
|
125
|
+
"sha256": "sha256:0139e5fc79beeab84fb31792bdac37c3331676e1f591c57390ecc6336873dcc9",
|
|
126
|
+
"size_bytes": 1797
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"path": "skills/model-lifecycle-management/references/packaging.md",
|
|
130
|
+
"sha256": "sha256:c987d4095f23a1f18ba55effb14f946e0f4cdcb661d7af28af560cc8f72d8b2c",
|
|
131
|
+
"size_bytes": 1101
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"path": "skills/model-lifecycle-management/references/training.md",
|
|
135
|
+
"sha256": "sha256:e6971f2ef9c2b8645388dd4bac4562408f7a7b1adb0b7171c7e6b4912ea48b50",
|
|
136
|
+
"size_bytes": 1852
|
|
112
137
|
}
|
|
113
138
|
],
|
|
114
139
|
"schema_version": "data_platform.ml_platform_checksums/v1"
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"entrypoint": "src/cli.js",
|
|
14
14
|
"name": "ml-platform",
|
|
15
15
|
"path": "runtime/business-client",
|
|
16
|
-
"sha256": "sha256:
|
|
16
|
+
"sha256": "sha256:bbb3c59d5d7e16f92e6496e5596e59af3af4fdf4f820487d9ceb6eb85ca0857b",
|
|
17
17
|
"version": "0.7.0"
|
|
18
18
|
},
|
|
19
19
|
"policy_sha256": "sha256:02fbde0134696b78c0c365d6e0b76c9814227208da5b2b3d4e5dbd82dc4a3606",
|
|
@@ -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.
|
|
28
|
+
"release_version": "0.1.7",
|
|
29
29
|
"runtime_requirements": {
|
|
30
30
|
"node": ">=18",
|
|
31
31
|
"os": [
|
|
@@ -38,9 +38,15 @@
|
|
|
38
38
|
"feature-management": {
|
|
39
39
|
"path": "skills/feature-management",
|
|
40
40
|
"requires_cli": ">=0.7.0 <0.8.0",
|
|
41
|
-
"revision": "0.1.
|
|
42
|
-
"sha256": "sha256:
|
|
41
|
+
"revision": "0.1.7",
|
|
42
|
+
"sha256": "sha256:15131c1303b143373e4b8671301f5b884013a1906a44cfc85dd6f93f43c5d8c8"
|
|
43
|
+
},
|
|
44
|
+
"model-lifecycle-management": {
|
|
45
|
+
"path": "skills/model-lifecycle-management",
|
|
46
|
+
"requires_cli": ">=0.7.0 <0.8.0",
|
|
47
|
+
"revision": "0.1.7",
|
|
48
|
+
"sha256": "sha256:b6eac3ac75a82cf27db2efe2daf2b5b8777c95a0841d48f93ada29ef08b8d8fe"
|
|
43
49
|
}
|
|
44
50
|
},
|
|
45
|
-
"source_commit": "
|
|
51
|
+
"source_commit": "62db427abfd17b9c8dc58964b03aec21927217b5"
|
|
46
52
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Business Client JS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
工业时序智能建模平台唯一受支持的业务 CLI 与 API client 实现。客户端合同由平台
|
|
4
|
+
OpenAPI 快照、JavaScript 单元测试和 npm 发布测试直接验证;
|
|
4
5
|
本目录是业务 CLI 的唯一 JavaScript 实现源码,Skill 通过外部 `ml-platform` 命令调用它,
|
|
5
6
|
不复制或内嵌本目录的 runtime。
|
|
6
7
|
|
|
@@ -35,6 +36,17 @@ ml-platform list-feature-versions <name> --project <project> --all
|
|
|
35
36
|
ml-platform list-jobs --project <project> --status succeeded --all
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
平台资源生命周期与部署绑定的因果上下文读取也由同一 CLI 覆盖:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ml-platform list-parameter-references <name> <version> --project <project>
|
|
43
|
+
ml-platform get-parameter-lifecycle <name> <version> --project <project>
|
|
44
|
+
ml-platform fetch-inference-context /absolute/path/to/request.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Parameter、Feature、FeatureSet 的删除命令执行软删除,并支持带 actor、reason 和幂等键的
|
|
48
|
+
恢复;调用方应在任何变更前查询直接与传递引用。
|
|
49
|
+
|
|
38
50
|
仓库中的 `api-spec/latest.json` 从 `origin/dev` 生成。同步或验证契约时运行:
|
|
39
51
|
|
|
40
52
|
```bash
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@zhuoyuezs/ml-platform-business-client",
|
|
3
3
|
"version": "0.7.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"description": "JavaScript
|
|
5
|
+
"description": "JavaScript implementation of the ML Platform business client",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ml-platform": "src/cli.js"
|
|
8
8
|
},
|
|
@@ -58,7 +58,6 @@ function normalizeQualityRules(value) { exactKeys(value, ["valid_range", "allow_
|
|
|
58
58
|
function normalizeCodeArtifact(value) { object(value, "operator code_artifact"); const sha256 = requiredString(value.sha256, "code_artifact sha256"); if (!wheelDigestPattern.test(sha256)) throw new Error("code_artifact sha256 is invalid"); const filename = requiredString(value.filename, "code_artifact filename"); if (path.basename(filename) !== filename || !wheelFilenamePattern.test(filename)) throw new Error("code_artifact filename is invalid"); if (!Number.isInteger(value.size_bytes) || value.size_bytes <= 0) throw new Error("code_artifact size_bytes must be a positive integer"); if (defaultIfAbsent(value, "format", "wheel") !== "wheel") throw new Error("code_artifact format must be wheel"); return { uri: requiredString(value.uri, "code_artifact uri"), sha256, format: "wheel", filename, size_bytes: value.size_bytes, python_version: optionalString(value.python_version, "code_artifact python_version"), dependency_lock_hash: optionalString(value.dependency_lock_hash, "code_artifact dependency_lock_hash") }; }
|
|
59
59
|
|
|
60
60
|
function normalizeParameter(payload) {
|
|
61
|
-
exactKeys(payload, ["schema_version", "project", "name", "display_name", "version", "data_type", "unit", "expected_frequency", "source", "time_semantics", "availability_sla", "value_field", "quality_rules", "rounding", "owner"], ["name", "display_name", "version", "source"], "parameter");
|
|
62
61
|
const source = object(payload.source, "parameter source");
|
|
63
62
|
if (Object.prototype.hasOwnProperty.call(source, "parameters")) throw new Error("parameter source uses unsupported field 'parameters'; use 'params'");
|
|
64
63
|
if (source.mode === "sql" && typeof source.sql === "string" && source.sql.trimEnd().endsWith(";")) throw new Error("SQL Parameter source.sql must not end with a semicolon");
|
|
@@ -66,7 +65,7 @@ function normalizeParameter(payload) {
|
|
|
66
65
|
return { schema_version: pythonString(defaultIfAbsent(payload, "schema_version", "ml_data_platform.parameter/v1")), project: identifier(defaultIfAbsent(payload, "project", "default"), "parameter project"), name: identifier(payload.name, "parameter name"), display_name: requiredString(payload.display_name, "parameter display_name"), version: identifier(payload.version, "parameter version"), data_type: pythonString(defaultIfAbsent(payload, "data_type", "time_series")), unit: pythonString(defaultIfAbsent(payload, "unit", "")), expected_frequency: pythonString(defaultIfAbsent(payload, "expected_frequency", "10min")), source: { adapter: pythonString(defaultIfAbsent(source, "adapter", "synthetic")), mode: choice(defaultIfAbsent(source, "mode", "direct_column"), ["direct_column", "sql", "derived"], "parameter source mode"), source_metric: optionalString(source.source_metric, "source_metric"), source_group: optionalString(source.source_group, "source_group"), measurement: optionalString(source.measurement, "measurement"), field: optionalString(source.field, "field"), tags: stringMap(defaultIfAbsent(source, "tags", {}), "source tags"), schema: optionalString(aliasIfAbsent(source, "schema", "schema_name"), "schema"), table: optionalString(source.table, "table"), time_column: optionalString(source.time_column, "time_column"), value_column: optionalString(source.value_column, "value_column"), metric_name_column: optionalString(source.metric_name_column, "metric_name_column"), metric_name: optionalString(source.metric_name, "metric_name"), unit_column: optionalString(source.unit_column, "unit_column"), unit: optionalString(aliasIfAbsent(source, "unit", "source_unit"), "source unit"), filters: stringMap(defaultIfAbsent(source, "filters", {}), "source filters"), sql: optionalString(source.sql, "source sql"), params: object(defaultIfAbsent(source, "params", {}), "source params") }, time_semantics: { event_time_field: pythonString(defaultIfAbsent(time, "event_time_field", "event_time")), ingested_at_field: pythonString(defaultIfAbsent(time, "ingested_at_field", "ingested_at")), timezone: pythonString(defaultIfAbsent(time, "timezone", "Asia/Shanghai")), availability: normalizeAvailability(time.availability) }, availability_sla: normalizeAvailabilitySla(payload.availability_sla), value_field: pythonString(defaultIfAbsent(payload, "value_field", "value")), quality_rules: normalizeQualityRules(object(defaultIfAbsent(payload, "quality_rules", {}), "parameter quality_rules")), rounding: normalizeRounding(payload.rounding), owner: pythonString(defaultIfAbsent(payload, "owner", "demo")) };
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
function normalizeOperator(payload) {
|
|
68
|
+
function normalizeOperator(payload) { object(payload, "operator"); const artifact = payload.code_artifact == null ? null : normalizeCodeArtifact(payload.code_artifact); const normalized = { schema_version: pythonString(defaultIfAbsent(payload, "schema_version", "ml_data_platform.operator/v1")), project: identifier(defaultIfAbsent(payload, "project", "default"), "operator project"), name: identifier(payload.name, "operator name"), version: identifier(payload.version, "operator version"), type: choice(defaultIfAbsent(payload, "type", "standard"), ["standard", "custom_python", "spark_udf", "feature"], "operator type"), function_hash: pythonString(defaultIfAbsent(payload, "function_hash", "demo")), entrypoint: optionalString(payload.entrypoint, "operator entrypoint"), code_hash: optionalString(payload.code_hash, "operator code_hash"), package_uri: optionalString(payload.package_uri, "operator package_uri"), code_artifact: artifact, input_schema: object(defaultIfAbsent(payload, "input_schema", {}), "operator input_schema"), output_schema: object(defaultIfAbsent(payload, "output_schema", {}), "operator output_schema"), config_schema: object(defaultIfAbsent(payload, "config_schema", {}), "operator config_schema"), runtime: object(defaultIfAbsent(payload, "runtime", {}), "operator runtime"), resources: object(defaultIfAbsent(payload, "resources", {}), "operator resources"), deterministic: bool(defaultIfAbsent(payload, "deterministic", true), "operator deterministic"), supports_batch: bool(defaultIfAbsent(payload, "supports_batch", true), "operator supports_batch"), supports_online: bool(defaultIfAbsent(payload, "supports_online", false), "operator supports_online"), owner: pythonString(defaultIfAbsent(payload, "owner", "demo")) }; if (artifact && normalized.code_hash !== artifact.sha256) throw new Error("operator code_hash must equal code_artifact.sha256"); if (artifact && normalized.package_uri !== artifact.uri) throw new Error("operator package_uri must equal code_artifact.uri"); return normalized; }
|
|
70
69
|
function normalizeRegistryOperator(payload) { const value = object(payload, "operator"); if (!hasOwn(value, "deleted_at")) return normalizeOperator(value); const { deleted_at, ...contract } = value; void deleted_at; return normalizeOperator(contract); }
|
|
71
70
|
function normalizeFeature(payload) { exactKeys(payload, ["schema_version", "project", "name", "version", "inputs", "operator", "operator_version", "config", "output_column", "output_dtype", "offline_online_supported", "owner", "description"], ["name", "version", "inputs", "operator", "operator_version", "output_column"], "feature"); const project = identifier(defaultIfAbsent(payload, "project", "default"), "feature project"); const inputs = objectList(payload.inputs, "feature inputs").map((item) => parameterRef(item, project)); if (!inputs.length) throw new Error("feature inputs must not be empty"); unique(inputs.map(parameterReferenceKey), "feature inputs"); if (typeof payload.output_column !== "string" || !outputColumnPattern.test(payload.output_column)) throw new Error("feature output_column is invalid"); return { schema_version: pythonString(defaultIfAbsent(payload, "schema_version", "ml_data_platform.feature/v1")), project, name: identifier(payload.name, "feature name"), version: identifier(payload.version, "feature version"), inputs, operator: identifier(payload.operator, "feature operator"), operator_version: identifier(payload.operator_version, "feature operator_version"), config: object(defaultIfAbsent(payload, "config", {}), "feature config"), output_column: payload.output_column, output_dtype: pythonString(defaultIfAbsent(payload, "output_dtype", "float64")), offline_online_supported: bool(defaultIfAbsent(payload, "offline_online_supported", true), "offline_online_supported"), owner: pythonString(defaultIfAbsent(payload, "owner", "demo")), description: pythonString(defaultIfAbsent(payload, "description", "")) }; }
|
|
72
71
|
function normalizeFeatureSet(payload) { exactKeys(payload, ["schema_version", "project", "name", "version", "features", "owner", "description"], ["name", "version", "features"], "feature set"); const features = objectList(payload.features, "feature set features").map((item) => ref(item, "feature reference")); if (!features.length) throw new Error("feature_set features must not be empty"); unique(features.map(referenceKey), "feature_set features"); return { schema_version: pythonString(defaultIfAbsent(payload, "schema_version", "ml_data_platform.feature_set/v1")), project: identifier(defaultIfAbsent(payload, "project", "default"), "feature set project"), name: identifier(payload.name, "feature set name"), version: identifier(payload.version, "feature set version"), features, owner: pythonString(defaultIfAbsent(payload, "owner", "demo")), description: pythonString(defaultIfAbsent(payload, "description", "")) }; }
|
|
@@ -15,11 +15,21 @@ const BUSINESS_COMMANDS = new Set([
|
|
|
15
15
|
"list-feature-sets", "list-datasets", "get-dataset", "list-jobs", "resolve-manifest",
|
|
16
16
|
"resolve-dataset", "build-dataset", "build-registered-dataset",
|
|
17
17
|
"get-parameter", "get-feature", "get-feature-set", "get-operator",
|
|
18
|
+
"delete-parameter", "delete-feature", "delete-feature-set",
|
|
19
|
+
"restore-parameter", "restore-feature", "restore-feature-set",
|
|
20
|
+
"get-parameter-lifecycle", "get-feature-lifecycle", "get-feature-set-lifecycle",
|
|
21
|
+
"list-parameter-references", "list-feature-references", "list-feature-set-references",
|
|
18
22
|
"list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions",
|
|
19
23
|
"add-parameter", "add-feature", "add-feature-set", "add-operator", "publish-operator",
|
|
20
24
|
"add-dataset", "update-dataset", "delete-dataset", "list-dataset-artifacts",
|
|
21
25
|
"register-operator", "show-operator-specs", "delete-operator", "run-operator",
|
|
22
|
-
"get-job", "wait-job", "retry-job", "cancel-job", "get-dataset-artifact", "download-dataset-artifact", "fetch-inference-data", "apply",
|
|
26
|
+
"get-job", "wait-job", "retry-job", "cancel-job", "get-dataset-artifact", "download-dataset-artifact", "fetch-inference-data", "fetch-inference-context", "apply",
|
|
27
|
+
"register-trainer", "list-trainer-definitions", "get-trainer-definition", "register-training-run", "validate-training-run", "submit-training-job", "get-training-job", "retry-training-job", "cancel-training-job",
|
|
28
|
+
"register-evaluation-config", "get-evaluation-config", "register-metric-definition", "get-metric-definition",
|
|
29
|
+
"register-executable-package", "verify-executable-package", "publish-executable-package",
|
|
30
|
+
"validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
|
|
31
|
+
"get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
|
|
32
|
+
"create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact",
|
|
23
33
|
]);
|
|
24
34
|
|
|
25
35
|
const COMMAND_USAGE = {
|
|
@@ -40,6 +50,18 @@ const COMMAND_USAGE = {
|
|
|
40
50
|
"get-feature": "get-feature NAME VERSION [--project PROJECT]",
|
|
41
51
|
"get-feature-set": "get-feature-set NAME VERSION [--project PROJECT]",
|
|
42
52
|
"get-operator": "get-operator NAME VERSION [--project PROJECT]",
|
|
53
|
+
"delete-parameter": "delete-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
54
|
+
"delete-feature": "delete-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
55
|
+
"delete-feature-set": "delete-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
56
|
+
"restore-parameter": "restore-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
57
|
+
"restore-feature": "restore-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
58
|
+
"restore-feature-set": "restore-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
59
|
+
"get-parameter-lifecycle": "get-parameter-lifecycle NAME VERSION [--project PROJECT]",
|
|
60
|
+
"get-feature-lifecycle": "get-feature-lifecycle NAME VERSION [--project PROJECT]",
|
|
61
|
+
"get-feature-set-lifecycle": "get-feature-set-lifecycle NAME VERSION [--project PROJECT]",
|
|
62
|
+
"list-parameter-references": "list-parameter-references NAME VERSION [--project PROJECT] [--direct]",
|
|
63
|
+
"list-feature-references": "list-feature-references NAME VERSION [--project PROJECT] [--direct]",
|
|
64
|
+
"list-feature-set-references": "list-feature-set-references NAME VERSION [--project PROJECT] [--direct]",
|
|
43
65
|
"list-parameter-versions": "list-parameter-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
44
66
|
"list-feature-versions": "list-feature-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
45
67
|
"list-feature-set-versions": "list-feature-set-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
@@ -70,6 +92,39 @@ const COMMAND_USAGE = {
|
|
|
70
92
|
"get-dataset-artifact": "get-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT]",
|
|
71
93
|
"download-dataset-artifact": "download-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT] --out-dir PATH [--file PATH] [--force]",
|
|
72
94
|
"fetch-inference-data": "fetch-inference-data MANIFEST --cutoff-time TIMESTAMP [--max-workers N] [--max-source-lag-hours HOURS] [--no-validate-freshness] [--allow-missing]",
|
|
95
|
+
"fetch-inference-context": "fetch-inference-context REQUEST_JSON",
|
|
96
|
+
"register-trainer": "register-trainer DEFINITION_JSON",
|
|
97
|
+
"list-trainer-definitions": "list-trainer-definitions",
|
|
98
|
+
"get-trainer-definition": "get-trainer-definition NAME VERSION [--project PROJECT]",
|
|
99
|
+
"register-training-run": "register-training-run RUN_JSON",
|
|
100
|
+
"validate-training-run": "validate-training-run RUN_JSON",
|
|
101
|
+
"submit-training-job": "submit-training-job REQUEST_JSON",
|
|
102
|
+
"get-training-job": "get-training-job JOB_ID",
|
|
103
|
+
"retry-training-job": "retry-training-job JOB_ID",
|
|
104
|
+
"cancel-training-job": "cancel-training-job JOB_ID",
|
|
105
|
+
"register-evaluation-config": "register-evaluation-config CONFIG_JSON",
|
|
106
|
+
"get-evaluation-config": "get-evaluation-config NAME VERSION",
|
|
107
|
+
"register-metric-definition": "register-metric-definition DEFINITION_JSON",
|
|
108
|
+
"get-metric-definition": "get-metric-definition NAME VERSION",
|
|
109
|
+
"register-executable-package": "register-executable-package SPEC_JSON",
|
|
110
|
+
"verify-executable-package": "verify-executable-package NAME VERSION",
|
|
111
|
+
"publish-executable-package": "publish-executable-package NAME VERSION",
|
|
112
|
+
"validate-evaluation-run": "validate-evaluation-run REQUEST_JSON",
|
|
113
|
+
"submit-evaluation": "submit-evaluation REQUEST_JSON",
|
|
114
|
+
"get-evaluation-run": "get-evaluation-run RUN_ID",
|
|
115
|
+
"get-evaluation-job": "get-evaluation-job JOB_ID",
|
|
116
|
+
"retry-evaluation-job": "retry-evaluation-job JOB_ID",
|
|
117
|
+
"cancel-evaluation-job": "cancel-evaluation-job JOB_ID",
|
|
118
|
+
"list-evaluation-attempts": "list-evaluation-attempts JOB_ID",
|
|
119
|
+
"get-evaluation-result": "get-evaluation-result RESULT_ID",
|
|
120
|
+
"get-evaluation-result-metrics": "get-evaluation-result-metrics RESULT_ID",
|
|
121
|
+
"get-evaluation-result-artifacts": "get-evaluation-result-artifacts RESULT_ID",
|
|
122
|
+
"summarize-evaluation-run": "summarize-evaluation-run RUN_ID",
|
|
123
|
+
"get-evaluation-summary": "get-evaluation-summary SUMMARY_ID",
|
|
124
|
+
"create-model-package": "create-model-package REQUEST_JSON",
|
|
125
|
+
"get-model-package": "get-model-package PACKAGE_ID [--package-version VERSION]",
|
|
126
|
+
"cancel-model-package": "cancel-model-package PACKAGE_ID [--package-version VERSION]",
|
|
127
|
+
"validate-model-artifact": "validate-model-artifact ARTIFACT_ID",
|
|
73
128
|
apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
|
|
74
129
|
};
|
|
75
130
|
|
|
@@ -153,6 +208,17 @@ function datasetEndpoint(project, datasetId, datasetVersion) {
|
|
|
153
208
|
return `/datasets/${encodeURIComponent(project)}/${encodeURIComponent(datasetId)}/${encodeURIComponent(datasetVersion)}`;
|
|
154
209
|
}
|
|
155
210
|
|
|
211
|
+
function lifecycleResource(command) {
|
|
212
|
+
if (command.includes("feature-set")) return "feature-sets";
|
|
213
|
+
if (command.includes("parameter")) return "parameters";
|
|
214
|
+
if (command.includes("feature")) return "features";
|
|
215
|
+
throw new Error(`unsupported lifecycle command: ${command}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function lifecycleEndpoint(resource, project, name, version, suffix = "") {
|
|
219
|
+
return `/registry/${resource}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}${suffix}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
156
222
|
async function registeredDataset(api, project, datasetId, datasetVersion, includeDeleted = false) {
|
|
157
223
|
const payload = await api.get(datasetEndpoint(project, datasetId, datasetVersion), { include_deleted: includeDeleted });
|
|
158
224
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("registered dataset response must be a JSON object");
|
|
@@ -262,6 +328,22 @@ async function runBusinessCli(argv) {
|
|
|
262
328
|
const version = positional(rest, `${resource} version`);
|
|
263
329
|
const project = take(rest, "--project", "default");
|
|
264
330
|
result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
331
|
+
} else if (/^delete-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
332
|
+
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
333
|
+
result = await client(options).delete(lifecycleEndpoint(lifecycleResource(options.command), project, name, version), {
|
|
334
|
+
actor: take(rest, "--actor"), reason: take(rest, "--reason"), idempotency_key: take(rest, "--idempotency-key"),
|
|
335
|
+
});
|
|
336
|
+
} else if (/^restore-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
337
|
+
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
338
|
+
result = await client(options).post(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/restore"), {
|
|
339
|
+
actor: take(rest, "--actor") || "anonymous", reason: take(rest, "--reason", ""), idempotency_key: take(rest, "--idempotency-key"),
|
|
340
|
+
});
|
|
341
|
+
} else if (/^get-(parameter|feature|feature-set)-lifecycle$/.test(options.command)) {
|
|
342
|
+
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
343
|
+
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/lifecycle"));
|
|
344
|
+
} else if (/^list-(parameter|feature|feature-set)-references$/.test(options.command)) {
|
|
345
|
+
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
346
|
+
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/references"), { transitive: !boolean(rest, "--direct") });
|
|
265
347
|
} else if (["list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions"].includes(options.command)) {
|
|
266
348
|
const resource = options.command.replace(/^list-/, "").replace(/-versions$/, "");
|
|
267
349
|
const endpoint = resource === "feature-set" ? "feature-sets" : `${resource}s`;
|
|
@@ -272,7 +354,7 @@ async function runBusinessCli(argv) {
|
|
|
272
354
|
if (allPages && (rawLimit !== undefined || rawOffset !== undefined)) throw new Error("--all cannot be combined with --limit or --offset");
|
|
273
355
|
if (allPages) result = await fetchAll(client(options), `/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`);
|
|
274
356
|
else { const limit = Number(rawLimit ?? 50); const offset = Number(rawOffset ?? 0); if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("limit must be between 1 and 500"); if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be >= 0"); result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`, { limit, offset }); }
|
|
275
|
-
} else if (options.command.startsWith("list-") && !["list-jobs", "list-dataset-artifacts"].includes(options.command)) {
|
|
357
|
+
} else if (options.command.startsWith("list-") && !["list-jobs", "list-dataset-artifacts", "list-trainer-definitions"].includes(options.command)) {
|
|
276
358
|
const endpoints = { "list-parameters": "/parameters", "list-operators": "/operators", "list-features": "/features", "list-feature-sets": "/feature-sets", "list-datasets": "/datasets" };
|
|
277
359
|
const project = take(rest, "--project");
|
|
278
360
|
const name = take(rest, "--name"); const version = take(rest, "--version");
|
|
@@ -355,6 +437,45 @@ async function runBusinessCli(argv) {
|
|
|
355
437
|
const manifest = positional(rest, "manifest"); const cutoffTime = take(rest, "--cutoff-time"); if (!cutoffTime) throw new Error("--cutoff-time is required");
|
|
356
438
|
const realtime = {}; const maxWorkers = take(rest, "--max-workers"); const maxLag = take(rest, "--max-source-lag-hours"); if (maxWorkers !== undefined) { realtime.max_workers = integer(maxWorkers, "--max-workers"); if (realtime.max_workers < 1) throw new Error("--max-workers must be at least 1"); } if (maxLag !== undefined) { realtime.max_source_lag_hours = finiteNumber(maxLag, "--max-source-lag-hours"); if (realtime.max_source_lag_hours < 0) throw new Error("--max-source-lag-hours must be non-negative"); } if (boolean(rest, "--no-validate-freshness")) realtime.validate_freshness = false; if (boolean(rest, "--allow-missing")) realtime.allow_missing = true;
|
|
357
439
|
const request = { manifest: readDataset(manifest), cutoff_time: cutoffTime }; if (Object.keys(realtime).length) request.realtime_fetch = realtime; result = await client(options).post("/inference-data/fetch", request);
|
|
440
|
+
} else if (options.command === "fetch-inference-context") {
|
|
441
|
+
result = await client(options).post("/inference-data/context", readSpec(positional(rest, "request JSON")));
|
|
442
|
+
} else if (options.command === "list-trainer-definitions") {
|
|
443
|
+
result = await client(options).get("/trainer-definitions");
|
|
444
|
+
} else if (options.command === "get-trainer-definition") {
|
|
445
|
+
const name = positional(rest, "trainer name"); const version = positional(rest, "trainer version"); const project = take(rest, "--project", "default");
|
|
446
|
+
result = await client(options).get(`/trainer-definitions/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
447
|
+
} else if ({
|
|
448
|
+
"register-trainer": "/trainer-definitions", "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate",
|
|
449
|
+
"submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions",
|
|
450
|
+
"register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs",
|
|
451
|
+
"create-model-package": "/model-packages",
|
|
452
|
+
}[options.command]) {
|
|
453
|
+
const endpoint = { "register-trainer": "/trainer-definitions", "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions", "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs", "create-model-package": "/model-packages" }[options.command];
|
|
454
|
+
result = await client(options).post(endpoint, readSpec(positional(rest, "request JSON")));
|
|
455
|
+
} else if (/^(get|retry|cancel)-training-job$/.test(options.command)) {
|
|
456
|
+
const jobId = positional(rest, "training job id"); const endpoint = `/training-jobs/${encodeURIComponent(jobId)}`;
|
|
457
|
+
result = options.command === "get-training-job" ? await client(options).get(endpoint) : await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
458
|
+
} else if (/^(get|retry|cancel)-evaluation-job$/.test(options.command) || options.command === "list-evaluation-attempts") {
|
|
459
|
+
const jobId = positional(rest, "evaluation job id"); const endpoint = `/evaluation-jobs/${encodeURIComponent(jobId)}`;
|
|
460
|
+
if (options.command === "get-evaluation-job") result = await client(options).get(endpoint);
|
|
461
|
+
else if (options.command === "list-evaluation-attempts") result = await client(options).get(`${endpoint}/attempts`);
|
|
462
|
+
else result = await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
463
|
+
} else if (options.command === "get-evaluation-run") result = await client(options).get(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}`);
|
|
464
|
+
else if (options.command === "summarize-evaluation-run") result = await client(options).post(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}/summarize`);
|
|
465
|
+
else if (options.command === "get-evaluation-summary") result = await client(options).get(`/evaluation-summaries/${encodeURIComponent(positional(rest, "evaluation summary id"))}`);
|
|
466
|
+
else if (/^get-evaluation-result(?:-metrics|-artifacts)?$/.test(options.command)) {
|
|
467
|
+
const resultId = positional(rest, "evaluation result id"); const suffix = options.command.endsWith("-metrics") ? "/metrics" : options.command.endsWith("-artifacts") ? "/artifacts" : "";
|
|
468
|
+
result = await client(options).get(`/evaluation-results/${encodeURIComponent(resultId)}${suffix}`);
|
|
469
|
+
} else if (/^(get-evaluation-config|get-metric-definition)$/.test(options.command)) {
|
|
470
|
+
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const base = options.command === "get-evaluation-config" ? "evaluation-configs" : "metric-definitions";
|
|
471
|
+
result = await client(options).get(`/${base}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
472
|
+
} else if (/^(verify|publish)-executable-package$/.test(options.command)) {
|
|
473
|
+
const name = positional(rest, "package name"); const version = positional(rest, "package version"); const action = options.command.split("-")[0];
|
|
474
|
+
result = await client(options).post(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${action}`);
|
|
475
|
+
} else if (options.command === "validate-model-artifact") result = await client(options).get(`/model-artifacts/${encodeURIComponent(positional(rest, "model artifact id"))}/validate`);
|
|
476
|
+
else if (/^(get|cancel)-model-package$/.test(options.command)) {
|
|
477
|
+
const packageId = positional(rest, "model package id"); const params = { package_version: take(rest, "--package-version") }; const endpoint = `/model-packages/${encodeURIComponent(packageId)}`;
|
|
478
|
+
result = options.command === "get-model-package" ? await client(options).get(endpoint, params) : await client(options).post(`${endpoint}/cancel`, undefined, params);
|
|
358
479
|
} else if (options.command === "apply") {
|
|
359
480
|
const directory = positional(rest, "catalog directory"); const dryRun = boolean(rest, "--dry-run"); const targetProject = take(rest, "--project");
|
|
360
481
|
result = await applyCatalog(loadCatalog(resolvedPath(directory)), client(options), dryRun, targetProject);
|
package/scripts/lib.js
CHANGED
|
@@ -152,19 +152,21 @@ function loadRelease(packageRoot = PACKAGE_ROOT) {
|
|
|
152
152
|
|| requirements.os.join(",") !== "darwin,linux") {
|
|
153
153
|
throw new Error("release manifest 缺少有效 runtime requirements");
|
|
154
154
|
}
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const skills = release.skills;
|
|
156
|
+
const skill = skills && skills[SKILL_NAME];
|
|
157
|
+
if (!skill || Object.keys(skills).length < 1) {
|
|
158
158
|
throw new Error("release manifest 缺少有效 Skill 信息");
|
|
159
159
|
}
|
|
160
|
+
for (const [name, item] of Object.entries(skills)) {
|
|
161
|
+
if (item.path !== `skills/${name}` || !isSha256(item.sha256)
|
|
162
|
+
|| !isSemver(item.revision) || typeof item.requires_cli !== "string") throw new Error("release manifest 缺少有效 Skill 信息");
|
|
163
|
+
}
|
|
160
164
|
const cli = release.cli;
|
|
161
165
|
if (!cli || cli.name !== CLI_NAME || cli.path !== "runtime/business-client"
|
|
162
166
|
|| !isSemver(cli.version) || !isSha256(cli.sha256) || cli.entrypoint !== "src/cli.js") {
|
|
163
167
|
throw new Error("release manifest 缺少有效 JavaScript CLI 信息");
|
|
164
168
|
}
|
|
165
|
-
if (!versionSatisfies(cli.version,
|
|
166
|
-
throw new Error("Skill requires_cli 与 release CLI version 不兼容");
|
|
167
|
-
}
|
|
169
|
+
for (const item of Object.values(skills)) if (!versionSatisfies(cli.version, item.requires_cli)) throw new Error("Skill requires_cli 与 release CLI version 不兼容");
|
|
168
170
|
return release;
|
|
169
171
|
}
|
|
170
172
|
|
|
@@ -201,7 +203,7 @@ function verifyPackage(packageRoot = PACKAGE_ROOT) {
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
assertTopLevel(path.join(packageRoot, "runtime"), new Set(["business-client"]), "runtime");
|
|
204
|
-
assertTopLevel(path.join(packageRoot, "skills"), new Set(
|
|
206
|
+
assertTopLevel(path.join(packageRoot, "skills"), new Set(Object.keys(release.skills)), "skills");
|
|
205
207
|
const skillRoot = resolveInside(packageRoot, release.skills[SKILL_NAME].path);
|
|
206
208
|
const clientRoot = resolveInside(packageRoot, release.cli.path);
|
|
207
209
|
assertTopLevel(skillRoot, SKILL_TOP_LEVEL, "Skill");
|
|
@@ -232,11 +234,18 @@ function verifyPackage(packageRoot = PACKAGE_ROOT) {
|
|
|
232
234
|
if (treeDigest(skillRoot) !== release.skills[SKILL_NAME].sha256) {
|
|
233
235
|
throw new Error("Skill 目录摘要与 release manifest 不一致");
|
|
234
236
|
}
|
|
237
|
+
for (const [name, item] of Object.entries(release.skills)) {
|
|
238
|
+
const root = resolveInside(packageRoot, item.path);
|
|
239
|
+
const actual = new Set(fs.readdirSync(root));
|
|
240
|
+
if (!actual.has("SKILL.md") || !actual.has("agents") || [...actual].some((entry) => !SKILL_TOP_LEVEL.has(entry))) throw new Error(`Skill ${name} 目录结构无效`);
|
|
241
|
+
rejectForbiddenTree(root, `Skill ${name}`);
|
|
242
|
+
if (treeDigest(root) !== item.sha256) throw new Error(`Skill ${name} 目录摘要与 release manifest 不一致`);
|
|
243
|
+
}
|
|
235
244
|
if (treeDigest(clientRoot) !== release.cli.sha256) {
|
|
236
245
|
throw new Error("JavaScript CLI 摘要与 release manifest 不一致");
|
|
237
246
|
}
|
|
238
247
|
if (!fs.existsSync(expectedEntry)) throw new Error("JavaScript CLI 入口不存在");
|
|
239
|
-
return { release, skillRoot, clientRoot };
|
|
248
|
+
return { release, skillRoot, skillRoots: Object.fromEntries(Object.entries(release.skills).map(([name, item]) => [name, resolveInside(packageRoot, item.path)])), clientRoot };
|
|
240
249
|
}
|
|
241
250
|
|
|
242
251
|
function status(options = {}, packageRoot = PACKAGE_ROOT) {
|
|
@@ -516,6 +525,9 @@ function installLocked(options, packageRoot, verified, paths) {
|
|
|
516
525
|
let hadShim = false;
|
|
517
526
|
let legacyBackupPath = null;
|
|
518
527
|
let legacyMoved = false;
|
|
528
|
+
const additionalSkills = Object.keys(verified.release.skills).filter((name) => name !== SKILL_NAME);
|
|
529
|
+
const additionalBackups = [];
|
|
530
|
+
const installedAdditional = [];
|
|
519
531
|
try {
|
|
520
532
|
stagePersistentInstall(stagedInstall, packageRoot, verified);
|
|
521
533
|
fs.cpSync(verified.skillRoot, stagedSkill, { recursive: true, errorOnExist: true });
|
|
@@ -540,6 +552,17 @@ function installLocked(options, packageRoot, verified, paths) {
|
|
|
540
552
|
}
|
|
541
553
|
fs.renameSync(stagedSkill, paths.target);
|
|
542
554
|
skillMoved = true;
|
|
555
|
+
for (const name of additionalSkills) {
|
|
556
|
+
const target = path.join(paths.skillsDir, name);
|
|
557
|
+
const backup = path.join(paths.skillsDir, `.${name}.backup.${process.pid}.${Date.now()}`);
|
|
558
|
+
if (pathEntryExists(target)) {
|
|
559
|
+
if (!current?.skills?.[name] || current.skills[name].path !== target) throw new Error(`拒绝覆盖未托管 Skill: ${name}`);
|
|
560
|
+
fs.renameSync(target, backup);
|
|
561
|
+
additionalBackups.push({ target, backup });
|
|
562
|
+
}
|
|
563
|
+
fs.cpSync(verified.skillRoots[name], target, { recursive: true, errorOnExist: true });
|
|
564
|
+
installedAdditional.push(target);
|
|
565
|
+
}
|
|
543
566
|
if (pathEntryExists(paths.shim)) {
|
|
544
567
|
fs.renameSync(paths.shim, shimBackup);
|
|
545
568
|
hadShim = true;
|
|
@@ -555,6 +578,7 @@ function installLocked(options, packageRoot, verified, paths) {
|
|
|
555
578
|
removeOwnedDirectory(skillStaging, paths.skillsDir, `.${SKILL_NAME}.install.`);
|
|
556
579
|
if (hadState) removeOwnedDirectory(stateBackup, paths.stateDir, ".backup.");
|
|
557
580
|
if (hadSkill) removeOwnedDirectory(skillBackup, paths.skillsDir, `.${SKILL_NAME}.backup.`);
|
|
581
|
+
for (const { backup } of additionalBackups) fs.rmSync(backup, { recursive: true, force: true });
|
|
558
582
|
if (hadShim) fs.rmSync(shimBackup, { force: true });
|
|
559
583
|
const result = installationResult(
|
|
560
584
|
options.migrateLegacy ? "migrated" : (current || (targetExists && !backingUpUnmanaged) ? "upgraded" : "installed"),
|
|
@@ -572,6 +596,8 @@ function installLocked(options, packageRoot, verified, paths) {
|
|
|
572
596
|
if (hadShim && pathEntryExists(shimBackup)) fs.renameSync(shimBackup, paths.shim);
|
|
573
597
|
if (skillMoved && pathEntryExists(paths.target)) removeOwnedDirectory(paths.target, paths.skillsDir, SKILL_NAME);
|
|
574
598
|
if (hadSkill && pathEntryExists(skillBackup)) fs.renameSync(skillBackup, paths.target);
|
|
599
|
+
for (const target of installedAdditional.reverse()) fs.rmSync(target, { recursive: true, force: true });
|
|
600
|
+
for (const { target, backup } of additionalBackups.reverse()) if (pathEntryExists(backup)) fs.renameSync(backup, target);
|
|
575
601
|
if (legacyMoved && legacyBackupPath && pathEntryExists(legacyBackupPath) && !pathEntryExists(paths.target)) {
|
|
576
602
|
fs.renameSync(legacyBackupPath, paths.target);
|
|
577
603
|
}
|
|
@@ -613,7 +639,7 @@ function stagePersistentInstall(target, packageRoot, verified) {
|
|
|
613
639
|
}
|
|
614
640
|
|
|
615
641
|
function buildInstallationRecord(paths, release) {
|
|
616
|
-
|
|
642
|
+
const record = {
|
|
617
643
|
schema_version: RECORD_SCHEMA,
|
|
618
644
|
owner: { package: PACKAGE_NAME, installation_id: installationId(paths) },
|
|
619
645
|
release_version: release.release_version,
|
|
@@ -637,11 +663,17 @@ function buildInstallationRecord(paths, release) {
|
|
|
637
663
|
release_sha256: sha256File(path.join(paths.installRoot, "release.json")),
|
|
638
664
|
policy_sha256: sha256File(path.join(paths.installRoot, "release-policy.json")),
|
|
639
665
|
};
|
|
666
|
+
record.skills = Object.fromEntries(Object.entries(release.skills).map(([name, item]) => [name, {
|
|
667
|
+
name, revision: item.revision, requires_cli: item.requires_cli,
|
|
668
|
+
path: path.join(paths.skillsDir, name), sha256: item.sha256,
|
|
669
|
+
}]));
|
|
670
|
+
return record;
|
|
640
671
|
}
|
|
641
672
|
|
|
642
673
|
function assertInstalledState(paths, record, prewarm) {
|
|
643
674
|
if (!digestEquals(paths.clientRoot, record.cli.sha256)) throw new Error("安装后的 CLI 摘要不匹配");
|
|
644
675
|
if (!digestEquals(paths.target, record.skill.sha256)) throw new Error("安装后的 Skill 摘要不匹配");
|
|
676
|
+
for (const skill of Object.values(record.skills || {})) if (!digestEquals(skill.path, skill.sha256)) throw new Error(`安装后的 Skill 摘要不匹配: ${skill.name}`);
|
|
645
677
|
if (sha256File(paths.shim) !== record.shim.sha256) throw new Error("安装后的 CLI shim 摘要不匹配");
|
|
646
678
|
if (prewarm) assertRuntime(paths.dispatcher);
|
|
647
679
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feature-management
|
|
3
|
-
description: Manage projects, versioned business feature assets, dataset rowsets and endpoint eligibility, and causal realtime
|
|
3
|
+
description: Manage projects, versioned business feature assets and their lifecycle, dataset rowsets and endpoint eligibility, and causal realtime row or sequence-context reads through the deployed ML Platform API using the ml-platform CLI installed with the same release. Use when a business user asks an agent to manage an empty Project, define or change Parameter source contracts, author/package/register an Operator wheel, define single-column Features, order a FeatureSet, create a DatasetManifest or catalog, inspect references, soft-delete or restore a Parameter/Feature/FeatureSet version, configure training/validation/test rowsets or abnormal-data endpoint exclusion, configure realtime freshness policy, run catalog dry-run/apply, build a dataset, fetch inference data, or inspect an artifact without downloading the platform source repository. Do not use for model training, Operator/Dataset deletion, image builds, or Kubernetes deployment.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Feature Management
|
|
@@ -117,6 +117,16 @@ Confirm:
|
|
|
117
117
|
|
|
118
118
|
Separate preparation from mutation. Creating files and running local tests or `apply --dry-run` does not authorize publishing a wheel, changing the Registry, or submitting a build Job.
|
|
119
119
|
|
|
120
|
+
Soft-delete and restore are separate Registry mutations. Before soft-deleting a
|
|
121
|
+
Parameter, Feature, or FeatureSet version, inspect its lifecycle state and both
|
|
122
|
+
direct and transitive references, report the affected immutable consumers, and
|
|
123
|
+
obtain explicit authorization for the exact `project/name:version`. Supply an
|
|
124
|
+
actor, reason, and stable idempotency key. Soft deletion preserves historical
|
|
125
|
+
references and hashes but hides the version from normal discovery and prevents
|
|
126
|
+
new consumers from binding it. Restore only after checking that all of the
|
|
127
|
+
resource's own dependencies are active. Never describe these operations as
|
|
128
|
+
physical deletion.
|
|
129
|
+
|
|
120
130
|
A label such as "10-minute mean" confirms neither its input Parameter nor a
|
|
121
131
|
complete formula. Before drafting an Operator or Feature, ask for the exact
|
|
122
132
|
versioned input Parameter(s), or for an approved source contract from which
|
|
@@ -10,8 +10,9 @@ Use the `ml-platform` executable installed on `PATH` by the same ML Platform rel
|
|
|
10
10
|
4. Validate and publish a catalog
|
|
11
11
|
5. Resolve and build a dataset
|
|
12
12
|
6. Fetch realtime inference data
|
|
13
|
-
7. Inspect and
|
|
14
|
-
8.
|
|
13
|
+
7. Inspect and change Registry lifecycle state
|
|
14
|
+
8. Inspect and download an artifact
|
|
15
|
+
9. Prohibited shortcuts
|
|
15
16
|
|
|
16
17
|
## Select A Target
|
|
17
18
|
|
|
@@ -257,6 +258,57 @@ write parquet, or publish a DatasetArtifact. Treat HTTP `503` as retryable sourc
|
|
|
257
258
|
freshness/missing-data failure and HTTP `400` as a contract error that requires a
|
|
258
259
|
manifest or request change.
|
|
259
260
|
|
|
261
|
+
For a deployment-bound row or full sequence context, prepare one JSON object
|
|
262
|
+
containing `feature_retrieval`, `cutoff_time`, and `entity_keys`, then run:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
ml-platform --profile server fetch-inference-context /tmp/inference-context.json
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The `FeatureRetrievalSpec` must bind an existing inference-mode DatasetManifest
|
|
269
|
+
by project, dataset id, version, and manifest hash; its FeatureSet and contract
|
|
270
|
+
signature must match the resolved online contract. Context mode also fixes
|
|
271
|
+
`context_length`, `frequency`, `context_end`, `stride`, input-adapter hash, and
|
|
272
|
+
temporal-binding hash. Do not replace these immutable deployment bindings with
|
|
273
|
+
ad-hoc CLI flags. As with row fetch, this operation writes no artifact.
|
|
274
|
+
|
|
275
|
+
## Inspect And Change Registry Lifecycle State
|
|
276
|
+
|
|
277
|
+
Lifecycle commands apply only to Parameter, Feature, and FeatureSet versions.
|
|
278
|
+
Before a mutation, inspect the exact resource and its references:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
ml-platform --profile server get-parameter-lifecycle <name> <version> --project <project>
|
|
282
|
+
ml-platform --profile server list-parameter-references <name> <version> --project <project> --direct
|
|
283
|
+
ml-platform --profile server list-parameter-references <name> <version> --project <project>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Equivalent `get-*-lifecycle` and `list-*-references` commands exist for
|
|
287
|
+
`feature` and `feature-set`. The default reference query returns the transitive
|
|
288
|
+
closure; `--direct` returns only immediate dependents.
|
|
289
|
+
|
|
290
|
+
After explicit authorization for the exact resource, soft-delete it with
|
|
291
|
+
auditable metadata:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
ml-platform --profile server delete-parameter <name> <version> \
|
|
295
|
+
--project <project> --actor <actor> --reason '<reason>' \
|
|
296
|
+
--idempotency-key <stable-key>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Restore uses the same audit fields and is rejected if the resource's own
|
|
300
|
+
dependencies are not active:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
ml-platform --profile server restore-parameter <name> <version> \
|
|
304
|
+
--project <project> --actor <actor> --reason '<reason>' \
|
|
305
|
+
--idempotency-key <stable-key>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
These operations never erase immutable specs, historical references, or
|
|
309
|
+
contract hashes. Do not use the Operator or Dataset deletion commands as a
|
|
310
|
+
substitute; they have separate lifecycle rules.
|
|
311
|
+
|
|
260
312
|
## Inspect And Download An Artifact
|
|
261
313
|
|
|
262
314
|
```bash
|
|
@@ -299,7 +351,7 @@ Use a structured Parquet reader for schema, row count, column order, and missing
|
|
|
299
351
|
|
|
300
352
|
- Do not use `seed-demo v95` for business catalogs.
|
|
301
353
|
- Do not call individual `add-*` commands when catalog `apply` is available for an end-to-end publication.
|
|
302
|
-
- Do not use `delete
|
|
354
|
+
- Do not use any `delete-*`, `restore-*`, `cancel-job`, or force overwrite operation unless separately requested.
|
|
303
355
|
- Do not build images, deploy Kubernetes, or change service configuration in this workflow.
|
|
304
356
|
- Do not claim a successful publication from `--dry-run` output.
|
|
305
357
|
- Do not claim Chronon compile/backfill from a successful first-phase Operator build.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: model-lifecycle-management
|
|
3
|
+
description: Manage ITSMP model training, evaluation, and immutable model packaging through the deployed platform API. Use when a user asks to list or inspect registered TrainerDefinitions, register a trainer, validate or submit a training run, inspect/retry/cancel training jobs, configure or run governed evaluation, inspect evaluation evidence, validate a ModelArtifact, or create and inspect a ModelPackage. Do not use for feature catalog authoring, dataset builds, model deployment, Kubernetes administration, or image release engineering.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Model Lifecycle Management
|
|
7
|
+
|
|
8
|
+
Use the `ml-platform` executable installed with the same npm release as this Skill. Verify `ml-platform version`, `ml-platform show-config`, and server health before API operations. Never use a platform source checkout or a second client runtime.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
DatasetArtifact -> TrainingRun -> TrainingJob -> ModelArtifact
|
|
12
|
+
-> EvaluationRun/Job -> EvaluationResult/Summary -> ModelPackage
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Read [references/training.md](references/training.md) for training work, [references/evaluation.md](references/evaluation.md) for governed evaluation, and [references/packaging.md](references/packaging.md) for ModelArtifact validation and packaging. Read only the references required by the request.
|
|
16
|
+
|
|
17
|
+
## Shared Gates
|
|
18
|
+
|
|
19
|
+
- Establish exact immutable inputs, TrainerDefinition `v1`, runtime image digest, model inputs/targets, resources, evaluation policy, and package destination from authoritative contracts. Do not infer them from model names.
|
|
20
|
+
- Validate, register, submit, retry, cancel, and package creation are separate actions. Read-only discovery and validation do not authorize mutation or workload submission.
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- Do not register caller-computed evaluation results. Workers produce predictions, metrics, decisions, and summaries from frozen DatasetArtifact rowsets.
|
|
25
|
+
- Keep credentials, registry secrets, Kubernetes details, and model binaries out of request JSON and reports.
|
|
26
|
+
|
|
27
|
+
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.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Evaluation Commands
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
ml-platform --profile server register-evaluation-config evaluation-config.json
|
|
5
|
+
ml-platform --profile server register-metric-definition metric-definition.json
|
|
6
|
+
ml-platform --profile server register-executable-package executable-package.json
|
|
7
|
+
ml-platform --profile server verify-executable-package <name> <version>
|
|
8
|
+
ml-platform --profile server publish-executable-package <name> <version>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Custom metric packages must be deterministic, digest-bound, network-restricted, verified, and separately authorized for publication.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ml-platform --profile server validate-evaluation-run evaluation-request.json
|
|
15
|
+
ml-platform --profile server submit-evaluation evaluation-request.json
|
|
16
|
+
ml-platform --profile server get-evaluation-run <run-id>
|
|
17
|
+
ml-platform --profile server get-evaluation-job <job-id>
|
|
18
|
+
ml-platform --profile server list-evaluation-attempts <job-id>
|
|
19
|
+
ml-platform --profile server retry-evaluation-job <job-id>
|
|
20
|
+
ml-platform --profile server cancel-evaluation-job <job-id>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
An EvaluationRunRequest binds ModelArtifact(s), a frozen DatasetArtifact rowset, targets, EvaluationConfig, runtime identity, and optional baseline. Retry/cancel require separate authorization.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ml-platform --profile server get-evaluation-result <result-id>
|
|
27
|
+
ml-platform --profile server get-evaluation-result-metrics <result-id>
|
|
28
|
+
ml-platform --profile server get-evaluation-result-artifacts <result-id>
|
|
29
|
+
ml-platform --profile server summarize-evaluation-run <run-id>
|
|
30
|
+
ml-platform --profile server get-evaluation-summary <summary-id>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Report candidate, eligible, labeled and evaluable coverage; calculation space; slices; baseline identity; required decisions; runtime/package digests; and Result/Summary hashes. Exploratory or incomplete evidence is never a release gate pass.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Model Packaging Commands
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
ml-platform --profile server validate-model-artifact <artifact-id>
|
|
5
|
+
ml-platform --profile server create-model-package model-package-request.json
|
|
6
|
+
ml-platform --profile server get-model-package <package-id> --package-version <version>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Confirm the ModelArtifact, signature, runtime, immutable base/builder/worker image digests, input modes, semantic package version, and destination repository. Creation submits a workload and needs separate authorization. Never put registry credentials in JSON.
|
|
10
|
+
|
|
11
|
+
A deliverable package is `READY` and includes its remote image digest, immutable reference, manifest, signature and adapter hashes, runtime capabilities, and a passing test report for the same digest. Other states are not deliverable.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ml-platform --profile server cancel-model-package <package-id> --package-version <version>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Cancellation requires exact-package authorization. Do not rebuild an immutable failed version; correct inputs and create a new semantic package version. This Skill does not deploy images or write model-center delivery status.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Training Commands
|
|
2
|
+
|
|
3
|
+
Discover already-registered TrainerDefinitions (read-only) before authoring a
|
|
4
|
+
TrainingRunSpec or deciding whether registration is needed:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
ml-platform --profile server list-trainer-definitions
|
|
8
|
+
ml-platform --profile server get-trainer-definition <name> <version> [--project default]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`get-trainer-definition` returns the entrypoint, immutable training image digest,
|
|
12
|
+
`artifact_format`, `model_file`, capabilities, and parameter schema. Use it to confirm
|
|
13
|
+
the referenced `v1` definition exists and its image digest matches the approved runtime
|
|
14
|
+
release; discovery never mutates the registry. Registering a new definition is the
|
|
15
|
+
separate mutation below, and a 404 means the definition (not the model plugin) is missing.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ml-platform --profile server register-trainer trainer-definition.json
|
|
19
|
+
ml-platform --profile server validate-training-run training-run.json
|
|
20
|
+
ml-platform --profile server register-training-run training-run.json
|
|
21
|
+
ml-platform --profile server submit-training-job training-job-request.json
|
|
22
|
+
ml-platform --profile server get-training-job <job-id>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The TrainingRun binds immutable DatasetArtifact identities, DataSchema, ordered inputs, targets, InputAdapter, TrainerDefinition `v1`, reproducibility controls, and runtime identity. Validation proves compatibility only. Submit the run and Job separately after authorization and record spec hash and Job ID. Terminal success must expose a ModelArtifact ID; validate it before evaluation or packaging.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ml-platform --profile server validate-model-artifact <artifact-id>
|
|
29
|
+
ml-platform --profile server retry-training-job <job-id>
|
|
30
|
+
ml-platform --profile server cancel-training-job <job-id>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Retry and cancellation require exact-Job authorization. Never change a run contract while retrying; create a new immutable run when semantic inputs change.
|