refactor: structured body (array|object, never string) for canonicalization
Message bodies are always arrays of strings (text lines) or objects (structured data like PUBKEY). Never raw strings. This enables: - Multiline messages without escape sequences - Deterministic JSON canonicalization (RFC 8785 JCS) for signing - Structured data where needed Update all schemas: body fields use array type with string items. Update message.json envelope: body is oneOf[array, object], id is UUID. Update README: message envelope table, examples, and canonicalization docs. Update schema/README.md: field types, examples with array bodies.
This commit is contained in:
@@ -6,15 +6,8 @@
|
||||
"$ref": "../message.json",
|
||||
"properties": {
|
||||
"command": { "const": "KICK" },
|
||||
"from": {
|
||||
"type": "string",
|
||||
"description": "Nick that performed the kick."
|
||||
},
|
||||
"to": {
|
||||
"type": "string",
|
||||
"description": "Channel name.",
|
||||
"pattern": "^#[a-zA-Z0-9_-]+$"
|
||||
},
|
||||
"from": { "type": "string", "description": "Nick that performed the kick." },
|
||||
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
|
||||
"params": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
@@ -23,12 +16,14 @@
|
||||
"maxItems": 1
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Optional kick reason."
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Optional kick reason.",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "to", "params"],
|
||||
"examples": [
|
||||
{ "command": "KICK", "from": "op1", "to": "#general", "params": ["troll"], "body": "Behave" }
|
||||
{ "command": "KICK", "from": "op1", "to": "#general", "params": ["troll"], "body": ["Behave"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
"properties": {
|
||||
"command": { "const": "NICK" },
|
||||
"from": { "type": "string", "description": "Old nick (S2C)." },
|
||||
"body": { "type": "string", "description": "New nick.", "minLength": 1, "maxLength": 32, "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "New nick (single-element array).",
|
||||
"minItems": 1,
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "body"],
|
||||
"examples": [
|
||||
{ "command": "NICK", "from": "oldnick", "body": "newnick" }
|
||||
{ "command": "NICK", "from": "oldnick", "body": ["newnick"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
"command": { "const": "NOTICE" },
|
||||
"from": { "type": "string" },
|
||||
"to": { "type": "string", "description": "Target: #channel, nick, or * (global)." },
|
||||
"body": { "type": "string", "description": "Notice text." }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Notice text lines."
|
||||
}
|
||||
},
|
||||
"required": ["command", "to", "body"],
|
||||
"examples": [
|
||||
{ "command": "NOTICE", "from": "server.example.com", "to": "*", "body": "Server restarting in 5 minutes" }
|
||||
{ "command": "NOTICE", "from": "server.example.com", "to": "*", "body": ["Server restarting in 5 minutes"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
"command": { "const": "PART" },
|
||||
"from": { "type": "string", "description": "Nick that left (S2C)." },
|
||||
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
|
||||
"body": { "type": "string", "description": "Optional part reason." }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Optional part reason.",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "to"],
|
||||
"examples": [
|
||||
{ "command": "PART", "from": "alice", "to": "#general", "body": "later" }
|
||||
{ "command": "PART", "from": "alice", "to": "#general", "body": ["later"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
"properties": {
|
||||
"command": { "const": "PING" },
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Opaque token to be echoed in PONG."
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Opaque token to be echoed in PONG (single-element array).",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
"examples": [
|
||||
{ "command": "PING", "body": "1707580000" }
|
||||
{ "command": "PING", "body": ["1707580000"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,17 +6,16 @@
|
||||
"$ref": "../message.json",
|
||||
"properties": {
|
||||
"command": { "const": "PONG" },
|
||||
"from": {
|
||||
"type": "string",
|
||||
"description": "Responding server name."
|
||||
},
|
||||
"from": { "type": "string", "description": "Responding server name." },
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Echoed token from PING."
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Echoed token from PING (single-element array).",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
"examples": [
|
||||
{ "command": "PONG", "from": "server.example.com", "body": "1707580000" }
|
||||
{ "command": "PONG", "from": "server.example.com", "body": ["1707580000"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
"command": { "const": "PRIVMSG" },
|
||||
"from": { "type": "string", "description": "Sender nick (set by server on relay)." },
|
||||
"to": { "type": "string", "description": "Target: #channel or nick.", "examples": ["#general", "alice"] },
|
||||
"body": { "type": "string", "description": "Message text.", "minLength": 1 }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Message lines. One string per line.",
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "to", "body"],
|
||||
"examples": [
|
||||
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": "hello world" },
|
||||
{ "command": "PRIVMSG", "from": "bob", "to": "alice", "body": "hey" }
|
||||
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": ["hello world"] },
|
||||
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": ["line one", "line two"] },
|
||||
{ "command": "PRIVMSG", "from": "bob", "to": "alice", "body": ["hey"], "meta": { "sig": "base64...", "alg": "ed25519" } }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,10 +7,15 @@
|
||||
"properties": {
|
||||
"command": { "const": "QUIT" },
|
||||
"from": { "type": "string", "description": "Nick that quit." },
|
||||
"body": { "type": "string", "description": "Optional quit reason." }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Optional quit reason.",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "from"],
|
||||
"examples": [
|
||||
{ "command": "QUIT", "from": "alice", "body": "Connection reset" }
|
||||
{ "command": "QUIT", "from": "alice", "body": ["Connection reset"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
"command": { "const": "TOPIC" },
|
||||
"from": { "type": "string", "description": "Nick that changed the topic (S2C)." },
|
||||
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
|
||||
"body": { "type": "string", "description": "New topic text. Empty string clears the topic.", "maxLength": 512 }
|
||||
"body": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "New topic text (single-element array). Empty array clears the topic.",
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["command", "to"],
|
||||
"examples": [
|
||||
{ "command": "TOPIC", "from": "alice", "to": "#general", "body": "Welcome to the chat" }
|
||||
{ "command": "TOPIC", "from": "alice", "to": "#general", "body": ["Welcome to the chat"] }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user