Skip to content
AI.info

MLOps

Versioning the ML Asset Graph

Design stable identities and compatibility rules for code, data, features, models, schemas, policies, and evaluation evidence.

By the end you can

“Which model is in production?” is an incomplete question

A registry reports that model version 42 is live. The answer still omits the feature pipeline, category vocabulary, request schema, threshold policy, runtime image, and retrieval index used by the service.

Knight Capital Americas LLC could have answered the registry question correctly on every server it owned and still lost $460 million in about 45 minutes. A staged deployment beginning 27 July 2012 was meant to put the new RLP code on eight SMARS servers. One server did not get it. The SEC's order of 16 October 2013 records what happened: “During the deployment of the new code, however, one of Knight’s technicians did not copy the new code to one of the eight SMARS computer servers.” A flag the new release had repurposed then re-activated the discontinued “Power Peg” code on that eighth server. On 1 August 2012, 212 incoming parent orders became roughly 4 million executions in 154 stocks for more than 397 million shares in about 45 minutes. The penalty was $12,000,000.

Parties with nothing to do with the settlement read the episode the same way. Carol Clark, writing for the Federal Reserve Bank of Chicago, reported the 1 August 2012 date, the 45-minute window, a near-bankruptcy and a $440 million loss caused by “a software malfunction”. A WilmerHale client alert put the cause at “a router that contained discontinued and therefore incorrect code”.

Seven servers ran the release. One ran something else, and no name in any registry distinguished them. An ML release is a connected asset graph. Versioning succeeds when a production decision can be traced through that graph without relying on mutable names or personal memory.

Example

A compatible model with an incompatible vocabulary

The Knight order describes a missing copy of code. The same shape of failure appears in machine learning when an edge is missing from the release record rather than a file from a server. Work through a text classifier that passes its offline test and fails after deployment.

  • Model identity: The correct weights were loaded from the registry.
  • Tokenizer mismatch: Serving used a newer vocabulary that split domain abbreviations differently.
  • Schema compatibility: The request shape remained valid, so contract checks did not fail.
  • Silent semantic break: Predictions shifted while latency and availability stayed normal.
  • Missing edge: The release record linked the model to code but not to the tokenizer artifact.

Visual

Assets that participate in one release

The model artifact is one node in a larger compatibility graph.

Each class below is a class of node. A release is valid only when the edges between the classes are declared rather than assumed. The division is not a taxonomy for its own sake. Evidence, transformation, learned, execution and decision assets each fail differently, and each of them fails quietly when the release record omits its edge.

FigureHierarchy · 5 levels
  • Evidence inputs

    Dataset snapshot, labels, split definition, evaluation set, and approval record.

    • Transformation assets

      Feature code, vocabularies, preprocessing state, and schema contracts.

      • Learned assets

        Model weights, calibration object, embedding index, or adapter.

        • Execution assets

          Runtime image, hardware target, inference code, and dependency graph.

          • Decision assets

            Thresholds, routing, fallback, review policy, and effective-time configuration.

Identity and reference serve different purposes

An immutable identifier answers which exact object was used. A mutable alias answers which object a role currently points to. Both are useful. They must not be confused.

A deployment can target an alias such as `champion`; the decision record should resolve and store the exact version at the moment the deployment runs. Otherwise what the team can rebuild from history changes whenever the alias moves.

Name-and-version is itself a reference, not an identity, and there is a timestamped incident to prove it. On Tuesday 22 March 2016, after a name dispute over the kik package, Azer Koçulu unpublished 273 packages from the npm public registry. Isaac Z. Schlueter wrote on the npm blog the next day: “In this case, though, without warning to developers of dependent projects, Azer unpublished his kik package and 272 other packages.” One of those 272 was left-pad. From shortly after 2:30 p.m. Pacific, npm observed hundreds of dependency-resolution failures per minute. A community replacement published as 1.0.0 did not help. Dependency chains including babel and atom explicitly requested 0.0.3, and for two and a half hours that string resolved to nothing at all. npm ended the outage only by restoring the original left-pad 0.0.3 from backup at 4:55 p.m.: “The duration of the disruption was 2.5 hours.” Chris Williams reported the same event for The Register that day.

A build that names a remote dependency by version is reproducible only for as long as some other party keeps serving that exact object under that exact name. Content identity — a digest resolved and stored at release time — is the part of the record that no third party can withdraw.

Case

When a moved tag leaves a cluster running two versions

Container tooling draws the line between an immutable identifier and a movable tag, and it draws it in published documentation. Docker’s `docker pull` reference says that pulling by digest “‘pins’ an image to a specific version in time”, so updated versions are not pulled. The Kubernetes image documentation states the consequence directly: “An image digest uniquely identifies a specific version of the image, so Kubernetes runs the same code every time it starts a container with that image name and digest specified”. It warns that when a registry changes what a tag represents, a cluster can be left with a mix of Pods running old and new code. That is the eighth-server problem, arriving through a name rather than through a technician. Kubernetes even derives its default imagePullPolicy from the reference type: Always when the tag is :latest or absent, IfNotPresent when a digest or a non-latest tag is given.

Comparison

Versions, aliases, and stages answer different questions

A clean release process uses each concept deliberately.

The immutable column is not a local convention. Semantic Versioning 2.0.0 states it as a rule: “Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version.” That MUST NOT is normative rather than advisory, because the document adopts the RFC 2119 keywords. The same specification requires MAJOR to be incremented for backward incompatible changes to the public API.

The npm public registry enforces the same property operationally, across millions of packages. Its Unpublish Policy states: “Registry data is immutable, meaning once published, a package cannot change... So if you've ever published a package called "bob" at version 1.1.0, no other package can ever be published with that name at that version. This is true even if that package is unpublished.” Unpublishing at all is bounded by the policy's 72-hour and 300-downloads-per-week conditions.

A mutable alias makes no such promise, and is not supposed to. That is the whole distinction. One column is a rule about contents. The other is a name whose target is expected to move.

FigureComparison · 3 columns

Immutable version

Names one exact artifact or snapshot.

  • Stable for audit and rollback
  • Referenced by digest or monotonic ID
  • Never changes its contents
  • Example: model version 42 with image digest abc

Mutable alias

Points a role name at a selected immutable version.

  • Convenient for deployment routing
  • Can move after approval
  • Must be resolved in decision logs
  • Example: `champion` points to version 42

Lifecycle status

Describes whether an asset is draft, approved, deprecated, or retired.

  • Supports governance and discovery
  • Does not replace immutable identity
  • Transitions need policy
  • Example: version 39 is deprecated but retained

Analogy

Version 42 is a part number, not a bill of materials

Medical devices ship with a bill of materials because a statute requires one. Section 524B of the U.S. Federal Food, Drug, and Cosmetic Act, added on 29 December 2022, obliges the sponsor of any premarket submission for a “cyber device” to do one thing in particular: “provide to the Secretary a software bill of materials, including commercial, open-source, and off-the-shelf software components; and”. Knowing the controller's version is not enough. The law does not accept it as enough.

FDA's cybersecurity guidance for medical devices, issued 3 February 2026, says what the document has to look like. It asks for machine-readable SBOMs consistent with the NTIA minimum elements. It then asks for two more fields per component: the level of support, and the end-of-support date. Those two are the interesting part for an ML release. They are not identity claims. They are statements about how long the component can be relied on.

A part number stays where it was stamped. Software references can be mutable — the eighth SMARS server and left-pad 0.0.3 are both cases of a name that did not hold. Some decisions are produced by remote dependencies that change independently of anything written in the manifest. So a release record needs runtime resolution and effective-time entries, not a static list.

The identity of a system is the identity of its compatible parts and their relationships.

Key idea

Semantic compatibility is stronger than schema compatibility

Two services can agree that a field is a float while disagreeing about units, time windows, missing-value semantics, or population. A schema checker sees shape and type. It cannot infer the intended meaning.

Apache Avro 1.11.1 shows precisely how little the checker inspects. Under “Schema Resolution”, a writer's schema and a reader's schema are reconciled using unqualified names and types alone. Record fields are matched by name, and “if the reader’s record schema has a field that contains a default value, and writer’s schema does not have a field with the same name, then the reader should use the default value from its field.” The reader receives a value. Nothing in the record marks that the writer never sent one.

The promotion table is equally indifferent to meaning. int is promotable to long, float or double; long to float or double; float to double; and string and bytes are mutually promotable. Every one of those conversions preserves the type. None of them preserves a unit.

The registry above it checks the same narrow thing. Confluent's documentation states that “Schema Registry enforces compatibility by comparing new schema versions against previous versions using configurable compatibility types”, and that “The Confluent Schema Registry default compatibility type is BACKWARD”. Comparing a schema against its own previous versions is not comparing what the values mean. No setting in that table can inspect units, time windows, missing-value semantics or population.

Compatibility rules should therefore include semantic invariants and representative contract examples, not only serialized schemas.

A value can be structurally valid and still mean something different.

Steps

Build the release bill of materials

Draw the graph before anything is promoted.

Step 1 does not have to be invented, because a published minimum already exists. Executive Order 14028, published in the Federal Register on 17 May 2021, defines an SBOM, and the supply-chain guidance it ordered has to cover “providing a purchaser a Software Bill of Materials (SBOM) for each product directly or by publishing it on a public website”. NTIA set the floor on 12 July 2021, at exactly seven per-component data fields: Supplier Name, Component Name, Version of the Component, Other Unique Identifiers, Dependency Relationship, Author of SBOM Data and Timestamp, alongside Automation Support and Practices and Processes. The report's “Data Fields” section gives the reason: “The goal of these fields is to enable sufficient identification of these components to track them across the software supply chain and map them to other beneficial sources of data, such as vulnerability databases or license databases.”

Supplier, name and version are the identity triple. The report defines the third as an “Identifier used by the supplier to specify a change in software from a previously identified version”. A release that cannot fill those three fields for its tokenizer vocabulary, its calibration object or its runtime image has not finished step 1, whatever the registry says about the model. Dependency Relationship is the field that carries the compatibility edges of step 3. Timestamp and Author of SBOM Data are what make the resolved aliases of step 4 evidence rather than assertion.

FigureProcess · 5 steps
  1. 1. Enumerate assets

    List every code, data, learned, runtime, and policy object used.

  2. 2. Assign immutable identities

    Use digests, snapshots, or registry versions for exact objects.

  3. 3. Declare compatibility edges

    Record which schemas, features, vocabularies, and runtimes are valid together.

  4. 4. Resolve mutable references

    Capture the exact targets of aliases and feature flags at release time.

  5. 5. Persist the graph

    Store the asset graph with evaluation, approval, deployment, and rollback records.

Trace one decision, not only one deployment

The strongest versioning test begins with a real production output. Can the team identify the input envelope, feature values, transformation versions, model, policy, runtime, and fallback that produced it?

If the answer relies on what an alias points to today, the record is not historical evidence. The SEC could say which of eight servers held which code, because the deployment left a trace that survived the deployment. A build that asked for left-pad 0.0.3 could say what it wanted but not what it had. A decision record is the artifact that closes that gap for one output at one moment. It closes it only if every mutable reference in it was resolved when the decision was made.

Case

MLflow retired its stages in favour of aliases

MLflow chose aliases over fixed stages, and made the change in the open. Its documentation states: “As of MLflow 2.9.0, Model Stages have been deprecated and will be removed in a future major release”. Aliases took their place, defined in the same documentation as “a mutable, named reference to a particular version of a registered model”. Mutable is in the definition, not a caveat attached to it afterwards. Databricks records the same move for Unity Catalog: “Stages are replaced by custom aliases and tags. Instead of four fixed stages, you can create up to 10 custom and reassignable aliases.” The binding at the other end stays exact — “In Unity Catalog, an alias is assigned to a unique model version.” A reassignable name pointing at a unique version is exactly the pairing this lesson asks a decision record to unpack. Keep the name for routing. Store the version it resolved to.

Key takeaways