DAG-ML Coordinator Short Spec¶
Status: working normative spec.
This document is the short alignment contract for the product. It resolves the ambiguity left by the long design documents: DAG-ML is a Rust pipeline coordinator, not an ML algorithm library and not a data framework.
Product Goal¶
DAG-ML must virtually execute any data pipeline that can be expressed as:
a compiled DAG of data/model/control dependencies;
an experimental campaign plan: variants, splits, repetitions, refit, predict, explain;
external operators reachable through language bindings/controllers;
typed data contracts and identity tables supplied by
dag-ml-dataor an equivalent data provider.
The product promise is:
reproducible execution;
traceable execution;
leakage-safe OOF campaigns;
real compiled DAG semantics;
high-performance Rust orchestration and scheduling;
external operators, data buffers and fitted objects kept outside the core.
Non-Negotiable Principles¶
The Rust core owns logic, not domain algorithms.
Operators are external payloads: sklearn estimators, torch modules, C++ methods, bioinformatics tools, filters, splitters, augmenters.
Controllers live in bindings or native plugin layers. They adapt external operators to the Rust core contract.
The Rust core owns compilation, planning, validation, scheduling, RNG, fingerprints, lineage, cache keys, OOF joins, and campaign state.
The Rust core never inspects feature buffers, images, spectra, tensors, sequences, graphs, or fitted model internals.
The Rust core may inspect only identity, fold membership, prediction tables, target/scoring tables, descriptors, fingerprints and controller manifests.
Every join, split, merge and prediction use is keyed by stable identities, not by row position.
Unsafe leakage paths are refused by default and must be explicit, traceable and searchable when allowed.
Ownership Boundary¶
Area |
Rust core owns |
Binding/controller owns |
Data layer owns |
|---|---|---|---|
DSL |
compiled IR target, not syntax sugar |
frontend syntax helpers |
none |
DAG |
graph topology, ports, branches, merges |
external node behavior |
source contracts only |
Campaign |
variants, split plan, phases, refit/replay |
splitter/model execution |
sample relation facts |
Controllers |
manifest validation, invocation protocol |
actual adapter code |
none |
Data |
views by identity, DataPlan refs |
host handles |
schema, sources, adapters, collation |
Execution |
scheduler, tasks, phase gates, reducers |
fit/predict/transform/split calls |
materialization |
Reproducibility |
RNG contexts, fingerprints, canonical plans |
local RNG consumption from seed |
schema/data fingerprints |
Traceability |
lineage records, cache keys, artifact refs |
artifact serialization hooks |
fitted data-adapter refs |
OOF safety |
fold validation, prediction store, OOF join |
emits predictions with identity |
group/origin/repetition facts |
Vocabulary¶
Operator¶
External business payload. Examples: sklearn estimator, torch module, C++ PLS, BLAST wrapper, aligner, filter predicate, augmenter, splitter.
An operator is never owned by the Rust core.
Frontend syntax should therefore prefer minimal opaque references when the
operator is unambiguous. A bare SNV is compiled as a transform node whose
operator payload remains "SNV"; a binding-side registry may instantiate the
class and route it to a generic transformer controller. Verbose descriptors
(class, function, explicit params, controller hints) are only required when
the payload needs parameters or when registry routing would otherwise be
ambiguous.
If controller manifests are present while compiling a DSL graph or execution
plan, their operator_selectors may also classify a minimal alias before the
graph ports are frozen. This lets a custom bare alias stay compact even when
Rust cannot infer its category by name; if multiple operator kinds claim it,
the compiler rejects the alias and asks for explicit syntax.
Controller¶
Binding-side or native adapter that knows how to call an operator. A Python controller may call sklearn. A native controller may call C++. An R controller may call mlr3. A bioinformatics controller may call a local library or process.
Controllers are registered through a manifest and invoked through ABI/binding contracts.
Rust Core¶
The coordinator. It compiles, plans, validates, schedules and records execution. It treats controllers as callable endpoints with declared capabilities.
Data Provider¶
The owner of source storage and heavy buffers. For this workspace, dag-ml-data
is the reference contract. Other data providers can exist if they satisfy the
same identity/data-plan boundary.
Controller Contract¶
Each controller must expose a manifest equivalent to:
Field |
Purpose |
|---|---|
|
stable unique id |
|
semantic version used in fingerprints |
|
model, transform, splitter, augmenter, filter, metric, generator, tuner |
|
compile, plan, fit_cv, select, refit, predict, explain |
|
typed port contracts |
|
|
|
deterministic, thread_safe, process_safe, needs_python_gil, emits_predictions, consumes_oof_predictions, emits_artifacts, stateful, emits_relation, accepts_task_batch, accepts_static_subgraph |
|
optional alias/class/function/ref/type selectors used by the registry to route opaque operator payloads before falling back to generic kind routing |
|
optional static patterns for controller-side multitask execution, including allowed node kinds, axes, limits and compatibility constraints |
|
stateless, fold_train, full_train, inference_only |
|
uses core seed, ignores seed, externally deterministic, nondeterministic |
|
serializable, host_only, content_addressed, replay_required |
Minimum callable operations:
Operation |
Used for |
|---|---|
|
manifest and static contracts |
|
controller-specific plan details, no heavy compute |
|
phase-specific execution over handles and identity views |
|
optional execution of a validated group of logical tasks or a closed static subgraph |
|
lifecycle cleanup for host handles |
Specialized operations such as fit, predict, transform, split,
augment, score may exist behind invoke, but the Rust core should schedule
typed tasks rather than know library-specific APIs.
invoke_batch is never a license for controller-owned topology. It is a
transport and scheduling optimisation for logical tasks that the Rust core has
already planned. A controller may use it to launch many preprocessings on one
GPU call or to execute a closed, static subgraph, but it must return one
logical response per member task so cache, OOF validation, lineage and replay
remain coordinator-owned.
Controller inputs:
opaque data handles;
selected identity view;
phase;
fold id, branch path, variant id, trial id;
deterministic
SeedContext;data-plan/fingerprint refs;
controller params.
Data-plan references are DataBinding contracts owned by the coordinator:
node id and input name;
dag-ml-datarequest id;schema fingerprint;
data-plan fingerprint;
optional relation fingerprint;
output representation;
feature set id used for
feature_arrowrequests, defaulting to input name when omitted;feature fusion selectors may be passed through the same
feature_arrowbytes-view when a data provider supportsdag-ml-datamulti-source fusion;source ids;
view policy for fold train, fold validation, refit and predict.
The actual data plan and relation table remain external. dag-ml-data can emit
a coordinator envelope containing these fingerprints plus coordinator relation
records; dag-ml validates that an execution campaign binds to the exact
envelope before a controller receives any handle.
The shared serialization contract is versioned as
CoordinatorDataPlanEnvelope v1. dag-ml consumes the subset represented by
ExternalDataPlanEnvelope, rejects unsupported future versions at runtime, and
publishes the current JSON Schema at
docs/contracts/coordinator_data_plan_envelope.schema.json. The schema is the
wire-contract artifact; Rust validation remains responsible for semantic
checks such as fingerprint equality and relation membership in the active
campaign fold set.
At execution time, the scheduler does not give the controller the raw
materialized data handle directly. It asks the data provider for a scoped view
derived from the active phase and DataViewPolicy: FIT_CV tasks receive a
fold-train view for fitting and a separate fold-validation view for OOF
prediction, refit/full-train views carry the full training sample ids, and
replay predict views are marked as predict partitions. The handles visible in
TaskRequest are scoped data-view handles, and the same request carries a
data_views map keyed like input_handles so bindings can inspect the selected
partition without guessing from the handle. The parent handle remains
traceability state owned by the provider. Unsafe data views are rejected unless
declared explicitly: FIT_CV fitting cannot use full-train or validation
partitions, validation/predict views cannot include augmented rows, and excluded
rows cannot be included unless the corresponding DataViewPolicy.unsafe_flags
entry is present.
Controller outputs:
opaque data/model/artifact handles;
prediction blocks;
sample relation deltas;
metrics;
structured errors;
artifact refs or serialization bytes.
Public Method Shape¶
The data/model piloting contract must be visible in public Rust and binding APIs. It must not be hidden inside controller-specific side effects.
Minimum public coordinator flow:
Method |
Inputs |
Output |
|---|---|---|
|
frontend pipeline IR, frontend registry |
|
|
graph, campaign, controller manifests, dataset schema, data planner |
immutable |
|
execution plan, data provider handle, controller registry, stores |
|
|
CV result, ranking policy |
|
|
selected graph, execution plan, data provider handle, stores |
|
|
selected graph, refit result, artifacts, fingerprints |
|
|
bundle, new data provider handle |
prediction blocks |
|
bundle, new data provider handle, target node/method |
explanation payload refs |
Minimum controller-facing request/response shapes:
Type |
Required fields |
|---|---|
|
node id, operator params, phase set, data requirements, input/output ports, data schema fingerprint |
|
identity table, sample relation table, split policy, seed context |
|
phase, node id, fold id, branch path, variant id plus generated choices/fingerprint, data view, data-plan refs, input handles, prediction input metadata, artifact input metadata, seed context |
|
output handles, prediction blocks, sample relation deltas, metrics, artifacts, lineage payload |
|
batch id, batch pattern id, canonical member task ids, ordered |
|
batch id, one |
Every shape-changing operation must declare the affected domain:
Domain |
Examples |
Contract |
|---|---|---|
row domain |
sample filtering, sample augmentation, repetitions, group splits |
changes identity/sample relation or view membership |
feature domain |
preprocessing, feature augmentation, feature selection, source fusion |
changes representation/schema/feature names |
target domain |
y-transform, target aggregation, multi-target mapping |
changes target space and inverse-transform requirements |
prediction domain |
model predict, OOF join, aggregation of repetitions |
changes prediction block shape and aggregation level |
DAG, Campaign Plan And Splits¶
This is the key correction.
There are two related but distinct plans:
GraphPlan¶
The compiled DAG of dependency semantics:
transforms;
y-transforms;
models;
feature joins;
prediction joins;
source joins;
fork/map/branch;
aggregators;
filters;
augmentation nodes;
generator nodes;
tuners;
explain nodes.
Graph nodes describe dependency and phase behavior. External behavior is executed by controllers.
CampaignPlan¶
The experimental execution plan around the graph:
root seed;
variants/search space;
split strategy;
repeated campaigns;
nested CV policy;
selection/ranking policy;
refit policy;
predict/explain replay policy;
scheduler/resource policy.
Generation Ownership¶
Generation has two different meanings and the ownership must stay explicit:
compile-time generation of variants/search spaces belongs to
dag-ml, because it changes campaign fingerprints, variant ids, seeds, lineage and selection;runtime generation of data, features, models or synthetic samples is an external operator capability. The payload lives in controllers or
dag-ml-data, whiledag-mlvalidates seeds, origin/sample relations, shape deltas, fold boundaries and lineage.
The current core represents compile-time generation through GenerationSpec
and VariantPlan. Choices may carry typed node-parameter overrides; the
scheduler lowers those into the effective NodePlan.params and
params_fingerprint sent to controllers for that variant. When a graph declares
search_space_fingerprint, plan compilation verifies it against the canonical
campaign GenerationSpec fingerprint before variants are enumerated. Runtime
generators are graph nodes/controllers with explicit capabilities such as
generates_data, generates_model or expands_variants; they are not allowed
to mutate identity or training boundaries without emitting relation and shape
deltas.
Splitters¶
Splitters are not ordinary data-transform operators.
A splitter is a controller capability invoked by the coordinator during campaign
planning or early execution. It produces a FoldSet. The Rust core validates the
FoldSet against identity, group, target, repetition and origin constraints.
Native identity splitters such as KFold and GroupKFold may be shipped by the Rust core for deterministic cross-language behavior. Feature-dependent splitters such as KS/SPXY/sklearn splitters are external controller calls.
Implementation consequence:
NodeKind::Splitmust not mean “data transform node”.If a split appears in frontend syntax, the compiler must lower it into
CampaignPlan.split_invocation, not a normal feature-flow node.A legacy or compatibility graph node may exist only as a control node that emits no feature data and is excluded from model/data transforms.
Identity And Leakage Units¶
The coordinator must distinguish physical rows from logical samples. This is
critical for products with repeated measurements: several X observations can
share one Y.
Identity |
Meaning |
Owned by |
|---|---|---|
|
physical row/acquisition in one source |
data provider |
|
logical sample requested by the user |
data provider, validated by core |
|
target/label unit; may cover one or more samples |
data provider, validated by core |
|
leakage unit such as plant, product, patient, plot, batch |
data provider, validated by core |
|
original sample/observation from which an augmented row was derived |
data provider/controller, validated by core |
|
repeated acquisition id inside a sample |
data provider |
Split policies must declare the leakage unit:
|
Rule |
|---|---|
|
only the same observation is atomic; unsafe for repeated X / one Y unless explicitly allowed |
|
all observations of one sample stay on the same fold side |
|
all samples sharing one target stay on the same fold side |
|
all samples/observations sharing one group stay on the same fold side |
Defaults:
repeated measurements default to
split_unit="sample";group ids, when present and requested, dominate sample ids for leakage;
augmentation origin constraints are always checked unless explicitly disabled by a traceable unsafe policy;
fold membership is stored at sample/target/group level and broadcast to observations during materialization.
The core refuses a split when:
a requested split unit is absent from the sample relation table;
one leakage unit appears in both train and validation of a fold;
an augmented row derived from a validation origin appears in train;
repeated observations of the same sample are split across train/validation under
split_unit="sample";a feature-dependent splitter returns folds not expressible in stable ids.
Heterogeneous Multi-Source Units¶
For pipelines where one physical sample carries several observations per source
with asymmetric cardinalities, unit domains are named by an EntityUnitLevel:
physical_sample (the SampleId-level leakage/target/output unit),
source_sample (one source’s observations of a physical sample), observation
(one acquisition, ObservationId) and combo (a derived observation built from
one observation per source for the same physical sample). In the mainline a
combo is a relation-backed derived observation/unit carrying
component_observation_ids and origin_sample_id; it is not a public
PredictionLevel. The public prediction levels stay
observation/sample/target/group, and cartesian/combo work reduces to
sample-level output by identity, never by row position. Promoting combo or
source_sample to a first-class public prediction level is a deferred,
explicitly-gated decision. The frozen vocabulary (EntityUnitLevel,
PredictionUnitId, ReductionPlan, RepresentationPlan, FitInfluencePolicy),
the mainline decision and the ADR-02 migration checklist are in
docs/adr/ADR-19-multisource-unit-vocabulary.md.
Repetitions, Aggregation And Refit¶
Repeated observations are first-class. The core must support models trained on individual observations while also evaluating and selecting on aggregated sample-level predictions.
Example: several spectra (ObservationId) for the same product (SampleId) with
one chemical value (TargetId).
Required prediction levels:
Level |
Meaning |
|---|---|
|
one prediction per physical acquisition |
|
aggregation of observations for the same sample |
|
aggregation of samples for the same target unit |
|
aggregation at group level, only if explicitly requested |
Required aggregation policies:
Policy field |
Purpose |
|---|---|
|
observation, sample, target, group |
|
mean, weighted_mean, median, vote, custom_controller |
|
none, quality, repetition_count, controller_emitted |
|
whether raw and aggregated metrics are both computed |
|
which level drives variant/model selection |
|
keep observation-level predictions for audit |
|
keep aggregated predictions for ranking/replay |
The Rust core implements deterministic observation-to-sample and
sample-to-target/group aggregation for mean, median, vote and
weighted_mean. Weighted means require an explicit weight policy;
controller_emitted and quality weights are read from observation prediction
blocks, while repetition_count weights sample-to-target/group aggregation by
the number of observations attached to each sample. custom_controller remains
an explicit controller responsibility.
FIT_CV requirements:
Models may fit on observation-level rows if the data plan says so.
Validation predictions are first captured at the controller-emitted level.
The Rust core aggregates predictions by identity according to policy.
Metrics are computed in parallel when requested. The core provides identity-aligned regression scoring for validated sample/target/group prediction blocks (
mse,rmse,mae,r2) and refuses positional-only or mismatched unit sets. Metric reports preserve the scored prediction origin: producer node, partition, optional fold and optional prediction id.Selection must declare which metric level is authoritative. Selection policies can require a
metric_level, and candidates whose metric metadata is missing or comes from another level are rejected before ranking.OOF joins use the declared aggregation level and must not mix raw and aggregated predictions implicitly.
Replay-facing prediction contracts carry
prediction_levelexplicitly. Sample-level replay caches carrysample_ids; target/group replay caches carryunit_idstyped asPredictionUnitId, validated in bundle records, payloads and file/in-memory stores. Aggregated caches are not preloaded into the sample OOF store.
REFIT requirements:
The final fit boundary is the selected training universe, excluding held-out test samples.
Refit may train on all repeated observations of selected samples if the
DataModelShapePlandeclares observation-level fitting.Refit prediction outputs must preserve the same aggregation policy used for selection unless the user explicitly chooses a different predict policy.
Final prediction blocks may store both raw repetition predictions and aggregated sample predictions.
A bundle must record whether the selected model was chosen by observation, sample, target or group metrics.
Prediction requirements, prediction-cache records, prediction-cache payloads and
NodeTask.prediction_inputsmust preserve the prediction level, so a target/group metric decision cannot be replayed through an implicit sample-level cache.
Data/Model Shape Plan¶
The coordinator must make shape control explicit because augmentations, selection, fusion and aggregation change what a controller sees.
Each model or transform node receives a DataModelShapePlan:
Field |
Meaning |
|---|---|
|
observation, sample, target, group |
|
observation, sample, target, group |
|
train observations/samples allowed for fit |
|
validation/test/final rows expected for predict |
|
source/branch/augmentation prefixes |
|
stable identity of column/feature layout |
|
raw, transformed, scaled, encoded |
|
how controller predictions are reduced |
|
sample/feature augmentation rules |
|
feature selection and supervised selection fit scope |
Shape-changing controllers must return a shape delta:
Delta |
Examples |
Required validation |
|---|---|---|
|
sample filter, sample augmentation, separation branch |
identity and fold boundaries remain valid |
|
feature augmentation, selection, source fusion |
feature schema fingerprint changes deterministically |
|
y scaling, target encoding |
inverse-transform and target space are recorded |
|
probability output, repetition aggregation |
prediction columns and aggregation level are recorded |
The core validates deltas before downstream tasks can consume them.
Feature deltas are checked against any declared
feature_schema_fingerprint; lineage may also echo the data/model shape and
aggregation-policy fingerprints, and when present those fingerprints must match
the compiled NodePlan.
Augmentation, Selection, Filtering And Fusion¶
These operations are leakage-sensitive because they can change rows, features or target spaces.
Sample Augmentation¶
Sample augmentation creates new observations or samples. Default policy:
FIT_CV: train partition only;
validation/test rows are never augmented for training metrics;
each augmented row carries
OriginId;an augmented row inherits target, group and fold boundary from its origin;
OOF and scoring are reported on original identities unless policy opts into augmented reporting. The default Rust contract rejects sample augmentation across all partitions, or sample augmentation without origin/target/group inheritance, unless the corresponding
AugmentationPolicy.unsafe_flagsentry is present.
Forbidden by default:
validation-origin augmentation appearing in train;
augmented rows counted as independent samples for group/sample metrics;
augmented validation/test rows entering view requests without an explicit unsafe flag;
sample augmentation after a prediction join unless explicitly declared safe.
Feature Augmentation¶
Feature augmentation changes columns/features but not identity.
Rules:
stateless feature augmentation may run on train/validation/test if declared deterministic and fitted nowhere;
stateful feature augmentation must fit on fold train and apply to validation;
supervised feature augmentation is treated like supervised feature selection and must use fold-train targets only;
feature namespaces and schema fingerprints must change deterministically;
source/branch/augmentation provenance is preserved for merge and explain.
Feature Selection¶
Feature selection is a transform that may be supervised or unsupervised.
Rules:
supervised feature selection fits only inside the current train boundary;
selected feature masks are artifacts with fold/refit lineage;
supervised feature selection must store masks so CV/refit replay can audit which features were fitted inside each train boundary;
validation/test/final data only receive
apply, neverfit;downstream feature joins must verify compatible selected schemas or use an explicit missing-feature policy;
selection masks used at REFIT are recorded separately from CV-fold masks.
Sample Filtering / Exclusion¶
Filtering changes row membership.
Rules:
filters must declare whether they affect train only, predict only, all partitions, or branch-local views;
train-only exclusion cannot silently remove validation samples from scoring;
separation branches must produce disjoint or explicitly overlapping identity sets according to branch policy;
selector-driven separation branches are declared as campaign
branch_view_plansover source ids, metadata, tags or provider-native filter expressions before host data providers materialize branch-local views;all filters emit a
row_deltawith before/after identity fingerprints.
Source Fusion And Merge¶
Feature/source fusion changes feature shape and possibly missingness.
Rules:
sample alignment remains data-layer work, but the core records and validates the chosen alignment plan fingerprint;
feature joins are namespace-stable and branch-aware;
prediction joins are OOF-checked by the core;
mixed joins must declare which inputs are raw features and which are predictions, with separate leakage checks.
Phase Model¶
The coordinator executes:
COMPILE -> PLAN -> FIT_CV -> SELECT -> REFIT -> PREDICT -> EXPLAIN
Phase |
Rust core responsibility |
Controller responsibility |
|---|---|---|
|
parse frontend IR into graph/campaign specs, freeze topology |
provide descriptors if needed |
|
resolve controllers, ports, data plans, variants, split invocations |
describe/plan only |
|
schedule fold/variant/branch tasks, enforce fold views, record predictions |
fit/transform/predict/split/augment |
|
rank variants, reject unsafe variants by policy |
optional metric helpers |
|
replay selected graph on full train boundary |
fit final artifacts |
|
replay bundle on new data, validate schema/fingerprints |
predict/transform only |
|
replay to explanation target |
controller-specific explanation |
ExecutionPlan¶
An ExecutionPlan is the Rust-owned, immutable plan after compile and plan:
graph fingerprint;
campaign fingerprint;
data-plan fingerprints;
controller manifests and versions;
variant list or lazy enumerator;
split invocation specs and resulting fold fingerprints;
leakage unit policy;
data/model shape plans per node;
aggregation policies for prediction/evaluation/refit;
topological order and deterministic parallel node levels;
phase execution schedules expanded by variant and fold;
controller multitask group templates for eligible known task groups or closed static subgraphs;
phase gates per node;
expected input/output contracts;
cache key templates;
lineage templates;
scheduler policy.
No controller may mutate the ExecutionPlan.
Runtime Execution¶
The Rust core runs task batches in deterministic dependency order.
Parallelism dimensions:
variants;
folds;
branches;
independent DAG nodes;
controller multitask groups;
controller-declared internal parallelism.
Scheduler rules:
Ready tasks may run in parallel only if dependencies and resource policy allow.
Reduction order is canonical: sort by variant id, fold id, branch path, node id.
Nested parallelism is controlled by controller capabilities and resource policy.
Python/GIL-bound controllers may be process-scheduled; native thread-safe controllers may be thread-scheduled.
Compatible ready tasks may be coalesced into a controller multitask batch only when a manifest-declared pattern admits them. Default compatibility is same phase, same fold, same data view and no hidden dependency between members.
Batches can be split by resource limits, and may fall back to scalar
TaskRequests unless the manifest explicitly marks the group as required.A task result is accepted only after runtime validation.
Runtime Validation¶
Before dispatch, the Rust core checks:
required ports exist;
phase is allowed by controller manifest;
data plan fingerprint matches;
requested data views match the active phase, fold id and partition;
view sample ids are in the active fold/partition;
validation prediction sample ids are contained in the fold-validation view;
leakage unit membership is compatible with the active fold;
task data/model shape plan matches the phase and controller manifest;
seed is derived from the canonical path;
unsafe policy is explicit if required.
For TaskBatchRequest, every member TaskRequest is checked first. The batch
itself is then checked against the selected batch_patterns entry: allowed
node kinds, coalescing axes, canonical member order, phase/fold/view/branch
compatibility, max_tasks and resource hints.
After dispatch, the Rust core checks:
output ports match declared contracts;
prediction blocks carry producer, fold, partition, sample ids and target names;
no prediction row is positional-only;
train/validation/test/final partitions are legal for the phase;
augmentation origins do not cross fold boundaries;
group/repetition/target leakage units remain on one side of a fold;
shape deltas are declared before downstream consumption;
aggregation level and prediction columns match the node policy;
artifact refs, portable backend/URI/content metadata and handle lifetimes are registered;
lineage was recorded.
For TaskBatchResponse, every member response is validated as if it had been
returned by a scalar invocation. Parent batch metrics and lineage are retained
only as extra provenance; they cannot replace member-level records.
OOF And Leakage Rules¶
OOF safety is a Rust core invariant.
Rules:
Training a downstream model on upstream predictions requires validation OOF predictions by default.
partition="train"predictions cannot feed downstream training unlessallow_train_predictions_as_features=trueand a second lineage flag recordsleakage_acknowledged=true.Every producer must provide exactly one validation prediction per requested sample unless an explicit aggregation policy says otherwise.
Producers merged by a prediction join must share compatible fold structure.
Augmented observations inherit origin leakage boundaries.
Group, target and repetition leakage units are validated from sample relation facts supplied by the data layer.
OOF joins are by sample identity, never by row position.
Repetition aggregation is explicit; raw observation predictions and aggregated sample/target predictions are never silently mixed.
Refit uses the selected aggregation and shape policies unless the predict/refit policy explicitly overrides them and records the override.
Stacking Refit Coverage Policy¶
Prediction-stacking meta-nodes default to
stacking_oof_refit_contract.policy="require_full_coverage". Under that
default, REFIT may train a meta-model only when each upstream OOF producer covers
the complete refit sample universe from validation OOF blocks. Partial OOF
coverage is rejected with the stable cause partial_oof_without_policy.
A stacking node may explicitly opt out of meta-model REFIT by carrying
metadata.stacking_oof_refit_contract.policy="cv_only", or may run the CV path
and skip REFIT only when coverage is incomplete with
"skip_refit_on_incomplete_oof". Both policies keep malformed OOF invalid:
non-validation blocks, missing fold ids, unknown folds, fold/sample mismatches
and duplicate validation samples are refused before any skip decision is
accepted.
Traceability¶
Every accepted task emits or updates a LineageRecord containing:
run id;
graph/campaign/variant fingerprints;
node id;
phase;
fold id;
branch path;
controller id/version;
params fingerprint;
data-plan fingerprint;
data/model shape fingerprint;
aggregation level and policy fingerprint;
input lineage refs;
output handle/artifact refs;
seed;
unsafe flags;
metrics;
timing and resource hints.
The lineage graph must be enough to answer:
why was this sample in this fold?
which controller produced this artifact?
which seed and params were used?
which data schema and data plan were used?
did any unsafe leakage path occur?
can this predict/explain run replay the training bundle?
Bundle and prediction-cache payload schemas are versioned artifacts. The core
publishes a SchemaMigrationPolicy for each artifact with current/min readable
and writable versions plus explicit automatic migration edges. No implicit
migration is allowed: old versions are accepted only if the policy declares a
migration edge, future versions are refused, and version 0 is always invalid.
Performance Requirements¶
The Rust core must be designed as the high-performance coordination layer:
compact immutable specs;
canonical fingerprints without reparsing large payloads;
parallel scheduler over variants/folds/branches/nodes;
deterministic reducers;
no feature-buffer copies through the core;
opaque handle arenas with explicit release;
prediction store optimized for identity joins;
controller capability-aware scheduling;
zero-copy Arrow/DLPack boundaries where identity, predictions or tensors cross.
The core must not chase performance by moving domain algorithms inside Rust. The performance target is orchestration, validation, scheduling, lineage, and OOF joins at scale.
Confrontation With Current nirs4all Pipeline¶
Current nirs4all is the prototype. It proves the product need, but not the final architecture.
nirs4all today |
DAG-ML target |
|---|---|
Python-only runtime |
Rust core with Python/R/JS/native bindings |
Conceptual DAG reconstructed from sequential steps |
Real compiled immutable DAG |
Controllers do work and also carry some orchestration side effects |
Controllers only execute declared external behavior |
Split step mutates pipeline/dataset state |
Splitter controller invocation produces |
Branching stored in mutable contexts/snapshots |
|
Merge reconstructs features/predictions in Python controllers |
Rust-owned feature/prediction join contracts and OOF validation |
Repetition averages such as |
Aggregation policy is explicit, fingerprinted and evaluated in parallel with raw predictions |
Several spectra can share one product target through dataset conventions |
|
Feature/sample augmentation and selection are controller-specific effects |
Shape deltas, fit scopes and augmentation/selection policies are validated by the core |
NIRS-shaped data assumptions leak through dataset/model logic |
Generic data contracts through |
Dynamic routing through Python registry |
Binding registries export controller manifests to Rust |
Trace, artifacts and prediction store are pipeline-specific |
Rust-owned lineage/cache/artifact/prediction contracts |
Reproducibility depends on Python conventions and local discipline |
Core-derived seeds, fingerprints and deterministic reducers |
Leakage safety exists but is embedded in controllers |
Leakage safety is a core invariant checked around every task |
The migration path is not “port nirs4all as-is”. It is:
extract the implicit DAG and campaign semantics;
encode them as
GraphPlanandCampaignPlan;move orchestration, validation and scheduling to Rust;
keep operators external through controllers;
keep data generic through data contracts;
replay nirs4all use cases as conformance tests, not as product constraints.
What Must Be Implemented Next¶
The next implementation layer must make the coordinator visible:
ControllerManifestControllerRegistryCampaignSpecGraphPlanExecutionPlanNodePlanNodeTaskNodeResultRunContextsequential scheduler
in-memory prediction store
in-memory lineage recorder
mock controller conformance tests
split invocation model that produces and validates
FoldSetLeakageUnitPolicyAggregationPolicyDataModelShapePlanShapeDeltaaugmentation, feature-selection and filtering policies
The existing OOF/fold/data-plan code remains useful, but it is not the product
shape by itself. The product shape starts when a compiled ExecutionPlan drives
controllers through tasks and validates their outputs.
Acceptance Checks For This Spec¶
An implementation is aligned only if all answers are “yes”:
Can the core compile a frozen graph and campaign plan without executing external code?
Can splitters be external controllers while the core owns the validated
FoldSet?Can model/process/augmentation/filter operators remain external payloads?
Can the scheduler parallelize variants, folds and branches without changing results?
Can the core reject OOF leakage without inspecting feature buffers?
Can a run be replayed from fingerprints, controller versions, data-plan refs, artifacts and lineage?
Can the same core coordinate Python, native C++, R, JS or bioinformatics controllers with the same safety rules?
Can repeated observations with one target be trained, scored, aggregated, selected and refit without leakage or hidden metric-level changes?
Can feature/sample augmentation, feature selection, filtering and fusion declare shape deltas that the core validates before downstream use?
If any answer is “no”, the implementation has drifted from the product goal.