@zhuoyuezs/ml-platform 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/checksums.json CHANGED
@@ -7,12 +7,12 @@
7
7
  },
8
8
  {
9
9
  "path": "runtime/business-client/package-lock.json",
10
- "sha256": "sha256:fcc57e214724294f6208ec192dbe42b9cabbb6324945b0e93471bf991f76f0bc",
10
+ "sha256": "sha256:744e49f72a879478a18899c6eb64cf3bbdc22700e9d8a4b61d45ad3a5927deb9",
11
11
  "size_bytes": 1628
12
12
  },
13
13
  {
14
14
  "path": "runtime/business-client/package.json",
15
- "sha256": "sha256:61b2a01e7611af07ad49a1789cbaddb5f8ec36d70b78ef72d54227039990e8dc",
15
+ "sha256": "sha256:97145119a4080392bbd2242b6a5a67046aa45e720c38977686fa0f4346c41bf0",
16
16
  "size_bytes": 671
17
17
  },
18
18
  {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  {
24
24
  "path": "runtime/business-client/src/cli.js",
25
- "sha256": "sha256:3913154693561ea4cea1538f560bf5e4f4b4488398abf69332f23f302481ee04",
26
- "size_bytes": 66966
25
+ "sha256": "sha256:6ad8fb06e410cb5a197a696f3cb9819e9e3b06919d443d303a3e9d285cf4c0e2",
26
+ "size_bytes": 68793
27
27
  },
28
28
  {
29
29
  "path": "runtime/business-client/src/config.js",
@@ -32,13 +32,13 @@
32
32
  },
33
33
  {
34
34
  "path": "runtime/business-client/src/http.js",
35
- "sha256": "sha256:517ad46944f3a4068a00a570dc09975c5b59de61b730e0acf8e009ee1f18c89d",
36
- "size_bytes": 18865
35
+ "sha256": "sha256:088ef5fa067cc830a95db7d8ad3887ae58276179b524533f338daa781e243979",
36
+ "size_bytes": 19452
37
37
  },
38
38
  {
39
39
  "path": "skills/data-source-exploration/SKILL.md",
40
- "sha256": "sha256:11b20671ad81b42cd2ddb1cb146196b9f5ab15e962249e7e07cfa148c601e881",
41
- "size_bytes": 4172
40
+ "sha256": "sha256:1f1f89d4ca41c3f7b206287e983027f50f65e462897584511259067fe94f2707",
41
+ "size_bytes": 5631
42
42
  },
43
43
  {
44
44
  "path": "skills/data-source-exploration/agents/openai.yaml",
@@ -127,8 +127,8 @@
127
127
  },
128
128
  {
129
129
  "path": "skills/model-deployment-management/SKILL.md",
130
- "sha256": "sha256:e8419e4d83031248b40783674813bf3f5a98544f05ea3fbe056547ae47632016",
131
- "size_bytes": 4397
130
+ "sha256": "sha256:e4ec21765e23a02ad86bd6b0c92414eee182d74a9f7032e738711c823478ecfe",
131
+ "size_bytes": 4960
132
132
  },
133
133
  {
134
134
  "path": "skills/model-deployment-management/agents/openai.yaml",
@@ -137,13 +137,13 @@
137
137
  },
138
138
  {
139
139
  "path": "skills/model-deployment-management/references/contracts.md",
140
- "sha256": "sha256:de9fd3de8d88179b09108ee0778fb83fbffa304353706a3bbc14ae3d9276ac01",
141
- "size_bytes": 5417
140
+ "sha256": "sha256:967d70e1d162ce0f8736683fd82009c7aa4227a309f38de14199469bb3bfe52c",
141
+ "size_bytes": 6722
142
142
  },
143
143
  {
144
144
  "path": "skills/model-deployment-management/references/operations.md",
145
- "sha256": "sha256:b6d8ac126033a1e3b463b53474f0b27d8687e1a7b05eb973d3aad79259838ac6",
146
- "size_bytes": 4968
145
+ "sha256": "sha256:2eb0fb62d971e21bfbdc1f398a5dcb91916dfccec35e7cbfa4a6404799bfdfb3",
146
+ "size_bytes": 6165
147
147
  },
148
148
  {
149
149
  "path": "skills/model-deployment-management/references/troubleshooting.md",
@@ -152,8 +152,8 @@
152
152
  },
153
153
  {
154
154
  "path": "skills/model-lifecycle-management/SKILL.md",
155
- "sha256": "sha256:9ad2428621b52a15a83dce34a2a95606d7d979017a227c0aeb8fe5c8acd5faa3",
156
- "size_bytes": 5442
155
+ "sha256": "sha256:785219483f917d83d9a371772ea107dc03ddf024a0c4bd098711c4689c5c2ce4",
156
+ "size_bytes": 5718
157
157
  },
158
158
  {
159
159
  "path": "skills/model-lifecycle-management/agents/openai.yaml",
@@ -172,8 +172,8 @@
172
172
  },
173
173
  {
174
174
  "path": "skills/model-lifecycle-management/references/packaging.md",
175
- "sha256": "sha256:1e3c5080fa085255b29223890c333333ee6011326b56f1b2e0b859b0d73d2ec0",
176
- "size_bytes": 6146
175
+ "sha256": "sha256:d090fa9df9db106eae98b0e961038a234768a124e4804cef2f86cb85550b2108",
176
+ "size_bytes": 7718
177
177
  },
178
178
  {
179
179
  "path": "skills/model-lifecycle-management/references/training-contracts.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "ML 数据平台 business CLI 与 Agent Skills 管理工具",
5
5
  "bin": {
6
6
  "ml-platform": "scripts/main.js"
package/release.json CHANGED
@@ -13,8 +13,8 @@
13
13
  "entrypoint": "src/cli.js",
14
14
  "name": "ml-platform",
15
15
  "path": "runtime/business-client",
16
- "sha256": "sha256:a002eb1fb35605ad13ad071b2131233fd4b798e3438984ae1c4c6f4ddf262902",
17
- "version": "0.8.4"
16
+ "sha256": "sha256:3cf1b6b441fc99bfc8e1615f7c0949a3234da0e4ccaa70fca2ddb4ed7b96cae4",
17
+ "version": "0.8.5"
18
18
  },
19
19
  "policy_sha256": "sha256:02fbde0134696b78c0c365d6e0b76c9814227208da5b2b3d4e5dbd82dc4a3606",
20
20
  "public_schema_versions": [
@@ -25,7 +25,7 @@
25
25
  "ml_data_platform.feature_set/v1",
26
26
  "ml_data_platform.dataset_manifest/v1"
27
27
  ],
28
- "release_version": "0.2.3",
28
+ "release_version": "0.2.5",
29
29
  "runtime_requirements": {
30
30
  "node": ">=18",
31
31
  "os": [
@@ -37,34 +37,34 @@
37
37
  "skills": {
38
38
  "data-source-exploration": {
39
39
  "path": "skills/data-source-exploration",
40
- "requires_cli": ">=0.8.4 <0.9.0",
41
- "revision": "0.2.3",
42
- "sha256": "sha256:2fa1e1802d799ee0c47b10263f2fde57b604e505fea5c98bfa23f51f382bbf69"
40
+ "requires_cli": ">=0.8.5 <0.9.0",
41
+ "revision": "0.2.5",
42
+ "sha256": "sha256:6cb1cad3e9531e5098582c532288edd9838c2acab2b324794ec99d2550418f32"
43
43
  },
44
44
  "feature-management": {
45
45
  "path": "skills/feature-management",
46
- "requires_cli": ">=0.8.4 <0.9.0",
47
- "revision": "0.2.3",
46
+ "requires_cli": ">=0.8.5 <0.9.0",
47
+ "revision": "0.2.5",
48
48
  "sha256": "sha256:6dfa708172c3b5b23af75997c82a6d43bc2eb8ca67cc2275c8b95022455b0c42"
49
49
  },
50
50
  "model-deployment-management": {
51
51
  "path": "skills/model-deployment-management",
52
- "requires_cli": ">=0.8.4 <0.9.0",
53
- "revision": "0.2.3",
54
- "sha256": "sha256:d3f3fe0e4a11b93d8f6f57758164aa236b84a49c1b9f0d0b700fd9d6d615a8d7"
52
+ "requires_cli": ">=0.8.5 <0.9.0",
53
+ "revision": "0.2.5",
54
+ "sha256": "sha256:5be65bc4c7b4d81297cc27dfd298c5970efd4230c144ca5289a09344ca2a66ee"
55
55
  },
56
56
  "model-lifecycle-management": {
57
57
  "path": "skills/model-lifecycle-management",
58
- "requires_cli": ">=0.8.4 <0.9.0",
59
- "revision": "0.2.3",
60
- "sha256": "sha256:8822fbec7037c2312d18b1d18648b60757fc569e6bf5f2856ea41592f3ba987d"
58
+ "requires_cli": ">=0.8.5 <0.9.0",
59
+ "revision": "0.2.5",
60
+ "sha256": "sha256:c268653c6520426f84ceaf7e1cf190708bdee5a2ad72018e4201da6f2d2a1a08"
61
61
  },
62
62
  "prediction-report-management": {
63
63
  "path": "skills/prediction-report-management",
64
- "requires_cli": ">=0.8.4 <0.9.0",
65
- "revision": "0.2.3",
64
+ "requires_cli": ">=0.8.5 <0.9.0",
65
+ "revision": "0.2.5",
66
66
  "sha256": "sha256:3a6991eec5cfa6bf30eaf43295f0c7d5c1e70e4f638904dfde1be2a39965d64f"
67
67
  }
68
68
  },
69
- "source_commit": "fc09e3233f9ba919b3c8ce7ff5044527ed0f7415"
69
+ "source_commit": "710dd6d1a923ad8fdec4144a928947c8bcda2fa9"
70
70
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zhuoyuezs/ml-platform-business-client",
9
- "version": "0.8.4",
9
+ "version": "0.8.5",
10
10
  "license": "UNLICENSED",
11
11
  "bin": {
12
12
  "ml-platform": "src/cli.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuoyuezs/ml-platform-business-client",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "private": true,
5
5
  "description": "JavaScript implementation of the ML Platform business client",
6
6
  "bin": {
@@ -15,7 +15,9 @@ const BUSINESS_COMMANDS = new Set([
15
15
  "version", "create-project", "list-projects", "get-project", "delete-project",
16
16
  "configure", "show-config", "health",
17
17
  "list-datasource-connections", "list-datasource-namespaces", "list-datasource-objects",
18
- "list-datasource-fields", "query-datasource-object",
18
+ "list-datasource-fields", "query-datasource-object", "create-datasource-connection",
19
+ "get-datasource-connection", "update-datasource-connection", "delete-datasource-connection",
20
+ "test-datasource-connection",
19
21
  "list-parameters", "list-operators", "list-features",
20
22
  "list-feature-sets", "list-datasets", "get-dataset", "list-jobs", "resolve-manifest",
21
23
  "resolve-dataset", "build-dataset", "build-registered-dataset",
@@ -62,6 +64,11 @@ const COMMAND_USAGE = {
62
64
  "list-datasource-objects": "list-datasource-objects CONNECTION_ID NAMESPACE_ID [--limit N] [--cursor CURSOR]",
63
65
  "list-datasource-fields": "list-datasource-fields CONNECTION_ID OBJECT_ID [--limit N] [--cursor CURSOR]",
64
66
  "query-datasource-object": "query-datasource-object CONNECTION_ID OBJECT_ID SPEC_JSON [--cursor CURSOR]",
67
+ "create-datasource-connection": "create-datasource-connection SPEC_JSON",
68
+ "get-datasource-connection": "get-datasource-connection CONNECTION_ID",
69
+ "update-datasource-connection": "update-datasource-connection CONNECTION_ID SPEC_JSON",
70
+ "delete-datasource-connection": "delete-datasource-connection CONNECTION_ID",
71
+ "test-datasource-connection": "test-datasource-connection CONNECTION_ID",
65
72
  "create-project": "create-project NAME [--display-name TEXT] [--description TEXT] [--owner OWNER]",
66
73
  "list-projects": "list-projects",
67
74
  "get-project": "get-project NAME",
@@ -445,6 +452,25 @@ async function runBusinessCli(argv) {
445
452
  spec.cursor = cursor;
446
453
  result = await client(options).post(`/datasource-connections/${encodeURIComponent(connectionId)}/objects/${encodeURIComponent(objectId)}/query`, spec);
447
454
  }
455
+ else if (options.command === "create-datasource-connection") {
456
+ result = await client(options).post("/datasource-connections", readSpec(positional(rest, "connection spec JSON")));
457
+ }
458
+ else if (options.command === "get-datasource-connection") {
459
+ const connectionId = positional(rest, "connection id");
460
+ result = await client(options).get(`/datasource-connections/${encodeURIComponent(connectionId)}`);
461
+ }
462
+ else if (options.command === "update-datasource-connection") {
463
+ const connectionId = positional(rest, "connection id");
464
+ result = await client(options).put(`/datasource-connections/${encodeURIComponent(connectionId)}`, readSpec(positional(rest, "connection spec JSON")));
465
+ }
466
+ else if (options.command === "delete-datasource-connection") {
467
+ const connectionId = positional(rest, "connection id");
468
+ result = await client(options).delete(`/datasource-connections/${encodeURIComponent(connectionId)}`);
469
+ }
470
+ else if (options.command === "test-datasource-connection") {
471
+ const connectionId = positional(rest, "connection id");
472
+ result = await client(options).post(`/datasource-connections/${encodeURIComponent(connectionId)}/test`, {});
473
+ }
448
474
  else if (options.command === "create-project") {
449
475
  const name = positional(rest, "project name");
450
476
  result = await client(options).post("/projects", { schema_version: "ml_data_platform.project/v1", name, display_name: take(rest, "--display-name", name), description: take(rest, "--description", ""), owner: take(rest, "--owner", "demo") });
@@ -727,7 +753,7 @@ async function runBusinessCli(argv) {
727
753
  realtime.validate_freshness = false;
728
754
  if (boolean(rest, "--allow-missing"))
729
755
  realtime.allow_missing = true;
730
- const request = { manifest: readDataset(manifest), cutoff_time: cutoffTime };
756
+ const request = { manifest: normalizeRegistryDataset(readObject(manifest)), cutoff_time: cutoffTime };
731
757
  if (Object.keys(realtime).length)
732
758
  request.realtime_fetch = realtime;
733
759
  result = await client(options).post("/inference-data/fetch", request);
@@ -5,6 +5,17 @@ const os = require("os");
5
5
  const path = require("path");
6
6
  const zlib = require("zlib");
7
7
  const { expandHome } = require("./config");
8
+ function apiErrorDetail(result, fallback) {
9
+ if (result === null || result === undefined)
10
+ return fallback;
11
+ if (typeof result === "string")
12
+ return result || fallback;
13
+ // Keep the familiar string detail, but serialize structured error responses
14
+ // in full so nested field errors and sibling diagnostics remain visible.
15
+ if (typeof result.detail === "string" && Object.keys(result).length === 1)
16
+ return result.detail;
17
+ return JSON.stringify(result, null, 2);
18
+ }
8
19
  class PlatformApiClient {
9
20
  constructor(baseUrl, timeoutSeconds = 30) {
10
21
  this.baseUrl = String(baseUrl).trim().replace(/\/+$/, "");
@@ -50,10 +61,11 @@ class PlatformApiClient {
50
61
  result = JSON.parse(text);
51
62
  }
52
63
  catch (_) {
53
- throw new Error("platform API returned invalid JSON");
64
+ if (response.ok)
65
+ throw new Error("platform API returned invalid JSON");
54
66
  }
55
67
  if (!response.ok)
56
- throw new Error(`platform API request failed: ${method} ${response.url}: HTTP ${response.status}: ${result?.detail ?? response.statusText}`);
68
+ throw new Error(`platform API request failed: ${method} ${response.url || this.url(endpoint, params)}: HTTP ${response.status}: ${apiErrorDetail(result, text.trim() || response.statusText)}`);
57
69
  if (!result || typeof result !== "object")
58
70
  throw new Error("platform API returned an unsupported JSON response");
59
71
  return result;
@@ -6,7 +6,41 @@ description: Discover platform-configured PostgreSQL, MySQL, SQL Server, or Infl
6
6
  # Data Source Exploration
7
7
 
8
8
  Use the platform API through `ml-platform`; never connect to a business database directly and
9
- never request credentials. The platform exposes configured connections and their discoverable objects.
9
+ never request credentials in chat. The platform exposes managed connections and their discoverable
10
+ objects. Connection credentials are write-only control-plane input and are never returned by the
11
+ platform.
12
+
13
+ ## Manage Connections
14
+
15
+ Datasource connections are business-owned platform resources, not ConfigMap entries. An operator
16
+ with the required platform permission can create or change one from a JSON file; the API encrypts
17
+ the connection configuration at rest and returns only metadata:
18
+
19
+ ```json
20
+ {
21
+ "connection_id": "postgresql_bf_monitoring",
22
+ "name": "BF monitoring",
23
+ "adapter": "postgresql_direct",
24
+ "connection_config": {
25
+ "dsn": "postgresql://readonly:password@db.example:5432/bf_monitoring_dev_db",
26
+ "timeout_seconds": 30
27
+ }
28
+ }
29
+ ```
30
+
31
+ ```bash
32
+ ml-platform create-datasource-connection connection.json
33
+ ml-platform test-datasource-connection postgresql_bf_monitoring
34
+ ml-platform list-datasource-connections
35
+ ml-platform update-datasource-connection postgresql_bf_monitoring connection.json
36
+ ml-platform delete-datasource-connection postgresql_bf_monitoring
37
+ ```
38
+
39
+ Do not put the JSON file in the repository or paste real credentials into logs or issue comments.
40
+ Updating a connection creates a new internal revision. Dataset Workers and realtime reads resolve
41
+ the latest enabled revision from platform storage when they start or when the API handles a request;
42
+ the business connection does not require a Pod restart. A platform-level encryption key remains
43
+ system security configuration, but the connection itself is managed through this resource API.
10
44
 
11
45
  ## Establish The Target
12
46
 
@@ -52,11 +52,19 @@ deployment does not reach `READY` or an operation is rejected.
52
52
  `NodePort` value creates one deployment-level direct Serving Service that
53
53
  follows the current revision; it does not replace validation, approval,
54
54
  rollout, READY gates, or audit history.
55
+ - Set `service_exposure.type=Ingress` to reuse the environment's existing
56
+ ingress-nginx controller while keeping the model Service as ClusterIP. The
57
+ observed `ingress` endpoint contains the stable path and URL template. This
58
+ option does not install a Gateway controller or configure authentication.
55
59
  - Every model Serving process publishes that model's Swagger UI at `/docs`,
56
60
  OpenAPI JSON at `/openapi.json`, and ReDoc at `/redoc`. The document embeds
57
61
  the deployed artifact identity and its actual input modes, ordered columns,
58
62
  horizon semantics, known-future contract, and output schema. These are model
59
63
  data-plane documents, not the control-plane API documentation.
64
+ - For context packages with an explicit `availability_boundary`, verify
65
+ `lineage.visibility` as described in [references/operations.md](references/operations.md).
66
+ Configure the boundary when packaging; prediction callers still send only
67
+ `cutoff_time`.
60
68
  - Never create, modify, or delete NetworkPolicy. Cluster operators own that
61
69
  boundary.
62
70
 
@@ -81,13 +81,32 @@ reports the assigned value. An explicit value must be in `30000..32767`.
81
81
  known to be reachable by callers. Without it, the endpoint returns a URL
82
82
  template rather than claiming external reachability.
83
83
 
84
+ Set `type=Ingress` to reuse an existing ingress-nginx controller while keeping
85
+ the stable model Service as ClusterIP. The default external path is
86
+ `/models/{service}`. `host` adds an HTTP Host rule,
87
+ `advertised_host` supplies the host used in the observed URL, `path_prefix`
88
+ overrides the default path, and `ingress_class_name` defaults to `nginx`.
89
+ Without `host` or `advertised_host`, the endpoint returns a path URL template
90
+ and the caller must supply the environment's ingress address. `advertised_host`
91
+ may include the ingress entry port. `host` must be a DNS hostname, while IP
92
+ access uses `advertised_host` with no Host rule. `path_prefix` is a non-root
93
+ literal path; regex expressions and traversal segments are rejected. Ingress routing
94
+ strips the external prefix and preserves the Serving `/v1/*` and `/docs` paths; this contract does
95
+ not configure authentication. Because `environment` is not part of the default public path,
96
+ same-service deployments in multiple environments must use different Hosts or explicit
97
+ `path_prefix` values when they share an ingress entry.
98
+
99
+ The Serving entrypoint receives an ASGI root path for Swagger and OpenAPI server
100
+ URLs. Older Serving images still support rewritten inference requests; prefix-aware
101
+ documentation requires an updated Serving runtime. Training images do not need
102
+ to change for this exposure option.
103
+
84
104
  The controller keeps preview, stable, and canary Services as ClusterIP for
85
- verification and Gateway traffic. NodePort exposure uses one fixed-name Service
86
- per DeploymentSpec and creates or updates it only after the revision passes all
87
- READY gates. Retained revisions of that deployment share the Service, while a
88
- replacement deployment receives a different Service so staging cannot rewrite
89
- the existing production route. When replacement deployments request explicit
90
- ports, their values must not collide while both deployments exist.
105
+ verification and Gateway or Ingress traffic. NodePort exposure uses one fixed-name Service
106
+ per service/environment and creates or updates it only after the revision passes all
107
+ READY gates. Retained revisions and replacement deployments share the Service;
108
+ staging verification uses a separate preview Service before switching the shared
109
+ selector, so an explicit NodePort is not allocated twice.
91
110
 
92
111
  ## Revision State
93
112
 
@@ -109,7 +128,7 @@ revision passed all READY gates.
109
128
 
110
129
  Each endpoint advertises `inference_path=/v1/inference`, `docs_url`,
111
130
  `openapi_url`, and `redoc_url`. When no reviewed host is available, the
112
- NodePort endpoint returns corresponding URL templates instead. The Serving
131
+ NodePort or Ingress endpoint returns corresponding URL templates instead. The Serving
113
132
  OpenAPI document is generated for the deployed ModelArtifact and embeds the
114
133
  effective model contract; it is not a generic platform API schema.
115
134
 
@@ -60,6 +60,18 @@ deployed model. NodePort network reachability remains an environment concern;
60
60
  do not report it as reachable unless `advertised_host` was reviewed and the
61
61
  client can connect.
62
62
 
63
+ For an ingress-backed ClusterIP deployment, read the observed `ingress`
64
+ endpoint after `READY` and use its base URL:
65
+
66
+ ```bash
67
+ curl http://<ingress-host>/models/<service>/v1/model
68
+ curl http://<ingress-host>/models/<service>/openapi.json
69
+ ```
70
+
71
+ When `host` is configured separately from `advertised_host`, send the declared
72
+ HTTP `Host` header. The platform does not configure authentication for this
73
+ exposure mode.
74
+
63
75
  ## Promotion And Approval
64
76
 
65
77
  ```bash
@@ -145,6 +157,18 @@ ml-platform --profile server predict-deployment <deployment-id> \
145
157
  /absolute/path/request.json
146
158
  ```
147
159
 
160
+ For a package with an explicit `availability_boundary`, inspect
161
+ `lineage.visibility` in the prediction response. It records the package's
162
+ boundary, `cutoff_time`, `attempt_started_at`, `knowledge_time`,
163
+ `read_started_at`, and `read_completed_at`. In `request_time` mode,
164
+ `knowledge_time` equals `attempt_started_at`; in `cutoff_time` mode it equals
165
+ the request cutoff. Check that the source's `available_at` evidence is no later
166
+ than `knowledge_time`, that the event-time context ends at the requested
167
+ cutoff, and that there are no missing or non-finite model inputs. The caller
168
+ does not supply `request_time`: each retrieval attempt receives a new time at
169
+ the API. A historical cutoff in `request_time` mode is therefore a current
170
+ read of historical events, not a historical replay.
171
+
148
172
  Prediction is allowed only for a current `READY` revision with all readiness
149
173
  conditions true. This command uses the control-plane proxy and does not require
150
174
  a cluster-local Serving URL.
@@ -14,6 +14,11 @@ DatasetArtifact -> TrainingRun -> TrainingJob -> ModelArtifact
14
14
 
15
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
16
 
17
+ For late-arriving context observations, read the package-bound
18
+ `availability_boundary` guidance in [references/packaging.md](references/packaging.md).
19
+ The caller supplies one cutoff; the package chooses strict cutoff visibility or
20
+ visibility at the actual retrieval attempt.
21
+
17
22
  Read [references/discovery.md](references/discovery.md) before locating existing
18
23
  resources or selecting runtime bindings. It also defines Job/package preflight
19
24
  and version-skew handling.
@@ -90,6 +90,35 @@ not namespace Secret/ConfigMap settings:
90
90
  }
91
91
  ```
92
92
 
93
+ This option applies to `feature_retrieval.request.mode=context`; row retrieval
94
+ and known-future covariate reads retain their cutoff visibility rules.
95
+ For a context model whose event-time cutoff can precede the arrival of its
96
+ last observation, set `feature_retrieval.availability_boundary` explicitly in
97
+ the new package request:
98
+
99
+ ```json
100
+ "availability_boundary": "request_time"
101
+ ```
102
+
103
+ The caller still sends only `cutoff_time`. This remains the end of the model's
104
+ event-time context; the API records the start of each actual retrieval attempt
105
+ as `request_time` and admits only source records with trustworthy
106
+ `available_at <= request_time`. Use this mode when a D 00:00 observation may
107
+ arrive at 08:10 and prediction runs after its arrival. Use
108
+ `"availability_boundary": "cutoff_time"` when a replay or decision must
109
+ exclude every record that arrived after the supplied cutoff. Omit the field
110
+ only for an existing package that intentionally retains its legacy contract.
111
+ The platform rejects explicit visibility policies when the source cannot
112
+ provide trustworthy availability timestamps; never replace ingestion times
113
+ with event times or fabricate missing observations.
114
+
115
+ This choice is immutable package identity. Validate and create a new package
116
+ version, then deploy its new Serving image; changing the API image alone does
117
+ not update existing packages. A historical `feature_lookup_test.cutoff_time`
118
+ with `request_time` tests present-day retrieval of old events, not what was
119
+ visible at that historical instant. Use `cutoff_time` mode for historical
120
+ availability replay.
121
+
93
122
  The inference DatasetManifest must exist with the supplied hash and use
94
123
  `mode=inference`. Its FeatureSet, stable contract signature, adapter/temporal
95
124
  hashes, entity keys, and row/context parameters must match the ModelArtifact.