V-1/B1: the vault's seat definition gains licence-put
mesh/delivery merged: source.main moved to novox/mesh-contracts@29ec1c3df312
mesh/check checking 3eba6b8299e0 on 29ec1c3df312 | novox/mesh-contracts#6 in group feat/39-licence-switch
mesh/delivery-group group feat/39-licence-switch merged: every member is merged

The licence manager hands the vault a licence's grant and access token through a verb of
the seat mesh-vault; the manifest validator refuses a seat verb the definition does not
serve, so the definition carries it, and the generated Go and TypeScript follow.

Builds: design 39 bus-and-switch §15, B1, B4, B5

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
This commit is contained in:
2026-11-11 23:07:44 +01:00
parent 5fd0bf86f1
commit 3eba6b8299
3 changed files with 71 additions and 1 deletions
@@ -30,11 +30,12 @@ const (
VerbNodeAccountConfirm = "node-account-confirm" VerbNodeAccountConfirm = "node-account-confirm"
VerbAbandon = "abandon" VerbAbandon = "abandon"
VerbRetire = "retire" VerbRetire = "retire"
VerbLicencePut = "licence-put"
) )
// Verbs is every verb the seat serves, in its definition's order: a new copy each call. // Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string { func Verbs() []string {
return []string{VerbGet, VerbSecretAsk, VerbIssued, VerbRotate, VerbMoveIn, VerbNodeKey, VerbOwn, VerbIssueAccount, VerbSign, VerbEnrolmentToken, VerbNodeAccount, VerbRotateNode, VerbNodeAccountConfirm, VerbAbandon, VerbRetire} return []string{VerbGet, VerbSecretAsk, VerbIssued, VerbRotate, VerbMoveIn, VerbNodeKey, VerbOwn, VerbIssueAccount, VerbSign, VerbEnrolmentToken, VerbNodeAccount, VerbRotateNode, VerbNodeAccountConfirm, VerbAbandon, VerbRetire, VerbLicencePut}
} }
// The holder's own events, listed beside the seat and published under the holder's subjects (ADR 0620 §2). // The holder's own events, listed beside the seat and published under the holder's subjects (ADR 0620 §2).
@@ -506,6 +507,23 @@ type RetireAnswer struct {
Retired bool `json:"retired"` Retired bool `json:"retired"`
} }
// LicencePutInput is what licence-put takes.
type LicencePutInput struct {
// the second step: the call the first step answered
Call string `json:"call,omitempty"`
// the first step: the licence's generation
Generation float64 `json:"generation,omitempty"`
// the first step: the licence's key
Licence string `json:"licence,omitempty"`
// the first step: grant or access
Part string `json:"part,omitempty"`
// the second step: the value sealed to the call's key — never the value
Sealed string `json:"sealed,omitempty"`
}
// LicencePutAnswer is what licence-put answers.
type LicencePutAnswer = map[string]any
// SignedHolderEvent is the body of the holder's event signed. // SignedHolderEvent is the body of the holder's event signed.
type SignedHolderEvent struct { type SignedHolderEvent struct {
// when it was signed, RFC 3339 // when it was signed, RFC 3339
@@ -642,6 +642,40 @@
"retired" "retired"
] ]
} }
},
{
"name": "licence-put",
"description": "Hand the vault one of a licence's two secrets, its grant or its access token, from the licence manager, in two steps: name the licence by its key, the part and its generation and get a call and a key made for it; then send the call and the value sealed to that key. Keeps the newest generation and refuses an older one; says secret-changed with the secret's name and generation, never the value (novox/hq design 39 bus-and-switch §15).",
"optional": true,
"input": {
"properties": {
"call": {
"description": "the second step: the call the first step answered",
"type": "string"
},
"generation": {
"description": "the first step: the licence's generation",
"type": "number"
},
"licence": {
"description": "the first step: the licence's key",
"type": "string"
},
"part": {
"description": "the first step: grant or access",
"type": "string"
},
"sealed": {
"description": "the second step: the value sealed to the call's key — never the value",
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "the holder's answer"
}
} }
], ],
"description": "The mesh's secrets. accept and move-asks, which no holder served and nothing called, are left out (ADR 0620 §3). The vault's own events stay under its own subjects (ADR 0620 §2).", "description": "The mesh's secrets. accept and move-asks, which no holder served and nothing called, are left out (ADR 0620 §3). The vault's own events stay under its own subjects (ADR 0620 §2).",
@@ -20,6 +20,7 @@ export const verbs = {
nodeAccountConfirm: "node-account-confirm", nodeAccountConfirm: "node-account-confirm",
abandon: "abandon", abandon: "abandon",
retire: "retire", retire: "retire",
licencePut: "licence-put",
} as const; } as const;
export const holderEvents = { export const holderEvents = {
@@ -416,6 +417,23 @@ export interface RetireAnswer {
"retired": boolean; "retired": boolean;
} }
/** is what licence-put takes. */
export interface LicencePutInput {
/** the second step: the call the first step answered */
"call"?: string;
/** the first step: the licence's generation */
"generation"?: number;
/** the first step: the licence's key */
"licence"?: string;
/** the first step: grant or access */
"part"?: string;
/** the second step: the value sealed to the call's key — never the value */
"sealed"?: string;
}
/** is what licence-put answers. */
export type LicencePutAnswer = Record<string, unknown>;
/** is the body of the holder's event signed. */ /** is the body of the holder's event signed. */
export interface SignedHolderEvent { export interface SignedHolderEvent {
/** when it was signed, RFC 3339 */ /** when it was signed, RFC 3339 */