summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2026-04-24 14:47:11 -0300
committerEuAndreh <eu@euandre.org>2026-04-24 14:47:11 -0300
commitec4b8a270bd2ab6b72efcc03be78006fe100f5f6 (patch)
tree7db283b1fb360ce0f1f9addac2524e62dc0a13fb
parentImplement core IRC commands: QUIT, AWAY, NOTICE, WHOIS, USERHOST (diff)
downloadpapod-ec4b8a270bd2ab6b72efcc03be78006fe100f5f6.tar.gz
papod-ec4b8a270bd2ab6b72efcc03be78006fe100f5f6.tar.xz
m
-rw-r--r--doc/edit-proposal.md201
-rw-r--r--src/schema.edn402
2 files changed, 603 insertions, 0 deletions
diff --git a/doc/edit-proposal.md b/doc/edit-proposal.md
new file mode 100644
index 0000000..806e674
--- /dev/null
+++ b/doc/edit-proposal.md
@@ -0,0 +1,201 @@
+---
+title: Message editing
+layout: spec
+work-in-progress: true
+copyrights:
+ -
+ name: "Andre Hora"
+ period: "2026"
+---
+
+## Notes for implementing work-in-progress version
+
+This is a work-in-progress specification.
+
+Software implementing this work-in-progress specification MUST NOT use the
+unprefixed `message-editing` capability name.
+Instead, implementations SHOULD use the `draft/message-editing` capability
+name to be interoperable with other software implementing a compatible
+work-in-progress version.
+
+The final version of the specification will use an unprefixed capability name.
+
+This proposal is adapted from the direction of
+[IRCv3 PR #425](https://github.com/ircv3/ircv3-specifications/pull/425)
+and mirrors the structure of [`draft/message-redaction`][].
+
+## Introduction
+
+This specification enables messages to be edited after they are sent.
+Use cases include correcting typos, updating information, and amending
+accidentally sent content. These are cosmetic use cases and do not provide any
+operational security guarantees. The original message content remains visible to
+clients that do not support this capability, and servers MAY retain the original
+content in history.
+
+## Architecture
+
+### Dependencies
+
+Clients wishing to use this capability MUST negotiate the [`message-tags`][]
+capability with the server.
+Clients SHOULD negotiate the [`echo-message`][] capability in order to receive
+message IDs for their own messages, so they can be edited.
+
+### Capability
+
+This specification adds the `draft/message-editing` capability.
+Clients MUST ignore this capability's value, if any.
+
+Implementations that negotiate this capability indicate that they are
+capable of handling the command described below.
+
+### Command
+
+To edit a message, a client MUST negotiate the `draft/message-editing`
+capability and send an `EDIT` command to a target nickname or channel.
+The command is defined as follows:
+
+ EDIT <target> <msgid> :<new content>
+
+Where `<msgid>` is the id of the message to be edited, which MUST be a
+`PRIVMSG` or `NOTICE`.
+
+The `<new content>` parameter is required and contains the full replacement text.
+As the last parameter, it MAY contain spaces.
+
+If the client is authorised to edit the message, the server:
+
+* SHOULD forward this `EDIT`, with an appropriate prefix, to the target
+ recipients that have negotiated the `draft/message-editing` capability, in the
+ same way as PRIVMSG messages.
+* MUST NOT forward this `EDIT` to target recipients that have not negotiated
+ this capability (see "Fallback" below).
+* SHOULD assign a new `msgid` to the EDIT event itself, distinct from the
+ original message's `msgid`.
+
+### Chat history
+
+After a message is edited, [`chathistory`][] responses SHOULD include the
+`EDIT` message after the original message. The original message's content
+SHOULD be preserved as-is, with the `EDIT` indicating the replacement.
+
+Clients reconstructing a conversation from history SHOULD display only the
+most recent edit for each original message.
+
+### Errors
+
+This specification defines `FAIL` messages using the [standard replies][]
+framework for notifying clients of errors with message editing.
+The following codes are defined, with sample plain text descriptions.
+
+* `FAIL EDIT INVALID_TARGET <target> :You cannot edit messages in <target>`
+* `FAIL EDIT EDIT_FORBIDDEN <target> <target-msgid> :You are not authorised to edit this message`
+* `FAIL EDIT EDIT_WINDOW_EXPIRED <target> <target-msgid> <window> :You can no longer edit this message`
+* `FAIL EDIT UNKNOWN_MSGID <target> <target-msgid> :This message does not exist or is too old`
+
+## Client implementation considerations
+
+It is strongly RECOMMENDED that clients provide visible edit history to users.
+This helps ensure accountability and mitigates abuse through surreptitious
+editing. This could be done via a tooltip showing the original text, an
+"(edited)" indicator, or a separate log.
+
+Clients SHOULD display the edited content as the primary message text, with
+an indication that the message was modified.
+
+For the purposes of user interface, clients MAY assume that their own messages
+are editable. However, this will not always be the case. Pending a mechanism
+for discovering edit permissions, clients SHOULD allow users to attempt to edit
+their own messages via some mechanism.
+
+When an `EDIT` command's `msgid` parameter references a known message not in
+the `target`'s history, clients MUST ignore it.
+
+## Server implementation considerations
+
+This section is non-normative.
+
+Servers SHOULD restrict editing to the original message author by default.
+Channel operators or server administrators MAY be granted edit permissions
+at the server's discretion, though this is less common than for redaction.
+
+Servers MAY impose a time window after which edits are no longer permitted.
+The `EDIT_WINDOW_EXPIRED` error code is provided for this purpose.
+
+Servers MAY choose to store the full edit history (all versions of a message)
+or only the latest version. Datomic-backed implementations naturally retain
+full history via immutable datoms.
+
+### Message validation
+
+To implement validation, servers require a mechanism for determining whether
+a particular edit action is permitted. The same considerations as
+[`draft/message-redaction`][] apply: servers can look up message properties
+from the ID, or encode required properties within the message ID itself.
+
+### Fallback
+
+Server implementations might choose to inform clients that haven't negotiated
+the capability that an edit has taken place. The fallback method used (if any)
+is left up to server implementations, but could take the form of a standard
+NOTICE or PRIVMSG with information about the action. For example:
+
+ :irc.example.com NOTICE #channel :nickname edited a message from 5 seconds ago
+
+Alternatively, servers could re-send the message as a new PRIVMSG with an
+indication that it replaces a prior message, though this risks confusion
+with standard clients.
+
+## Security considerations
+
+The ability to edit messages does not offer any information or operational
+security guarantees. Once a message has been sent, assume that the original
+content will remain visible to any recipients or servers, whether or not it
+is subsequently edited. Above all else, clients that do not support this
+specification will not see any changes to the original message.
+
+Servers SHOULD retain the original message content in their history backend,
+even after an edit, to support audit and accountability use cases.
+
+## Examples
+
+Editing a PRIVMSG:
+
+ C: PRIVMSG #channel :an exmple with a tpyo
+ S: @msgid=123 :nick!u@h PRIVMSG #channel :an exmple with a tpyo
+ C: EDIT #channel 123 :an example with no typos
+ S: @msgid=456 :nick!u@h EDIT #channel 123 :an example with no typos
+
+Editing a message in a DM:
+
+ C: PRIVMSG friend :meet me at 3pm
+ S: @msgid=789 :nick!u@h PRIVMSG friend :meet me at 3pm
+ C: EDIT friend 789 :meet me at 4pm actually
+ S: @msgid=790 :nick!u@h EDIT friend 789 :meet me at 4pm actually
+
+Failed edit (not authorised):
+
+ C: EDIT #channel 123 :trying to edit someone else's message
+ S: FAIL EDIT EDIT_FORBIDDEN #channel 123 :You are not authorised to edit this message
+
+Failed edit (window expired):
+
+ C: EDIT #channel 123 :too late
+ S: FAIL EDIT EDIT_WINDOW_EXPIRED #channel 123 300 :You can no longer edit this message
+
+Interaction with chat history:
+
+ C: CHATHISTORY LATEST #channel * 50
+ S: BATCH +abc chathistory #channel
+ S: @batch=abc;msgid=123;time=2026-04-22T10:00:00.000Z :nick!u@h PRIVMSG #channel :an exmple with a tpyo
+ S: @batch=abc;msgid=456;time=2026-04-22T10:00:05.000Z :nick!u@h EDIT #channel 123 :an example with no typos
+ S: BATCH -abc
+
+
+[`echo-message`]: ../extensions/echo-message.html
+[standard replies]: ../extensions/standard-replies.html
+[`message-tags`]: ../extensions/message-tags.html
+[`msgid`]: ../extensions/message-ids.html
+[`chathistory`]: ../extensions/chathistory.html
+[`draft/message-redaction`]: ../extensions/message-redaction.html
diff --git a/src/schema.edn b/src/schema.edn
new file mode 100644
index 0000000..61cf5fd
--- /dev/null
+++ b/src/schema.edn
@@ -0,0 +1,402 @@
+[#:db{:ident :papod.network/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ {:db/ident :papod.network/name,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/unique :db.unique/value,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.network/description,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ {:db/ident :papod.network/type,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.network/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.member/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.member/network,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.member/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.member/status,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.member/joined-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.member/network+nick,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs [:papod.member/network :papod.member/nick]}
+ #:db{:ident :papod.member-role/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.member-role/member,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.member-role/role,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.member-role/member+role,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs [:papod.member-role/member :papod.member-role/role]}
+ #:db{:ident :papod.channel/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.channel/network,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.channel/name,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value}
+ #:db{:ident :papod.channel.type/public}
+ #:db{:ident :papod.channel.type/private}
+ #:db{:ident :papod.channel.type/unlisted}
+ #:db{:ident :papod.channel/type,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.channel/label,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.channel/description,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.channel/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ {:db/ident :papod.channel/owner,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.channel/topic,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.channel/last-event-seq,
+ :valueType :db.type/long,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.access/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.access/channel,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.access/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.access/level,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.access/channel+nick+level,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs
+ [:papod.access/channel :papod.access/nick :papod.access/level]}
+ #:db{:ident :papod.memo/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ {:db/ident :papod.memo/from,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.memo/to,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.memo/content,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.memo/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.memo/read?,
+ :valueType :db.type/boolean,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.membership/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.membership/channel,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.membership/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.membership/joined-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.membership/channel+nick,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs [:papod.membership/channel :papod.membership/nick]}
+ #:db{:ident :papod.session/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ {:db/ident :papod.session/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.session/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.session/finished-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.connection/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.connection/process,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.connection/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.connection/finished-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.logon/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.logon/session,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.logon/connection,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.logon/session+connection,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs [:papod.logon/session :papod.logon/connection]}
+ #:db{:ident :papod.process/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.process/pid,
+ :valueType :db.type/long,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.process/hostname,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.process/started-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.event/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.event/channel,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.event/target-nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.event/type,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.event/source-nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.event/payload,
+ :valueType :db.type/string,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.event/created-at,
+ :valueType :db.type/instant,
+ :cardinality :db.cardinality/one}
+ #:db{:ident :papod.event/seq,
+ :valueType :db.type/long,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.event/reply-to,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.event/edit-of,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.event/delete-of,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ #:db{:ident :papod.reaction/id,
+ :valueType :db.type/uuid,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity}
+ #:db{:ident :papod.reaction/event,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.reaction/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.reaction/emoji,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.reaction/event+nick+emoji,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/value,
+ :tupleAttrs
+ [:papod.reaction/event
+ :papod.reaction/nick
+ :papod.reaction/emoji]}
+ #:db{:ident :papod.read-marker/channel,
+ :valueType :db.type/ref,
+ :cardinality :db.cardinality/one,
+ :index true}
+ {:db/ident :papod.read-marker/nick,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db/index true,
+ :db.attr/preds papod/non-empty?}
+ {:db/ident :papod.read-marker/timestamp,
+ :db/valueType :db.type/string,
+ :db/cardinality :db.cardinality/one,
+ :db.attr/preds papod/non-empty?}
+ #:db{:ident :papod.read-marker/channel+nick,
+ :valueType :db.type/tuple,
+ :cardinality :db.cardinality/one,
+ :unique :db.unique/identity,
+ :tupleAttrs [:papod.read-marker/channel :papod.read-marker/nick]}
+ {:db/ident :papod.network/attrs,
+ :db.entity/attrs
+ #{:papod.network/id
+ :papod.network/type
+ :papod.network/created-at
+ :papod.network/name}}
+ {:db/ident :papod.member/attrs,
+ :db.entity/attrs
+ #{:papod.member/id
+ :papod.member/network
+ :papod.member/joined-at
+ :papod.member/status
+ :papod.member/nick}}
+ {:db/ident :papod.member-role/attrs,
+ :db.entity/attrs
+ #{:papod.member-role/id
+ :papod.member-role/role
+ :papod.member-role/member}}
+ {:db/ident :papod.channel/attrs,
+ :db.entity/attrs
+ #{:papod.channel/type
+ :papod.channel/id
+ :papod.channel/network
+ :papod.channel/created-at}}
+ {:db/ident :papod.access/attrs,
+ :db.entity/attrs
+ #{:papod.access/nick
+ :papod.access/channel
+ :papod.access/level
+ :papod.access/id}}
+ {:db/ident :papod.memo/attrs,
+ :db.entity/attrs
+ #{:papod.memo/id
+ :papod.memo/created-at
+ :papod.memo/from
+ :papod.memo/content
+ :papod.memo/to
+ :papod.memo/read?}}
+ {:db/ident :papod.membership/attrs,
+ :db.entity/attrs
+ #{:papod.membership/id
+ :papod.membership/nick
+ :papod.membership/joined-at
+ :papod.membership/channel}}
+ {:db/ident :papod.session/attrs,
+ :db.entity/attrs
+ #{:papod.session/created-at :papod.session/id :papod.session/nick}}
+ {:db/ident :papod.process/attrs,
+ :db.entity/attrs
+ #{:papod.process/started-at
+ :papod.process/hostname
+ :papod.process/pid
+ :papod.process/id}}
+ {:db/ident :papod.connection/attrs,
+ :db.entity/attrs
+ #{:papod.connection/process
+ :papod.connection/created-at
+ :papod.connection/id}}
+ {:db/ident :papod.logon/attrs,
+ :db.entity/attrs
+ #{:papod.logon/connection :papod.logon/session :papod.logon/id}}
+ {:db/ident :papod.event/attrs,
+ :db.entity/attrs
+ #{:papod.event/type
+ :papod.event/source-nick
+ :papod.event/id
+ :papod.event/created-at}}
+ {:db/ident :papod.reaction/attrs,
+ :db.entity/attrs
+ #{:papod.reaction/emoji
+ :papod.reaction/nick
+ :papod.reaction/id
+ :papod.reaction/event}}
+ #:db{:ident :papod.event/add-seq,
+ :fn
+ {:lang :clojure,
+ :imports [],
+ :requires [],
+ :params [db event-eid channel-uuid],
+ :code
+ "^{:line 38, :column 6} (let [channel-eid ^{:line 38, :column 24} (or ^{:line 38, :column 28} (ffirst ^{:line 39, :column 29} (d/q (quote {:find [?e], :in [$ ?id], :where [[?e :papod.channel/id ?id]]}) db channel-uuid)) ^{:line 43, :column 28} (d/tempid :db.part/user)) last-seq ^{:line 44, :column 24} (or ^{:line 44, :column 28} (:papod.channel/last-event-seq ^{:line 45, :column 29} (d/entity db channel-eid)) 0) new-seq ^{:line 46, :column 24} (inc last-seq)] [[:db/add channel-eid :papod.channel/id channel-uuid] [:db/add channel-eid :papod.channel/last-event-seq new-seq] [:db/add event-eid :papod.event/seq new-seq]])",
+ :fnref #<Delay@65b5b5ed: :not-delivered>}}
+ #:db{:ident :papod.access/ensure-unique,
+ :fn
+ {:lang :clojure,
+ :imports [],
+ :requires [],
+ :params [db channel nick level],
+ :code
+ "^{:line 56, :column 6} (when ^{:line 56, :column 12} (seq ^{:line 57, :column 13} (d/q (quote {:find [?a], :in [$ ?ch ?n ?l], :where [[?a :papod.access/channel ?ch] [?a :papod.access/nick ?n] [?a :papod.access/level ?l]]}) db channel nick level)) ^{:line 63, :column 8} (d/cancel #:cognitect.anomalies{:category :cognitect.anomalies/conflict, :message \"Duplicate access entry\"}))",
+ :fnref #<Delay@df432ec: :not-delivered>}}]