1 Commits
Author SHA1 Message Date
clawbot 58efb7d476 Rewrite schema/README.md sentence by sentence
check / check (push) Waiting to run
Rewrite the description of the JSON Schema directory from scratch,
without tables, as a reader of the files needs it. Describe the
envelope as the server sends it: the reply name in `command` and the
number in `code`. Drop the federation section, which describes nothing
the server does. List where the schema files disagree with the server,
compared file by file with the HTTP API, so a reader is not misled; the
files themselves are unchanged.

Part of #190.

Model: fable-5-1 (writing); opus-5-5 (rework)
2026-10-27 14:32:22 +00:00
+61 -71
View File
@@ -1,42 +1,37 @@
# Message schemas
This directory holds JSON Schema files (draft 2020-12) for the messages that the
neoirc HTTP API carries. `message.json` describes the fields that every message
can have. `commands/` has files for some commands. `numerics/` has files for
some of the numeric replies. A numeric reply is a server reply named by a
three-digit number, such as `001`. `README.md` at the repository root documents
neoirc HTTP API carries. `message.json` describes the envelope every message
uses. `commands/` has files for some of the IRC commands. `numerics/` has files
for some of the numeric replies. `README.md` at the repository root documents
the protocol.
The server can also run an IRC listener for ordinary IRC clients. The IRC
listener sends plain IRC lines, which these files do not describe.
## The envelope
## Message fields
Every message is one JSON object, which `message.json` calls the envelope. These
are the fields it can have:
Every message is one JSON object. These are the fields it can have:
- `command`: the IRC command name, such as `PRIVMSG`, or the name of a numeric
reply, such as `RPL_WELCOME`. Always present.
- `code`: the number of a numeric reply, such as `1` for `RPL_WELCOME`. Absent
on other messages.
- `id`: the message's UUID, a unique ID that the server assigns.
- `id`: the message's UUID, assigned by the server.
- `from`: the sender's nick, or the server name on a server message.
- `to`: the target, such as a channel name starting with `#` or a nick.
- `params`: an array of strings with the message's other parameters, in order.
`KICK` events and many numeric replies have it. The HTTP API ignores `params`
sent by a client.
- `params`: other parameters, in order, as an array of strings. `KICK` events
and many numeric replies have it. The server ignores `params` sent by a
client.
- `body`: an array of strings. It is never a bare string. In a `PRIVMSG` or
`NOTICE`, each string is one line of text.
- `ts`: the time the server stored the message. It is an RFC 3339 time in UTC,
such as `2026-10-27T12:00:00.123Z`.
- `ts`: when the server stored the message, as an RFC 3339 time in UTC, such as
`2026-10-27T12:00:00.123Z`.
- `meta`: an object of extra data, such as a hashcash stamp in `meta.hashcash`.
A hashcash stamp is a short string that takes a computer some work to make. A
`PRIVMSG` to a channel with mode `+H` needs one. The HTTP API relays the
channel with mode `+H` needs one in every `PRIVMSG`. The server relays the
`meta` of a `PRIVMSG` or `NOTICE` unchanged. It drops the `meta` of every
other command.
Here is one `PRIVMSG` as the IRC listener sends it and as the HTTP API sends it.
The JSON leaves out `id`, `ts` and `meta`:
A `PRIVMSG` as the IRC listener sends it, and as the HTTP API sends it without
`id`, `ts` and `meta`:
```
IRC: :alice!alice@* PRIVMSG #general :hello world
@@ -44,15 +39,14 @@ JSON: {"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["hello
```
A numeric reply has its name in `command` and its number in `code`. This `353`
reply lists the members of `#general`. The JSON again leaves out `id`, `ts` and
`meta`:
reply lists the members of `#general`, again without `id`, `ts` and `meta`:
```
{"command": "RPL_NAMREPLY", "code": 353, "from": "neoirc", "to": "alice",
"params": ["=", "#general"], "body": ["@op1!op1@host1 alice!alice@host2"]}
```
A `PRIVMSG` whose `body` has two strings is a two-line message:
A `body` with two strings is a two-line message:
```
{"command": "PRIVMSG", "to": "#general", "body": ["line one", "line two"]}
@@ -63,8 +57,7 @@ A `PRIVMSG` whose `body` has two strings is a two-line message:
Commands, in `commands/`:
- `PRIVMSG.json`: a message to a channel or a user.
- `NOTICE.json`: a notice, which is a message that a client must not answer
automatically.
- `NOTICE.json`: a notice. A client must not answer a notice automatically.
- `JOIN.json`: a user joined a channel.
- `PART.json`: a user left a channel.
- `QUIT.json`: a user left the server.
@@ -75,13 +68,13 @@ Commands, in `commands/`:
- `PING.json`: a check that the server still answers.
- `PONG.json`: the answer to a `PING`.
- `PUBKEY.json`: an announcement of a user's public signing key. The server has
no such command. It answers a `PUBKEY` with `ERR_UNKNOWNCOMMAND` (`421`).
no such command. It answers a `PUBKEY` with `ERR_UNKNOWNCOMMAND` (421).
Numeric replies, in `numerics/`:
- `001.json`: `RPL_WELCOME`, which welcomes a new user.
- `002.json`: `RPL_YOURHOST`, the server's name and version.
- `003.json`: `RPL_CREATED`, which says when the server was created.
- `003.json`: `RPL_CREATED`, the date the server started.
- `004.json`: `RPL_MYINFO`, the server's version and its lists of modes.
- `322.json`: `RPL_LIST`, one channel in the answer to `LIST`.
- `323.json`: `RPL_LISTEND`, the end of the answer to `LIST`.
@@ -97,70 +90,67 @@ Numeric replies, in `numerics/`:
- `442.json`: `ERR_NOTONCHANNEL`, the user is not on that channel.
- `482.json`: `ERR_CHANOPRIVSNEEDED`, the user is not a channel operator.
## Where the files differ from the HTTP API
## Where the files differ from the server
Where a schema file and the HTTP API disagree, the HTTP API is right:
Where a schema file and the server disagree, the server is right:
- `message.json` and the files in `numerics/` put the three-digit code in
`command`. They have no `code` field. The HTTP API puts the reply's name in
`command` and its number in `code`.
- `message.json` allows an object as `body`. The HTTP API reads only arrays of
strings. A `PRIVMSG` with an object as `body` gets `ERR_NOTEXTTOSEND` (`412`).
- `message.json` and the numeric files put the three-digit code in `command` and
have no `code` field. The server puts the reply's name in `command` and its
number in `code`.
- `message.json` allows an object as `body`. The server reads only arrays of
strings. A `PRIVMSG` with an object gets `ERR_NOTEXTTOSEND` (412).
- `message.json` says `to` is a channel or a nick. A `WALLOPS` message has `*`
there.
- `message.json` allows `meta` on every message. The HTTP API keeps a client's
`meta` only on a `PRIVMSG` or `NOTICE`.
- `message.json` allows `meta` on every message. The server keeps `meta` only on
a `PRIVMSG` or `NOTICE`.
- `message.json` describes signature fields in `meta`. The server never checks a
signature.
- `message.json` does not describe `meta.hashcash`. The HTTP API reads it in a
- `message.json` does not describe `meta.hashcash`. The server reads it in a
`PRIVMSG` to a channel with mode `+H`.
- `JOIN.json` has no `body`. A client's `JOIN` can carry the channel key in
`body`. When a user joins, the `JOIN` event repeats the channel name in
`body`.
`body`. When a user joins, the server's `JOIN` event repeats the channel name
there.
- `JOIN.json` allows a channel name of any length. The server allows at most 63
characters after the `#`. It answers a longer name with `ERR_NOSUCHCHANNEL`
(`403`).
- `KICK.json` puts the kicked nick in `params`. The HTTP API reads a client's
(403).
- `KICK.json` puts the kicked nick in `params`. The server reads a client's
`KICK` from `body`: the nick, then an optional reason.
- `MODE.json` puts the mode change in `params`. The HTTP API uses `body`
instead. A client sends the change and any value as separate strings, such as
`"+o"` and `"bob"`. In a `MODE` event, `body` holds the change as one string,
such as `"+o bob"`, or as two strings, such as `"+k"` and `"secret"`.
- `NOTICE.json` allows `*` as `to`. The server answers a `NOTICE` to `*` with
`ERR_NOSUCHNICK` (`401`).
- `MODE.json` puts the mode change in `params`. The server uses `body` instead.
A client sends the change and any value as separate strings, such as `"+o"`
and `"bob"`. The server's `MODE` event has one string such as `"+o bob"`, or
the change and its value as two strings, such as `"+k"` and `"secret"`.
- `NOTICE.json` allows `*` as `to`. The server answers that with
`ERR_NOSUCHNICK` (401).
- `NOTICE.json` has an example notice from the server to `*`. The server sends
no such notice.
- `PING.json` and `PONG.json` describe a token that the `PONG` repeats. The HTTP
API ignores the `body` of a `PING`. Its answer is the HTTP response
- `PING.json` and `PONG.json` describe a token that the `PONG` repeats. The
server ignores the `body` of a `PING`. It answers the HTTP request itself with
`{"command": "PONG", "from": "<server name>"}`.
- `PING.json` and `PONG.json` also describe messages between servers. The server
does not connect to other IRC servers.
- `PUBKEY.json` describes a command the server does not have.
- `QUIT.json` says that `QUIT` comes only from the server. A client can send it
too, to end its session. A client's `QUIT` can carry a reason in `body`.
- `TOPIC.json` says a `TOPIC` without a `body` queries the topic. The HTTP API
answers such a `TOPIC` with `ERR_NEEDMOREPARAMS` (`461`).
- `TOPIC.json` says an empty array clears the topic. The HTTP API answers `[]`
with `ERR_NEEDMOREPARAMS` (`461`). A `body` of `[""]` clears the topic.
- `003.json` describes the server's creation date. The HTTP API sends the date
the server last started.
- `004.json` has example mode lists. The HTTP API sends an empty string as the
- `QUIT.json` says only the server sends `QUIT`. A client can send it too, to
end its session. The client's `QUIT` can carry a reason in `body`.
- `TOPIC.json` says a `TOPIC` without a `body` queries the topic. Over the HTTP
API a `TOPIC` without a `body` gets `ERR_NEEDMOREPARAMS` (461).
- `TOPIC.json` says an empty array clears the topic. The server answers `[]`
with `ERR_NEEDMOREPARAMS` (461). A `body` of `[""]` clears the topic.
- `003.json` describes the server's creation date. The server sends the date it
last started.
- `004.json` has example mode lists. The server sends an empty string as the
user modes and `ikmnostl` as the channel modes.
- `322.json` says `LIST` gets one reply per channel. The server leaves out a
secret channel, one with mode `+s`, unless the user is on it.
- `332.json` says `RPL_TOPIC` answers a `TOPIC` query. The HTTP API has no such
query. It sends `RPL_TOPIC` after a client sets the topic, and after a `JOIN`
to a channel that has one.
- `353.json` says `body` lists nicks. The HTTP API lists `nick!user@host` in
place of each nick, such as `@op1!op1@host1`.
- `322.json` says `LIST` gets one reply per channel. The server leaves out each
secret channel that the user is not on.
- `353.json` says `body` lists nicks. Over the HTTP API the server lists
`nick!user@host` in place of each nick, such as `@op1!op1@host1`.
- `353.json` says the first parameter marks a channel as public, private or
secret. The server always sends `=`, the mark for a public channel.
- `376.json` gives the text as `End of /MOTD command`. The HTTP API adds a full
- `376.json` gives the text as `End of /MOTD command`. The server adds a full
stop.
- `401.json` gives the text as `No such nick/channel`. The server sends
`No such nick` in answer to a `PRIVMSG` or `NOTICE` to an unknown nick.
- `433.json` has an example with `*` as `to`. The HTTP API sends `433` only in
answer to a `NICK`, with the user's nick as `to`.
- `401.json` gives the text as `No such nick/channel`. For a `PRIVMSG` or
`NOTICE` to an unknown nick, the server sends `No such nick`.
- `433.json` has an example with `*` as `to`. Over the HTTP API the server sends
`433` only in answer to a `NICK`, with the user's nick as `to`.
The HTTP API also accepts some messages that the files do not allow, such as a
`JOIN` whose `to` has no `#`.
The server also accepts some messages that the files do not allow, such as a
channel name without its `#`.