From 8174f5c41e02c132bb8c25a425dcf6cb17c9066b Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 1 Sep 2026 21:32:57 +0200 Subject: [PATCH] `plan` is the send without the sending, so it allocates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confining allocation to the push path took `plan` with it, and `plan` belongs on the other side: it is a person asking what a push would do to one named machine, so the port it shows and the secret it seals must be the ones a push would use. Both are kept once chosen, so showing numbers a later push would replace answers a question nobody asked. Caught by the lab: composing the real modules stopped producing postgres's sealed superuser, because nothing had minted it and the read-only path correctly declined to. The line is not question versus command. It is a person asking once about one machine, against the mesh asking continuously about all of them — the second is what hung, and the second is what reads. --- cmd/mesh-control/plan.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/mesh-control/plan.go b/cmd/mesh-control/plan.go index d1cea2d..56fe989 100644 --- a/cmd/mesh-control/plan.go +++ b/cmd/mesh-control/plan.go @@ -244,19 +244,31 @@ func declarationFor(ctx context.Context, open *stores, node string, if err != nil { return nil, err } - return declarationWith(ctx, open, node, plan, settings, gens, Reading) + // **Allocating, because `plan` is the send without the sending.** It is one machine, named by + // a person, who is asking what a push would do — so the port it shows and the secret it seals + // have to be the ones a push would use, and both are kept once chosen. Showing numbers that a + // later push would replace would make the command answer a question nobody asked. + // + // The line is not "a question may not write". It is who is asking and how often: this is a + // person, about one machine, on purpose. What may not write is the comparison the mesh runs + // over every machine to answer whether each is up to date — see Choosing. + return declarationWith(ctx, open, node, plan, settings, gens, Allocating) } // declarationWith is the same, for a caller that has already worked out the generators once and // is about to use them for every node. // Choosing says whether this composition may allocate what has not been allocated yet. // -// **Asking what the mesh would send must not change what the mesh would send.** Composing a -// declaration assigns each module a machine port, and `status` composes one for every node to -// answer *is this machine running what I would send it* — so the question allocated, wrote, and -// contended with the very machine it was asking about. A status command that polls every two -// seconds while a node is applying is then two writers on the same rows, which is how it came to -// hang rather than answer. +// **The comparison the mesh runs over every machine must not change what it is comparing.** +// Composing a declaration assigns each module a machine port and seals its secrets, and `status` +// composes one for every node to answer *is this machine running what I would send it* — so that +// question allocated, minted, wrote, and contended with the very machine it was asking about. A +// status polled every two seconds while a node applies is then two writers on the same rows, +// which is how it came to hang rather than answer. +// +// `plan` sits on the other side of this and allocates, because it is a person asking what a push +// would do to one named machine. The distinction is not question versus command; it is a person +// asking once about one machine versus the mesh asking continuously about all of them. // // So the mesh chooses a port when it commits to sending one, and every other caller reads what // was chosen. A module with nothing assigned yet has never been sent, which is exactly what a