@zhuoyuezs/ml-platform 0.1.9 → 0.1.10

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.
Files changed (34) hide show
  1. package/README.md +7 -3
  2. package/package.json +1 -1
  3. package/checksums.json +0 -155
  4. package/release.json +0 -52
  5. package/runtime/business-client/README.md +0 -78
  6. package/runtime/business-client/package-lock.json +0 -19
  7. package/runtime/business-client/package.json +0 -23
  8. package/runtime/business-client/src/catalog.js +0 -206
  9. package/runtime/business-client/src/cli.js +0 -533
  10. package/runtime/business-client/src/config.js +0 -56
  11. package/runtime/business-client/src/http.js +0 -254
  12. package/skills/feature-management/SKILL.md +0 -480
  13. package/skills/feature-management/agents/openai.yaml +0 -4
  14. package/skills/feature-management/assets/catalog-template/catalog.json +0 -23
  15. package/skills/feature-management/assets/catalog-template/datasets/example_temperature_training.v1.json +0 -40
  16. package/skills/feature-management/assets/catalog-template/feature_sets/example_temperature_core.v1.json +0 -14
  17. package/skills/feature-management/assets/catalog-template/features/example_temperature_mean_5m.v1.json +0 -28
  18. package/skills/feature-management/assets/catalog-template/operator_package/pyproject.toml +0 -12
  19. package/skills/feature-management/assets/catalog-template/operator_package/src/business_feature_operator_template/__init__.py +0 -39
  20. package/skills/feature-management/assets/catalog-template/operator_package/tests/test_operator.py +0 -83
  21. package/skills/feature-management/assets/catalog-template/operators/example_temperature_features.v1.json +0 -58
  22. package/skills/feature-management/assets/catalog-template/parameters/example_temperature.v1.json +0 -58
  23. package/skills/feature-management/references/commands.md +0 -358
  24. package/skills/feature-management/references/contracts.md +0 -731
  25. package/skills/feature-management/references/operator-authoring.md +0 -175
  26. package/skills/feature-management/references/platform-capability-guide.md +0 -75
  27. package/skills/feature-management/references/supervised-datasets.md +0 -101
  28. package/skills/model-lifecycle-management/SKILL.md +0 -38
  29. package/skills/model-lifecycle-management/agents/openai.yaml +0 -4
  30. package/skills/model-lifecycle-management/references/discovery.md +0 -89
  31. package/skills/model-lifecycle-management/references/evaluation.md +0 -172
  32. package/skills/model-lifecycle-management/references/packaging.md +0 -56
  33. package/skills/model-lifecycle-management/references/training-contracts.md +0 -164
  34. package/skills/model-lifecycle-management/references/training.md +0 -85
@@ -1,731 +0,0 @@
1
- # Current Contract Reference
2
-
3
- Use this reference for the implemented V1 schemas. Prefer the running CLI and `src/data_platform_demo/schemas.py` when they disagree with historical design documents.
4
-
5
- ## Contents
6
-
7
- 1. Naming and versioning
8
- 2. Projects and namespacing
9
- 3. Parameter
10
- 4. Operator
11
- 5. Feature
12
- 6. FeatureSet
13
- 7. DatasetManifest
14
- 8. Catalog
15
- 9. Cross-object validation
16
-
17
- ## Naming And Versioning
18
-
19
- - Use identifiers matching `^[A-Za-z0-9][A-Za-z0-9_-]*$` and at most 128 characters.
20
- - Prefer lowercase snake_case business identifiers.
21
- - Use explicit immutable versions such as `v1`, `v2`.
22
- - Create a new version when any semantic field changes.
23
- - Keep credentials in the configured profile or server Secret, never in an asset.
24
-
25
- ## Projects And Namespacing
26
-
27
- - Every registry asset is namespaced by a `project`. Its true registry key is
28
- `project/name:version`, so `name:version` alone is unique only within one
29
- project; the same `name:version` may exist independently under other projects.
30
- - The platform ships a built-in `default` project. When no project is given,
31
- assets are created and read under `default`.
32
- - Conceptually a project is a namespace, not an attribute of the asset. In the
33
- current implementation, however, the project a Parameter/Operator/Feature/
34
- FeatureSet/DatasetManifest is **created** under comes from the optional
35
- `project` field in its own JSON spec (it defaults to `default` when omitted).
36
- So to place an asset in a non-default project, set `"project": "<name>"` in the
37
- spec before `apply`.
38
- - For a confirmed non-default target, include that field in every draft
39
- Parameter, Operator, Feature, Feature input, FeatureSet, and DatasetManifest;
40
- do not rely on Pydantic's `default` fallback during authoring.
41
- - Reads and filters, by contrast, take the project out-of-band: `list-* --project`
42
- and the leading project argument of the artifact commands. `apply --project`
43
- scopes the comparison snapshot that publication is validated against.
44
- - All references inside one catalog resolve within a single project. Cross-project
45
- references (a Feature in project A depending on a Parameter in project B) are
46
- rejected by the platform.
47
- - Immutability is per project: publishing `name:v1` into project A never touches
48
- `name:v1` in project B.
49
-
50
- ## Parameter
51
-
52
- Required shape:
53
-
54
- ```json
55
- {
56
- "schema_version": "ml_data_platform.parameter/v1",
57
- "name": "hot_blast_pressure",
58
- "display_name": "Hot blast pressure",
59
- "version": "v1",
60
- "data_type": "time_series",
61
- "unit": "kPa",
62
- "expected_frequency": "5s",
63
- "source": {},
64
- "time_semantics": {
65
- "event_time_field": "event_time",
66
- "ingested_at_field": "ingested_at",
67
- "timezone": "Asia/Shanghai",
68
- "availability": {
69
- "strategy": "source_field",
70
- "field": "ingested_at",
71
- "accuracy": "exact"
72
- }
73
- },
74
- "availability_sla": {
75
- "max_delay": "PT10M"
76
- },
77
- "value_field": "value",
78
- "quality_rules": {
79
- "valid_range": [0.0, 450.0],
80
- "allow_missing": true,
81
- "rules": [
82
- {
83
- "id": "finite_source_values",
84
- "stage": "normalized_source",
85
- "check": {"type": "finite"},
86
- "acceptance": {"max_violation_rate": 0.0},
87
- "enforcement": "fail"
88
- }
89
- ]
90
- },
91
- "owner": "business_owner"
92
- }
93
- ```
94
-
95
- Supported source modes are `direct_column`, `sql`, and `derived`. Prefer `direct_column` for ordinary business parameters.
96
-
97
- For `sql`, `source.sql` must contain one statement without a trailing semicolon,
98
- and named placeholders must be supplied through `source.params`; `start` and
99
- `end` are reserved runtime window parameters. `source.parameters` is not a
100
- supported field.
101
-
102
- `availability.strategy` 支持 `source_field`、`fixed_delay` 和 `unsupported`。只有一个
103
- 事件时间列时,可以使用经过数据 owner 确认的固定延迟上界,例如
104
- `{"strategy":"fixed_delay","delay":"PT10M","accuracy":"upper_bound"}`。
105
- 当前严格 `as_of` 只接受 `exact` 或有合同支撑的 `upper_bound`;修改这些语义需要发布
106
- 新的 Parameter 业务版本。
107
-
108
- An optional `rounding` section states the fixed-point precision the platform
109
- enforces at the Parameter boundary:
110
-
111
- ```json
112
- "rounding": {"mode": "half_up", "decimals": 2}
113
- ```
114
-
115
- Fixing only the decimal count leaves the tie case to whichever engine evaluates
116
- it: PostgreSQL `ROUND` on `numeric` rounds halves away from zero (5.625 becomes
117
- 5.63) while Python and NumPy round halves to even (5.625 becomes 5.62). A single
118
- 0.01 difference spreads through rolling windows into many derived Features, so
119
- declare both fields. `mode` is `half_up` or `half_even`.
120
-
121
- The platform applies the rule with deterministic decimal quantization after the
122
- adapter produces the standard metric frame and before quality checks, source
123
- cache publication/replay, and Operator execution. Prefer an unrounded source
124
- expression. A source that already applies the same rule is accepted because the
125
- second application is idempotent, but an upstream path that already discarded a
126
- tie with a different rule cannot be repaired downstream. Do not repeat the same
127
- Parameter-level rounding in an Operator. Changing the mode, decimals, or stage
128
- requires a new Parameter version and the affected reverse-dependency closure.
129
-
130
- PostgreSQL, MySQL, and SQL Server direct-column Parameters share this source
131
- shape. Set `adapter` to `postgresql_direct`, `mysql_direct`, or
132
- `sqlserver_direct`; keep database-specific DSNs in server-managed Secrets.
133
-
134
- ```json
135
- {
136
- "adapter": "postgresql_direct",
137
- "mode": "direct_column",
138
- "schema": "process_data",
139
- "table": "sensor_readings",
140
- "time_column": "event_time",
141
- "value_column": "hot_blast_pressure",
142
- "metric_name": "hot_blast_pressure",
143
- "unit_column": "unit",
144
- "filters": {"furnace_id": "BF12"}
145
- }
146
- ```
147
-
148
- The same contract for MySQL or SQL Server changes only the adapter name:
149
-
150
- ```json
151
- {"adapter": "mysql_direct", "mode": "direct_column", "schema": "process_data", "table": "sensor_readings", "time_column": "event_time", "value_column": "hot_blast_pressure", "metric_name": "hot_blast_pressure"}
152
- ```
153
-
154
- ```json
155
- {"adapter": "sqlserver_direct", "mode": "direct_column", "schema": "process_data", "table": "sensor_readings", "time_column": "event_time", "value_column": "hot_blast_pressure", "metric_name": "hot_blast_pressure"}
156
- ```
157
-
158
- InfluxDB direct-column example:
159
-
160
- ```json
161
- {
162
- "adapter": "influxdb_direct",
163
- "mode": "direct_column",
164
- "measurement": "blast_furnace",
165
- "field": "hot_blast_pressure",
166
- "metric_name": "hot_blast_pressure",
167
- "tags": {"furnace_id": "BF12"}
168
- }
169
- ```
170
-
171
- Do not put DSNs, access keys, tokens, passwords, or connection endpoints containing credentials in `source`.
172
-
173
- `quality_rules.rules` defines immutable single-Parameter assertions. Supported
174
- checks are `not_null`, `finite`, `range`, and `max_gap`. `stage` is one of
175
- `normalized_source`, `post_preprocess`, or `aligned_grid`; execution follows
176
- that order. `acceptance` controls evidence thresholds, while `enforcement`
177
- chooses `observe`, `warn`, or `fail`. Keep cross-Parameter assertions out of a
178
- Parameter version and declare them in DatasetManifest `parameter_relationships`.
179
-
180
- `quality_rules.missing_detection` controls source-observation gap detection and
181
- is independent from value validity. It defaults to `disabled`, which is the
182
- safe behavior for irregular event Parameters such as tapping events or silicon
183
- samples. Use `fixed_frequency` only when `expected_frequency` is a real fixed
184
- duration. For irregular Parameters, use an explicit `max_gap` or an
185
- `interval_multiplier` over a `mean` or `median` observed interval:
186
-
187
- ```json
188
- "missing_detection": {
189
- "mode": "interval_multiplier",
190
- "multiplier": 10,
191
- "baseline": "median",
192
- "min_observations": 2
193
- }
194
- ```
195
-
196
- `max_gap` accepts positive short durations including second precision (for
197
- example `30s`, `10min`, or `2h`). Detection runs on the normalized,
198
- post-preprocess source frame before target-grid interpolation, and its source
199
- windows are recorded in the build's `missing_windows.json` artifact.
200
-
201
- ## Operator
202
-
203
- Feature Operator draft shape:
204
-
205
- ```json
206
- {
207
- "schema_version": "ml_data_platform.operator/v1",
208
- "name": "pressure_features",
209
- "version": "v1",
210
- "type": "feature",
211
- "function_hash": "pressure_features.formulas.v1",
212
- "entrypoint": "pressure_features:compute_features",
213
- "code_hash": null,
214
- "package_uri": null,
215
- "code_artifact": null,
216
- "input_schema": {
217
- "parameters": ["hot_blast_pressure:v1"]
218
- },
219
- "output_schema": {
220
- "columns": ["event_time", "pressure_mean_5m"]
221
- },
222
- "config_schema": {
223
- "properties": {
224
- "window": {"type": "string"}
225
- },
226
- "required": ["window"]
227
- },
228
- "runtime": {"engine": "python_entrypoint", "network": "none"},
229
- "resources": {"timeout_seconds": 60},
230
- "deterministic": true,
231
- "supports_batch": true,
232
- "supports_online": false,
233
- "owner": "business_owner"
234
- }
235
- ```
236
-
237
- For a catalog wheel entry, `catalog.json` supplies the package file. Publication calculates and stores `code_hash`, `package_uri`, and `code_artifact`.
238
-
239
- Treat `pythonpath://` as platform-owned example behavior. Publish formal business Operators as immutable pure-Python wheels.
240
-
241
- ## Feature
242
-
243
- ```json
244
- {
245
- "schema_version": "ml_data_platform.feature/v1",
246
- "name": "pressure_mean_5m",
247
- "version": "v1",
248
- "inputs": [
249
- {"parameter": "hot_blast_pressure", "version": "v1"}
250
- ],
251
- "operator": "pressure_features",
252
- "operator_version": "v1",
253
- "config": {"window": "5min"},
254
- "output_column": "pressure_mean_5m",
255
- "output_dtype": "float64",
256
- "offline_online_supported": false,
257
- "owner": "business_owner",
258
- "description": "Mean pressure over the causal five-minute window."
259
- }
260
- ```
261
-
262
- Rules:
263
-
264
- - Define exactly one public output column per Feature.
265
- - Keep `inputs` nonempty, unique, versioned, and formula-exact.
266
- - Require the referenced Operator to have `type=feature`.
267
- - Require `output_column` to appear in the Operator output schema.
268
- - Use `name` as the final dataset column; use `output_column` as the Operator's physical result column.
269
-
270
- ## FeatureSet
271
-
272
- ```json
273
- {
274
- "schema_version": "ml_data_platform.feature_set/v1",
275
- "name": "pressure_core",
276
- "version": "v1",
277
- "features": [
278
- {"name": "pressure_mean_5m", "version": "v1"}
279
- ],
280
- "owner": "business_owner",
281
- "description": "Ordered pressure model inputs."
282
- }
283
- ```
284
-
285
- The list must be nonempty, unique, and ordered exactly as consumers expect.
286
-
287
- ## DatasetManifest
288
-
289
- ```json
290
- {
291
- "schema_version": "ml_data_platform.dataset_manifest/v1",
292
- "dataset_id": "pressure_training_20260701_20260702",
293
- "dataset_version": "v1",
294
- "mode": "training",
295
- "read_policy": "snapshot",
296
- "time_range": {
297
- "start": "2026-07-01T00:00:00+08:00",
298
- "end": "2026-07-02T00:00:00+08:00",
299
- "grid": "10min"
300
- },
301
- "prediction": {
302
- "horizon": "10min"
303
- },
304
- "parameters": [],
305
- "parameter_missing_policies": [
306
- {
307
- "parameter": "hot_blast_pressure",
308
- "version": "v1",
309
- "project": "default",
310
- "policy": "report_only"
311
- }
312
- ],
313
- "feature_set": {"name": "pressure_core", "version": "v1"},
314
- "preprocess": [],
315
- "output": {
316
- "format": "parquet",
317
- "include_stats": true,
318
- "include_lineage": true,
319
- "engine": "chronon"
320
- }
321
- }
322
- ```
323
-
324
- Current schema rules:
325
-
326
- - Accept exactly one `feature_set`.
327
- - Reject old top-level `features` and `recipe` fields.
328
- - Use a half-open `[start, end)` target grid. `start` must be earlier than
329
- `end`, the window must span at least one grid step, both bounds must agree on
330
- timezone awareness, and `grid` must be one of `1min`, `5min`, `10min`,
331
- `15min`, `20min`, `30min`, `1h`, `2h`, `1d`. Violations are rejected at
332
- submission with a field-level error on `time_range`; no Job is created.
333
- - Require `as_of` only with `read_policy=as_of`.
334
- - Allow `snapshot_id` only with `read_policy=snapshot`.
335
- - Use `parameters` only to request explicit Parameter output columns.
336
- - Declare one dataset-wide `prediction` contract for forecast Features. `horizon`
337
- accepts non-negative whole-minute durations such as `0min`, `10min`, or `2h`.
338
- The platform always derives `cutoff_times = target_times - horizon` and records
339
- that derivation in resolved lineage; it is not a user-configurable policy.
340
-
341
- ### Optional sections
342
-
343
- These ten sections may be omitted: `source_read`, `realtime_fetch`,
344
- `abnormal_windows`, `target`, `prediction`, `parameter_relationships`,
345
- `parameter_missing_policies`, `rowset`, `rowset_splits`, and `endpoint_policy`.
346
- Omitting a section keeps older manifest hashes stable.
347
- Sections containing only inert empty values are also treated as unset, but a
348
- declared `realtime_fetch` policy contains meaningful defaults and changes the
349
- manifest hash. Declaring `endpoint_policy` also changes the manifest hash.
350
-
351
- Each explicit `parameters` request may set `missing_policy`. A
352
- `parameter_missing_policies` entry is reserved for a Parameter used only through
353
- the selected FeatureSet's input closure; it must not also appear in
354
- `parameters`. Its exact V1 shape is `{"parameter": "<name>", "version":
355
- "<version>", "project": "<project>", "policy": "<policy>"}`. Do not use
356
- the `ParameterRequest` field names `name` or `missing_policy` in this list. For
357
- a confirmed non-default target, set `project` explicitly instead of relying on
358
- its `default` fallback. The policies are:
359
-
360
- - `report_only`: retain all target rows and report source gaps.
361
- - `fail_on_requested_range_gap`: fail when a detected gap affects the requested
362
- target range.
363
- - `drop_target_and_lookback`: remove the target rows in the detected window and
364
- the following rows whose resolved Feature input lookback still reaches into
365
- that window. The same merged row mask is applied to both
366
- `feature_dataset.parquet` and `parameter_dataset.parquet`.
367
-
368
- The default `report_only` value is omitted from canonical manifest identity so
369
- manifests published before `missing_policy` existed keep their artifact hash.
370
- When `endpoint_policy` is declared, `drop_target_and_lookback` is rejected:
371
- endpoint eligibility requires the canonical datasets and
372
- `rowset_membership.parquet` to retain the same complete candidate rowset. Use
373
- `report_only` or `fail_on_requested_range_gap` for those manifests.
374
-
375
- The resolved per-Parameter windows, policy, affected rows, and dropped rows are
376
- written to `missing_windows.json` and summarized in `validation.json`. A
377
- partitioned build with a drop policy must be materialized as one full source
378
- window so the same global gap contract is used for every rowset.
379
-
380
- `source_read` pages the source reads. A long target window read in one request
381
- can exceed the driver read timeout; chunking keeps one manifest covering the
382
- whole window instead of splitting it into per-day manifests. Chunk windows,
383
- attempts, rows, and elapsed time land in
384
- `latency_stats.fetch_timing_summary`.
385
-
386
- ```json
387
- "source_read": {
388
- "chunk_days": 1,
389
- "timeout_seconds": 60,
390
- "max_retries": 3,
391
- "retry_sleep_seconds": 2
392
- }
393
- ```
394
-
395
- `realtime_fetch` declares the policy for one low-latency causal-cutoff read:
396
-
397
- ```json
398
- "realtime_fetch": {
399
- "max_workers": 4,
400
- "min_lookback": "1h",
401
- "validate_freshness": true,
402
- "max_source_lag_hours": 3.0,
403
- "max_source_lag_hours_by_parameter": {},
404
- "allow_missing": false,
405
- "allow_tail_edge_missing": true,
406
- "max_tail_edge_missing": 1,
407
- "tail_edge_missing_max_age_minutes": 65.0,
408
- "allow_internal_gap_fill": true,
409
- "internal_gap_fill_max_age_hours": 5.0
410
- }
411
- ```
412
-
413
- `POST /inference-data/fetch` serves one cutoff without running a batch build,
414
- writing parquet, or publishing a DatasetArtifact. It derives
415
- `target_time = cutoff_time + prediction.horizon`, filters every source to
416
- `timestamp <= cutoff_time`, and derives each Parameter read window from the
417
- selected Operators' `input_schema.history_requirements`. The response carries
418
- `contract.manifest_hash` and a replayable `read_policy=as_of` manifest instead
419
- of an artifact.
420
-
421
- Keep the default strict: `allow_missing=false`. Tail-edge and bounded internal
422
- gap-fill tolerance may return a row, but the response must mark
423
- `freshness.degraded=true` and include validation evidence. A freshness or
424
- missing-data condition that may recover returns HTTP `503`; an invalid contract
425
- returns HTTP `400`. Declaring `realtime_fetch` changes the manifest hash; leaving
426
- it unset preserves older contract hashes and uses environment fallbacks.
427
-
428
- `abnormal_windows` excludes target windows whose data must not be trusted. The
429
- policy and the lookback apply to the whole window set, not to individual
430
- windows: a dataset is either a filtered rowset or a marked full rowset.
431
-
432
- ```json
433
- "abnormal_windows": {
434
- "policy": "drop_target_and_lookback",
435
- "windows": [
436
- {"start": "2026-05-25T19:30:00+08:00",
437
- "end": "2026-05-26T09:50:00+08:00",
438
- "reason": "shutdown"}
439
- ]
440
- }
441
- ```
442
-
443
- | policy | Meaning |
444
- |-|-|
445
- | `drop_target_and_lookback` | Drop rows in a window, and rows after it whose feature input lookback reaches back in. Default. |
446
- | `drop_target_only` | Drop only rows inside a window. |
447
- | `mark_only` | Drop nothing; record what is affected in lineage and validation. |
448
-
449
- How far past a window to drop is derived from the widest resolved Operator history
450
- requirement. Cutoff-relative history contributes `prediction.horizon + lookback`;
451
- target-time-relative history contributes its declared lookback directly. The
452
- maximum is used for source fetch and abnormal-window propagation. Do not restate
453
- that number unless you want it asserted: an optional `input_lookback_hours` is
454
- checked against the derived value and a mismatch is rejected. Note that a window
455
- near the end of the target range can have its lookback clipped by the range end,
456
- and a window close to the end can remove most remaining rows.
457
-
458
- `target` states which instant the label is aligned to, so reviewing a manifest
459
- shows the target definition rather than only a Parameter version string. When
460
- the target is a Parameter, keep its complete versioned reference **inside**
461
- `target.parameter`; do not flatten `version` or `project` onto `target`.
462
- `si_time_source` is `weight_time` or `dispatch_time`. `interpolation` is an
463
- object with a `method`, never a bare string.
464
-
465
- ```json
466
- "target": {
467
- "parameter": {
468
- "parameter": "hot_metal_si",
469
- "version": "v1",
470
- "project": "replace_with_project"
471
- },
472
- "si_time_source": "weight_time",
473
- "offset_minutes": 0,
474
- "interpolation": {"method": "linear"}
475
- }
476
- ```
477
-
478
- Allowed interpolation methods are `none`, `linear`, and `forward_fill`. A
479
- business phrase such as "allow and report target nulls" belongs in an existing
480
- Parameter missing policy, Feature configuration, or the semantic review; it is
481
- not a free-form `target` or DatasetManifest field.
482
-
483
- The declaration is checked against the selected Si Parameter, but only for
484
- `direct_column` reads, which name their time column in the contract. A `sql` or
485
- `derived` Parameter can apply an offset inside its query text, so those are
486
- recorded as unverified with a reason and a warning — do not read the absence of
487
- an error as confirmation that the label instant matches.
488
-
489
- `parameter_relationships` evaluates manifest-scoped assertions over exact
490
- Parameter versions after preprocessing and grid alignment:
491
-
492
- ```json
493
- "parameter_relationships": [
494
- {
495
- "id": "temperature_pair_consistency",
496
- "inputs": [
497
- {"alias": "left", "parameter": "temperature_left", "version": "v1"},
498
- {"alias": "right", "parameter": "temperature_right", "version": "v1"}
499
- ],
500
- "alignment": {"method": "last_before_or_at", "tolerance": "PT5M"},
501
- "rules": [
502
- {
503
- "id": "bounded_difference",
504
- "stage": "aligned_grid",
505
- "check": {
506
- "type": "absolute_difference",
507
- "left": "left",
508
- "right": "right",
509
- "max_difference": 10.0
510
- },
511
- "acceptance": {"min_comparable_rate": 0.9},
512
- "enforcement": "fail"
513
- }
514
- ]
515
- }
516
- ]
517
- ```
518
-
519
- Relationship checks are `compare`, `absolute_difference`, `ratio_range`,
520
- `sum_equals`, and `co_presence`. `exact` compares only exact grid timestamps.
521
- `last_before_or_at` is causal. With a tolerance, each relationship input fetch
522
- starts at `manifest.start - tolerance`; without one, lookup is bounded to data
523
- inside the manifest target window and does not trigger an unbounded history read.
524
-
525
- `rowset` selects which instants become dataset rows:
526
-
527
- ```json
528
- "rowset": {
529
- "strategy": "fixed_grid",
530
- "grid": "10min"
531
- }
532
- ```
533
-
534
- Supported strategies are `event_driven`, `fixed_grid`, and
535
- `exact_horizon_measured`. `fixed_grid` requires `grid`;
536
- `exact_horizon_measured` requires `horizon` and accepts a `tolerance`. An
537
- optional rowset `time_range` overrides the manifest range for that strategy.
538
- Use `rowset_splits` instead of `rowset` when training needs named strategies
539
- such as `support`, `validation`, and `test`; it is mutually exclusive with the
540
- single `rowset`. Split ranges must not overlap and must cover every emitted
541
- target row. A split-level `fixed_grid.grid` is an explicit assertion and must
542
- equal `DatasetManifest.time_range.grid`; it cannot silently resample one split.
543
-
544
- `rowset_splits` defines labels inside one dataset contract. It does not by
545
- itself publish three DatasetManifests or three DatasetArtifacts. A build emits
546
- one canonical Feature dataset and one Parameter dataset, with `rowset_split`
547
- identifying each candidate row. Publish separate manifests only when separate
548
- artifact identities, permissions, retention, or build lifecycles are required.
549
-
550
- ### Endpoint eligibility and membership
551
-
552
- Use `endpoint_policy` when the platform must retain the complete canonical
553
- rowset while declaring which target endpoints are safe for model training or
554
- evaluation. Candidate construction and eligibility evaluation are two stages:
555
-
556
- ```text
557
- time_range + rowset_splits -> candidate target rows and rowset labels
558
- candidate rows + endpoint_policy -> eligibility membership and reasons
559
- ```
560
-
561
- The policy is scoped to named rowsets, so one manifest can apply it to
562
- `validation` and `test` while leaving `training` as pass-through:
563
-
564
- ```json
565
- {
566
- "prediction": {"horizon": "2h"},
567
- "rowset_splits": {
568
- "training": {
569
- "strategy": "fixed_grid",
570
- "grid": "10min",
571
- "time_range": {
572
- "start": "2025-01-01T00:00:00+08:00",
573
- "end": "2026-01-01T00:00:00+08:00",
574
- "grid": "10min"
575
- }
576
- },
577
- "validation": {
578
- "strategy": "fixed_grid",
579
- "grid": "10min",
580
- "time_range": {
581
- "start": "2026-01-01T00:00:00+08:00",
582
- "end": "2026-04-01T00:00:00+08:00",
583
- "grid": "10min"
584
- }
585
- },
586
- "test": {
587
- "strategy": "fixed_grid",
588
- "grid": "10min",
589
- "time_range": {
590
- "start": "2026-04-01T00:00:00+08:00",
591
- "end": "2026-06-01T00:00:00+08:00",
592
- "grid": "10min"
593
- }
594
- }
595
- },
596
- "abnormal_windows": {
597
- "policy": "mark_only",
598
- "windows": [
599
- {
600
- "start": "2026-03-20T00:00:00+08:00",
601
- "end": "2026-03-28T00:00:00+08:00",
602
- "reason": "shutdown"
603
- }
604
- ]
605
- },
606
- "endpoint_policy": {
607
- "scope": ["validation", "test"],
608
- "policy": "exclude_target_or_input_overlap",
609
- "anchor": "cutoff",
610
- "context_lookback": "28h",
611
- "window_source": "abnormal_windows",
612
- "membership_column": "endpoint_eligible",
613
- "emit_reason": true
614
- }
615
- }
616
- ```
617
-
618
- `endpoint_policy` requires `prediction.horizon`, at least one abnormal window,
619
- and `abnormal_windows.policy=mark_only`. Every `scope` name must exist in
620
- `rowset_splits`; without splits, the only valid scope is `default`. The
621
- membership column must not collide with reserved or materialized dataset
622
- columns.
623
-
624
- Do not derive `context_lookback` from an Operator automatically. Operator
625
- history requirements answer how much source history is needed to compute a
626
- Feature. `endpoint_policy.context_lookback` answers how much model input context
627
- must be free of abnormal observations before an endpoint is usable. They can be
628
- different and are versioned independently in the manifest contract.
629
-
630
- For target time `t`, prediction horizon `h`, context lookback `L`, and abnormal
631
- window `[a, b)`, evaluation uses:
632
-
633
- ```text
634
- cutoff c = t - h
635
- model context = [c - L, c]
636
- target overlap = a <= t < b
637
- context overlap = c >= a and c - L < b
638
- eligible = not (target overlap or context overlap), within policy scope
639
- ```
640
-
641
- The abnormal window is half-open and the cutoff side is closed. Its effective
642
- exclusion tail ends at `b + h + L`; a target exactly at that instant is
643
- eligible. A scoped row receives `target_in_abnormal_window`,
644
- `input_context_overlap`, or both in `reason_codes`. Overlapping abnormal windows
645
- do not duplicate a reason code, but all matching `window_id` values are retained.
646
-
647
- The build does not delete ineligible rows. It writes
648
- `rowset_membership.parquet` with one row per candidate target:
649
-
650
- | Column | Meaning |
651
- |-|-|
652
- | `target_time` | Candidate model target time. |
653
- | `cutoff_time` | `target_time - prediction.horizon`. |
654
- | `rowset_name` | Named split, or `default` without `rowset_splits`. |
655
- | `candidate` | Always true for a row emitted by the current candidate resolver. |
656
- | `policy_applied` | Whether `rowset_name` belongs to `endpoint_policy.scope`. |
657
- | `eligible` | Overlap result; pass-through rows outside scope remain true. |
658
- | `reason_codes` | Stable list of exclusion reasons; empty when eligible or `emit_reason=false`. |
659
- | `matched_window_ids` | Stable IDs of all matching abnormal windows. |
660
- | `effective_exclusion_end` | Latest matched window end plus horizon and context lookback. |
661
- | `policy_hash` | Hash of the resolved endpoint policy and normalized windows. |
662
- | `manifest_hash` | Dataset contract identity used for the build. |
663
-
664
- For compatibility, `feature_dataset.parquet` and `parameter_dataset.parquet`
665
- also receive the configured `membership_column`. Its value is:
666
-
667
- ```text
668
- endpoint_eligible = policy_applied AND eligible
669
- ```
670
-
671
- Consequently, a row outside `scope` is a pass-through membership row with
672
- `eligible=true`, but its compatibility column is false. Use the membership
673
- artifact when consumers need to distinguish "not evaluated" from "evaluated
674
- and rejected"; do not interpret the compatibility column alone as a global
675
- data-quality flag.
676
-
677
- The resolved manifest, `quality_report.json`, `validation.json`, and lineage
678
- record the resolved policy and counts. Reconcile at least
679
- `candidate_rows`, `policy_applied_rows`, `evaluated_eligible_rows`,
680
- `ineligible_rows`, `pass_through_rows`, `reason_counts`, and per-rowset counts.
681
- `eligible_rows` includes true pass-through rows, whereas
682
- `evaluated_eligible_rows` counts only scoped eligible rows. Single and
683
- partitioned builds of the same manifest must produce equivalent merged
684
- membership, counts, lineage, and `policy_hash`.
685
-
686
- For the EXP301 validation contract, `h=2h` and `L=28h`, so the exclusion tail is
687
- 30 hours after each abnormal-window end. The fixed 10-minute validation rowset
688
- contains 20,853 candidates: 19,263 are evaluated eligible and 1,590 are
689
- ineligible. Treat those figures as an example-specific regression contract,
690
- not platform defaults.
691
-
692
- `endpoint_policy` selects historical dataset rows and is not applied to
693
- `POST /inference-data/fetch`. Realtime inference reports it as not applicable
694
- and omits it from the replay manifest.
695
-
696
- The current public schema still records `output.engine=chronon`, while the first-phase bridge executes Python Feature Operators and does not run Chronon compile/backfill. Report this limitation accurately.
697
-
698
- ## Catalog
699
-
700
- ```json
701
- {
702
- "schema_version": "ml_data_platform.catalog/v1",
703
- "name": "pressure_feature_catalog",
704
- "version": "v1",
705
- "parameters": ["parameters/hot_blast_pressure.v1.json"],
706
- "operators": [
707
- {
708
- "spec": "operators/pressure_features.v1.json",
709
- "package": "operator_package/dist/pressure_features-1.0.0-py3-none-any.whl"
710
- }
711
- ],
712
- "features": ["features/pressure_mean_5m.v1.json"],
713
- "feature_sets": ["feature_sets/pressure_core.v1.json"],
714
- "datasets": ["datasets/pressure_training_20260701_20260702.v1.json"]
715
- }
716
- ```
717
-
718
- All paths must be unique, relative, remain inside the catalog directory, exist, and have the expected `.json` or `.whl` suffix.
719
-
720
- ## Cross-Object Validation
721
-
722
- Require:
723
-
724
- - every Feature input Parameter to exist in the target Registry or the same catalog;
725
- - every Feature Operator to exist and declare `type=feature`;
726
- - every Feature input to be allowed by the Operator input schema when that schema declares parameters;
727
- - every Feature physical output to be declared by the Operator output schema;
728
- - every FeatureSet reference to exist;
729
- - every Dataset FeatureSet and preprocess Operator to exist;
730
- - every Dataset relationship input Parameter to exist;
731
- - every new immutable version to differ by key from conflicting registered content.