plan is the send without the sending, so it allocates

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.
This commit is contained in:
2026-09-01 21:32:57 +02:00
parent be62f49eab
commit 8174f5c41e
+19 -7
View File
@@ -244,19 +244,31 @@ func declarationFor(ctx context.Context, open *stores, node string,
if err != nil { if err != nil {
return nil, err 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 // declarationWith is the same, for a caller that has already worked out the generators once and
// is about to use them for every node. // is about to use them for every node.
// Choosing says whether this composition may allocate what has not been allocated yet. // 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 // **The comparison the mesh runs over every machine must not change what it is comparing.**
// declaration assigns each module a machine port, and `status` composes one for every node to // Composing a declaration assigns each module a machine port and seals its secrets, and `status`
// answer *is this machine running what I would send it* — so the question allocated, wrote, and // composes one for every node to answer *is this machine running what I would send it* — so that
// contended with the very machine it was asking about. A status command that polls every two // question allocated, minted, wrote, and contended with the very machine it was asking about. A
// seconds while a node is applying is then two writers on the same rows, which is how it came to // status polled every two seconds while a node applies is then two writers on the same rows,
// hang rather than answer. // 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 // 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 // was chosen. A module with nothing assigned yet has never been sent, which is exactly what a