generated from coulomb/repo-seed
feat(provenance): layered ProvenanceEnvelope + effective() leaf (WP-0007 T1)
Dependency-free leaf rail: ProvenanceEnvelope + SpanProvenanceDelta + effective() (page envelope ⊕ span delta; zero-cost inheritance when uniform). Liveness/ Staleness/OverlayState enums. 6 tests green. (blueprint §7.3, §11) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
116
src/shard_wiki/provenance/__init__.py
Normal file
116
src/shard_wiki/provenance/__init__.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Provenance — the cross-cutting leaf rail (CoreArchitectureBlueprint §7.3, §11).
|
||||
|
||||
Every artifact in the union carries a :class:`ProvenanceEnvelope`. To keep per-span cost near
|
||||
zero when provenance is uniform, envelopes are **layered**: a page-level envelope plus optional
|
||||
per-span deltas. The *effective* provenance of a span is ``page_envelope ⊕ span_delta`` — a span
|
||||
with no delta inherits the page envelope at zero cost ("effective-vs-own", the same pattern the
|
||||
page model uses for computed metadata).
|
||||
|
||||
This module is a **dependency-free leaf**: it imports nothing else in the package and contains
|
||||
only stable data types plus the pure ``effective`` composition. Mechanism never lives here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
__all__ = [
|
||||
"Liveness",
|
||||
"Staleness",
|
||||
"OverlayState",
|
||||
"ProvenanceEnvelope",
|
||||
"SpanProvenanceDelta",
|
||||
"effective",
|
||||
]
|
||||
|
||||
|
||||
class Liveness(Enum):
|
||||
"""Where a view sits on the live↔snapshot axis (blueprint §8, T18)."""
|
||||
|
||||
STATIC = "static"
|
||||
CAPTURED_SNAPSHOT = "captured-snapshot"
|
||||
LIVE_OVER_FILES = "live-over-files"
|
||||
VIEW_TIME = "view-time"
|
||||
IRREDUCIBLY_LIVE = "irreducibly-live"
|
||||
|
||||
|
||||
class Staleness(Enum):
|
||||
"""Freshness state of cached/projected content (blueprint §8.8). ``UNAVAILABLE`` is the
|
||||
dead-shard state (FederationRequirements ADR-04)."""
|
||||
|
||||
LIVE = "live"
|
||||
FRESH = "fresh"
|
||||
STALE = "stale"
|
||||
UNAVAILABLE = "unavailable"
|
||||
|
||||
|
||||
class OverlayState(Enum):
|
||||
"""Overlay lifecycle state of an artifact (FederationRequirements ADR-05)."""
|
||||
|
||||
NONE = "none"
|
||||
DRAFT = "draft"
|
||||
PATCH_PENDING = "patch-pending"
|
||||
APPLIED = "applied"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ProvenanceEnvelope:
|
||||
"""The metadata every page/span carries — union without erasure (INTENT I-4).
|
||||
|
||||
This is the *page-level* (or fully-resolved *effective*) form. Per-span overrides are
|
||||
expressed as a :class:`SpanProvenanceDelta` and composed via :func:`effective`.
|
||||
"""
|
||||
|
||||
source_shard: str
|
||||
liveness: Liveness = Liveness.STATIC
|
||||
staleness: Staleness = Staleness.FRESH
|
||||
overlay_state: OverlayState = OverlayState.NONE
|
||||
source_rev: str | None = None
|
||||
observed_at: datetime | None = None
|
||||
authz_context: str | None = None
|
||||
divergence: tuple[str, ...] = ()
|
||||
lineage: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SpanProvenanceDelta:
|
||||
"""A per-span override of a page envelope. Every field is optional; ``None`` (or, for
|
||||
``divergence``, the sentinel default) means *inherit the page-level value*. A span with an
|
||||
all-default delta — or no delta at all — costs nothing and resolves to the page envelope.
|
||||
"""
|
||||
|
||||
source_shard: str | None = None
|
||||
liveness: Liveness | None = None
|
||||
staleness: Staleness | None = None
|
||||
overlay_state: OverlayState | None = None
|
||||
source_rev: str | None = None
|
||||
observed_at: datetime | None = None
|
||||
authz_context: str | None = None
|
||||
# Sentinel: ``None`` means inherit; an explicit (possibly empty) tuple overrides.
|
||||
divergence: tuple[str, ...] | None = None
|
||||
lineage: str | None = None
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
"""True when the delta overrides nothing (the common, zero-cost case)."""
|
||||
return all(getattr(self, f.name) is None for f in dataclasses.fields(self))
|
||||
|
||||
|
||||
def effective(
|
||||
base: ProvenanceEnvelope, delta: SpanProvenanceDelta | None = None
|
||||
) -> ProvenanceEnvelope:
|
||||
"""Compose a page envelope with an optional span delta → the span's effective provenance.
|
||||
|
||||
``effective(base, None)`` and ``effective(base, <empty delta>)`` both return ``base``
|
||||
unchanged (zero-cost inheritance). Any non-``None`` delta field overrides ``base``.
|
||||
"""
|
||||
if delta is None or delta.is_empty():
|
||||
return base
|
||||
overrides = {
|
||||
f.name: value
|
||||
for f in dataclasses.fields(delta)
|
||||
if (value := getattr(delta, f.name)) is not None
|
||||
}
|
||||
return dataclasses.replace(base, **overrides)
|
||||
73
tests/test_provenance.py
Normal file
73
tests/test_provenance.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Tests for the provenance leaf rail (SHARD-WP-0007 T1)."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from shard_wiki.provenance import (
|
||||
Liveness,
|
||||
OverlayState,
|
||||
ProvenanceEnvelope,
|
||||
SpanProvenanceDelta,
|
||||
Staleness,
|
||||
effective,
|
||||
)
|
||||
|
||||
|
||||
def _base() -> ProvenanceEnvelope:
|
||||
return ProvenanceEnvelope(
|
||||
source_shard="shardA",
|
||||
liveness=Liveness.STATIC,
|
||||
staleness=Staleness.FRESH,
|
||||
source_rev="r1",
|
||||
observed_at=datetime(2026, 6, 15, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
|
||||
def test_no_delta_returns_base_identity():
|
||||
base = _base()
|
||||
assert effective(base) is base
|
||||
assert effective(base, SpanProvenanceDelta()) is base
|
||||
|
||||
|
||||
def test_empty_delta_is_detected():
|
||||
assert SpanProvenanceDelta().is_empty()
|
||||
assert not SpanProvenanceDelta(source_shard="other").is_empty()
|
||||
|
||||
|
||||
def test_single_field_override():
|
||||
base = _base()
|
||||
eff = effective(base, SpanProvenanceDelta(source_shard="shardB"))
|
||||
assert eff.source_shard == "shardB"
|
||||
# Untouched fields inherit from the page envelope.
|
||||
assert eff.source_rev == "r1"
|
||||
assert eff.liveness is Liveness.STATIC
|
||||
# Base is unchanged (frozen, pure composition).
|
||||
assert base.source_shard == "shardA"
|
||||
|
||||
|
||||
def test_partial_delta_overrides_several_fields():
|
||||
base = _base()
|
||||
eff = effective(
|
||||
base,
|
||||
SpanProvenanceDelta(
|
||||
staleness=Staleness.STALE,
|
||||
overlay_state=OverlayState.DRAFT,
|
||||
divergence=("shardB:Home",),
|
||||
),
|
||||
)
|
||||
assert eff.staleness is Staleness.STALE
|
||||
assert eff.overlay_state is OverlayState.DRAFT
|
||||
assert eff.divergence == ("shardB:Home",)
|
||||
assert eff.source_shard == "shardA" # inherited
|
||||
|
||||
|
||||
def test_divergence_empty_tuple_overrides_but_none_inherits():
|
||||
base = ProvenanceEnvelope(source_shard="s", divergence=("x",))
|
||||
# None → inherit the page's divergence.
|
||||
assert effective(base, SpanProvenanceDelta(divergence=None)).divergence == ("x",)
|
||||
# Explicit empty tuple → override (clear it).
|
||||
assert effective(base, SpanProvenanceDelta(divergence=())).divergence == ()
|
||||
|
||||
|
||||
def test_envelope_is_hashable_and_value_equal():
|
||||
assert _base() == _base()
|
||||
assert len({_base(), _base()}) == 1
|
||||
@@ -11,6 +11,7 @@ created: "2026-06-15"
|
||||
updated: "2026-06-15"
|
||||
depends_on:
|
||||
- SHARD-WP-0002
|
||||
state_hub_workstream_id: "d551fba1-9c48-4841-a9a5-a9f190f73a60"
|
||||
---
|
||||
|
||||
# SHARD-WP-0007 — Foundation implementation
|
||||
@@ -41,8 +42,9 @@ projection, authz beyond the null provider, a network API. Those are later workp
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0007-T1
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "be8f9efe-cfba-46d0-be91-f9b6e87bc0d2"
|
||||
```
|
||||
|
||||
`src/shard_wiki/provenance/`: the `ProvenanceEnvelope` value type (source_shard, source_rev?,
|
||||
@@ -56,6 +58,7 @@ delta). Frozen dataclasses, no tree deps. Tests: ⊕ identity (no delta), overri
|
||||
id: SHARD-WP-0007-T2
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "780ad01f-c3e1-4b49-9ae9-60e0324178a7"
|
||||
```
|
||||
|
||||
`src/shard_wiki/model/`: `Identity` (shard-scoped stable handle, value-equal, survives edits),
|
||||
@@ -70,6 +73,7 @@ identity stability vs content change; profile validation accepts/rejects.
|
||||
id: SHARD-WP-0007-T3
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "f6e35ddb-ab1e-406a-82f8-563244455f6b"
|
||||
```
|
||||
|
||||
`src/shard_wiki/adapters/`: `AdapterContract` (a `Protocol`/ABC with the operation verbs;
|
||||
@@ -84,6 +88,7 @@ git-or-none history). Imports `model/`, `provenance/`. Tests: read a temp folder
|
||||
id: SHARD-WP-0007-T4
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "12d7fb8d-3842-4142-a462-9d1e6efe58bd"
|
||||
```
|
||||
|
||||
`src/shard_wiki/adapters/conformance.py`: a battery that, given a binding, **verifies declared
|
||||
@@ -98,6 +103,7 @@ lying stub fails with a precise diff.
|
||||
id: SHARD-WP-0007-T5
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "c87b1896-59a5-4cde-a292-1086caebd085"
|
||||
```
|
||||
|
||||
`src/shard_wiki/coordination/`: `DecisionLog` — append-only, **totally ordered per space**
|
||||
@@ -112,6 +118,7 @@ append→ordered read; fold reproduces current state; read-your-writes.
|
||||
id: SHARD-WP-0007-T6
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "fed38b60-dc0b-40cf-93e9-ab0260aa3ff9"
|
||||
```
|
||||
|
||||
`src/shard_wiki/policy/` (leaf: presets + `resolve()` for canonical-source = chorus default);
|
||||
@@ -126,6 +133,7 @@ the same name → chorus; alias from the log redirects resolution.
|
||||
id: SHARD-WP-0007-T7
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "86c0b255-e373-460d-859d-a39bf6ea4ffb"
|
||||
```
|
||||
|
||||
A thin `shard_wiki` entry (attach shard, resolve, read) tying the slice together; an
|
||||
|
||||
Reference in New Issue
Block a user