Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ repos:
- id: ruff-format

- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.409
rev: v1.1.414
hooks:
- id: pyright
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ before publication. XGBoost supports exact XGBClassifier/XGBRegressor tree
wrappers with closed probability/regression objectives and one target. Original
class labels supplied through output-major `class_labels=` are bound to a native
copy through an mlbundle-owned booster attribute and verified on load. Omitted
labels use encoded indices; sklearn/CatBoost reject `class_labels=` because they own classes_. Local `training_data=` inference uses mlschema with `__`, excludes a named target,
and records training dtypes. Only explicit `sample_rows=N` includes real rows.
Schema/report and sample validation is shared by export, inspect and load.
labels use encoded indices; sklearn/CatBoost reject `class_labels=` because they own classes_. Every export requires the schema extra: mlschema infers from `training_data=` or one float-1.0 row, recording schema v2 `source` and configurable `onehot_separator` (default `__`). Real frames exclude every declared target and record training dtypes; default dtypes stay null. Only explicit `sample_rows=N` includes real rows.
Model-origin mappings use names; other origins use positions and generated names start at `feature_0`. `target_names` declares one unique bounded name per output; real frames exclude all target columns. Reports map by output position with unique string labels, while manifest labels retain their types.
Schema/report and sample validation is shared by export, inspect and load without schema libraries; missing schema and old schema versions require re-export.
ONNX preserves path/bytes unchanged (ModelProto is serialized once), validates
bytes once per export/load with bounded scalar scans, and rejects external tensors,
custom domains, functions and training/runtime configuration before a CPU session. It verifies graph signatures, metadata and environment
Expand All @@ -220,14 +220,14 @@ labels use count-checked declarations. Structure is null. See the normative
ONNX section linked from `docs/format.md` for exact layouts and resource bounds.
CatBoost supports exact numeric single-target symmetric classifiers/regressors, with native state commitments and in-memory loading. Exact outer sklearn Pipelines ending in XGBoost/CatBoost use separately checked skops/native components; other native placements remain rejected.
The closed member list is: required `manifest.json`, one
native member, or declared `model.skops` + `model.ubj`/`model.cbm` pair, optional `schema.json`, and
native member, or declared `model.skops` + `model.ubj`/`model.cbm` pair, required `schema.json`, and
`structure.json` when the supported model has extractable structure. Optional
`samples.json` requires explicit user intent. The ceiling is six members; no others are allowed.

The manifest must record format name/version, bundle id, UTC creation time and
writer version; model library, estimator class, classifier/regressor kind,
hyperparameters and feature/class/output counts; feature names, order, dtypes
and origin; original class labels and target name; training Python, OS,
and origin; original class labels and output-major target names; training Python, OS,
architecture and package versions; member SHA-256 hashes and sizes; and
free-form user metadata. Sample rows are allowed only on explicit request.
All of this is untrusted input, and model-derived fields must be verified.
Expand Down Expand Up @@ -263,14 +263,14 @@ its kind/count inference remains ONNX-owned. Keep native behavior in adapters; f
- `src/mlbundle/canonical.py` and `_canonical_work.py` — deterministic JSON and bounded decoding/expansion.
- `src/mlbundle/model_io.py`, `_native_io.py`, `_export_manifest.py` — public I/O, native round trips and manifest construction.
- `src/mlbundle/_composite/`, `_pipeline_contract.py` — strict recipe, separate I/O, closed reconstruction/claims/prediction and input/inner boundaries. Read `docs/format-pipeline.md` when changing composites.
- `src/mlbundle/training.py` — lazy local DataFrame alignment and schema inference.
- `src/mlbundle/training.py` — lazy local/default inference and feature origin/dtype resolution.
- `src/mlbundle/schema.py` and `_schema_fields.py` — closed schema/report declarations and reader cross-checks without mlschema.
- `src/mlbundle/samples.py` — explicit feature-row selection and bounded cell conversion.
- `src/mlbundle/_sklearn/` — owned trust/skops/state/parameters/structure, transformer route and closed fitted-width/name proofs. Composite commitments/native structure bind the rebuilt Pipeline; all-sklearn structure stays null.
- `src/mlbundle/_xgboost/` — closed wrappers/objectives, native UBJSON preflight/loading, label binding, recoverable config, complete tree structure and validated prediction.
- `src/mlbundle/_catboost/` — closed numeric symmetric wrappers/losses, CBM preflight/in-memory loading, complete native state/structure and validated prediction.
- `src/mlbundle/_onnx/` — bounded protobuf/graph validation, unchanged bytes, native signatures/labels, CPU sessions and tabular prediction.
- `src/mlbundle/declarations.py` — export-time class labels and resolution for native models without labels.
- `src/mlbundle/declarations.py` — class-label resolution and shared output-major target-name validation.
- `src/mlbundle/claims.py` — shared extracted claims and exact claim/structure comparison.
- `src/mlbundle/manifest.py` — manifest sections, cross-field validation and JSON boundaries.
- `src/mlbundle/integrity.py` — member SHA-256/size records and verification.
Expand Down
39 changes: 21 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,17 @@ mlbundle replaces three gaps in separate model/DataFrame uploads:
- **Training data leaving the machine:** schema inference runs locally;
training rows are included only when explicitly requested as samples.

**Pre-alpha v0.1.0a1, published to reserve the PyPI name.** Format version 1 is
**Pre-alpha v0.1.0a2, not yet published.** Version 0.1.0a1 reserved the PyPI name. Format version 1 is
not frozen; bundles may need re-export. The security design has not had an
independent review: do not accept bundles from untrusted parties.
Requires **Python >=3.14**; tested on Windows/Linux. Schema requires Python 3.14.

## Install and try scikit-learn

Install the pre-release with
`python -m pip install --pre "mlbundle[sklearn]"`.
Replace `[sklearn]` with `[xgboost]`, `[catboost]`, `[onnx]`, `[schema]` or `[all]` for the
corresponding dependencies. Combine extras as `[sklearn,schema]` for local
DataFrame inference. Omit the brackets for a base install that can inspect
every bundle without model libraries. Load/verify needs the matching model extra.
`python -m pip install "mlbundle[sklearn,schema]==0.1.0a2"` once published.
Replace `sklearn` with `xgboost`, `catboost` or `onnx`, or use `[all]` for all dependencies.
Export needs the `schema` extra; reading does not. Omit the brackets for metadata-only inspection; load/verify needs the matching model extra.

```python
from sklearn.tree import DecisionTreeClassifier
Expand All @@ -47,8 +45,9 @@ regressors use `bundle.predict`. Results are plain lists: output-major classifie
labels/probabilities, row-major regression values. `native_model` supports native
inference/explanations; mutating it invalidates the metadata verification.

Native names win; otherwise origin is `declared`, `dataframe`, or `generated` (`feature_1` through `feature_N`).
`target_name=` and `metadata=` remain author declarations.
Native names win; otherwise origin is `declared`, `dataframe`, or `generated` (`feature_0` through `feature_{N-1}`).
For models without native names, pass a 2-D NumPy array or nested numeric rows in manifest feature order; declared names do not add name-based prediction.
`target_names=` and `metadata=` remain author declarations.

## XGBoost

Expand Down Expand Up @@ -163,8 +162,8 @@ no visualization structure. See [the exact ONNX contract](https://github.com/Ull
## Local schema and explicit samples

Install a model extra plus `schema` on Python 3.14. Supply a nonempty pandas
DataFrame with string feature names in model input order. A named target must
exist and is excluded; without `target_name`, the frame contains features only.
DataFrame with string feature names in model input order. `target_names` has one
unique name per output; every target column must exist and is excluded. Without it the frame contains features only.

```python
import pandas as pd
Expand All @@ -177,7 +176,7 @@ training = pd.DataFrame(
)
features = training.drop(columns="target")
model = DecisionTreeClassifier(random_state=7).fit(features, training["target"])
export(model, "schema.mlbundle", training_data=training, target_name="target")
export(model, "schema.mlbundle", training_data=training, target_names=["target"])
view = inspect("schema.mlbundle")
assert view.manifest.features.dtypes == ["float64", "float64"]
assert view.schema["reports"][0]["labels"] == ["cold", "warm"]
Expand All @@ -188,12 +187,16 @@ export(model, "sampled.mlbundle", training_data=features, sample_rows=2)
assert inspect("sampled.mlbundle").samples == features.head(2).to_dict("records")
```

`schema.json` contains a schema version, locally inferred fields and one report
per target output. `color__red`/`color__blue` binary columns can become one
category field using mlschema's `__` convention. Frame names must match native
names in order; `feature_names=` cannot rename/reorder a supplied frame.
`schema.json` is required and uses schema version `2`, with `source` equal to
`training_data` or `default`. Without a frame, mlschema infers fields from one
synthetic row of float `1.0` values; training dtypes remain null. `onehot_separator=`
defaults to `__` and is stored in the schema (1–16 characters). `color__red`/`color__blue`
binary columns can become one category field. Native-name mappings are strings; all other origins map by zero-based feature position. Reports map by output position,
including `0` for one output, with string display labels; manifest class labels retain
their original types. Frame names must match native names in order; `feature_names=` cannot rename/reorder a supplied frame.
Training dtypes are declarations, separate from verified model dtypes. Inspect/load
validate schema mappings, reports and sample keys without pandas or mlschema.
Bundles written by 0.1.0a1 without schema, or with schema version `1`, must be re-exported.

**Data leaving the machine:** names/dtypes, field kinds/nullability, dtype-based
steps, categories (including unused), one-hot suffixes, pair-axis metadata and
Expand Down Expand Up @@ -261,12 +264,12 @@ bounds do not cap process memory, prediction rows or CPU.

## Bundle contents and security

Every bundle contains `manifest.json` and one native member, or the declared
`model.skops` + `model.ubj`/`model.cbm` composite pair. Single-step composites use only the leaf. Optional members are `schema.json`, `structure.json`
Every bundle contains `manifest.json`, `schema.json` and one native member, or the declared
`model.skops` + `model.ubj`/`model.cbm` composite pair. Single-step composites use only the leaf. Optional members are `structure.json`
(required when the adapter extracts structure) and explicitly requested
`samples.json`. No training DataFrame or arbitrary files are included.
The manifest records identity/time/writer, model parameters/counts/state,
ordered features/dtypes/origin, target labels/name, environment, member
ordered features/dtypes/origin, target labels/names, environment, member
SHA-256/sizes, references and free-form user metadata. The normative
[format specification](https://github.com/UlloaSP/mlbundle/blob/main/docs/format.md) owns these records and verification rules.

Expand Down
12 changes: 7 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Security policy

The published v0.1.0a1 pre-alpha has not had an independent security review;
The v0.1.0a2 development pre-alpha has not had an independent security review;
do not accept bundles from untrusted parties. Format version 1 is not frozen.
It implements strict manifest validation, secure container reading/writing, metadata-only
`inspect`, export/load, sklearn, XGBoost, CatBoost and ONNX adapters, local schema inference,
Expand Down Expand Up @@ -256,8 +256,10 @@ Omitting samples does not prevent model memorization or model-side disclosure.

Only explicit `sample_rows=N` includes real feature rows: exactly the first N,
without target or index. The schema/sample members have 16 MiB/depth-32 limits.
Inspect and load validate closed shapes, feature mappings/row keys and report
kind/labels without mlschema. Load also verifies native model claims; label-free
Schema v2 is required, inferred locally from training data or a synthetic default;
source/separator remain declarations. Older bundles require re-export. Inspect/load
validate mappings by feature origin and string reports by output position without
mlschema/pandas; native label verification retains original types. Load also verifies native model claims; label-free
ONNX original labels remain declarations. Consumers must treat schema and sample
values as untrusted display/input data, never executable instructions.
See [the exact data-derived contract](docs/format-schema.md) and review what is shared.
Expand All @@ -276,5 +278,5 @@ behavior, and the impact. Share malicious bundles or sensitive details through
that private report rather than a public issue or pull request. Remove personal
or confidential training data from any reproducer you submit.

There are no published versions yet. Report findings against the repository's
current code and identify the commit you tested.
Version 0.1.0a1 is published; 0.1.0a2 is not yet published. Report the affected
version and commit you tested.
8 changes: 4 additions & 4 deletions docs/format-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ optional dependency reports `pip install "mlbundle[EXTRA]"`. An unrecognized
object raises `UnsupportedModelError`.

`export(model, destination, *, training_data=None, feature_names=None, class_labels=None,
target_name=None, sample_rows=None, metadata=None, overwrite=False, limits=None)`
target_names=None, sample_rows=None, onehot_separator="__", metadata=None, overwrite=False, limits=None)`
returns a `MetadataView` of the written
bundle. It serializes, extracts, reloads and compares both claims and structure
before writing. Model-exposed names take precedence over supplied names. It
captures distribution metadata, including `claims.onnx` in the environment for
the ONNX adapter. [Local schema export](format-schema.md) owns frame alignment,
the ONNX adapter. [Required schema export](format-schema.md) owns local/default inference, frame alignment,
training dtypes, schema/report declarations and explicit sample selection.
`load(source, *, limits=None)` validates the container, resolves its closed enum,
deserializes, recomputes claims/structure and compares canonical bytes before
Expand Down Expand Up @@ -210,9 +210,9 @@ bounded `MLBundleError` subclasses without cell contents. Shape errors additiona
retain `ValueError` compatibility through `PredictionInputError`.

`MetadataView` carries `manifest`, a tuple of `MemberName` values including
the manifest, and optional `schema` and `samples` JSON values; it has no model.
the manifest, required `schema` and optional `samples` JSON values; it has no model.
`LoadedBundle` additionally carries the exact
native object, a uniform predictor, optional schema/structure/sample values, and
native object, a uniform predictor, required schema and optional structure/samples, and
properties for kind, library, class name, ordered feature names and origin, and
output-major original class labels. Calling the prediction method for the wrong
kind raises a bounded `ValueError`. Result records and typed models prevent field
Expand Down
6 changes: 3 additions & 3 deletions docs/format-container.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ deserializes a native model or resolves an adapter.

Writing accepts a `Manifest` or JSON-ready manifest parts, plus non-manifest
member bytes. The writer computes and replaces all integrity records; callers
must already set the model pairing and optional references. JSON-ready parts
must already set the model pairing, required schema reference and optional references. JSON-ready parts
may omit integrity. Optional JSON members pass the bounded canonical decoder;
their original bytes are preserved. The container validates JSON syntax and
bounds, while later model/schema APIs own semantic structure and schema checks.
Metadata inspection does not infer whether a model requires structure.

Internally, optional JSON members retain their validated values within one
Internally, schema and optional JSON members retain their validated values within one
operation. Inspect/load reuse that decode for schema/sample checks, and load
compares freshly encoded native extraction with stored structure bytes. Equality
proves canonical identity; other wire spellings encode the retained value once.
Expand Down Expand Up @@ -148,6 +148,6 @@ Container/layout/CRC inconsistencies raise `InvalidContainerError`; resource
ceilings raise `LimitExceededError`. Invalid manifest JSON or typed fields raise
`InvalidManifestError`; unknown version strings under the mlbundle identity
raise `UnsupportedFormatVersionError` before validating version-specific fields.
Invalid optional JSON raises `InvalidJSONError`. Member hash or integrity-size
Invalid member JSON raises `InvalidJSONError`. Member hash or integrity-size
disagreement raises `IntegrityMismatchError` naming the allowed member.
Messages do not include untrusted names, JSON blobs or ZIP exception text.
2 changes: 1 addition & 1 deletion docs/format-onnx.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ hyperparameter ceiling applies; no training parameters are inferred from it.
by original domain), IR version and producer name/version. These fields, graph
signatures, counts, kind, names/dtypes and native labels are all recomputed with
the writer's extraction code and compared by exact canonical bytes. Historical
environment, training dtypes, supplied matrix feature names, target name and
environment, training dtypes, supplied matrix feature names, target names and
label-free mappings remain declarations.

Producer name/version each allow 0–65,536 characters, including multiline build
Expand Down
6 changes: 3 additions & 3 deletions docs/format-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ A single-step composite has only its native member and null `transformers`.
Passthrough/None preceding steps still require the prefix member to preserve
the recipe. A pair without its declaration, a missing declared member, ONNX
pairs, two boosters or three model members fail. Manifest integrity covers
exactly the declared model members and referenced optional members.
exactly the declared model members, required schema and referenced optional members.

Composite `structure.json` is required. Schema and explicit samples are optional;
six members permit both with the two model members and manifest. Every other
Composite `structure.json` and schema are required; explicit samples are optional.
Six members permit all with the two model members and manifest. Every other
archive ceiling is unchanged. Callers may lower member count and other limits.

Export gates scope/options before serialization. The leaf adapter receives
Expand Down
Loading
Loading