Appendix C - 15. `derive-changelog` (reconstruct mutations)
The Structure of derive-changelog (reconstruct mutations) — its shape at a glance:
The command reads the embedded stamps from the finished artifact, orders and groups them by pass, and emits a structured change log a human or tool can read.
flowchart LR Art[/Remediated artifact/] -->|read embedded stamps| Reader[Changelog reader] Reader -->|group by pass| Log[/ChangeLog JSON/] Log --> RCA([RCA / transparency])
Accessible description: the command reads the embedded attribution stamps out of the finished artifact, groups them by the pass that made each change, and emits a structured change log consumed for root-cause analysis and user transparency.
Projected from the catalogue entry provenance-and-attribution / derive-changelog (reconstruct mutations).
On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns
Intent
Intent — A command that reconstructs the document's mutation history from the embedded stamp registry, turning the attribution stamps into a readable, attributed ChangeLog after the fact.
Motivation
The stamps are embedded in the artifact, but embedded data isn't useful until it can be read back into a coherent history. Without a reader, attribution is present but inert — you have the evidence and no way to assemble it into "pass X made change Y." The failure is attribution that exists but can't be consumed, which shows up exactly when someone needs the history for RCA or user transparency.
Applicability
- The stamps must exist and be structured. mutator-stamps is a hard dependency.
- A reader that knows the stamp schema and reconstructs the ordered, attributed history.
Structure
The Structure diagram appears at the top of this page.
Sample Code
The reader is a projection over the embedded stamps: read them out of the artifact, sort by the order they were applied, and emit one attributed entry per mutation, each carrying its visibility. What a raw before/after diff cannot give you is the who and why — the stamp carries the pass and the visibility, so the reconstruction is attributed, not just a delta.
import json
from dataclasses import dataclass, asdict
@dataclass
class ChangeEntry:
order: int
pass_name: str # who made the change
target: str # what was changed
visibility: str # "debug" (stripped before delivery) or "preserved"
def derive_changelog(read_stamps) -> str:
"""`read_stamps` yields the embedded provenance records out of the artifact.
We turn them into an ordered, attributed change log — the who/why a diff omits."""
stamps = sorted(read_stamps(), key=lambda s: s["order"])
entries = [ChangeEntry(s["order"], s["pass"], s["target"], s.get("visibility", "debug"))
for s in stamps]
return json.dumps([asdict(e) for e in entries], indent=2)
Consequences
- Only as complete as the stamps. An attribution gap (which the F10 lint exists to prevent) becomes a hole in the reconstructed history.
- Schema-coupled. It depends on the stamp registry's shape staying stable across versions.
Example use within DocAble
- The
derive-changelogcommand (stamp registry → ChangeLog JSON, per pass, with visibility).
Related Patterns
- Consumer — reads mutator-stamps: it is the read side of the attribution substrate.
- Enabler — of RCA and user-facing change transparency (a human reads the ChangeLog).
- See also (counterpart) — f10-wiring-lint guarantees the stamps this reader depends on are complete.