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"` }