Let enrolment-token take the first tunnel, so the token the vault makes carries it as today
Lead's decision on milestone point 7 (a progressive insight of ADR 0587 §3): mesh-enrolment builds the joining node's first tunnel at issue and passes it to the vault's enrolment-token as `tunnel`, an object of hub_key, hub_endpoint and address, each required; the vault puts it in the token (delivery.TokenTunnel). Tested as a row of TestTheVaultsSeatPromisesItsIntakeVerbs; no new test function (1438 unit tests). (issue 774, review round 1) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -249,11 +249,16 @@ var defaultSeats = append([]Seat{
|
||||
"written as the bus user enrol.<node>. Answers only what the joining machine must trust (the bus's " +
|
||||
"address, the bus certificate's fingerprint, the vault's public signing key), never the token; with " +
|
||||
"sealed, the token sealed to the operator key, for tokens to relay; with drop, the token and its hash " +
|
||||
"dropped, at the enrolment's end. Asked by mesh-enrolment alone, only from the lease write.",
|
||||
Input: schema(map[string]string{"node": "the node the token is for",
|
||||
"dropped, at the enrolment's end. Asked by mesh-enrolment alone, only from the lease write. The token " +
|
||||
"carries the first tunnel mesh-enrolment built at issue (ADR 0587 §3, insight of 2026-11-02).",
|
||||
Input: withObject(schema(map[string]string{"node": "the node the token is for",
|
||||
"expires": "when the token expires (RFC 3339), as the enrolment's key says",
|
||||
"sealed": "\"true\": answer the token sealed to the operator key",
|
||||
"drop": "\"true\": drop the node's token and its hash"}, []string{"node"}, "sealed", "drop")},
|
||||
"drop": "\"true\": drop the node's token and its hash"}, []string{"node"}, "sealed", "drop"),
|
||||
"tunnel", "the node's first tunnel, built by mesh-enrolment at issue and carried in the token: the hub's "+
|
||||
"public key, the hub's endpoint and the node's tunnel address",
|
||||
map[string]string{"hub_key": "the hub's tunnel public key", "hub_endpoint": "the hub's endpoint, host:port",
|
||||
"address": "the joining node's tunnel address"})},
|
||||
{Name: "node-account", Optional: true, Description: "Mint a joining node's bus account node.<node>, sealed " +
|
||||
"to the sealing key it presented, and write its hash; asked again for the same node and keys, answers the " +
|
||||
"account it already minted; with retire, the account it minted for a revoked enrolment retired. Writes the " +
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package catalogue
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The vault's provision is one the controller itself dereferences — every minted credential is
|
||||
// sealed with it — so it is delivered by a seat of the mesh's own, and a second provider is a second
|
||||
@@ -66,7 +69,7 @@ func TestTheVaultsSeatPromisesItsIntakeVerbs(t *testing.T) {
|
||||
"issued": {"provision", "provider", "module", "call", "sealed", "holders"},
|
||||
"rotate": {"node", "module", "name", "provider", "provision", "by"},
|
||||
// 774/S37/AC1: the enrolment verbs mesh-enrolment asks (ADR 0587 §3, §5).
|
||||
"enrolment-token": {"node", "expires", "sealed", "drop"},
|
||||
"enrolment-token": {"node", "expires", "sealed", "drop", "tunnel"},
|
||||
"node-account": {"node", "sealing_key", "keys_digest", "retire"},
|
||||
"sign": {"kind", "node", "serving_key", "reply"},
|
||||
// 774/S38: the rotation verbs (ADR 0583 §2, §3, §5).
|
||||
@@ -97,6 +100,14 @@ func TestTheVaultsSeatPromisesItsIntakeVerbs(t *testing.T) {
|
||||
if props["value"] != nil {
|
||||
t.Errorf("%s takes a value in the clear", name)
|
||||
}
|
||||
// The first tunnel the token carries (ADR 0587 §3, insight of 2026-11-02): an object of the hub's key, its endpoint
|
||||
// and the node's address.
|
||||
if name == "enrolment-token" {
|
||||
tunnel, _ := props["tunnel"].(map[string]any)
|
||||
if tunnel["type"] != "object" || !slices.Equal(tunnel["required"].([]string), []string{"address", "hub_endpoint", "hub_key"}) {
|
||||
t.Errorf("enrolment-token's tunnel is %v", tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
// The holders `issued` names (issue 749) are a list of {node, module}.
|
||||
for _, verb := range seat.Serves {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// A Verb is one tool a role serves: its name, what it does, and the schema of its arguments and of
|
||||
@@ -596,6 +598,14 @@ var ControllerVerbs = []Verb{
|
||||
//
|
||||
// **The schema is the whole of what a verb takes** (novox/hq issue 244): an argument it does not
|
||||
// name is refused when the verb is called, never passed over.
|
||||
// withObject adds to a verb's input s the property name, an object of the string fields given, each required.
|
||||
func withObject(s map[string]any, name, description string, fields map[string]string) map[string]any {
|
||||
inner := schema(fields, slices.Sorted(maps.Keys(fields)))
|
||||
inner["description"], inner["additionalProperties"] = description, false
|
||||
s["properties"].(map[string]any)[name] = inner
|
||||
return s
|
||||
}
|
||||
|
||||
func schema(properties map[string]string, required []string, switches ...string) map[string]any {
|
||||
isSwitch := map[string]bool{}
|
||||
for _, s := range switches {
|
||||
|
||||
Reference in New Issue
Block a user