bayesmith.artifacts.identity
Source-derived signatures and contracts. See the reference index for coverage and conventions.
Module contract
Model references, fingerprints, and what a changed input invalidates. Identity here is deliberately of two unrelated kinds, and keeping them apart is most of the module: **A fingerprint is semantic.** It is a SHA-256 over the canonical bytes of a payload (:mod:``bayesmith.artifacts._codec``), so two runs that saw the same model, the same data and the same task carry the same digest on different machines, in different processes, in either order of a mapping's keys. Fingerprints are what caching, reuse and invalidation are decided by. **An artifact id is not.** ``artifact_id`` and ``run_id`` are UUID4s minted once (:mod:``bayesmith.artifacts.base``) -- they say *which* artifact, never *what it was made of*. Two artifacts with one fingerprint bundle are interchangeable in content and still separate artifacts with separate lineage. Plan §0.3 fixes seven fingerprint slots, and the boundary of each is a ruling rather than a detail. The two that pay for the rest: the **data** slot holds the bytes of every Const, observation and mask, so a single flipped bit is a different data set; the **task** slot holds statistical semantics and budget and holds no progress bar, no print width and not the task's own id, so renaming a label invalidates nothing. What a slot contains is the caller's payload -- :func:``fingerprint`` hashes what it is handed -- and the slot table in §0.3 is the contract those callers are written against. ``ModelRef`` exists because a model is a callable and a callable has no canonical form. Its ``repr`` carries a memory address, which would make one model two in a single process and a third after a restart, so :func:``model_ref_from_callable`` digests ``inspect.getsource`` or refuses -- plan §0.3, and §0 ruling 4: an artifact is data, not a runtime object dump. The invalidation matrix (§0.3) is a table, not an ``if`` chain: :class:``InvalidationPolicy`` holds one row per :class:``ArtifactKind`` naming the fingerprint kinds that artifact kind is sensitive to. A compilation change spares the Plan; an evaluation threshold spares the Result and re-runs only the report; a display option is in no slot at all, so it changes nothing and reuses everything. Refusals follow the same contract as :mod:``bayesmith.artifacts.base``: a ``TypeError`` is "that is the wrong kind of thing" (a bare string where an enum member belongs), a ``ValueError`` is "right kind of thing, malformed" (a digest that is not lowercase SHA-256 hex). ``ArtifactCodecError`` -- a ``ValueError`` -- is what an uncanonical payload raises, and it arrives from the codec unwrapped so that the offending type is still named. Layering: ``_codec`` only. Nothing here may import ``base``, the Graph, or JAX -- ``_codec ← identity ← base`` is the ladder, and the envelope in ``base`` names these types rather than the other way round.
FINGERPRINT_ALGORITHM FingerprintKind ArtifactKind Fingerprint FingerprintBundle ModelRef fingerprint model_ref_from_callable changed_fingerprints InvalidationPolicy
FINGERPRINT_ALGORITHM
Defined in bayesmith.artifacts.identity, line 79.
FINGERPRINT_ALGORITHM = 'sha256-v1'FingerprintKind
Defined in bayesmith.artifacts.identity, line 86.
class FingerprintKind(StrEnum)The seven slots of plan §0.3, one per independently changeable input. Named after the slots of :class:``FingerprintBundle`` and checked against them, so a slot cannot come to hold a digest of some other kind -- a ``data`` slot holding a task digest would answer "the data changed" for a renamed solver, and answer it consistently enough to be believed.
MODEL_SOURCE = 'model_source'
GRAPH_STRUCTURE = 'graph_structure'
DATA = 'data'
TASK = 'task'
COMPILATION = 'compilation'
EVALUATION = 'evaluation'
ENVIRONMENT = 'environment'ArtifactKind
Defined in bayesmith.artifacts.identity, line 105.
class ArtifactKind(StrEnum)The rows of the §0.3 invalidation matrix: what invalidation reasons about. Four members, because the matrix has four columns and each is a distinct sensitivity: a Plan is made before compilation, a Result is made by it, an EvaluationReport is a judgement about a Result, and an Estimator is the fitted weights a heuristic posterior was trained on (§0.7). This is the INVALIDATION taxonomy and not a catalogue of artifacts -- which of the five Results a reference points at is ``ResultKind``'s business (Task 3), and asking this enum to carry that too would put five identical rows in the matrix.
PLAN = 'plan'
RESULT = 'result'
EVALUATION_REPORT = 'evaluation_report'
ESTIMATOR = 'estimator'Fingerprint
Defined in bayesmith.artifacts.identity, line 126.
class Fingerprint
kind: FingerprintKind
algorithm: str
digest: strOne slot's digest, with the kind it belongs to and the algorithm used.
FingerprintBundle
Defined in bayesmith.artifacts.identity, line 175.
class FingerprintBundle
model_source: Fingerprint
graph_structure: Fingerprint
data: Fingerprint
task: Fingerprint
compilation: Fingerprint | None = None
evaluation: Fingerprint | None = None
environment: Fingerprint | None = NoneThe seven slots of §0.3, in the order the plan lists them. The first four always exist: every artifact was made from a model, a graph, data and a task. The last three are ``None`` when they do not apply -- a Plan has no compilation digest before it is compiled, and nothing but a report has an evaluation one. ``None`` and a digest are different values, so a slot appearing or vanishing is itself a change.
FingerprintBundle.slots
slots(self) -> tuple[tuple[FingerprintKind, Fingerprint | None], ...]The bundle as (kind, fingerprint-or-None) pairs, in slot order.
ModelRef
Defined in bayesmith.artifacts.identity, line 220.
class ModelRef
identifier: str
source_digest: str | None = None
package: str | None = None
package_version: str | None = None
build_arguments: tuple[tuple[str, object], ...] = ()A stable reference to the model a graph was built from. Never the callable itself: §0 ruling 4. ``identifier`` is what a human calls the model, ``source_digest`` pins its text, and ``package``/``package_version`` pin a released model whose source is not at hand. At least one of those two pins is required, because a reference with only a name is a reference to whatever that name means today. ``build_arguments`` carries the state that would otherwise hide in a closure -- §0.3 does not promise to discover it, so a builder's arguments are part of the reference or they are lost. They are canonical values (arrays included, since a knot vector is an argument), key-unique, and sorted, so two callers who passed the same arguments in different order hold the same reference.
fingerprint
Defined in bayesmith.artifacts.identity, line 157.
fingerprint(kind: FingerprintKind, payload: object) -> FingerprintThe fingerprint of ``payload`` in slot ``kind``. What goes into ``payload`` is the §0.3 boundary for that slot, and it is the caller's to decide: this function hashes what it is handed. A payload the codec cannot encode -- a callable, an object array, an unregistered class -- raises :class:``ArtifactCodecError`` here rather than being rendered best-effort, because a best-effort digest of a callable is its address.
model_ref_from_callable
Defined in bayesmith.artifacts.identity, line 303.
model_ref_from_callable(fn: Callable[..., object], *, identifier: str, source_digest: str | None=None, package: str | None=None, package_version: str | None=None, build_arguments: tuple[tuple[str, object], ...]=()) -> ModelRefA :class:``ModelRef`` for ``fn``, digesting its source or refusing. §0.3: an automatic source digest exists only when ``inspect.getsource`` returns stable text. When it does not -- a function built by ``exec``, a builtin, a callable defined in a REPL -- the caller must supply ``source_digest``. There is no fallback, and the missing fallback is the feature: ``repr(fn)`` carries a memory address, so a digest that degraded to it would report a changed model on every restart and an unchanged one for a genuine edit that happened to reuse an address. ``package`` defaults to the top-level package of ``fn.__module__``, which is read off the function object rather than imported.
changed_fingerprints
Defined in bayesmith.artifacts.identity, line 355.
changed_fingerprints(before: FingerprintBundle, after: FingerprintBundle) -> frozenset[FingerprintKind]The slots whose fingerprint differs between two bundles. Slot by slot, including the optional ones: a compilation digest that appears where there was ``None`` is a change, and so is one that vanishes. The result is a frozenset because invalidation is a question about a SET of changed inputs -- the order they were discovered in is not part of it.
InvalidationPolicy
Defined in bayesmith.artifacts.identity, line 376.
class InvalidationPolicy
sensitivities: tuple[tuple[ArtifactKind, tuple[FingerprintKind, ...]], ...]The §0.3 matrix: which fingerprint kinds each artifact kind depends on. A table, and a table for a reason. The rule set is small enough to write as branches and exactly the kind of thing that grows an unwritten case when it is: "compilation invalidates a Result but not a Plan" and "an evaluation threshold invalidates the report but not the Result" are not related statements, and nothing but a table keeps them from being merged by somebody simplifying. Not registered with the codec: a policy is code that reads artifacts, not an artifact. What an invalidated artifact records is which inputs changed (``LifecycleRecord.changed_inputs``), which stays true whoever judged it.
InvalidationPolicy.default
default(cls) -> InvalidationPolicyThe §0.3 matrix as shipped.
InvalidationPolicy.affected
affected(self, artifact_type: ArtifactKind, changed: Iterable[FingerprintKind]) -> boolDoes an artifact of this kind stop being current, given ``changed``? ``changed`` is any iterable of :class:``FingerprintKind`` -- normally the frozenset :func:``changed_fingerprints`` returned. An empty one answers ``False`` for every artifact kind, which is the matrix's display-option row: a display option is in no slot, so nothing moved and everything is reusable.