Attach author disclosure
You want to ship the environment and the source code that produced a result alongside the result itself, so anyone who receives your archive can see exactly what you ran.
Disclosure has no facade verb: use sample.core to attach the environment record and source bundle to the same history you recorded through Sample.
This guide assumes: you have finished the tutorial path, including Transform and plot a spectrum, because the example uses both a registered operation and your own Python function as transformations.
MTRL offers two kinds of author disclosure (voluntary statements by you, the author, about how a result came to be). An environment record is one archive-root note about the software environment, such as a lockfile digest. A source bundle is the text of one piece of source code, attached to the exact record that the code produced. Both travel inside the archive, so the disclosure cannot be separated from the data it describes.
from pathlib import Pathfrom tempfile import TemporaryDirectory
import numpy as np
from mtrl import InstrumentRun, MaterialData, Mtrl, Unitfrom mtrl.disclosure import EnvironmentRecord, IncompleteCaptureReason, SourceBundle
application = Mtrl(sample_id="polymer-film-01")run = InstrumentRun(run_name="ftir-replicate-01")axis = np.linspace(2000.0, 3000.0, 101)content = np.empty( axis.size, dtype=[("wavenumber", "<f8"), ("absorbance", "<f8")],)content["wavenumber"] = axiscontent["absorbance"] = 0.015 * (axis - 2000.0) / 1000.0content["absorbance"] += np.exp(-((axis - 2500.0) / 35.0) ** 2)checkpoint = MaterialData( content=content, type="raw", checkpoint_name="spectrum", units=Unit.from_raw_map({"wavenumber": "cm^-1", "absorbance": "a.u."}),)run.add_checkpoint(checkpoint)application.add(run)
def double_absorbance(content): return content["absorbance"] * 2.0
custom = application.transform(run.run_id, checkpoint.checkpoint_id, double_absorbance)scaled = application.transform( run.run_id, checkpoint.checkpoint_id, "scale", parameters={"factor": 2.0, "y_field": "absorbance"},)application.attach_environment_record( EnvironmentRecord( label="analysis environment", lockfile_digest="sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b", description="One lockfile digest for the analysis environment.", ))application.attach_source_bundle( run.run_id, custom.checkpoint_id, SourceBundle( source_text=( "def double_absorbance(content):\n" ' return content["absorbance"] * 2.0\n' ), entry_point="double_absorbance", environment_records={"numpy": "2.5.2"}, capture_limitations=[IncompleteCaptureReason.GLOBALS], ),)
with TemporaryDirectory() as directory: archive_path = application.save(Path(directory) / "polymer-film-01.mtrl") recipient = Mtrl.load(archive_path) print(f"environment record: {recipient.environment_record.label}") exported = recipient.export_source_bundle(custom.record_id, Path(directory) / "bundle") print(f"bundle files: {sorted(path.name for path in exported.iterdir())}") document = recipient.export_execution_plan( run.run_id, scaled.checkpoint_id, Path(directory) / "execution-plan.json", ) print(f"last step: {document.steps[-1].qualified_identifier}")environment record: analysis environmentbundle files: ['bundle.json', 'recorded-facts.json', 'source.py']last step: mtrl.arithmetic.scaleThe first printed line is environment record: analysis environment, read back by the
recipient from the loaded archive. The second is a sorted list of the three file names that
the bundle export wrote. The third is last step: mtrl.arithmetic.scale, the qualified
identifier of the final step in the replay plan of the registered transformation.
What just happened?
Section titled “What just happened?”attach_environment_record stored one archive-root environment record. There is at most
one per archive, and Mtrl.environment_record reads it back as an inert value.
attach_source_bundle stored your function’s source text against the exact record that
the custom transformation produced. The binding is to that precise record, so if you later
correct the data, the bundle stays on the historical version it describes. Note the
division of labour in the example: the bundle attaches to the custom output
(custom.checkpoint_id), because only a record produced by your own callable can carry a
source bundle. A record produced by a registered operation such as scale refuses one,
since its code already lives in the installed registry.
Here is the crucial safety idea: disclosure is inert. MTRL stores the author’s text and
never imports, compiles, or executes it, and never resolves it through the operation
registry. export_source_bundle writes the bundle, its document, and the owning record’s
recorded provenance facts as three plain files for a human to read. Likewise,
export_execution_plan writes the replay plan of a registered chain as a standalone
document. That document holds recorded facts only and grants no execution approval.
Caution — Source text and configuration can contain secrets, such as tokens or internal hostnames. MTRL adds no scanner and certifies nothing as safe. Attach only content that you have reviewed.
Tip — The
lockfile_digestyou supply is claim text: MTRL stores it but never verifies it. The digest that proves an attachment is unchanged is the storage digest MTRL computes itself, which archive verification checks. Treat your digest as a label for humans, and MTRL’s digest as the fact for machines.
Where next
Section titled “Where next”- Lineage and the recorded recipe — how lineage differs from disclosure: lineage is what MTRL records, disclosure is what you declare.
- Reference — the exact signatures of the disclosure routes and their
types in
mtrl.disclosure.