diff --git a/.loom/event-log.md b/.loom/event-log.md index 7183ffd..19f5b8b 100644 --- a/.loom/event-log.md +++ b/.loom/event-log.md @@ -570,3 +570,47 @@ protocol this repository works under says **commit before dissolving, git is the only archive of the conversation.*** **An ignored cart has no archive**, so dissolving a notes file destroys the annotations outright. *One of the two is wrong and it is not ours to settle.* + +## 2026-09-07 — what the planted change actually proved `marmalade` + +**`cart.md` was changed upstream deliberately, without telling us, to see whether +the tool would notice.** *It did — and the sequence is worth recording, because +the obvious reading is wrong.* + +**`check` did not catch it.** *All eight documents were unlocked, and `unlocked` +means **I cannot tell you**.* **It was `add` that revealed the change, by +overwriting the file** — *and the only reason anybody saw what had changed is that +`git` happened to be watching the working tree.* + +> **So the mechanism is proven and the workflow is not.** *A conditional request +> against a lock works. **A document nobody has locked is a document nobody is +> checking**, and it stays that way silently.* + +**Decided, as a consequence:** *`add` now reports when it replaces local content +that differs from what the publisher is serving.* **It used to say only +`adopted`.** *A copy that differs is the only evidence that something moved while +the document was unlocked, and destroying it silently is how a change nobody saw +becomes a change nobody can find.* + +**Belief that could be shown wrong:** *that a note is enough.* **`add` still +overwrites** — *it does not stash the old bytes anywhere* — **and outside a git +working tree the note tells you something was lost without letting you see it.** +*If that bites, `add` needs `--dry-run` or a refusal.* + +## 2026-09-07 — the unowned half: somebody has to run it `marmalade` + +**Nothing here answers *when* `check` runs.** + +*`bedrock` says it about running systems and it is just as true of this:* +**nothing serves the truth, so the only mechanism is somebody looking.** *The tool +makes looking cheap; it does not make it happen.* + +**Recorded as a need with no owner rather than a feature**, *because the answers +are all outside the tool* — **a git hook, a CI job, an agent's session start, a +scheduled run** — *and choosing one here would put a scheduler inside a fetcher and +a comparator.* + +**Belief that could be shown wrong:** *that staying out of it is right.* **If in +practice nobody ever runs `check` unaided, a tool that only reports when asked is +a tool that reports nothing**, *and the thing we declined to build is the thing +that was needed.* diff --git a/internal/external/external.go b/internal/external/external.go index 211deba..b2500f3 100644 --- a/internal/external/external.go +++ b/internal/external/external.go @@ -6,6 +6,7 @@ package external import ( + "bytes" "errors" "fmt" "io" @@ -127,6 +128,17 @@ func Add(root, raw, override string, out io.Writer) error { if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { return err } + + // Say what is being replaced before replacing it. add overwrites the local + // copy, and a copy that differs from what the publisher is serving is the + // only evidence that anything changed while the document was unlocked — + // destroying it silently is how a change nobody saw becomes a change nobody + // can find. + replaced := "" + if old, err := os.ReadFile(dest); err == nil && !bytes.Equal(old, body) { + replaced = fmt.Sprintf("replaced %d bytes that differed — diff the working tree before committing", len(old)) + } + if err := os.WriteFile(dest, body, 0o644); err != nil { return err } @@ -142,6 +154,9 @@ func Add(root, raw, override string, out io.Writer) error { fmt.Fprintf(out, "adopted %s\n", rel) fmt.Fprintf(out, " from %s\n", resp.Request.URL) + if replaced != "" { + fmt.Fprintf(out, " NOTE %s\n", replaced) + } if etag == "" { fmt.Fprintf(out, " etag (none served — check cannot ask conditionally)\n") } else {