From 715ab1ca009cb4f6aad80a211a36edb6a6b894bb Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 15 Jun 2026 12:32:47 +0200 Subject: [PATCH] feat(coordination): patch rendering (unified diff) (WP-0008 T3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render_patch(overlay, base_body) → Patch (pure difflib unified diff, target- labelled); is_empty when unchanged. 3 tests green, pyflakes clean. (ADR-05) Co-Authored-By: Claude Opus 4.8 --- src/shard_wiki/coordination/__init__.py | 3 ++ src/shard_wiki/coordination/patch.py | 38 +++++++++++++++++++++++++ tests/test_patch.py | 29 +++++++++++++++++++ workplans/SHARD-WP-0008-write-path.md | 2 +- 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/shard_wiki/coordination/patch.py create mode 100644 tests/test_patch.py diff --git a/src/shard_wiki/coordination/__init__.py b/src/shard_wiki/coordination/__init__.py index 52eed65..7fc3be3 100644 --- a/src/shard_wiki/coordination/__init__.py +++ b/src/shard_wiki/coordination/__init__.py @@ -7,6 +7,7 @@ from shard_wiki.coordination.decision_log import ( EventType, ) from shard_wiki.coordination.overlay import Overlay, OverlayEngine +from shard_wiki.coordination.patch import Patch, render_patch __all__ = [ "DecisionLog", @@ -15,4 +16,6 @@ __all__ = [ "CoordinationState", "Overlay", "OverlayEngine", + "Patch", + "render_patch", ] diff --git a/src/shard_wiki/coordination/patch.py b/src/shard_wiki/coordination/patch.py new file mode 100644 index 0000000..3a49ab5 --- /dev/null +++ b/src/shard_wiki/coordination/patch.py @@ -0,0 +1,38 @@ +"""Patch rendering — an overlay as a reviewable diff (FederationRequirements ADR-05). + +A pure function over (base body, overlay body): the auditable change proposal that an overlay +becomes before it is applied. Markdown/native text in this slice (lossless); a lossy +native-syntax-with-fidelity-report variant is later (TSD §A.6). +""" + +from __future__ import annotations + +import difflib +from dataclasses import dataclass + +from shard_wiki.coordination.overlay import Overlay +from shard_wiki.model import Identity + +__all__ = ["Patch", "render_patch"] + + +@dataclass(frozen=True, slots=True) +class Patch: + target: Identity + diff: str + + @property + def is_empty(self) -> bool: + return self.diff == "" + + +def render_patch(overlay: Overlay, base_body: str) -> Patch: + """Render ``base_body`` → ``overlay.body`` as a unified diff against the overlay target.""" + label = str(overlay.target) + lines = difflib.unified_diff( + base_body.splitlines(keepends=True), + overlay.body.splitlines(keepends=True), + fromfile=f"a/{label}", + tofile=f"b/{label}", + ) + return Patch(target=overlay.target, diff="".join(lines)) diff --git a/tests/test_patch.py b/tests/test_patch.py new file mode 100644 index 0000000..a8e0f0a --- /dev/null +++ b/tests/test_patch.py @@ -0,0 +1,29 @@ +"""Tests for patch rendering (SHARD-WP-0008 T3).""" + +from shard_wiki.coordination import Overlay, Patch, render_patch +from shard_wiki.model import Identity + + +def _overlay(body: str) -> Overlay: + return Overlay("ov1", Identity("shardA", "Home"), base_rev="r1", body=body) + + +def test_patch_shows_the_change(): + patch = render_patch(_overlay("line one\nline TWO\n"), "line one\nline two\n") + assert isinstance(patch, Patch) + assert not patch.is_empty + assert "-line two" in patch.diff + assert "+line TWO" in patch.diff + assert patch.target == Identity("shardA", "Home") + + +def test_empty_patch_when_unchanged(): + patch = render_patch(_overlay("same\n"), "same\n") + assert patch.is_empty + assert patch.diff == "" + + +def test_patch_labels_name_the_target(): + patch = render_patch(_overlay("new\n"), "old\n") + assert "a/shardA:Home" in patch.diff + assert "b/shardA:Home" in patch.diff diff --git a/workplans/SHARD-WP-0008-write-path.md b/workplans/SHARD-WP-0008-write-path.md index c0bf163..3cb8119 100644 --- a/workplans/SHARD-WP-0008-write-path.md +++ b/workplans/SHARD-WP-0008-write-path.md @@ -72,7 +72,7 @@ overlays. Tests: draft recorded + retrievable via fold; overlay id stable. ```task id: SHARD-WP-0008-T3 -status: todo +status: done priority: medium state_hub_task_id: "90d98c16-ed3b-414f-802c-b0400eca6ede" ```