@cello-protocol/daemon 0.0.131 → 0.0.133
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/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +29 -11
- package/dist/content-park.js.map +1 -1
- package/dist/daemon.d.ts +11 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +398 -18
- package/dist/daemon.js.map +1 -1
- package/dist/document-ack-inbound.d.ts +57 -0
- package/dist/document-ack-inbound.d.ts.map +1 -0
- package/dist/document-ack-inbound.js +174 -0
- package/dist/document-ack-inbound.js.map +1 -0
- package/dist/document-control-notifier.d.ts +61 -0
- package/dist/document-control-notifier.d.ts.map +1 -0
- package/dist/document-control-notifier.js +70 -0
- package/dist/document-control-notifier.js.map +1 -0
- package/dist/document-delivery-transport.d.ts +94 -0
- package/dist/document-delivery-transport.d.ts.map +1 -0
- package/dist/document-delivery-transport.js +179 -0
- package/dist/document-delivery-transport.js.map +1 -0
- package/dist/document-delivery.d.ts +181 -0
- package/dist/document-delivery.d.ts.map +1 -0
- package/dist/document-delivery.js +289 -0
- package/dist/document-delivery.js.map +1 -0
- package/dist/document-frame-router.d.ts +210 -0
- package/dist/document-frame-router.d.ts.map +1 -0
- package/dist/document-frame-router.js +396 -0
- package/dist/document-frame-router.js.map +1 -0
- package/dist/document-handlers.d.ts +47 -0
- package/dist/document-handlers.d.ts.map +1 -0
- package/dist/document-handlers.js +657 -0
- package/dist/document-handlers.js.map +1 -0
- package/dist/document-handshake.d.ts +156 -0
- package/dist/document-handshake.d.ts.map +1 -0
- package/dist/document-handshake.js +398 -0
- package/dist/document-handshake.js.map +1 -0
- package/dist/document-inbound.d.ts +91 -0
- package/dist/document-inbound.d.ts.map +1 -0
- package/dist/document-inbound.js +290 -0
- package/dist/document-inbound.js.map +1 -0
- package/dist/document-layer.d.ts +137 -0
- package/dist/document-layer.d.ts.map +1 -0
- package/dist/document-layer.js +255 -0
- package/dist/document-layer.js.map +1 -0
- package/dist/document-lifecycle.d.ts +125 -0
- package/dist/document-lifecycle.d.ts.map +1 -0
- package/dist/document-lifecycle.js +433 -0
- package/dist/document-lifecycle.js.map +1 -0
- package/dist/document-live-docs.d.ts +58 -0
- package/dist/document-live-docs.d.ts.map +1 -0
- package/dist/document-live-docs.js +126 -0
- package/dist/document-live-docs.js.map +1 -0
- package/dist/document-notify.d.ts +173 -0
- package/dist/document-notify.d.ts.map +1 -0
- package/dist/document-notify.js +438 -0
- package/dist/document-notify.js.map +1 -0
- package/dist/document-publish.d.ts +67 -0
- package/dist/document-publish.d.ts.map +1 -0
- package/dist/document-publish.js +149 -0
- package/dist/document-publish.js.map +1 -0
- package/dist/document-reachability.d.ts +42 -0
- package/dist/document-reachability.d.ts.map +1 -0
- package/dist/document-reachability.js +80 -0
- package/dist/document-reachability.js.map +1 -0
- package/dist/document-rejection.d.ts +240 -0
- package/dist/document-rejection.d.ts.map +1 -0
- package/dist/document-rejection.js +407 -0
- package/dist/document-rejection.js.map +1 -0
- package/dist/document-store.d.ts +154 -8
- package/dist/document-store.d.ts.map +1 -1
- package/dist/document-store.js +462 -4
- package/dist/document-store.js.map +1 -1
- package/dist/document-write-path.d.ts.map +1 -1
- package/dist/document-write-path.js +10 -43
- package/dist/document-write-path.js.map +1 -1
- package/dist/inbound-sessions.d.ts +25 -0
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +60 -0
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/initiate-session-handler.d.ts +24 -1
- package/dist/initiate-session-handler.d.ts.map +1 -1
- package/dist/initiate-session-handler.js +35 -9
- package/dist/initiate-session-handler.js.map +1 -1
- package/dist/ipc-server.d.ts +11 -1
- package/dist/ipc-server.d.ts.map +1 -1
- package/dist/ipc-server.js +7 -1
- package/dist/ipc-server.js.map +1 -1
- package/dist/line-lcs.d.ts +51 -0
- package/dist/line-lcs.d.ts.map +1 -0
- package/dist/line-lcs.js +71 -0
- package/dist/line-lcs.js.map +1 -0
- package/dist/notification-handlers.d.ts +2 -1
- package/dist/notification-handlers.d.ts.map +1 -1
- package/dist/notification-handlers.js +11 -2
- package/dist/notification-handlers.js.map +1 -1
- package/dist/outbound-sessions.d.ts +2 -0
- package/dist/outbound-sessions.d.ts.map +1 -1
- package/dist/outbound-sessions.js +23 -2
- package/dist/outbound-sessions.js.map +1 -1
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +3 -2
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-node-manager.d.ts +15 -0
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +188 -9
- package/dist/session-node-manager.js.map +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +16 -0
- package/dist/vocabulary.js.map +1 -1
- package/dist/wire-content-hash.d.ts +27 -0
- package/dist/wire-content-hash.d.ts.map +1 -0
- package/dist/wire-content-hash.js +37 -0
- package/dist/wire-content-hash.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-DOC-DELIVERY-1 — daemon-autonomous delivery (§16.4).
|
|
3
|
+
*
|
|
4
|
+
* Publish writes the envelope to the log and returns. Delivery is somebody else's job, and that
|
|
5
|
+
* somebody is this worker: it derives what is pending FROM THE LOG, checks the peer is reachable
|
|
6
|
+
* before dialing, opens or reuses a session itself, delivers, and records the ack — with zero agent
|
|
7
|
+
* attention on either end. Two agents in opposite time zones sync overnight without either agent
|
|
8
|
+
* doing anything.
|
|
9
|
+
*
|
|
10
|
+
* ── PENDING IS DERIVED, NEVER HELD ────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* "Unacknowledged envelopes I authored" is a WHERE clause over the log, and that is the whole
|
|
13
|
+
* definition. A queue in memory does not survive a restart — the daemon is a long-running local
|
|
14
|
+
* process that gets restarted routinely — and a queue in its own table is a second source of truth
|
|
15
|
+
* that can disagree with the log about what was sent. The attempt counter and the next-attempt
|
|
16
|
+
* time live on the envelope row for the same reason: a backoff that resets on restart is not a
|
|
17
|
+
* backoff, and a daemon restarting in a reconnect loop would hammer an unreachable peer at full
|
|
18
|
+
* rate forever.
|
|
19
|
+
*
|
|
20
|
+
* ── WHY LOOKUP BEFORE DIAL ────────────────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* §16.4's design is presence-driven push, but there is no presence subscription today (parked,
|
|
23
|
+
* M14-P4). Without one, the honest substitute is: ask the directory whether the peer is reachable,
|
|
24
|
+
* and if it is not, do not burn a dial — schedule a retry on a capped backoff. Dialing an offline
|
|
25
|
+
* peer to find out it is offline is the same information at much higher cost, and it is the cost
|
|
26
|
+
* that would be paid on every pending envelope, on every tick, for as long as the peer is away.
|
|
27
|
+
*/
|
|
28
|
+
import type { DocumentStore, DocumentEnvelopeRow } from "./document-store.js";
|
|
29
|
+
import type { Logger } from "./types.js";
|
|
30
|
+
/**
|
|
31
|
+
* The transport seam. Narrow on purpose (M4 rule: add to an interface only when a failing test or
|
|
32
|
+
* a production behaviour requires it) — the worker's job is scheduling and bookkeeping, and every
|
|
33
|
+
* dial-level concern belongs to the adapter behind this.
|
|
34
|
+
*/
|
|
35
|
+
export interface DocumentDeliveryTransport {
|
|
36
|
+
/**
|
|
37
|
+
* Is the peer reachable right now? `discovery_lookup` today. Returns false rather than throwing
|
|
38
|
+
* for an ordinary "not online"; a throw means the LOOKUP failed, which is a different fact and
|
|
39
|
+
* must not be recorded as the peer being away.
|
|
40
|
+
*/
|
|
41
|
+
isPeerReachable(peerAgentId: string, correlationId: string): Promise<{
|
|
42
|
+
reachable: boolean;
|
|
43
|
+
unknownAgent: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Deliver one envelope over a session the transport opens or reuses (§16.4: daemon-chooses by
|
|
47
|
+
* default; `sessionHint` is the one case with audit value — the agent is mid-conversation about
|
|
48
|
+
* the document and wants the discussion and the change in one sealed record).
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* `ok` means the peer's daemon ANSWERED about this envelope — it is not "the peer liked it".
|
|
52
|
+
* `admitted: false` is a rejection (§3.2's `0x05`), and a rejection IS an ack for delivery
|
|
53
|
+
* purposes: the peer has decided, so there is nothing left to retry. Without the distinction the
|
|
54
|
+
* adapter has to map a rejection onto one of two lies — `ok: true` makes the delivery record say
|
|
55
|
+
* the peer admitted content it refused, and `ok: false` redelivers an envelope the peer has
|
|
56
|
+
* already ruled on, forever, re-triggering their gate and their retry counter until the document
|
|
57
|
+
* stalls for reasons the operator cannot see.
|
|
58
|
+
*
|
|
59
|
+
* `sessionOpened` distinguishes a session this delivery opened from one it reused — the audit
|
|
60
|
+
* distinction §16.4 cares about, and unrecoverable after the fact.
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Send already-encoded bytes to a peer over the same open-or-reuse-then-seal path `deliver` uses.
|
|
64
|
+
*
|
|
65
|
+
* Exists for the frames that are NOT in the envelope log and therefore have no delivery record to
|
|
66
|
+
* schedule: a proposal (there is no document yet), an ack, a rejection. Those still have to reach
|
|
67
|
+
* the peer, and the alternative — a second implementation of session acquisition — would drift on
|
|
68
|
+
* exactly the things that matter here: which session gets reused, and whether one this daemon
|
|
69
|
+
* opened gets sealed.
|
|
70
|
+
*
|
|
71
|
+
* `documentId` is for the log line only; nothing about the send depends on it.
|
|
72
|
+
*/
|
|
73
|
+
sendBytes(input: {
|
|
74
|
+
peerAgentId: string;
|
|
75
|
+
documentId: string;
|
|
76
|
+
bytes: Uint8Array;
|
|
77
|
+
sessionHint?: string;
|
|
78
|
+
correlationId: string;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
ok: true;
|
|
81
|
+
sessionId: string;
|
|
82
|
+
sessionOpened: boolean;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
reason: string;
|
|
86
|
+
detail?: string;
|
|
87
|
+
}>;
|
|
88
|
+
deliver(input: {
|
|
89
|
+
peerAgentId: string;
|
|
90
|
+
documentId: string;
|
|
91
|
+
envelope: DocumentEnvelopeRow;
|
|
92
|
+
sessionHint?: string;
|
|
93
|
+
correlationId: string;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
ok: true;
|
|
96
|
+
sessionId: string;
|
|
97
|
+
sessionOpened: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* `true` admitted, `false` rejected — both are ACKS, the peer has decided. `null` means the
|
|
100
|
+
* envelope LEFT (or was parked for an offline peer) and no answer has come back yet.
|
|
101
|
+
*
|
|
102
|
+
* The third state is not hedging; both two-valued answers are dishonest for a send whose
|
|
103
|
+
* outcome is unknown. `true` marks the envelope acknowledged in the log while the peer may
|
|
104
|
+
* never have applied it — and the log being right about what the peer holds is the entire
|
|
105
|
+
* reason pending is derived from it. `false` counts a send that WORKED as a failure and
|
|
106
|
+
* re-sends content already in flight.
|
|
107
|
+
*/
|
|
108
|
+
admitted: boolean | null;
|
|
109
|
+
rejectionReason?: string;
|
|
110
|
+
} | {
|
|
111
|
+
ok: false;
|
|
112
|
+
reason: string;
|
|
113
|
+
detail?: string;
|
|
114
|
+
}>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Backoff schedule in ms: ~1s, 5s, 30s, 2m, 10m, then the last entry repeats.
|
|
118
|
+
*
|
|
119
|
+
* The final entry IS the cap — there is no separate ceiling constant. There was one, set to 900s,
|
|
120
|
+
* and it was unreachable: the index clamp meant the schedule never produced a value above 600s, so
|
|
121
|
+
* the exported "cap" was a number the code could not emit and the tests were using it as a synonym
|
|
122
|
+
* for "much later". A documented limit the implementation cannot reach is worse than none.
|
|
123
|
+
*
|
|
124
|
+
* Capped at all because the peer coming back online is the event we are waiting for and it can
|
|
125
|
+
* happen at any moment — an uncapped curve leaves a peer that returned after a long absence waiting
|
|
126
|
+
* hours for a delivery that has been ready the whole time.
|
|
127
|
+
*/
|
|
128
|
+
export declare const DELIVERY_BACKOFF_MS: readonly [1000, 5000, 30000, 120000, 600000];
|
|
129
|
+
export declare const DELIVERY_BACKOFF_CAP_MS: 5000 | 1000 | 30000 | 120000 | 600000;
|
|
130
|
+
/**
|
|
131
|
+
* How long to wait for an ACK after a SUCCESSFUL send, before sending again.
|
|
132
|
+
*
|
|
133
|
+
* Deliberately NOT the failure backoff. "How long do I wait after a failure" and "how long do I
|
|
134
|
+
* wait for an answer" are different questions, and reusing the first for the second re-sent a
|
|
135
|
+
* successfully-delivered envelope one second later — and every resend is not free: `sendContent`
|
|
136
|
+
* witnesses a message-leaf hash to the relay and appends a leaf to the session chain, so a
|
|
137
|
+
* never-acked envelope would pollute the peer's sealed conversation record forever.
|
|
138
|
+
*/
|
|
139
|
+
export declare const DELIVERY_ACK_TIMEOUT_MS: 5000 | 1000 | 30000 | 120000 | 600000;
|
|
140
|
+
/**
|
|
141
|
+
* How many times an envelope may be SENT without ever being acknowledged before the document
|
|
142
|
+
* stalls.
|
|
143
|
+
*
|
|
144
|
+
* A peer that will never answer must eventually stop being treated as a transient. Without a
|
|
145
|
+
* ceiling, an envelope sent to a peer whose client cannot ack — which is every peer until the
|
|
146
|
+
* inbound handler ships on both sides — is re-sent forever and is indistinguishable, in the log
|
|
147
|
+
* and in `list`, from one that is about to land.
|
|
148
|
+
*/
|
|
149
|
+
export declare const DELIVERY_MAX_UNACKED_SENDS = 5;
|
|
150
|
+
/**
|
|
151
|
+
* A document whose peer cannot be resolved is not transient, so it gets the cap immediately rather
|
|
152
|
+
* than climbing to it — but it DOES get scheduled. See the `no_peer` branch.
|
|
153
|
+
*/
|
|
154
|
+
export declare const DELIVERY_UNRESOLVABLE_RETRY_MS: 5000 | 1000 | 30000 | 120000 | 600000;
|
|
155
|
+
export declare function backoffFor(attempts: number): number;
|
|
156
|
+
export interface DeliveryTickResult {
|
|
157
|
+
attempted: number;
|
|
158
|
+
delivered: number;
|
|
159
|
+
/** Sent (or parked) with no answer yet — in flight, neither done nor failed. */
|
|
160
|
+
sent: number;
|
|
161
|
+
/** Answered but refused. Acked all the same — the peer has decided. */
|
|
162
|
+
rejected: number;
|
|
163
|
+
deferred: number;
|
|
164
|
+
failed: number;
|
|
165
|
+
}
|
|
166
|
+
export declare class DocumentDelivery {
|
|
167
|
+
#private;
|
|
168
|
+
constructor(store: DocumentStore, transport: DocumentDeliveryTransport, logger: Logger);
|
|
169
|
+
/**
|
|
170
|
+
* One pass over what is due. Returns counts rather than throwing on a per-envelope failure: one
|
|
171
|
+
* unreachable peer must not stop delivery to every other peer, which is precisely the "works
|
|
172
|
+
* only when all nodes are healthy" shape the project forbids.
|
|
173
|
+
*/
|
|
174
|
+
tick(ownerAgentId: string, peerFor: (documentId: string) => string | null, nowMs: number, opts?: {
|
|
175
|
+
sessionHints?: ReadonlyMap<string, string>;
|
|
176
|
+
correlationId?: string;
|
|
177
|
+
/** Our own wire sender id. Defaults to the owner key for callers whose ids coincide. */
|
|
178
|
+
senderAgentId?: string;
|
|
179
|
+
}): Promise<DeliveryTickResult>;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=document-delivery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-delivery.d.ts","sourceRoot":"","sources":["../src/document-delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,eAAe,CACb,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1D;;;;OAIG;IACH;;;;;;;;;;;OAWG;IACH;;;;;;;;;;OAUG;IACH,SAAS,CAAC,KAAK,EAAE;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,UAAU,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CACP;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,GACvD;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CACjD,CAAC;IACF,OAAO,CAAC,KAAK,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,mBAAmB,CAAC;QAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CACP;QACE,EAAE,EAAE,IAAI,CAAC;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB;;;;;;;;;WASG;QACH,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GACD;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CACjD,CAAC;CACH;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,8CAAoD,CAAC;AACrF,eAAO,MAAM,uBAAuB,uCAAsD,CAAC;AAE3F;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,uCAA0B,CAAC;AAE/D;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,8BAA8B,uCAA0B,CAAC;AAEtE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAgB;;gBAMf,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,EAAE,MAAM;IAMtF;;;;OAIG;IACG,IAAI,CACR,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EAC9C,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;QACJ,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wFAAwF;QACxF,aAAa,CAAC,EAAE,MAAM,CAAC;KACnB,GACL,OAAO,CAAC,kBAAkB,CAAC;CAiP/B"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-DOC-DELIVERY-1 — daemon-autonomous delivery (§16.4).
|
|
3
|
+
*
|
|
4
|
+
* Publish writes the envelope to the log and returns. Delivery is somebody else's job, and that
|
|
5
|
+
* somebody is this worker: it derives what is pending FROM THE LOG, checks the peer is reachable
|
|
6
|
+
* before dialing, opens or reuses a session itself, delivers, and records the ack — with zero agent
|
|
7
|
+
* attention on either end. Two agents in opposite time zones sync overnight without either agent
|
|
8
|
+
* doing anything.
|
|
9
|
+
*
|
|
10
|
+
* ── PENDING IS DERIVED, NEVER HELD ────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* "Unacknowledged envelopes I authored" is a WHERE clause over the log, and that is the whole
|
|
13
|
+
* definition. A queue in memory does not survive a restart — the daemon is a long-running local
|
|
14
|
+
* process that gets restarted routinely — and a queue in its own table is a second source of truth
|
|
15
|
+
* that can disagree with the log about what was sent. The attempt counter and the next-attempt
|
|
16
|
+
* time live on the envelope row for the same reason: a backoff that resets on restart is not a
|
|
17
|
+
* backoff, and a daemon restarting in a reconnect loop would hammer an unreachable peer at full
|
|
18
|
+
* rate forever.
|
|
19
|
+
*
|
|
20
|
+
* ── WHY LOOKUP BEFORE DIAL ────────────────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* §16.4's design is presence-driven push, but there is no presence subscription today (parked,
|
|
23
|
+
* M14-P4). Without one, the honest substitute is: ask the directory whether the peer is reachable,
|
|
24
|
+
* and if it is not, do not burn a dial — schedule a retry on a capped backoff. Dialing an offline
|
|
25
|
+
* peer to find out it is offline is the same information at much higher cost, and it is the cost
|
|
26
|
+
* that would be paid on every pending envelope, on every tick, for as long as the peer is away.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Backoff schedule in ms: ~1s, 5s, 30s, 2m, 10m, then the last entry repeats.
|
|
30
|
+
*
|
|
31
|
+
* The final entry IS the cap — there is no separate ceiling constant. There was one, set to 900s,
|
|
32
|
+
* and it was unreachable: the index clamp meant the schedule never produced a value above 600s, so
|
|
33
|
+
* the exported "cap" was a number the code could not emit and the tests were using it as a synonym
|
|
34
|
+
* for "much later". A documented limit the implementation cannot reach is worse than none.
|
|
35
|
+
*
|
|
36
|
+
* Capped at all because the peer coming back online is the event we are waiting for and it can
|
|
37
|
+
* happen at any moment — an uncapped curve leaves a peer that returned after a long absence waiting
|
|
38
|
+
* hours for a delivery that has been ready the whole time.
|
|
39
|
+
*/
|
|
40
|
+
export const DELIVERY_BACKOFF_MS = [1_000, 5_000, 30_000, 120_000, 600_000];
|
|
41
|
+
export const DELIVERY_BACKOFF_CAP_MS = DELIVERY_BACKOFF_MS[DELIVERY_BACKOFF_MS.length - 1];
|
|
42
|
+
/**
|
|
43
|
+
* How long to wait for an ACK after a SUCCESSFUL send, before sending again.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately NOT the failure backoff. "How long do I wait after a failure" and "how long do I
|
|
46
|
+
* wait for an answer" are different questions, and reusing the first for the second re-sent a
|
|
47
|
+
* successfully-delivered envelope one second later — and every resend is not free: `sendContent`
|
|
48
|
+
* witnesses a message-leaf hash to the relay and appends a leaf to the session chain, so a
|
|
49
|
+
* never-acked envelope would pollute the peer's sealed conversation record forever.
|
|
50
|
+
*/
|
|
51
|
+
export const DELIVERY_ACK_TIMEOUT_MS = DELIVERY_BACKOFF_CAP_MS;
|
|
52
|
+
/**
|
|
53
|
+
* How many times an envelope may be SENT without ever being acknowledged before the document
|
|
54
|
+
* stalls.
|
|
55
|
+
*
|
|
56
|
+
* A peer that will never answer must eventually stop being treated as a transient. Without a
|
|
57
|
+
* ceiling, an envelope sent to a peer whose client cannot ack — which is every peer until the
|
|
58
|
+
* inbound handler ships on both sides — is re-sent forever and is indistinguishable, in the log
|
|
59
|
+
* and in `list`, from one that is about to land.
|
|
60
|
+
*/
|
|
61
|
+
export const DELIVERY_MAX_UNACKED_SENDS = 5;
|
|
62
|
+
/**
|
|
63
|
+
* A document whose peer cannot be resolved is not transient, so it gets the cap immediately rather
|
|
64
|
+
* than climbing to it — but it DOES get scheduled. See the `no_peer` branch.
|
|
65
|
+
*/
|
|
66
|
+
export const DELIVERY_UNRESOLVABLE_RETRY_MS = DELIVERY_BACKOFF_CAP_MS;
|
|
67
|
+
export function backoffFor(attempts) {
|
|
68
|
+
const idx = Math.min(Math.max(attempts, 0), DELIVERY_BACKOFF_MS.length - 1);
|
|
69
|
+
return DELIVERY_BACKOFF_MS[idx];
|
|
70
|
+
}
|
|
71
|
+
export class DocumentDelivery {
|
|
72
|
+
#store;
|
|
73
|
+
#transport;
|
|
74
|
+
#logger;
|
|
75
|
+
#inFlight = null;
|
|
76
|
+
constructor(store, transport, logger) {
|
|
77
|
+
this.#store = store;
|
|
78
|
+
this.#transport = transport;
|
|
79
|
+
this.#logger = logger;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One pass over what is due. Returns counts rather than throwing on a per-envelope failure: one
|
|
83
|
+
* unreachable peer must not stop delivery to every other peer, which is precisely the "works
|
|
84
|
+
* only when all nodes are healthy" shape the project forbids.
|
|
85
|
+
*/
|
|
86
|
+
async tick(ownerAgentId, peerFor, nowMs, opts = {}) {
|
|
87
|
+
// NO RE-ENTRY. Nothing marks a row in-flight until its outcome lands, so a `deliver` slower
|
|
88
|
+
// than the tick interval — a dial to a distant peer is exactly that — would have the next tick
|
|
89
|
+
// return the same rows and dial again: two autonomous sessions and two seals for one envelope,
|
|
90
|
+
// and the loser's markAcked returning false, which logs as if the PEER had redelivered an ack.
|
|
91
|
+
if (this.#inFlight)
|
|
92
|
+
return this.#inFlight;
|
|
93
|
+
const run = this.#run(ownerAgentId, peerFor, nowMs, opts);
|
|
94
|
+
this.#inFlight = run;
|
|
95
|
+
try {
|
|
96
|
+
return await run;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
this.#inFlight = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async #run(ownerAgentId, peerFor, nowMs, opts) {
|
|
103
|
+
// Minted once per pass and threaded through every event, so an operator can tie a failure back
|
|
104
|
+
// to the lookup that preceded it and the session that carried it. Delivery is precisely the
|
|
105
|
+
// async multi-step flow the convention exists for: lookup, dial, deliver, ack — across ticks
|
|
106
|
+
// and across restarts.
|
|
107
|
+
const correlationId = opts.correlationId ?? `dlv-${ownerAgentId.slice(0, 8)}-${nowMs}`;
|
|
108
|
+
// OUR sender id on the wire, which M14-D5 makes the pubkey hex rather than the owner key.
|
|
109
|
+
// Passing the owner key here returned nothing pending and every published update sat in the log
|
|
110
|
+
// undelivered, with no error on any path.
|
|
111
|
+
const pending = this.#store.pendingDeliveries(ownerAgentId, nowMs, opts.senderAgentId);
|
|
112
|
+
const result = {
|
|
113
|
+
attempted: 0, delivered: 0, sent: 0, rejected: 0, deferred: 0, failed: 0,
|
|
114
|
+
};
|
|
115
|
+
// Group by document so one reachability lookup serves every pending envelope for that peer.
|
|
116
|
+
// Per-envelope lookups would multiply directory traffic by the size of the backlog, which is
|
|
117
|
+
// largest exactly when the peer has been away longest.
|
|
118
|
+
const byDocument = new Map();
|
|
119
|
+
for (const e of pending) {
|
|
120
|
+
const list = byDocument.get(e.documentId);
|
|
121
|
+
if (list)
|
|
122
|
+
list.push(e);
|
|
123
|
+
else
|
|
124
|
+
byDocument.set(e.documentId, [e]);
|
|
125
|
+
}
|
|
126
|
+
for (const [documentId, envelopes] of byDocument) {
|
|
127
|
+
const peerAgentId = peerFor(documentId);
|
|
128
|
+
if (peerAgentId === null) {
|
|
129
|
+
// No peer means the document row is gone or malformed, which is NOT transient — so it goes
|
|
130
|
+
// straight to the cap rather than climbing to it, and crucially it is SCHEDULED. Skipping
|
|
131
|
+
// the schedule made this the one exit that never self-corrects: the rows stayed due on
|
|
132
|
+
// every tick forever, emitting an error line each time, and — because the pending window
|
|
133
|
+
// is ordered and bounded — they filled it and starved every other document. The operator's
|
|
134
|
+
// work on an unrelated document would silently never leave the machine, with the only
|
|
135
|
+
// signal being an error naming a different one.
|
|
136
|
+
this.#logger.error("document.delivery.no_peer", {
|
|
137
|
+
documentId,
|
|
138
|
+
pending: envelopes.length,
|
|
139
|
+
correlationId,
|
|
140
|
+
});
|
|
141
|
+
for (const e of envelopes) {
|
|
142
|
+
this.#store.recordDeliveryAttempt(ownerAgentId, documentId, e.envelopeHash, nowMs + DELIVERY_UNRESOLVABLE_RETRY_MS);
|
|
143
|
+
}
|
|
144
|
+
result.failed += envelopes.length;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
let reach;
|
|
148
|
+
try {
|
|
149
|
+
reach = await this.#transport.isPeerReachable(peerAgentId, correlationId);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
// A FAILED LOOKUP IS NOT AN OFFLINE PEER. Recording it as "away" would report a directory
|
|
153
|
+
// outage to the operator as their collaborator being absent — an error substituted for a
|
|
154
|
+
// different error, and one that sends them to ask the wrong person.
|
|
155
|
+
this.#logger.warn("document.delivery.lookup_failed", {
|
|
156
|
+
documentId,
|
|
157
|
+
peerAgentId,
|
|
158
|
+
correlationId,
|
|
159
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
160
|
+
});
|
|
161
|
+
this.#defer(ownerAgentId, documentId, envelopes, nowMs);
|
|
162
|
+
result.deferred += envelopes.length;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (!reach.reachable) {
|
|
166
|
+
// The unknown-agent case is announced AGAINST THE DOCUMENT, not in a separate line that
|
|
167
|
+
// cannot be joined to it. "This address does not resolve" and "your collaborator stepped
|
|
168
|
+
// away" are different things to tell an operator, and the second is what they saw for both.
|
|
169
|
+
this.#logger.info(reach.unknownAgent ? "document.delivery.peer_unknown" : "document.delivery.peer_unreachable", { documentId, peerAgentId, pending: envelopes.length, correlationId });
|
|
170
|
+
this.#defer(ownerAgentId, documentId, envelopes, nowMs);
|
|
171
|
+
result.deferred += envelopes.length;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const envelope of envelopes) {
|
|
175
|
+
result.attempted += 1;
|
|
176
|
+
// CLAIM THE ROW BEFORE DIALING. Scheduling only on the outcome leaves the row due for the
|
|
177
|
+
// whole duration of the dial, and leaves it due FOREVER if the daemon dies mid-dial. The
|
|
178
|
+
// claim is corrected below when the outcome lands.
|
|
179
|
+
const attempts = this.#store.recordDeliveryAttempt(ownerAgentId, documentId, envelope.envelopeHash, nowMs + backoffFor(envelope.attempts ?? 0));
|
|
180
|
+
let outcome;
|
|
181
|
+
try {
|
|
182
|
+
outcome = await this.#transport.deliver({
|
|
183
|
+
peerAgentId,
|
|
184
|
+
documentId,
|
|
185
|
+
envelope,
|
|
186
|
+
// Per DOCUMENT. One hint applied to every envelope in the pass refused every OTHER
|
|
187
|
+
// document's delivery — their peer's active sessions cannot contain this document's
|
|
188
|
+
// session — so an unrelated update failed and backed off, citing a session the
|
|
189
|
+
// operator never associated with it.
|
|
190
|
+
sessionHint: opts.sessionHints?.get(documentId),
|
|
191
|
+
correlationId,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
// A THROW IS A FAILURE, never a success. Wrapped here rather than left to propagate so
|
|
196
|
+
// one envelope's transport fault does not abandon the rest of the backlog mid-pass.
|
|
197
|
+
outcome = {
|
|
198
|
+
ok: false,
|
|
199
|
+
reason: "document_delivery_threw",
|
|
200
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (outcome.ok && outcome.admitted === null) {
|
|
204
|
+
// IN FLIGHT. The content left and the peer has not answered.
|
|
205
|
+
this.#store.markDelivered(ownerAgentId, documentId, envelope.envelopeHash, nowMs);
|
|
206
|
+
// RE-SCHEDULE ON THE ACK TIMEOUT, overwriting the pre-dial failure claim. That claim is
|
|
207
|
+
// `backoffFor(attempts)`, which for a first send is ONE SECOND — so a successfully
|
|
208
|
+
// delivered envelope was re-sent a second later, and every resend appends a leaf to the
|
|
209
|
+
// peer's sealed conversation record. The failure backoff answers a different question.
|
|
210
|
+
this.#store.recordDeliveryAttempt(ownerAgentId, documentId, envelope.envelopeHash, nowMs + DELIVERY_ACK_TIMEOUT_MS);
|
|
211
|
+
this.#logger.info("document.delivery.sent", {
|
|
212
|
+
documentId,
|
|
213
|
+
envelopeHash: envelope.envelopeHash,
|
|
214
|
+
sessionId: outcome.sessionId,
|
|
215
|
+
sessionOpened: outcome.sessionOpened,
|
|
216
|
+
unackedSends: attempts,
|
|
217
|
+
correlationId,
|
|
218
|
+
});
|
|
219
|
+
result.sent += 1;
|
|
220
|
+
if (attempts >= DELIVERY_MAX_UNACKED_SENDS) {
|
|
221
|
+
// A peer that will never answer must stop being a transient. Otherwise this envelope is
|
|
222
|
+
// re-sent forever and looks, in the log and in `list`, exactly like one about to land.
|
|
223
|
+
this.#store.setDocumentStatus(ownerAgentId, documentId, "stalled");
|
|
224
|
+
this.#logger.error("document.delivery.unacked_limit", {
|
|
225
|
+
documentId,
|
|
226
|
+
envelopeHash: envelope.envelopeHash,
|
|
227
|
+
sends: attempts,
|
|
228
|
+
correlationId,
|
|
229
|
+
detail: `this update has been delivered ${attempts} times without the peer's daemon ever ` +
|
|
230
|
+
`confirming it — the document has stopped publishing. Their client may not support ` +
|
|
231
|
+
`shared documents yet.`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (outcome.ok) {
|
|
236
|
+
// ACK = the peer's daemon ANSWERED, admitted or not. A rejection is an ack for delivery
|
|
237
|
+
// purposes (§3.2's 0x05): the peer has decided, so there is nothing left to retry, and
|
|
238
|
+
// retrying would re-trigger their gate and their retry counter until the document stalls
|
|
239
|
+
// for reasons the operator cannot see. Supersession is REJECT-1's job, not the worker's.
|
|
240
|
+
const first = this.#store.markAcked(ownerAgentId, documentId, envelope.envelopeHash, nowMs);
|
|
241
|
+
this.#logger.info("document.delivery.session", {
|
|
242
|
+
documentId,
|
|
243
|
+
sessionId: outcome.sessionId,
|
|
244
|
+
opened: outcome.sessionOpened,
|
|
245
|
+
correlationId,
|
|
246
|
+
});
|
|
247
|
+
if (outcome.admitted) {
|
|
248
|
+
this.#logger.info("document.delivery.acked", {
|
|
249
|
+
documentId,
|
|
250
|
+
envelopeHash: envelope.envelopeHash,
|
|
251
|
+
sessionId: outcome.sessionId,
|
|
252
|
+
firstAck: first,
|
|
253
|
+
correlationId,
|
|
254
|
+
});
|
|
255
|
+
result.delivered += 1;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.#logger.warn("document.delivery.rejected", {
|
|
259
|
+
documentId,
|
|
260
|
+
envelopeHash: envelope.envelopeHash,
|
|
261
|
+
sessionId: outcome.sessionId,
|
|
262
|
+
reason: outcome.rejectionReason,
|
|
263
|
+
correlationId,
|
|
264
|
+
});
|
|
265
|
+
result.rejected += 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
this.#logger.warn("document.delivery.failed", {
|
|
270
|
+
documentId,
|
|
271
|
+
envelopeHash: envelope.envelopeHash,
|
|
272
|
+
reason: outcome.reason,
|
|
273
|
+
detail: outcome.detail,
|
|
274
|
+
attempts,
|
|
275
|
+
correlationId,
|
|
276
|
+
});
|
|
277
|
+
result.failed += 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
#defer(ownerAgentId, documentId, envelopes, nowMs) {
|
|
284
|
+
for (const e of envelopes) {
|
|
285
|
+
this.#store.recordDeliveryAttempt(ownerAgentId, documentId, e.envelopeHash, nowMs + backoffFor(e.attempts ?? 0));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=document-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-delivery.js","sourceRoot":"","sources":["../src/document-delivery.ts"],"names":[],"mappings":"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"}
|