Add the seat mesh-enrolment: six verbs and five events, so the module that joins a machine has a role the judge knows
ADR 0587 §1 makes joining a machine a seat held once by the module mesh-enrolment, the vault's only caller for its enrolment verbs. The seat is in the mesh's own set, its verbs not optional (the seat is new with its holder), and its events the five that carry no secret. The test is a block in TestHoldingAMeshSeatRequiresServingItsVerbs, no new test function. (issue 774, 774/S34) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package catalogue
|
||||
|
||||
// **Joining a machine is the seat mesh-enrolment, held once** (novox/hq ADR 0587 §1, issue 774): held for the mesh by
|
||||
// the module mesh-enrolment, the only caller the vault's manifest names for its enrolment-token and node-account verbs
|
||||
// and a caller it names for `sign`. Six verbs, five events, none carrying a secret or anything derived from the token:
|
||||
// the one hash an event carries is the digest of the public keys a node presented. It mints and keeps no secret and
|
||||
// holds no signing key. Its state and its key layout are go/delivery's (enrolment.go).
|
||||
|
||||
// EnrolmentSeat is the seat mesh-enrolment.
|
||||
const EnrolmentSeat = "mesh-enrolment"
|
||||
|
||||
// The seat's six verbs (ADR 0587 §1, §4-§8).
|
||||
const (
|
||||
EnrolmentVerbIssue = "issue"
|
||||
EnrolmentVerbRedeem = "redeem"
|
||||
EnrolmentVerbConfirm = "confirm"
|
||||
EnrolmentVerbRevoke = "revoke"
|
||||
EnrolmentVerbTokens = "tokens"
|
||||
EnrolmentVerbAgain = "again"
|
||||
)
|
||||
|
||||
// The seat's five events (ADR 0587 §1, §2, §5): said by its holder on the seat, each carrying an enrolment key's whole
|
||||
// content except enrolment-expired, which names the node and the enrolment-issued event that carried it.
|
||||
const (
|
||||
EnrolmentIssuedEvent = "enrolment-issued"
|
||||
NodeEnrolledEvent = "node-enrolled"
|
||||
EnrolmentRefusedEvent = "enrolment-refused"
|
||||
EnrolmentRevokedEvent = "enrolment-revoked"
|
||||
EnrolmentExpiredEvent = "enrolment-expired"
|
||||
)
|
||||
|
||||
// EnrolmentEvents are the seat's events, in the order an enrolment says them; the last four each end one.
|
||||
var EnrolmentEvents = []string{EnrolmentIssuedEvent, NodeEnrolledEvent, EnrolmentRefusedEvent, EnrolmentRevokedEvent,
|
||||
EnrolmentExpiredEvent}
|
||||
|
||||
// enrolmentDecision is the record that made the seat.
|
||||
const enrolmentDecision = "novox/hq ADR 0587"
|
||||
|
||||
// enrolmentVerbs are the seat's verbs: what reads all it holds and does by hand all it does (ADR 0587 §8). Not optional:
|
||||
// the seat is new with its holder, so no holder of before serves fewer.
|
||||
func enrolmentVerbs() []Verb {
|
||||
str := func(description string) map[string]any {
|
||||
return map[string]any{"type": "string", "description": description}
|
||||
}
|
||||
return []Verb{
|
||||
{Name: EnrolmentVerbIssue, Description: "Issue a one-time token for a new machine, on the operator's Approve: " +
|
||||
"refused at once, naming every finding, for a name that is not a node name or that a node record, a placement " +
|
||||
"key or an open enrolment holds, and for every call before the controller's lease is handed away. Otherwise " +
|
||||
"reserves the lowest free private-network address, asks the operator under the seat's own name and, on the " +
|
||||
"warrant, asks the vault for the token and says enrolment-issued. The token itself is read only through tokens.",
|
||||
Input: schema(map[string]string{
|
||||
"node": "the new machine's name in the mesh",
|
||||
"why": "why it joins, shown to the operator with the ask",
|
||||
"expires": "how long the token lasts, a duration such as 1h or 24h: one hour when not given, at most 24 hours",
|
||||
}, []string{"node", "why"})},
|
||||
{Name: EnrolmentVerbRedeem, Description: "Spend a token: asked by a joining node-engine dialled as enrol.<node> " +
|
||||
"alone, with the public halves of its keys and its identity key's proof over them. Spends the token, asks the " +
|
||||
"vault for the node's account and certificate, writes the node into placement, and answers the account sealed " +
|
||||
"to the node, its certificate and the vault's public half, signed by the vault. Asked again with the same keys " +
|
||||
"it finishes what is not done and answers the same; with other keys it is refused.",
|
||||
Input: map[string]any{"type": "object", "additionalProperties": false,
|
||||
"required": []string{"node", "public_key", "sealing_key", "proof"},
|
||||
"properties": map[string]any{
|
||||
"node": str("the node the token was issued for"),
|
||||
"public_key": str("the identity key's public half, base64"),
|
||||
"overlay_key": str("the private network key's public half"),
|
||||
"sealing_key": str("the sealing key's public half, which the account is sealed to"),
|
||||
"serving_key": str("the serving key's public half, which the certificate binds"),
|
||||
"profile": map[string]any{"type": "object", "description": "what the machine is, as its record says it"},
|
||||
"tunnel": map[string]any{"type": "object", "description": "a tunnel found on the machine: everything but its private key"},
|
||||
"proof": str("the identity key's signature over the redeem's proof bytes (delivery.RedeemProof), base64"),
|
||||
}}},
|
||||
{Name: EnrolmentVerbConfirm, Description: "Confirm an enrolment: asked by the node-engine dialled as its own " +
|
||||
"node login alone, the runtime's proof of that caller being the confirmation. Says node-enrolled and ends the " +
|
||||
"enrolment.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
{Name: EnrolmentVerbRevoke, Description: "End an enrolment at any stage, with why, without an Approve: withdraws " +
|
||||
"an open ask, deletes the node's key and its address, has the vault retire an account it already minted for " +
|
||||
"it, records the act as a hand act and says enrolment-revoked.",
|
||||
Input: schema(map[string]string{"node": "the node", "why": "why, recorded with the hand act"},
|
||||
[]string{"node", "why"})},
|
||||
{Name: EnrolmentVerbTokens, Description: "Read every open enrolment, or one: its stage, expiry, ask and revision, " +
|
||||
"the bucket's fill against its cap, and whether issuing is on and why (the lease). For a node at live, its " +
|
||||
"token sealed to the operator key, asked of the vault at that moment and relayed as the vault sealed it. With " +
|
||||
"history, the ended enrolments from the seat's events, as far back as the events are kept.",
|
||||
Input: schema(map[string]string{"node": "one node", "history": "\"true\": the ended enrolments too"},
|
||||
nil, "history")},
|
||||
{Name: EnrolmentVerbAgain, Description: "Raise an issue's ask again when the operator's channel does not hold it, " +
|
||||
"and nothing more.",
|
||||
Input: schema(map[string]string{"node": "the node whose ask is raised again"}, []string{"node"})},
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,8 @@ var defaultSeats = append([]Seat{
|
||||
// The lease write (novox/hq ADR 0582 clause 1, grants_seat.go): the grants part hands the controller's lease to
|
||||
// itself and back on the operator's warrant.
|
||||
{Name: GrantsSeat, Scope: ScopeMesh, Decision: grantsDecision, Emits: grantsEvents(), Serves: grantsVerbs()},
|
||||
// Joining a machine (novox/hq ADR 0587 §1, enrolment_seat.go): held once by the module mesh-enrolment.
|
||||
{Name: EnrolmentSeat, Scope: ScopeMesh, Decision: enrolmentDecision, Emits: EnrolmentEvents, Serves: enrolmentVerbs()},
|
||||
// The artifact sweep (novox/hq ADR 0571 clause 2, artifact_sweep_seat.go).
|
||||
{Name: ArtifactSweepSeat, Scope: ScopeMesh, Decision: "novox/hq ADR 0571", Emits: []string{"artifacts-gone"},
|
||||
Serves: artifactSweepVerbs()},
|
||||
|
||||
@@ -25,6 +25,26 @@ func TestAServedVerbIsANameOrADefinition(t *testing.T) {
|
||||
|
||||
// Holding a mesh seat that serves verbs requires serving them, and the refusal names the verbs.
|
||||
func TestHoldingAMeshSeatRequiresServingItsVerbs(t *testing.T) {
|
||||
// 774/S34/AC1: the seat mesh-enrolment is held by a module serving its six verbs, and a seventh is refused.
|
||||
enrolment, ok := SeatNamed(EnrolmentSeat)
|
||||
if !ok || enrolment.Scope != ScopeMesh || len(enrolment.Emits) != 5 {
|
||||
t.Fatalf("mesh-enrolment: in the set %v, %+v", ok, enrolment)
|
||||
}
|
||||
six := []string{"issue", "redeem", "confirm", "revoke", "tokens", "again"}
|
||||
holder := func(serves ...string) Manifest {
|
||||
return Manifest{Module: "mesh-enrolment", Tools: serves,
|
||||
Claims: []Claim{{Name: EnrolmentSeat, Scope: ScopeMesh, Serves: serves}}}
|
||||
}
|
||||
if err := CanHold(holder(six...), enrolment); err != nil {
|
||||
t.Errorf("the holder serving the six verbs was refused: %v", err)
|
||||
}
|
||||
if err := CanHold(holder(six[:5]...), enrolment); err == nil || !strings.Contains(err.Error(), "does not serve again") {
|
||||
t.Errorf("a holder missing again: %v", err)
|
||||
}
|
||||
if err := CanHold(holder(append(six, "mint")...), enrolment); err == nil || !strings.Contains(err.Error(), "mint") {
|
||||
t.Errorf("a holder saying it serves mint: %v", err)
|
||||
}
|
||||
|
||||
was := Seats()
|
||||
t.Cleanup(func() { UseSeats(was) })
|
||||
UseSeats([]Seat{{Name: "mesh-controller", Scope: ScopeMesh, Decision: "test",
|
||||
|
||||
Reference in New Issue
Block a user