Route-proxy and the overlay name the assigner's new places: the contracts' go/ path and the state assignments (issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:00:49 +01:00
parent db50239373
commit 2f0b7c9741
10 changed files with 37 additions and 37 deletions
@@ -9,13 +9,13 @@ import (
// The modules the mesh provides itself (issue 1126): no repository holds them, so the indexer reads none; their
// manifests are built in code, here, and always win over any other. Today the private network alone, built by nobody
// and run inside each node's own composition. One predicate for every part that judges or composes a placement: the
// placement's check, the seat's holder, the composer's input and the grants part.
// and run inside each node's own composition. One predicate for every part that judges or composes an assignment: the
// assignment's check, the seat's holder, the composer's input and the grants part.
// ProvidedModules is every module the mesh provides itself, sorted.
func ProvidedModules() []string { return []string{Name} }
// ProvidedByMesh says whether the mesh provides module itself, so a placement may name it with no repository holding it.
// ProvidedByMesh says whether the mesh provides module itself, so an assignment may name it with no repository holding it.
func ProvidedByMesh(module string) bool { return module == Name }
// ProvidedManifest is a provided module's manifest, as its readers parse it, and its raw JSON; an error for a module the
+2 -2
View File
@@ -42,13 +42,13 @@ migration scaffolding still listed there.
|---|---|---|
| `provides` | `route` (scope `mesh`, answered by the helper) | a module on any node may require it |
| `serves.route` | `{}` | a route hands back a name, not a credential — the consumer already knows the name it asked for |
| `reads` | `mesh-provisioner.plans`, `mesh-provisioner.provisions`, `mesh-delivery-assigner.placement` | the plans are where the routes are; the helper reads both; the placement says who is inside |
| `reads` | `mesh-provisioner.plans`, `mesh-provisioner.provisions`, `mesh-delivery-assigner.assignments` | the plans are where the routes are; the helper reads both; the assignments say who is inside |
| `emits` | `route.served`, `route.withdrawn`, `route.refused` | what it serves, said as it changes (the contract's `Event`) |
| `listens` | `80` and `443`, both `from: anywhere` | the one machine with a public opening; the firewall opens these for free (ADR 0045) |
Until the bus answers, the proxy routes nothing and says so on its log; it refuses to start without its broker
credential (`MESH_BROKER_FILE`). Who is inside the private network, and so may be served an internal-only name, is
every node's address on it as the seat mesh-placement decides it (mesh-sdk `delivery.InsideAddresses`); until that
every node's address on it as the seat mesh-module-assigner decides it (mesh-sdk `delivery.InsideAddresses`); until that
is read, an internal-only name is served to this machine alone.
## Which issuer: Let's Encrypt, stated here (novox/hq ADR 0226)
@@ -5,7 +5,7 @@ package main
// name from the entry in this module's bucket, the one its machine's client fills its files from.
//
// **It composes nothing.** A route's public name is the label its consumer chose joined to the node's public domain,
// which only placement holds, so the seat mesh-provisioner composes it and carries it in the
// which only the assignments hold, so the seat mesh-provisioner composes it and carries it in the
// consumer's plan entry (`names`); the helper copies it into the entry's values unchanged. There is no credential for
// a route and nothing to make. The proxy itself serves the same plans' entries (cmd/route-proxy plans.go), the one
// source of routes (issue 774, research 072 row 34).
+12 -12
View File
@@ -10,7 +10,7 @@ import (
"github.com/nats-io/nats.go"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/process"
@@ -18,9 +18,9 @@ import (
"route-proxy/contract"
)
// placementRead is the seat mesh-module-assigner's state placement as this module's manifest reads it (`reads`); no seat's
// assignmentsRead is the seat mesh-module-assigner's state assignments as this module's manifest reads it (`reads`); no seat's
// client names a holder (novox/hq ADR 0625 §4), so the name is kept here and held to the manifest by TestTheReadIsTheManifests.
const placementRead = "mesh-delivery-assigner.placement"
const assignmentsRead = "mesh-delivery-assigner.assignments"
// plansRead is the seat mesh-provisioner's state plans as this module's manifest reads it (`reads`; TestTheReadIsTheManifests).
const plansRead = "mesh-provisioner.plans"
@@ -40,11 +40,11 @@ func keptStates() []announce.State {
// **The routes are the seat mesh-provisioner's plans, alone** (issue 774, research 072 row 34): every plan's entry
// naming this proxy as a route's provider, followed, so a route added or withdrawn reaches a running proxy without a
// restart, and said on the bus as it changes (contract.EventServed, EventWithdrawn, EventRefused). **Who is inside**
// is every node's address on the private network as the seat mesh-placement decides it (the module assigner's
// is every node's address on the private network as the seat mesh-module-assigner decides it (the module assigner's
// client, InsideAddresses), which is who may be served an internal name; until it is read, an internal name is
// served to this machine alone.
// follow connects with the credential in path, follows the plans and the placement, and retries the connection for
// follow connects with the credential in path, follows the plans and the assignments, and retries the connection for
// as long as it takes: until the bus answers, the proxy routes nothing, and says so.
func follow(path string, held *table) {
for {
@@ -80,8 +80,8 @@ func followOnce(path string, held *table) error {
}
}
// Who is inside first, so the first table is served to the machines it is meant for.
if err := followInside(conn.Read(placementRead), held); err != nil {
log.Printf("cannot follow %s, so an internal name is served to this machine alone: %v", placementRead, err)
if err := followInside(conn.Read(assignmentsRead), held); err != nil {
log.Printf("cannot follow %s, so an internal name is served to this machine alone: %v", assignmentsRead, err)
}
if err := followPlans(conn.Read(plansRead), newPlanned(account.Node, say), held, say); err != nil {
conn.Close()
@@ -164,25 +164,25 @@ func sortedNames(m map[string]contract.Event) []string {
return out
}
// followInside follows the seat mesh-placement's state and serves an internal name to every node's address on the
// followInside follows the seat mesh-module-assigner's state and serves an internal name to every node's address on the
// private network it decides, each time it changes (the module assigner client's InsideAddresses; research 072 rows 33 and 34:
// the membership that carried them went with the assignments stream). A key that does not read keeps who is inside
// as it was.
func followInside(placement planWatch, held *table) error {
func followInside(assignments planWatch, held *table) error {
keys := meshmoduleassignerclient.NewKeys()
var read atomic.Bool
apply := func() {
inside, err := sourcesOf(meshmoduleassignerclient.InsideAddresses(keys.All()))
if err != nil {
log.Printf("the placement says an address that is not one, so who is inside stays as it was: %v", err)
log.Printf("the assignments say an address that is not one, so who is inside stays as it was: %v", err)
return
}
held.setInside(inside)
log.Printf("internal names are served to %d machine(s)", len(inside))
}
err := placement.Watch("", func(c stdio.StateChange) error {
err := assignments.Watch("", func(c stdio.StateChange) error {
if _, err := keys.Put(c.Key, c.Value, c.Revision, c.Op == "delete" || c.Op == "purge"); err != nil {
log.Printf("the placement's key %s does not read, so who is inside stays as it was: %v", c.Key, err)
log.Printf("the assignments key %s does not read, so who is inside stays as it was: %v", c.Key, err)
return nil
}
if read.Load() {
@@ -19,7 +19,7 @@ func TestTheReadIsTheManifests(t *testing.T) {
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
for _, read := range []string{placementRead, plansRead} {
for _, read := range []string{assignmentsRead, plansRead} {
if !slices.Contains(m.Reads, read) {
t.Errorf("the proxy follows %s, its manifest reads %v", read, m.Reads)
}
+1 -1
View File
@@ -6,7 +6,7 @@ package main
// **A route is a provision, in route-proxy's own contract.** A module that must be reachable requires `route`; the
// seat mesh-provisioner composes its plan on its node, and the plan's entry for the route names this proxy as its provider and
// carries what the module contributes, composed (its names joined to the node's domains, the machine port its
// placement assigned its endpoint, never the software's own), and where the module's machine is on the private
// the assignments gave its endpoint, never the software's own), and where the module's machine is on the private
// network, as one opaque payload the seat never reads (mesh-sdk catalogue.PlanProvision.Contributed). The proxy decodes each contribution with its contract (contract.DecodeRequest)
// and serves every entry naming it. There is no file and no second source: a route the plans do not name is not
// served, and a route the contract refuses is said refused (contract.EventRefused), never served half.
@@ -15,8 +15,8 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
stdio "git.novox.be/novox/mesh-sdk/go"
"route-proxy/contract"
@@ -208,8 +208,8 @@ func contributed(at string, each ...string) string {
// loopback on this machine and at the plan's address on another; an entry routing elsewhere, carrying nothing, naming
// no address or a route the contract refuses is not served, and is said refused. A change is served as it comes and
// said served or withdrawn, a plan that cannot be read keeps what it gave, and a deleted one withdraws it. The
// placement says only who is inside: an internal-only name is served to the machines it decides addresses for.
func TestThePlansAreServedAndThePlacementSaysWhoIsInside(t *testing.T) {
// the assignments say only who is inside: an internal-only name is served to the machines it decides addresses for.
func TestThePlansAreServedAndTheAssignmentsSayWhoIsInside(t *testing.T) {
const route = `{"provision":"route","provider":"anchor","provider-module":"route-proxy",`
gitea := route + contributed("10.10.0.7",
"web", `{"name":"git.example","internal-name":"git.anchor.internal","port":3000}`,
@@ -304,29 +304,29 @@ func TestThePlansAreServedAndThePlacementSaysWhoIsInside(t *testing.T) {
held = behind(t)
if code, _ := askFrom(held, "admin.anchor.internal", "10.10.0.7:1"); code != http.StatusNotFound {
t.Errorf("before the placement is read, a machine of the mesh was served an internal-only name: %d", code)
t.Errorf("before the assignments are read, a machine of the mesh was served an internal-only name: %d", code)
}
laptop, err := meshmoduleassignerclient.PlacementValues(seat.Placement{V: meshmoduleassignerclient.Version, Node: "laptop", Decided: seat.NodeDecided{Address: "10.10.0.7"}})
laptop, err := meshmoduleassignerclient.AssignmentsValues(seat.Assignments{V: meshmoduleassignerclient.Version, Node: "laptop", Decided: seat.NodeDecided{Address: "10.10.0.7"}})
if err != nil {
t.Fatal(err)
}
placement := &fakePlans{current: map[string]string{}}
assignments := &fakePlans{current: map[string]string{}}
for k, v := range laptop {
placement.current[k] = string(v)
assignments.current[k] = string(v)
}
if err := followInside(placement, held); err != nil {
if err := followInside(assignments, held); err != nil {
t.Fatal(err)
}
if code, _ := askFrom(held, "admin.anchor.internal", "10.10.0.7:1"); code == http.StatusNotFound {
t.Error("a machine the placement decides an address for was refused the internal-only route")
t.Error("a machine the assignments decide an address for was refused the internal-only route")
}
if code, _ := askFrom(held, "admin.anchor.internal", "10.10.0.8:1"); code != http.StatusNotFound {
t.Error("a machine the placement does not name was served the internal-only route")
t.Error("a machine the assignments do not name was served the internal-only route")
}
if err := placement.handle(stdio.StateChange{Key: "laptop.node", Op: "put", Value: json.RawMessage(`{`)}); err != nil {
if err := assignments.handle(stdio.StateChange{Key: "laptop.node", Op: "put", Value: json.RawMessage(`{`)}); err != nil {
t.Fatal(err)
}
if code, _ := askFrom(held, "admin.anchor.internal", "10.10.0.7:1"); code == http.StatusNotFound {
t.Error("an unreadable placement key changed who is inside")
t.Error("an unreadable assignments key changed who is inside")
}
}
@@ -158,7 +158,7 @@ func TestARouteWithNoInternalNameGetsNoAlias(t *testing.T) {
// 3000, published at 20000, and grafana's 3000 on another machine, published at 20012, answered 502): over loopback
// when the plan is the one of this proxy's own node, which is the only thing that works without a private network, and
// at the address the entry gives for its machine otherwise. The plans here are written as the seat mesh-provisioner
// carries them; that the port is the placement's and not the software's own is the composition's, proved by the
// carries them; that the port is the assignments' and not the software's own is the composition's, proved by the
// node-engine's composer, not here.
func TestAPlanEntryIsDialledAtTheMachinePortItCarries(t *testing.T) {
for _, c := range []struct {
+2 -2
View File
@@ -3,8 +3,8 @@ module route-proxy
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/provisioning/go/seats/meshprovisioner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
+1 -1
View File
@@ -265,7 +265,7 @@
]
},
"reads": [
"mesh-delivery-assigner.placement",
"mesh-delivery-assigner.assignments",
"mesh-provisioner.plans",
"mesh-provisioner.provisions"
]