From 3eba6b8299e040f524518a3d383e14ba5e499c0f Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 23:07:44 +0100 Subject: [PATCH] V-1/B1: the vault's seat definition gains licence-put MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../go/seats/meshvault/contract.go | 20 ++++++++++- .../seats/mesh-vault/seat.json | 34 +++++++++++++++++++ .../ts/seats/meshvault/src/index.ts | 18 ++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/identity-and-access/go/seats/meshvault/contract.go b/identity-and-access/go/seats/meshvault/contract.go index 9d51257..07a19f4 100644 --- a/identity-and-access/go/seats/meshvault/contract.go +++ b/identity-and-access/go/seats/meshvault/contract.go @@ -30,11 +30,12 @@ const ( VerbNodeAccountConfirm = "node-account-confirm" VerbAbandon = "abandon" VerbRetire = "retire" + VerbLicencePut = "licence-put" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. 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). @@ -506,6 +507,23 @@ type RetireAnswer struct { 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. type SignedHolderEvent struct { // when it was signed, RFC 3339 diff --git a/identity-and-access/seats/mesh-vault/seat.json b/identity-and-access/seats/mesh-vault/seat.json index 7c93f1b..ab06050 100644 --- a/identity-and-access/seats/mesh-vault/seat.json +++ b/identity-and-access/seats/mesh-vault/seat.json @@ -642,6 +642,40 @@ "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).", diff --git a/identity-and-access/ts/seats/meshvault/src/index.ts b/identity-and-access/ts/seats/meshvault/src/index.ts index e3f2c11..1c776bc 100644 --- a/identity-and-access/ts/seats/meshvault/src/index.ts +++ b/identity-and-access/ts/seats/meshvault/src/index.ts @@ -20,6 +20,7 @@ export const verbs = { nodeAccountConfirm: "node-account-confirm", abandon: "abandon", retire: "retire", + licencePut: "licence-put", } as const; export const holderEvents = { @@ -416,6 +417,23 @@ export interface RetireAnswer { "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; + /** is the body of the holder's event signed. */ export interface SignedHolderEvent { /** when it was signed, RFC 3339 */