bayesmithDocumentation0.10.0 source

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

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: str
One 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 = None
The 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) -> Fingerprint
The 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], ...]=()) -> ModelRef
A :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) -> InvalidationPolicy
The §0.3 matrix as shipped.

InvalidationPolicy.affected

affected(self, artifact_type: ArtifactKind, changed: Iterable[FingerprintKind]) -> bool
Does 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.

Type a concept or API name.