nw.bodies.verifying_trace#
Body schema for verifying traces — what makes early cutoff possible.
URI: annot://schema/verifying-trace/v1
A verifying trace records, for one derived annotation, the *content digest* each of its provenance parents had at the moment it was written:
output annotation X was_derived_from = [A, B]
verifying trace T for_annotation_id = X
upstream = [(A, sha256…), (B, sha256…)]
provenance.was_derived_from alone says only which annotations X came
from. That makes freshness a reachability question — “A changed, so
everything reachable from A is suspect” — which is the Make cell of the
Build Systems à la Carte taxonomy and cannot cut off early. The digests
turn it into a verifying-trace rebuilder (Ninja / Shake / Salsa): when
A’s current value digest still equals the one X recorded, X is provably
unaffected and the walk stops there. See nw.freshness for the query
side.
Why a sidecar annotation rather than a field on lacing.Provenance#
lacing.Provenance is frozen / extra="forbid" and the
envelope has no migration ladder — lacing.schema.register_migration
migrates bodies, keyed by body_schema_uri, and the persisted store
refuses to open at a different SCHEMA_VERSION. Adding a field there is a
real on-disk migration against live project data.
lacing stores body as free-form JSON validated by body_schema_uri,
and register_body_schema is public API nw already calls six times, so a
new body type costs nothing. nw.bodies.decision is the precedent —
a timeless, project-local, typed provenance record stored under a sentinel
zero-duration reference. This is the same shape with a typed payload.
(thorwhalen/reelee#253 decision D6.)
Two properties this schema deliberately has#
A trace is not a descendant of what it describes. Its
was_derived_fromis empty and the link runs through the body’sfor_annotation_idinstead. Wiring it as a provenance edge would put every trace into its target’sdescendants_ofset — i.e. bookkeeping would show up in the user-facing freshness answer.A missing trace means “stale”, never “fresh”.
build_verifying_trace()returnsNonerather than a partial record when a parent cannot be resolved, andnw.freshnesstreats an annotation with no usable trace exactly as today’s reachability walk does. That is what makes this change need no migration: every pre-existing annotation keeps its current behaviour.
Functions
|
Build the trace annotation for one derived annotation, or |
Classes
|
One |
|
Body of a verifying-trace annotation. |
- class nw.bodies.verifying_trace.UpstreamDigestV1(**data)[source]#
Bases:
BaseModelOne
(upstream annotation, its value digest)pair.
- class nw.bodies.verifying_trace.VerifyingTraceBodyV1(**data)[source]#
Bases:
BaseModelBody of a verifying-trace annotation.
digest_schemeis recorded rather than assumed: lacing documents that changingVALUE_FIELDSor the canonicalisation is a breaking cache-invalidation event and bumps the scheme string. A trace written under an older scheme is not comparable, sonw.freshnesstreats the mismatch as unverifiable (therefore stale) instead of comparing digests that mean different things.
- nw.bodies.verifying_trace.build_verifying_trace(*, for_annotation_id, parent_ids, upstream, asset_id)[source]#
Build the trace annotation for one derived annotation, or
None.- Parameters:
for_annotation_id (
UUID) – Id of the annotation being described.parent_ids (
Iterable[UUID|str]) – Itsprovenance.was_derived_from— annotation ids (UUID) and artifact asset ids (64-hexstr, nw#55). Duplicates are collapsed, order preserved.upstream (
Sequence[Annotation]) – The resolved parent annotations. **Must cover every annotation id in ``parent_ids``** — a trace that omits a parent would let that parent change unnoticed. Asset ids need no resolving: they are recorded as they are.asset_id (
str) – The project’s asset id, for the sentinel reference.
- Return type:
Optional[Annotation]- Returns:
The trace annotation, or
Nonewhen there is nothing to verify (no parents) or the trace would be incomplete (a parent could not be resolved, or its value could not be digested).Noneis the safe answer in both cases:nw.freshnessreads no trace as unverifiable, so the annotation keeps today’s conservative reachability behaviour instead of being silently declared fresh.