operator-channel: the bench shapes of channel and intake (Message, Button, Sender, Choice, LinkAsked, Failed, Standing, Code, ProofAnswer) in the definition, with a test against mesh-sdk's channel wire (C-12; ADR 0634 §1; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package operatorchannelclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
"git.novox.be/novox/mesh-sdk/go/channel"
|
||||
)
|
||||
|
||||
// TestTheBenchShapesAreTheChannelsWire: each shape the benches channel and intake carry, written by mesh-sdk's channel
|
||||
// package, reads into the contract's type with no field dropped and reads back as the same wire.
|
||||
func TestTheBenchShapesAreTheChannelsWire(t *testing.T) {
|
||||
at := time.Date(2026, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||
who := channel.Sender{Identity: "42", Display: "Op", Authenticated: true}
|
||||
cases := []struct {
|
||||
name string
|
||||
sdk any
|
||||
into any
|
||||
}{
|
||||
{"message", channel.Message{Handle: "h1", Title: "t", Body: "b", Buttons: []channel.Button{{Label: "Approve", Ticket: "tk"}},
|
||||
Note: "n", Urgent: true, Silent: true, Reply: "r", To: "42", Secret: true}, &seat.Message{}},
|
||||
{"a send", channel.Message{Handle: "h2", Title: "t", Body: "b"}, &seat.Message{}},
|
||||
{"choice", channel.Choice{ID: "c1", Ticket: "tk", Handle: "h1", Sender: who, At: at}, &seat.Choice{}},
|
||||
{"link", channel.LinkAsked{ID: "l1", Sender: who, At: at}, &seat.LinkAsked{}},
|
||||
{"failed", channel.Failed{Handle: "h1", Why: "blocked", Permanent: true, At: at}, &seat.Failed{}},
|
||||
{"standing", channel.Standing{Ready: true, Why: "w", EditsSilently: true, At: at}, &seat.Standing{}},
|
||||
{"code", channel.Code{Sender: who, Code: "123456", Purpose: "approve"}, &seat.Code{}},
|
||||
{"proof answer", channel.ProofAnswer{Accepted: true, Words: "ok"}, &seat.ProofAnswer{}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
wire, err := json.Marshal(c.sdk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(wire))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(c.into); err != nil {
|
||||
t.Errorf("%s: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
back, _ := json.Marshal(c.into)
|
||||
var got, want any
|
||||
_ = json.Unmarshal(back, &got)
|
||||
_ = json.Unmarshal(wire, &want)
|
||||
if !jsonEqual(got, want) {
|
||||
t.Errorf("%s: reads back as %s, want %s", c.name, back, wire)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqual(a, b any) bool {
|
||||
x, _ := json.Marshal(a)
|
||||
y, _ := json.Marshal(b)
|
||||
return bytes.Equal(x, y)
|
||||
}
|
||||
@@ -149,6 +149,36 @@ type Ask struct {
|
||||
WholeDetails string `json:"whole-details,omitempty"`
|
||||
}
|
||||
|
||||
// Button is one answer a channel offers: its label and the router's one-time ticket for it.
|
||||
type Button struct {
|
||||
// the button's label
|
||||
Label string `json:"label"`
|
||||
// the router's one-time ticket for it
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
// Choice is a button chosen on a channel, said on the bench intake as the event `choice`.
|
||||
type Choice struct {
|
||||
// when, RFC 3339
|
||||
At string `json:"at"`
|
||||
// the message's handle
|
||||
Handle string `json:"handle,omitempty"`
|
||||
// the channel's own for this arrival, unique, so the router acts on it once
|
||||
ID string `json:"id"`
|
||||
Sender Sender `json:"sender"`
|
||||
// the ticket of the button
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
// Code is a code a person typed on a channel, asked as the proof `code` on the bench intake by request and reply: never in an event, never kept.
|
||||
type Code struct {
|
||||
// the code
|
||||
Code string `json:"code"`
|
||||
// what it is for
|
||||
Purpose string `json:"purpose"`
|
||||
Sender Sender `json:"sender"`
|
||||
}
|
||||
|
||||
// ConditionActionsItem is a type of the contract.
|
||||
type ConditionActionsItem struct {
|
||||
Arguments map[string]string `json:"arguments,omitempty"`
|
||||
@@ -197,6 +227,51 @@ type Condition struct {
|
||||
V int64 `json:"v"`
|
||||
}
|
||||
|
||||
// Failed is a message a channel could not deliver, said on the bench intake as the event `failed`.
|
||||
type Failed struct {
|
||||
// when, RFC 3339
|
||||
At string `json:"at"`
|
||||
// the message's handle
|
||||
Handle string `json:"handle"`
|
||||
// trying again cannot help
|
||||
Permanent bool `json:"permanent,omitempty"`
|
||||
// why
|
||||
Why string `json:"why"`
|
||||
}
|
||||
|
||||
// LinkAsked is somebody on a channel asking to be linked as the operator, said on the bench intake as the event `link`.
|
||||
type LinkAsked struct {
|
||||
// when, RFC 3339
|
||||
At string `json:"at"`
|
||||
// the channel's own for this arrival
|
||||
ID string `json:"id"`
|
||||
Sender Sender `json:"sender"`
|
||||
}
|
||||
|
||||
// Message is the work a channel takes on the bench channel: shown with buttons (show), shown again in place (edit) or said (send); the words are the router's, shown as given.
|
||||
type Message struct {
|
||||
// its body
|
||||
Body string `json:"body"`
|
||||
// the answers it offers (none for a send)
|
||||
Buttons []Button `json:"buttons,omitempty"`
|
||||
// the router's name for it, the same across a show and its edits
|
||||
Handle string `json:"handle"`
|
||||
// its secondary words after the body and the buttons: who asked and when, what each answer does, when it expires, the ask's details
|
||||
Note string `json:"note,omitempty"`
|
||||
// the choice or link request this answers, by its id: the channel shows it where that was made
|
||||
Reply string `json:"reply,omitempty"`
|
||||
// the words carry something shown once (a link's code): the channel keeps no copy
|
||||
Secret bool `json:"secret,omitempty"`
|
||||
// shown without notifying
|
||||
Silent bool `json:"silent,omitempty"`
|
||||
// its title
|
||||
Title string `json:"title"`
|
||||
// the account linked as the operator on this kind, where the channel sends what is not a reply; empty when none is linked
|
||||
To string `json:"to,omitempty"`
|
||||
// urgent
|
||||
Urgent bool `json:"urgent,omitempty"`
|
||||
}
|
||||
|
||||
// Option is a type of the contract.
|
||||
type Option struct {
|
||||
// the digest of exactly what the asker performs when it is chosen
|
||||
@@ -225,6 +300,36 @@ type Person struct {
|
||||
Who string `json:"who"`
|
||||
}
|
||||
|
||||
// ProofAnswer is the router's answer to a proof.
|
||||
type ProofAnswer struct {
|
||||
// whether it was taken
|
||||
Accepted bool `json:"accepted"`
|
||||
// words to say to the person
|
||||
Words string `json:"words"`
|
||||
}
|
||||
|
||||
// Sender is who a channel's service says sent something; the router alone judges whether that is the operator.
|
||||
type Sender struct {
|
||||
// whether the service authenticated it
|
||||
Authenticated bool `json:"authenticated"`
|
||||
// the name it shows
|
||||
Display string `json:"display,omitempty"`
|
||||
// the account
|
||||
Identity string `json:"identity"`
|
||||
}
|
||||
|
||||
// Standing is what a channel says of itself at least every five minutes and whenever it changes, on the bench intake as the event `standing`.
|
||||
type Standing struct {
|
||||
// when, RFC 3339
|
||||
At string `json:"at"`
|
||||
// its edits notify nobody
|
||||
EditsSilently bool `json:"edits-silently,omitempty"`
|
||||
// it can send now
|
||||
Ready bool `json:"ready"`
|
||||
// why not
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
// Warrant is a type of the contract.
|
||||
type Warrant struct {
|
||||
// what it was about
|
||||
|
||||
@@ -563,6 +563,246 @@
|
||||
"subject",
|
||||
"state"
|
||||
]
|
||||
},
|
||||
"Button": {
|
||||
"type": "object",
|
||||
"description": "one answer a channel offers: its label and the router's one-time ticket for it",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "the button's label"
|
||||
},
|
||||
"ticket": {
|
||||
"type": "string",
|
||||
"description": "the router's one-time ticket for it"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"ticket"
|
||||
]
|
||||
},
|
||||
"Message": {
|
||||
"type": "object",
|
||||
"description": "the work a channel takes on the bench channel: shown with buttons (show), shown again in place (edit) or said (send); the words are the router's, shown as given",
|
||||
"properties": {
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"description": "the router's name for it, the same across a show and its edits"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "its title"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "its body"
|
||||
},
|
||||
"buttons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/defs/Button"
|
||||
},
|
||||
"description": "the answers it offers (none for a send)"
|
||||
},
|
||||
"note": {
|
||||
"type": "string",
|
||||
"description": "its secondary words after the body and the buttons: who asked and when, what each answer does, when it expires, the ask's details"
|
||||
},
|
||||
"urgent": {
|
||||
"type": "boolean",
|
||||
"description": "urgent"
|
||||
},
|
||||
"silent": {
|
||||
"type": "boolean",
|
||||
"description": "shown without notifying"
|
||||
},
|
||||
"reply": {
|
||||
"type": "string",
|
||||
"description": "the choice or link request this answers, by its id: the channel shows it where that was made"
|
||||
},
|
||||
"to": {
|
||||
"type": "string",
|
||||
"description": "the account linked as the operator on this kind, where the channel sends what is not a reply; empty when none is linked"
|
||||
},
|
||||
"secret": {
|
||||
"type": "boolean",
|
||||
"description": "the words carry something shown once (a link's code): the channel keeps no copy"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"handle",
|
||||
"title",
|
||||
"body"
|
||||
]
|
||||
},
|
||||
"Sender": {
|
||||
"type": "object",
|
||||
"description": "who a channel's service says sent something; the router alone judges whether that is the operator",
|
||||
"properties": {
|
||||
"identity": {
|
||||
"type": "string",
|
||||
"description": "the account"
|
||||
},
|
||||
"display": {
|
||||
"type": "string",
|
||||
"description": "the name it shows"
|
||||
},
|
||||
"authenticated": {
|
||||
"type": "boolean",
|
||||
"description": "whether the service authenticated it"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"identity",
|
||||
"authenticated"
|
||||
]
|
||||
},
|
||||
"Choice": {
|
||||
"type": "object",
|
||||
"description": "a button chosen on a channel, said on the bench intake as the event `choice`",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "the channel's own for this arrival, unique, so the router acts on it once"
|
||||
},
|
||||
"ticket": {
|
||||
"type": "string",
|
||||
"description": "the ticket of the button"
|
||||
},
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"description": "the message's handle"
|
||||
},
|
||||
"sender": {
|
||||
"$ref": "#/defs/Sender"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when, RFC 3339"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"ticket",
|
||||
"sender",
|
||||
"at"
|
||||
]
|
||||
},
|
||||
"LinkAsked": {
|
||||
"type": "object",
|
||||
"description": "somebody on a channel asking to be linked as the operator, said on the bench intake as the event `link`",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "the channel's own for this arrival"
|
||||
},
|
||||
"sender": {
|
||||
"$ref": "#/defs/Sender"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when, RFC 3339"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"sender",
|
||||
"at"
|
||||
]
|
||||
},
|
||||
"Failed": {
|
||||
"type": "object",
|
||||
"description": "a message a channel could not deliver, said on the bench intake as the event `failed`",
|
||||
"properties": {
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"description": "the message's handle"
|
||||
},
|
||||
"why": {
|
||||
"type": "string",
|
||||
"description": "why"
|
||||
},
|
||||
"permanent": {
|
||||
"type": "boolean",
|
||||
"description": "trying again cannot help"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when, RFC 3339"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"handle",
|
||||
"why",
|
||||
"at"
|
||||
]
|
||||
},
|
||||
"Standing": {
|
||||
"type": "object",
|
||||
"description": "what a channel says of itself at least every five minutes and whenever it changes, on the bench intake as the event `standing`",
|
||||
"properties": {
|
||||
"ready": {
|
||||
"type": "boolean",
|
||||
"description": "it can send now"
|
||||
},
|
||||
"why": {
|
||||
"type": "string",
|
||||
"description": "why not"
|
||||
},
|
||||
"edits-silently": {
|
||||
"type": "boolean",
|
||||
"description": "its edits notify nobody"
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "when, RFC 3339"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ready",
|
||||
"at"
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"type": "object",
|
||||
"description": "a code a person typed on a channel, asked as the proof `code` on the bench intake by request and reply: never in an event, never kept",
|
||||
"properties": {
|
||||
"sender": {
|
||||
"$ref": "#/defs/Sender"
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "the code"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string",
|
||||
"description": "what it is for"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sender",
|
||||
"code",
|
||||
"purpose"
|
||||
]
|
||||
},
|
||||
"ProofAnswer": {
|
||||
"type": "object",
|
||||
"description": "the router's answer to a proof",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean",
|
||||
"description": "whether it was taken"
|
||||
},
|
||||
"words": {
|
||||
"type": "string",
|
||||
"description": "words to say to the person"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted",
|
||||
"words"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,36 @@ export interface Ask {
|
||||
"whole-details"?: string;
|
||||
}
|
||||
|
||||
/** one answer a channel offers: its label and the router's one-time ticket for it */
|
||||
export interface Button {
|
||||
/** the button's label */
|
||||
"label": string;
|
||||
/** the router's one-time ticket for it */
|
||||
"ticket": string;
|
||||
}
|
||||
|
||||
/** a button chosen on a channel, said on the bench intake as the event `choice` */
|
||||
export interface Choice {
|
||||
/** when, RFC 3339 */
|
||||
"at": string;
|
||||
/** the message's handle */
|
||||
"handle"?: string;
|
||||
/** the channel's own for this arrival, unique, so the router acts on it once */
|
||||
"id": string;
|
||||
"sender": Sender;
|
||||
/** the ticket of the button */
|
||||
"ticket": string;
|
||||
}
|
||||
|
||||
/** a code a person typed on a channel, asked as the proof `code` on the bench intake by request and reply: never in an event, never kept */
|
||||
export interface Code {
|
||||
/** the code */
|
||||
"code": string;
|
||||
/** what it is for */
|
||||
"purpose": string;
|
||||
"sender": Sender;
|
||||
}
|
||||
|
||||
export interface ConditionActionsItem {
|
||||
"arguments"?: Record<string, string>;
|
||||
/** label */
|
||||
@@ -153,6 +183,51 @@ export interface Condition {
|
||||
"v": number;
|
||||
}
|
||||
|
||||
/** a message a channel could not deliver, said on the bench intake as the event `failed` */
|
||||
export interface Failed {
|
||||
/** when, RFC 3339 */
|
||||
"at": string;
|
||||
/** the message's handle */
|
||||
"handle": string;
|
||||
/** trying again cannot help */
|
||||
"permanent"?: boolean;
|
||||
/** why */
|
||||
"why": string;
|
||||
}
|
||||
|
||||
/** somebody on a channel asking to be linked as the operator, said on the bench intake as the event `link` */
|
||||
export interface LinkAsked {
|
||||
/** when, RFC 3339 */
|
||||
"at": string;
|
||||
/** the channel's own for this arrival */
|
||||
"id": string;
|
||||
"sender": Sender;
|
||||
}
|
||||
|
||||
/** the work a channel takes on the bench channel: shown with buttons (show), shown again in place (edit) or said (send); the words are the router's, shown as given */
|
||||
export interface Message {
|
||||
/** its body */
|
||||
"body": string;
|
||||
/** the answers it offers (none for a send) */
|
||||
"buttons"?: Button[];
|
||||
/** the router's name for it, the same across a show and its edits */
|
||||
"handle": string;
|
||||
/** its secondary words after the body and the buttons: who asked and when, what each answer does, when it expires, the ask's details */
|
||||
"note"?: string;
|
||||
/** the choice or link request this answers, by its id: the channel shows it where that was made */
|
||||
"reply"?: string;
|
||||
/** the words carry something shown once (a link's code): the channel keeps no copy */
|
||||
"secret"?: boolean;
|
||||
/** shown without notifying */
|
||||
"silent"?: boolean;
|
||||
/** its title */
|
||||
"title": string;
|
||||
/** the account linked as the operator on this kind, where the channel sends what is not a reply; empty when none is linked */
|
||||
"to"?: string;
|
||||
/** urgent */
|
||||
"urgent"?: boolean;
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
/** the digest of exactly what the asker performs when it is chosen */
|
||||
"binds"?: string;
|
||||
@@ -179,6 +254,36 @@ export interface Person {
|
||||
"who": string;
|
||||
}
|
||||
|
||||
/** the router's answer to a proof */
|
||||
export interface ProofAnswer {
|
||||
/** whether it was taken */
|
||||
"accepted": boolean;
|
||||
/** words to say to the person */
|
||||
"words": string;
|
||||
}
|
||||
|
||||
/** who a channel's service says sent something; the router alone judges whether that is the operator */
|
||||
export interface Sender {
|
||||
/** whether the service authenticated it */
|
||||
"authenticated": boolean;
|
||||
/** the name it shows */
|
||||
"display"?: string;
|
||||
/** the account */
|
||||
"identity": string;
|
||||
}
|
||||
|
||||
/** what a channel says of itself at least every five minutes and whenever it changes, on the bench intake as the event `standing` */
|
||||
export interface Standing {
|
||||
/** when, RFC 3339 */
|
||||
"at": string;
|
||||
/** its edits notify nobody */
|
||||
"edits-silently"?: boolean;
|
||||
/** it can send now */
|
||||
"ready": boolean;
|
||||
/** why not */
|
||||
"why"?: string;
|
||||
}
|
||||
|
||||
export interface Warrant {
|
||||
/** what it was about */
|
||||
"about"?: string;
|
||||
|
||||
Reference in New Issue
Block a user