@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 CHANGED
@@ -14,7 +14,8 @@
14
14
  用户级 Skill,不要求额外安装参数。
15
15
  - 管理命令默认输出简洁结果;Agent 和 CI 显式使用 `--json` 获取稳定结构化输出。
16
16
  - npm `postinstall` 不得修改 Agent Skills 目录。
17
- - Python business client 只保留为迁移 oracle,不进入 release
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`、测试 oracle、旧 wheel、Skill runtime、旧 wrapper、
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
- - 补齐全部 Python CLI 命令和参数契约;
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 differential tests;
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
- 完成标准:Python 只剩 oracle 作用,正式 CLI 不需要 Python、`uv` 或第三方 npm runtime 依赖。
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
- - 删除 `runtime/business-client-js/`、`assets/wheels/`、Skill installer 和 CLI launcher
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
- - Python oracle tests;
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
- - 一份纯 `feature-management` Skill;
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:72b0cbbb18a7d0f1bca5d97706588fbebcd2781833ae023bae0317be13342601",
6
- "size_bytes": 1880
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:b784fa27db6e40f72da11b9bca007155146174bc33edc4511c61d4ea4af93cf1",
16
- "size_bytes": 508
15
+ "sha256": "sha256:9e815ffe0487eb216447f4a4b31d248fe37392e08f0fd4bae2aba9c4f4285547",
16
+ "size_bytes": 501
17
17
  },
18
18
  {
19
19
  "path": "runtime/business-client/src/catalog.js",
20
- "sha256": "sha256:afc2ba28eee2d1dcd833a2e5c4ac923421dc60302b31bf985aa638849bbf3d36",
21
- "size_bytes": 54246
20
+ "sha256": "sha256:8dcc4c4baff7f292a6fb09d6f82693b5ce7da16b9f0a9e03b22e1f18e893240f",
21
+ "size_bytes": 53659
22
22
  },
23
23
  {
24
24
  "path": "runtime/business-client/src/cli.js",
25
- "sha256": "sha256:f2634e346a4c5bdc42c81e316791906f809b768a77672e36d29cc51410c010ed",
26
- "size_bytes": 29818
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:7b58cc4ff2a7b83a29a6dbf9ad54e9e4a02efd332cd01c5d8fb83020b51ea506",
41
- "size_bytes": 29676
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:c20ccf9ad8d505a1880a7e35773edb5040bbbcfeba3a64316e3148a6e337bdc2",
96
- "size_bytes": 11546
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "ML 数据平台 business CLI 与 Agent Skills 管理工具",
5
5
  "bin": {
6
6
  "ml-platform": "scripts/main.js"
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:b509f9ccdedc4aa5d4d01c77609e4a16020ac6f74c62efc3af861877d897fe94",
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.5",
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.5",
42
- "sha256": "sha256:ea7ef622f10300a3b5ee200adcf10070d18fadfb899d1bf2cc031ef42ffc5c03"
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": "d18a5b535a2a6c41fe4732e8e73739f2728c1e58"
51
+ "source_commit": "62db427abfd17b9c8dc58964b03aec21927217b5"
46
52
  }
@@ -1,6 +1,7 @@
1
1
  # Business Client JS
2
2
 
3
- `business-client/` JavaScript 迁移实现。Python client 目前仅作为迁移期 parity oracle;
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 parity implementation of the ML Platform business client",
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) { exactKeys(payload, ["schema_version", "project", "name", "version", "type", "function_hash", "entrypoint", "code_hash", "package_uri", "code_artifact", "input_schema", "output_schema", "config_schema", "runtime", "resources", "deterministic", "supports_batch", "supports_online", "owner"], ["name", "version"], "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; }
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 skill = release.skills && release.skills[SKILL_NAME];
156
- if (!skill || skill.path !== `skills/${SKILL_NAME}` || !isSha256(skill.sha256)
157
- || !isSemver(skill.revision) || typeof skill.requires_cli !== "string") {
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, skill.requires_cli)) {
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([SKILL_NAME]), "skills");
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
- return {
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 feature 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, configure training/validation/test rowsets or abnormal-data endpoint exclusion, configure realtime freshness policy, run catalog dry-run/apply, build a dataset, fetch one inference row, or inspect an artifact without downloading the platform source repository. Do not use for model training, versioned Registry asset deletion, image builds, or Kubernetes deployment.
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 download an artifact
14
- 8. Prohibited shortcuts
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-dataset`, `cancel-job`, or force overwrite operations unless separately requested.
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,4 @@
1
+ interface:
2
+ display_name: "模型训练、评估与打包"
3
+ short_description: "管理 ITSMP 模型训练、评估与不可变模型打包流程"
4
+ default_prompt: "使用 $model-lifecycle-management 管理模型训练、评估或打包流程。"
@@ -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.