Skip to content

The commit boundary

Imagine a photocopier standing next to a sealed archive box. When you hand in a document, the machine copies every page and seals the copy in the box. Your original stays on your desk, and whatever you do to it afterwards — crossing out a line, swapping a page — cannot touch what is inside. Nobody can sneak a page out of the box and rewrite it either. The only way to change the archived story is to file a new page, and the box keeps both.

That is what application.add(run) does. The moment you call it, your run crosses the commit boundary: MTRL detaches a copy of everything the run carries and seals it into the record. This page explains what happens at that moment.

Your checkpoint content is a NumPy array sitting in your own variables. At the boundary, MTRL copies that array and keeps the copy. The committed array shares no memory with your buffer, so later edits to your variables cannot alter the record. If you normalise your spectrum, shift the axis, or reuse the array for ftir-replicate-02, the committed spectrum checkpoint of ftir-replicate-01 stays exactly as measured.

What you read back is a read-only view of that sealed copy. You can inspect every value, but you cannot write through the view. Even asking NumPy to flip the writeable flag fails, because the view rests on a read-only owning copy. As part of the copy, MTRL also tidies the array into a standard memory layout, so the record never depends on how your buffer happened to be arranged.

Arrays are not the only things sealed. Metadata, units, and transformation parameters are frozen at the same boundary, and the freezing is recursive. A list nested inside a dictionary inside your metadata is exactly as frozen as the top level. Nested mappings and sequences become read-only structures, and the units mapping on a committed checkpoint is read-only as well.

Even MTRL respects the seal. When a transformation runs, it receives its own writable copy of the committed content, so no operation can scribble on the sealed storage underneath.

A committed run refuses direct mutation through its public methods. Calling add_checkpoint, replace_active_checkpoint, or remove_checkpoint on a committed run raises CommittedRecordMutationError. The run is marked as committed at the boundary, and it enforces the rule itself.

This is not stubbornness. It is how the promise “every change is recorded” is kept. All change goes through the application — adding a checkpoint, transforming, correcting — and each successful change creates exactly one revision. The application mutates the runs it owns through internal helpers, past the same transaction boundary that guards everything else. You use the public door, and the public door is watched.

The boundary is also a customs check. Some values are rejected at commit time, not at save time, so you find out immediately rather than when you archive. The refused list includes object-dtype arrays, unsafe structured dtypes, and dtypes outside the closed set of plain numeric widths. Callables, custom objects, and other values that cannot be frozen into plain data are refused too. Metadata itself must be plain data: text, booleans, numbers, lists, and dictionaries with string keys.

Each refusal raises UnsupportedCommittedValueError. Because validation happens before anything is sealed, a rejected commit leaves your record, and its revision history, untouched.

Every later promise MTRL makes starts here. Lineage can point at an exact version and trust that it will never change underneath. Verification can check an archive and know the bytes it examines are the bytes you committed. Receipts can state what was recorded and be believed. None of that is possible if committed data can quietly drift, so the boundary makes drift impossible first. Trust starts here.

The boundary seals one version of a result; two kinds of identity explains how MTRL names the logical result and each sealed version of it. Or revisit your first record to watch the boundary in action.