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"
|
updated: "2026-06-15"
|
||||||
depends_on:
|
depends_on:
|
||||||
- SHARD-WP-0002
|
- SHARD-WP-0002
|
||||||
|
state_hub_workstream_id: "d551fba1-9c48-4841-a9a5-a9f190f73a60"
|
||||||
---
|
---
|
||||||
|
|
||||||
# SHARD-WP-0007 — Foundation implementation
|
# SHARD-WP-0007 — Foundation implementation
|
||||||
@@ -41,8 +42,9 @@ projection, authz beyond the null provider, a network API. Those are later workp
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: SHARD-WP-0007-T1
|
id: SHARD-WP-0007-T1
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "be8f9efe-cfba-46d0-be91-f9b6e87bc0d2"
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/shard_wiki/provenance/`: the `ProvenanceEnvelope` value type (source_shard, source_rev?,
|
`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
|
id: SHARD-WP-0007-T2
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "780ad01f-c3e1-4b49-9ae9-60e0324178a7"
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/shard_wiki/model/`: `Identity` (shard-scoped stable handle, value-equal, survives edits),
|
`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
|
id: SHARD-WP-0007-T3
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "f6e35ddb-ab1e-406a-82f8-563244455f6b"
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/shard_wiki/adapters/`: `AdapterContract` (a `Protocol`/ABC with the operation verbs;
|
`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
|
id: SHARD-WP-0007-T4
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
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
|
`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
|
id: SHARD-WP-0007-T5
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "c87b1896-59a5-4cde-a292-1086caebd085"
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/shard_wiki/coordination/`: `DecisionLog` — append-only, **totally ordered per space**
|
`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
|
id: SHARD-WP-0007-T6
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "fed38b60-dc0b-40cf-93e9-ab0260aa3ff9"
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/shard_wiki/policy/` (leaf: presets + `resolve()` for canonical-source = chorus default);
|
`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
|
id: SHARD-WP-0007-T7
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
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
|
A thin `shard_wiki` entry (attach shard, resolve, read) tying the slice together; an
|
||||||
|
|||||||
Reference in New Issue
Block a user