From 8211d8b6fbc833ea184626a71cec4f9568165b80 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 2 Sep 2026 00:01:12 +0200 Subject: [PATCH] A report says which declaration it is about MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mesh decided "has this machine caught up" by comparing its send time to the report's arrival, and lost the race it invited: an apply started under the previous declaration finishes after the next one is sent, its report lands newer than the send, and the machine reads as caught up with words it has not read yet. The lab hit exactly that — one test's closing push was still being applied when the next test's push recorded its send, and the next test then read files that were never going to be there yet. Clocks cannot answer "which". The report now carries the digest of the exact bytes it applied — the same bytes, hashed the same way, that the mesh recorded when it sent them — and which-declaration becomes an equality the mesh checks rather than an ordering it hopes. --- cmd/mesh-host/main.go | 12 +++++++++++- internal/link/messages.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cmd/mesh-host/main.go b/cmd/mesh-host/main.go index 39fbd3f..77b0558 100644 --- a/cmd/mesh-host/main.go +++ b/cmd/mesh-host/main.go @@ -8,7 +8,9 @@ package main import ( "context" + "crypto/sha256" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "flag" @@ -734,7 +736,7 @@ func applyAndKeep(ctx context.Context, opts options, raw []byte, signed *store.D saveErr.Error()} } - report := link.Report{Carried: carriedPorts(updated)} + report := link.Report{Carried: carriedPorts(updated), Declared: digestOf(raw)} for _, change := range outcome.Outcomes { report.Applied = append(report.Applied, change.ID) } @@ -809,6 +811,14 @@ func sealOpener(statePath string) apply.Unseal { // // Only what was carried. What the mesh itself put here it already knows about, and reporting it // back would make the machine an authority on the mesh's own bookkeeping. +// digestOf names a declaration by its bytes, exactly as the mesh names what it sends. The two +// sides never exchange the digest of different things: this hashes the same raw bytes the mesh +// hashed when it recorded the send. +func digestOf(body []byte) string { + sum := sha256.Sum256(body) + return hex.EncodeToString(sum[:]) +} + func carriedPorts(state store.State) []int { seen := map[int]bool{} var out []int diff --git a/internal/link/messages.go b/internal/link/messages.go index ed57fcf..becc2ef 100644 --- a/internal/link/messages.go +++ b/internal/link/messages.go @@ -67,4 +67,14 @@ type Report struct { // A node *states* and the mesh writes, which is the whole shape of this message: this is the // machine saying what is true of it, not asking for anything. Carried []int `json:"carried,omitempty"` + + // Declared is the digest of the declaration this report is about — sha256 of the exact bytes + // the mesh sent, which the mesh recorded when it sent them. + // + // **Which declaration, not when.** The mesh compared its send time to this report's arrival + // to decide whether a machine had caught up, and lost the race it invited: an apply started + // under the previous declaration finishes after the next one is sent, its report lands newer + // than the send, and the machine reads as caught up with words it has not read yet. Clocks + // cannot answer "which"; the digest is the answer itself. + Declared string `json:"declared,omitempty"` }