@agentunion/fastaun 0.4.10 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/_packed_docs/CHANGELOG.md +54 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +2 -2
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
- package/_packed_docs/sdk/09-group-rpc-manual.md +222 -38
- package/_packed_docs/sdk/09-message-rpc-manual.md +50 -26
- package/_packed_docs/sdk/09-payload-reference.md +1 -1
- package/_packed_docs/sdk/09-storage-rpc-manual.md +259 -37
- package/dist/client/delivery.d.ts +10 -1
- package/dist/client/delivery.js +180 -50
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.js +33 -8
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +2 -0
- package/dist/client/v2-e2ee.js +25 -1
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +58 -17
- package/dist/client.js.map +1 -1
- package/dist/service-proxy.js +17 -2
- package/dist/service-proxy.js.map +1 -1
- package/dist/v2/session/keystore.js +6 -6
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -4,6 +4,17 @@ import { isJsonObject } from '../types.js';
|
|
|
4
4
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
5
5
|
const PENDING_ORDERED_LIMIT = 50_000;
|
|
6
6
|
const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
7
|
+
const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
8
|
+
'module_id', 'message_id', 'id', 'seq', 'msg_seq', 'message_type', 'type', 'kind', 'version',
|
|
9
|
+
'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
|
|
10
|
+
'timestamp', 'created_at', 't_server', 'encrypted', 'status', 'delivery_mode',
|
|
11
|
+
'dispatch_mode', 'dispatch', 'device_id', 'slot_id',
|
|
12
|
+
];
|
|
13
|
+
const APP_GROUP_EVENT_ENVELOPE_KEYS = [
|
|
14
|
+
'module_id', 'event_id', 'event_seq', 'seq', 'event_type', 'action', 'group_id',
|
|
15
|
+
'actor_aid', 'sender_aid', 'member_aid', 'target_aid', 'operator_aid',
|
|
16
|
+
'created_at', 'timestamp', 't_server', 'status', 'device_id', 'slot_id',
|
|
17
|
+
];
|
|
7
18
|
function isPromiseLike(value) {
|
|
8
19
|
return Boolean(value && typeof value.then === 'function');
|
|
9
20
|
}
|
|
@@ -52,6 +63,41 @@ export class MessageDeliveryEngine {
|
|
|
52
63
|
queue.delete(oldSeq);
|
|
53
64
|
}
|
|
54
65
|
}
|
|
66
|
+
isGroupEventNamespace(ns) {
|
|
67
|
+
return ns.startsWith('group_event:');
|
|
68
|
+
}
|
|
69
|
+
async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false) {
|
|
70
|
+
const client = this.runtime.client;
|
|
71
|
+
if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
|
|
72
|
+
await this.publishOrderedGroupChanged(payload, source);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (pullResponse) {
|
|
76
|
+
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
77
|
+
if (isPromiseLike(published))
|
|
78
|
+
await published;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const published = client._publishAppEvent(event, payload, source);
|
|
82
|
+
if (isPromiseLike(published))
|
|
83
|
+
await published;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
87
|
+
const client = this.runtime.client;
|
|
88
|
+
if (isJsonObject(payload)) {
|
|
89
|
+
const eventPayload = payload;
|
|
90
|
+
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
91
|
+
if (eventPayload.action === 'dissolved') {
|
|
92
|
+
const groupId = normalizeGroupId(String(eventPayload.group_id ?? '')) || String(eventPayload.group_id ?? '').trim();
|
|
93
|
+
if (groupId)
|
|
94
|
+
client._cleanupDissolvedGroup?.(groupId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const published = client._publishAppEvent('group.changed', payload, source);
|
|
98
|
+
if (isPromiseLike(published))
|
|
99
|
+
await published;
|
|
100
|
+
}
|
|
55
101
|
isInstanceScopedMessageEvent(event) {
|
|
56
102
|
return event === 'message.received'
|
|
57
103
|
|| event === 'message.recalled'
|
|
@@ -74,9 +120,54 @@ export class MessageDeliveryEngine {
|
|
|
74
120
|
return result;
|
|
75
121
|
}
|
|
76
122
|
normalizePublishedMessagePayload(event, payload) {
|
|
77
|
-
if (
|
|
123
|
+
if (this.isInstanceScopedMessageEvent(event)) {
|
|
124
|
+
return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
|
|
125
|
+
}
|
|
126
|
+
if (this.isGroupScopedEvent(event)) {
|
|
127
|
+
return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
|
|
128
|
+
}
|
|
129
|
+
return payload;
|
|
130
|
+
}
|
|
131
|
+
appMessageEnvelope(payload) {
|
|
132
|
+
if (!isJsonObject(payload))
|
|
133
|
+
return {};
|
|
134
|
+
const message = payload;
|
|
135
|
+
const envelope = {};
|
|
136
|
+
for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(message, key))
|
|
138
|
+
envelope[key] = message[key];
|
|
139
|
+
}
|
|
140
|
+
return envelope;
|
|
141
|
+
}
|
|
142
|
+
isGroupScopedEvent(event) {
|
|
143
|
+
return event === 'group.changed';
|
|
144
|
+
}
|
|
145
|
+
appGroupEventEnvelope(payload) {
|
|
146
|
+
if (!isJsonObject(payload))
|
|
147
|
+
return {};
|
|
148
|
+
const groupEvent = payload;
|
|
149
|
+
const envelope = {};
|
|
150
|
+
for (const key of APP_GROUP_EVENT_ENVELOPE_KEYS) {
|
|
151
|
+
if (Object.prototype.hasOwnProperty.call(groupEvent, key))
|
|
152
|
+
envelope[key] = groupEvent[key];
|
|
153
|
+
}
|
|
154
|
+
return envelope;
|
|
155
|
+
}
|
|
156
|
+
attachAppMessageEnvelope(payload) {
|
|
157
|
+
if (!isJsonObject(payload))
|
|
78
158
|
return payload;
|
|
79
|
-
|
|
159
|
+
const result = { ...payload };
|
|
160
|
+
// 兼容期保留顶层信封字段;下一个大版本 0.5.* 将移除这些顶层别名,请通过 envelope.* 访问。
|
|
161
|
+
result.envelope = this.appMessageEnvelope(result);
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
attachAppGroupEventEnvelope(payload) {
|
|
165
|
+
if (!isJsonObject(payload))
|
|
166
|
+
return payload;
|
|
167
|
+
const result = { ...payload };
|
|
168
|
+
// 兼容期保留顶层群事件信封字段;下一个大版本 0.5.* 将移除这些顶层别名,请通过 envelope.* 访问。
|
|
169
|
+
result.envelope = this.appGroupEventEnvelope(result);
|
|
170
|
+
return result;
|
|
80
171
|
}
|
|
81
172
|
stripInternalSenderDeviceFields(payload) {
|
|
82
173
|
if (!isJsonObject(payload))
|
|
@@ -99,6 +190,11 @@ export class MessageDeliveryEngine {
|
|
|
99
190
|
if (msgType !== 'message.recalled' && payloadType !== 'message.recalled')
|
|
100
191
|
return null;
|
|
101
192
|
const event = { ...payload };
|
|
193
|
+
for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
|
|
194
|
+
if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
|
|
195
|
+
event[key] = msg[key];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
102
198
|
const rawIds = event.message_ids;
|
|
103
199
|
let messageIds = Array.isArray(rawIds)
|
|
104
200
|
? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
|
|
@@ -123,8 +219,11 @@ export class MessageDeliveryEngine {
|
|
|
123
219
|
event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
|
|
124
220
|
if ('seq' in msg && !('seq' in event))
|
|
125
221
|
event.seq = msg.seq;
|
|
126
|
-
if ('message_id' in msg
|
|
127
|
-
event.
|
|
222
|
+
if ('message_id' in msg) {
|
|
223
|
+
event.message_id = msg.message_id;
|
|
224
|
+
if (!('tombstone_message_id' in event))
|
|
225
|
+
event.tombstone_message_id = msg.message_id;
|
|
226
|
+
}
|
|
128
227
|
if ('device_id' in msg && !('device_id' in event))
|
|
129
228
|
event.device_id = msg.device_id;
|
|
130
229
|
if ('slot_id' in msg && !('slot_id' in event))
|
|
@@ -148,6 +247,11 @@ export class MessageDeliveryEngine {
|
|
|
148
247
|
if (msgType !== 'group.message_recalled' && payloadType !== 'group.message_recalled')
|
|
149
248
|
return null;
|
|
150
249
|
const event = { ...payload };
|
|
250
|
+
for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
|
|
251
|
+
if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
|
|
252
|
+
event[key] = msg[key];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
151
255
|
const rawIds = event.message_ids;
|
|
152
256
|
let messageIds = Array.isArray(rawIds)
|
|
153
257
|
? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
|
|
@@ -170,8 +274,11 @@ export class MessageDeliveryEngine {
|
|
|
170
274
|
event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
|
|
171
275
|
if ('seq' in msg)
|
|
172
276
|
event.seq = msg.seq;
|
|
173
|
-
if ('message_id' in msg
|
|
174
|
-
event.
|
|
277
|
+
if ('message_id' in msg) {
|
|
278
|
+
event.message_id = msg.message_id;
|
|
279
|
+
if (!('tombstone_message_id' in event))
|
|
280
|
+
event.tombstone_message_id = msg.message_id;
|
|
281
|
+
}
|
|
175
282
|
return event;
|
|
176
283
|
}
|
|
177
284
|
groupRecallDedupKey(groupId, payload) {
|
|
@@ -703,20 +810,27 @@ export class MessageDeliveryEngine {
|
|
|
703
810
|
if (eventObjects.length > 0) {
|
|
704
811
|
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
|
|
705
812
|
}
|
|
706
|
-
const
|
|
813
|
+
const cursor = isJsonObject(result.cursor)
|
|
814
|
+
? result.cursor
|
|
815
|
+
: null;
|
|
816
|
+
const cursorCurrentSeq = Number(cursor?.current_seq ?? 0);
|
|
817
|
+
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_event_seq', 'retention_floor_event_seq'), Number.isFinite(cursorCurrentSeq) ? cursorCurrentSeq : 0);
|
|
707
818
|
if (retentionFloor > 0) {
|
|
708
819
|
const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
|
|
709
820
|
if (contigBeforeFloor < retentionFloor) {
|
|
710
|
-
client._clientLog.info(`group.pull_events
|
|
821
|
+
client._clientLog.info(`group.pull_events cursor/floor advance: ns=${ns} contiguous=${contigBeforeFloor} -> cursor.current_seq=${cursorCurrentSeq} floor=${retentionFloor}`);
|
|
711
822
|
client._seqTracker.forceContiguousSeq(ns, retentionFloor);
|
|
712
823
|
}
|
|
713
824
|
}
|
|
714
825
|
const eventSeqs = [];
|
|
826
|
+
let hasDissolvedEvent = false;
|
|
715
827
|
for (const evt of eventObjects) {
|
|
716
828
|
const eventSeq = Number(evt.event_seq ?? 0);
|
|
717
829
|
if (Number.isFinite(eventSeq) && eventSeq > 0)
|
|
718
830
|
eventSeqs.push(eventSeq);
|
|
719
831
|
evt._from_gap_fill = true;
|
|
832
|
+
if (evt.action === 'dissolved')
|
|
833
|
+
hasDissolvedEvent = true;
|
|
720
834
|
const et = String(evt.event_type ?? '');
|
|
721
835
|
if (et !== 'group.message_created') {
|
|
722
836
|
const cs = evt.client_signature;
|
|
@@ -728,19 +842,19 @@ export class MessageDeliveryEngine {
|
|
|
728
842
|
evt._verified = await client._verifyEventSignatureAsync(evt, cs);
|
|
729
843
|
}
|
|
730
844
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
client._markPulledSeqDelivered(ns, eventSeq);
|
|
845
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
846
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
|
|
847
|
+
}
|
|
735
848
|
}
|
|
736
849
|
filled += 1;
|
|
737
850
|
}
|
|
738
|
-
const
|
|
739
|
-
|
|
851
|
+
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
852
|
+
await this.drainOrderedMessages(ns);
|
|
853
|
+
if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
|
|
740
854
|
this.saveSeqTrackerState();
|
|
741
855
|
}
|
|
742
|
-
if (eventObjects.length > 0 &&
|
|
743
|
-
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns,
|
|
856
|
+
if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
|
|
857
|
+
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
744
858
|
client._transport.call('group.ack_events', {
|
|
745
859
|
group_id: groupId,
|
|
746
860
|
event_seq: ackSeq,
|
|
@@ -771,36 +885,65 @@ export class MessageDeliveryEngine {
|
|
|
771
885
|
}
|
|
772
886
|
}
|
|
773
887
|
}
|
|
774
|
-
handleGroupChangedEventSeq(data, groupId) {
|
|
888
|
+
async handleGroupChangedEventSeq(data, groupId) {
|
|
775
889
|
const client = this.runtime.client;
|
|
776
890
|
let needPull = false;
|
|
777
891
|
const rawEventSeq = data.event_seq;
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
892
|
+
const eventSeq = Number(rawEventSeq);
|
|
893
|
+
if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
|
|
894
|
+
await this.publishOrderedGroupChanged(data, 'legacy');
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const ns = `group_event:${groupId}`;
|
|
898
|
+
if (this.isSelfJoinGroupChanged(data)) {
|
|
785
899
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
client.
|
|
789
|
-
|
|
790
|
-
event_seq: ackSeq,
|
|
791
|
-
device_id: client._deviceId,
|
|
792
|
-
slot_id: client._slotId,
|
|
793
|
-
}, undefined, undefined, true).catch((e) => {
|
|
794
|
-
client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
795
|
-
});
|
|
900
|
+
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
901
|
+
if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
|
|
902
|
+
client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
|
|
903
|
+
client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
|
|
796
904
|
}
|
|
797
905
|
}
|
|
906
|
+
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
907
|
+
if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
908
|
+
client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, data);
|
|
912
|
+
needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
|
|
913
|
+
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
914
|
+
await this.drainOrderedMessages(ns);
|
|
915
|
+
if (ackContig > 0 && ackContig !== contigBefore) {
|
|
916
|
+
if (data.action !== 'dissolved')
|
|
917
|
+
this.saveSeqTrackerState();
|
|
918
|
+
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
919
|
+
client._transport.call('group.ack_events', {
|
|
920
|
+
group_id: groupId,
|
|
921
|
+
event_seq: ackSeq,
|
|
922
|
+
device_id: client._deviceId,
|
|
923
|
+
slot_id: client._slotId,
|
|
924
|
+
}, undefined, undefined, true).catch((e) => {
|
|
925
|
+
client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
926
|
+
});
|
|
927
|
+
}
|
|
798
928
|
if (needPull && groupId && !data._from_gap_fill) {
|
|
799
929
|
this.fillGroupEventGap(groupId).catch((exc) => {
|
|
800
930
|
client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`);
|
|
801
931
|
});
|
|
802
932
|
}
|
|
803
933
|
}
|
|
934
|
+
isSelfJoinGroupChanged(data) {
|
|
935
|
+
const action = String(data.action ?? '').trim();
|
|
936
|
+
if (!['member_added', 'joined', 'join_approved', 'invite_code_used'].includes(action))
|
|
937
|
+
return false;
|
|
938
|
+
const selfAid = String(this.runtime.client._aid ?? '').trim();
|
|
939
|
+
if (!selfAid)
|
|
940
|
+
return false;
|
|
941
|
+
const joinedAid = String(data.joined_aid ?? data.member_aid ?? data.aid ?? '').trim();
|
|
942
|
+
if (joinedAid === selfAid)
|
|
943
|
+
return true;
|
|
944
|
+
const actorAid = String(data.actor_aid ?? '').trim();
|
|
945
|
+
return !joinedAid && ['joined', 'invite_code_used'].includes(action) && actorAid === selfAid;
|
|
946
|
+
}
|
|
804
947
|
async onV2PushNotification(data) {
|
|
805
948
|
const client = this.runtime.client;
|
|
806
949
|
if (!client._v2Session)
|
|
@@ -1205,16 +1348,7 @@ export class MessageDeliveryEngine {
|
|
|
1205
1348
|
client._markOrderedSeqDelivered(ns, seq);
|
|
1206
1349
|
continue;
|
|
1207
1350
|
}
|
|
1208
|
-
|
|
1209
|
-
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(item.event, item.payload, 'ordered-drain'));
|
|
1210
|
-
if (isPromiseLike(published))
|
|
1211
|
-
await published;
|
|
1212
|
-
}
|
|
1213
|
-
else {
|
|
1214
|
-
const published = client._publishAppEvent(item.event, item.payload, 'ordered-drain');
|
|
1215
|
-
if (isPromiseLike(published))
|
|
1216
|
-
await published;
|
|
1217
|
-
}
|
|
1351
|
+
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
|
|
1218
1352
|
this.markPublishedSeq(ns, seq);
|
|
1219
1353
|
client._markOrderedSeqDelivered(ns, seq);
|
|
1220
1354
|
client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
@@ -1227,9 +1361,7 @@ export class MessageDeliveryEngine {
|
|
|
1227
1361
|
const seqNum = Number(seq);
|
|
1228
1362
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
1229
1363
|
client._clientLog.debug(`publish ordered direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
1230
|
-
|
|
1231
|
-
if (isPromiseLike(published))
|
|
1232
|
-
await published;
|
|
1364
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
|
|
1233
1365
|
return true;
|
|
1234
1366
|
}
|
|
1235
1367
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
@@ -1263,9 +1395,7 @@ export class MessageDeliveryEngine {
|
|
|
1263
1395
|
queue?.delete(seqNum);
|
|
1264
1396
|
if (queue && queue.size === 0)
|
|
1265
1397
|
client._pendingOrderedMsgs.delete(ns);
|
|
1266
|
-
|
|
1267
|
-
if (isPromiseLike(published))
|
|
1268
|
-
await published;
|
|
1398
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
|
|
1269
1399
|
this.markPublishedSeq(ns, seqNum);
|
|
1270
1400
|
client._markOrderedSeqDelivered(ns, seqNum);
|
|
1271
1401
|
client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|