@cello-protocol/daemon 0.0.132 → 0.0.134

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.
Files changed (118) hide show
  1. package/dist/bin/cello-daemon.js +25 -0
  2. package/dist/bin/cello-daemon.js.map +1 -1
  3. package/dist/content-park.d.ts.map +1 -1
  4. package/dist/content-park.js +29 -11
  5. package/dist/content-park.js.map +1 -1
  6. package/dist/daemon.d.ts +11 -0
  7. package/dist/daemon.d.ts.map +1 -1
  8. package/dist/daemon.js +455 -17
  9. package/dist/daemon.js.map +1 -1
  10. package/dist/document-ack-inbound.d.ts +57 -0
  11. package/dist/document-ack-inbound.d.ts.map +1 -0
  12. package/dist/document-ack-inbound.js +174 -0
  13. package/dist/document-ack-inbound.js.map +1 -0
  14. package/dist/document-control-notifier.d.ts +61 -0
  15. package/dist/document-control-notifier.d.ts.map +1 -0
  16. package/dist/document-control-notifier.js +70 -0
  17. package/dist/document-control-notifier.js.map +1 -0
  18. package/dist/document-delivery-transport.d.ts +94 -0
  19. package/dist/document-delivery-transport.d.ts.map +1 -0
  20. package/dist/document-delivery-transport.js +179 -0
  21. package/dist/document-delivery-transport.js.map +1 -0
  22. package/dist/document-delivery.d.ts +181 -0
  23. package/dist/document-delivery.d.ts.map +1 -0
  24. package/dist/document-delivery.js +289 -0
  25. package/dist/document-delivery.js.map +1 -0
  26. package/dist/document-frame-router.d.ts +210 -0
  27. package/dist/document-frame-router.d.ts.map +1 -0
  28. package/dist/document-frame-router.js +396 -0
  29. package/dist/document-frame-router.js.map +1 -0
  30. package/dist/document-handlers.d.ts +47 -0
  31. package/dist/document-handlers.d.ts.map +1 -0
  32. package/dist/document-handlers.js +657 -0
  33. package/dist/document-handlers.js.map +1 -0
  34. package/dist/document-handshake.d.ts +156 -0
  35. package/dist/document-handshake.d.ts.map +1 -0
  36. package/dist/document-handshake.js +398 -0
  37. package/dist/document-handshake.js.map +1 -0
  38. package/dist/document-inbound.d.ts +91 -0
  39. package/dist/document-inbound.d.ts.map +1 -0
  40. package/dist/document-inbound.js +290 -0
  41. package/dist/document-inbound.js.map +1 -0
  42. package/dist/document-layer.d.ts +137 -0
  43. package/dist/document-layer.d.ts.map +1 -0
  44. package/dist/document-layer.js +255 -0
  45. package/dist/document-layer.js.map +1 -0
  46. package/dist/document-lifecycle.d.ts +125 -0
  47. package/dist/document-lifecycle.d.ts.map +1 -0
  48. package/dist/document-lifecycle.js +433 -0
  49. package/dist/document-lifecycle.js.map +1 -0
  50. package/dist/document-live-docs.d.ts +58 -0
  51. package/dist/document-live-docs.d.ts.map +1 -0
  52. package/dist/document-live-docs.js +126 -0
  53. package/dist/document-live-docs.js.map +1 -0
  54. package/dist/document-notify.d.ts +173 -0
  55. package/dist/document-notify.d.ts.map +1 -0
  56. package/dist/document-notify.js +438 -0
  57. package/dist/document-notify.js.map +1 -0
  58. package/dist/document-publish.d.ts +67 -0
  59. package/dist/document-publish.d.ts.map +1 -0
  60. package/dist/document-publish.js +149 -0
  61. package/dist/document-publish.js.map +1 -0
  62. package/dist/document-reachability.d.ts +42 -0
  63. package/dist/document-reachability.d.ts.map +1 -0
  64. package/dist/document-reachability.js +80 -0
  65. package/dist/document-reachability.js.map +1 -0
  66. package/dist/document-rejection.d.ts +240 -0
  67. package/dist/document-rejection.d.ts.map +1 -0
  68. package/dist/document-rejection.js +407 -0
  69. package/dist/document-rejection.js.map +1 -0
  70. package/dist/document-store.d.ts +154 -8
  71. package/dist/document-store.d.ts.map +1 -1
  72. package/dist/document-store.js +462 -4
  73. package/dist/document-store.js.map +1 -1
  74. package/dist/document-write-path.d.ts.map +1 -1
  75. package/dist/document-write-path.js +10 -43
  76. package/dist/document-write-path.js.map +1 -1
  77. package/dist/inbound-sessions.d.ts.map +1 -1
  78. package/dist/inbound-sessions.js +4 -0
  79. package/dist/inbound-sessions.js.map +1 -1
  80. package/dist/initiate-session-handler.d.ts +24 -1
  81. package/dist/initiate-session-handler.d.ts.map +1 -1
  82. package/dist/initiate-session-handler.js +35 -9
  83. package/dist/initiate-session-handler.js.map +1 -1
  84. package/dist/ipc-server.d.ts +11 -1
  85. package/dist/ipc-server.d.ts.map +1 -1
  86. package/dist/ipc-server.js +7 -1
  87. package/dist/ipc-server.js.map +1 -1
  88. package/dist/line-lcs.d.ts +51 -0
  89. package/dist/line-lcs.d.ts.map +1 -0
  90. package/dist/line-lcs.js +71 -0
  91. package/dist/line-lcs.js.map +1 -0
  92. package/dist/notification-handlers.d.ts.map +1 -1
  93. package/dist/notification-handlers.js +31 -14
  94. package/dist/notification-handlers.js.map +1 -1
  95. package/dist/outbound-sessions.d.ts +2 -0
  96. package/dist/outbound-sessions.d.ts.map +1 -1
  97. package/dist/outbound-sessions.js +11 -1
  98. package/dist/outbound-sessions.js.map +1 -1
  99. package/dist/session-content-handlers.d.ts.map +1 -1
  100. package/dist/session-content-handlers.js +3 -2
  101. package/dist/session-content-handlers.js.map +1 -1
  102. package/dist/session-node-manager.d.ts +26 -4
  103. package/dist/session-node-manager.d.ts.map +1 -1
  104. package/dist/session-node-manager.js +201 -15
  105. package/dist/session-node-manager.js.map +1 -1
  106. package/dist/session-read-handlers.js +1 -1
  107. package/dist/session-read-handlers.js.map +1 -1
  108. package/dist/types.d.ts +40 -0
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js.map +1 -1
  111. package/dist/vocabulary.d.ts.map +1 -1
  112. package/dist/vocabulary.js +16 -0
  113. package/dist/vocabulary.js.map +1 -1
  114. package/dist/wire-content-hash.d.ts +27 -0
  115. package/dist/wire-content-hash.d.ts.map +1 -0
  116. package/dist/wire-content-hash.js +37 -0
  117. package/dist/wire-content-hash.js.map +1 -0
  118. package/package.json +5 -5
@@ -0,0 +1,210 @@
1
+ /**
2
+ * DOD-DOC-INBOUND-2 — routing an arriving session frame to the document layer.
3
+ *
4
+ * Document traffic and conversation traffic share the session content channel, so something has to
5
+ * decide which is which. That decision has consequences beyond dispatch, and they are the reason
6
+ * this is a unit rather than an `if` at the call site:
7
+ *
8
+ * - A document frame is NOT a transcript message. Recording one would put CRDT bytes into the
9
+ * operator's conversation history, where `cello_receive` would hand them to an agent as
10
+ * something a person said.
11
+ * - A document frame raises NO DOORBELL (§11.3 — doorbell-on-update is parked). A collaborator
12
+ * typing produces a stream of updates, and a doorbell per update would interrupt the operator's
13
+ * agent continuously for something with no deadline.
14
+ * - A document frame is still a LEAF. The seal covers it as a `0x04` doc-op leaf; that is what
15
+ * makes the exchange provable. Consuming a frame means "do not treat this as conversation", not
16
+ * "pretend it did not arrive".
17
+ *
18
+ * ── CLASSIFICATION IS BY DECODE, BEHIND A STRUCTURAL GUARD ────────────────────────────────────
19
+ *
20
+ * A frame is a document frame iff it DECODES as one. No content heuristic, no first-byte guess
21
+ * about what the bytes mean — the same reasoning that made the update envelope carry its encoding
22
+ * explicitly rather than let a decoder infer it. Anything that does not decode is conversation,
23
+ * which is the safe direction: a misrouted document frame lands in a transcript where it is
24
+ * visible, while a misrouted MESSAGE would vanish into the document layer and never reach the
25
+ * operator.
26
+ *
27
+ * A CONVERSATION MESSAGE CANNOT REACH A DECODER AT ALL, and the reason is structural rather than
28
+ * statistical. Both halves verified here, not assumed:
29
+ *
30
+ * - `couldBeDocumentFrame` requires byte 0 in `0xa0`–`0xb9`, and NONE of those can begin a valid
31
+ * UTF-8 sequence — they are all continuation bytes (checked against a fatal `TextDecoder`).
32
+ * - A legitimate message IS UTF-8, by construction rather than by convention: the send path
33
+ * encodes it with `new TextEncoder().encode(...)` (`session-content-handlers.ts`), and there is
34
+ * no binary-content path. Its first byte is therefore ASCII (`< 0x80`) or a LEAD byte
35
+ * (`0xc2`–`0xf4`) — never a continuation byte. Note it is NOT always `< 0x80`: a message
36
+ * beginning in French, Arabic or Chinese starts at `0xc2` or above and is still clear of the
37
+ * admitted range. Getting that wrong would invite someone to widen the range on a false premise.
38
+ *
39
+ * So no text an operator types, and no text an attacker persuades them to send, is ever classified
40
+ * as a document frame. (A hostile PEER can put raw non-UTF-8 bytes on the channel and have them
41
+ * classified as document traffic — but that only suppresses their own message.)
42
+ *
43
+ * ── THE HEADER GUARD IS A FAST PATH, NOT THE SECURITY BOUNDARY ────────────────────────────────
44
+ *
45
+ * Handing hostile bytes to a CBOR decoder is a denial of service — measured at seconds and
46
+ * gigabytes for a few bytes — and `classify` runs on EVERY inbound frame, so a peer could stall or
47
+ * OOM the daemon's whole content path and stop the operator's ordinary MESSAGES.
48
+ *
49
+ * `couldBeDocumentFrame` was written as the fix and IS NOT ONE. It inspects the header, and the cost
50
+ * lives in structures NESTED inside it: `a1 9f` — a one-pair map whose first key is an
51
+ * indefinite-length array — is three bytes, passes any header check, and cost 9.6 seconds and
52
+ * 1.1 GB. Prefixing the real frames' own `b9 000a` does the same. No header-shaped guard can be
53
+ * sound here.
54
+ *
55
+ * The decoder limit in `cbor.ts` cuts the per-byte amplification by ~43,000×, and it is still not
56
+ * the whole answer: cbor-x pre-allocates `new Array(declaredCount)` before reading any element, so
57
+ * NESTED arrays each under that cap still allocate — measured, 15 KB of them costs ~230 ms and
58
+ * ~2.3 GB. What makes the nesting depth finite is an INPUT LENGTH CAP, which is why one is applied
59
+ * here, at the peer-bytes boundary, before anything else looks at the frame.
60
+ *
61
+ * So the three layers, in the order they run and with what each is actually for:
62
+ * MAX_DOCUMENT_FRAME_BYTES — bounds the nesting depth. The one that closes the class.
63
+ * couldBeDocumentFrame — a cheap fast path; keeps every conversation message out of the
64
+ * decoder, and is what the UTF-8 argument above rests on.
65
+ * cbor.ts size limits — bounds a single container. Reduces amplification; not a boundary.
66
+ *
67
+ * The guard must never again be described as what makes hostile input safe. It was, and it was not.
68
+ */
69
+ import type { DocumentInbound } from "./document-inbound.js";
70
+ import type { DocumentAckInbound } from "./document-ack-inbound.js";
71
+ import type { Logger } from "./types.js";
72
+ /**
73
+ * What the session layer should do with the frame.
74
+ *
75
+ * `consumed: false` means "this is not document traffic" — the caller records it as a transcript
76
+ * message and fires the doorbell exactly as before. Nothing about the conversation path changes.
77
+ */
78
+ /** Every document frame kind that can arrive on the session channel. */
79
+ export type DocumentFrameKind = "update" | "ack" | "proposal" | "rejection" | "proposal_ack" | "control";
80
+ export type FrameRouting = {
81
+ consumed: false;
82
+ } | {
83
+ consumed: true;
84
+ kind: DocumentFrameKind;
85
+ ok: boolean;
86
+ reason?: string;
87
+ };
88
+ /**
89
+ * What the session layer needs SYNCHRONOUSLY: is this document traffic, and therefore which leaf
90
+ * kind does the frame get.
91
+ *
92
+ * The handling itself is async — a gate refusal has to SIGN a `0x05` leaf, and signing goes through
93
+ * an async key provider — but `#appendVerifiedContent` decides the leaf kind inline and cannot wait.
94
+ * Splitting the two is what lets both be true without making the whole content path async.
95
+ */
96
+ export type FrameClassification = {
97
+ consumed: false;
98
+ } | {
99
+ consumed: true;
100
+ kind: DocumentFrameKind;
101
+ };
102
+ export interface DocumentFrameRouterDeps {
103
+ inbound: DocumentInbound;
104
+ ackInbound: DocumentAckInbound;
105
+ /**
106
+ * Record an arriving PROPOSAL. Without this a proposal frame is not document traffic as far as
107
+ * the router is concerned, so it falls through to the conversation path and lands in the
108
+ * operator's transcript as unreadable bytes — the exact failure the classification exists to
109
+ * prevent, arriving through the one frame kind nobody had wired.
110
+ */
111
+ recordProposal(ownerAgentId: string, wire: Uint8Array, nowMs: number): void;
112
+ /** Record a rejection the PEER sent us — the receiving half of §3.2. */
113
+ recordRejection(ownerAgentId: string, wire: Uint8Array, nowMs: number): void;
114
+ /**
115
+ * Record the peer's ANSWER to a proposal we authored. Unclassified, this frame falls through to
116
+ * the conversation path and an operator's agent is handed a CBOR ack as something a person said.
117
+ */
118
+ recordProposalAck(ownerAgentId: string, wire: Uint8Array, nowMs: number): void;
119
+ /**
120
+ * The peer has CLOSED or KILLED the document. Unclassified, this frame falls through to the
121
+ * conversation path and the operator keeps publishing into a document that will never answer,
122
+ * with nothing on their screen explaining why.
123
+ */
124
+ recordControl(ownerAgentId: string, wire: Uint8Array, nowMs: number): void;
125
+ /**
126
+ * Tell the sender what happened to their envelope — admitted or refused, both of which SETTLE it.
127
+ *
128
+ * REQUIRED, not optional. Without it the sender has no answer and redelivers until the document
129
+ * stalls; an optional callback would make that outcome a configuration rather than a bug.
130
+ */
131
+ /**
132
+ * Re-project the document onto disk after admitting a peer's update.
133
+ *
134
+ * Required rather than optional: a file surface that only ever writes the local operator's edits
135
+ * out is worse than none, because the stale file reads as the document and gets published back
136
+ * over the peer's work. A daemon with no workspace configured implements this as a no-op.
137
+ */
138
+ rewriteFile(ownerAgentId: string, inResponseTo: Uint8Array): Promise<void>;
139
+ /** Put an already-signed frame on the wire to whoever authored `wire` — the rejection, today. */
140
+ sendFrameToPeer(ownerAgentId: string, inResponseTo: Uint8Array, bytes: Uint8Array): Promise<void>;
141
+ sendAck(ownerAgentId: string,
142
+ /** The envelope as it arrived — the document id and the party to answer are both inside it. */
143
+ wire: Uint8Array, outcome: {
144
+ envelopeHash: string;
145
+ admitted: boolean;
146
+ rejectionReason?: string;
147
+ correlationId: string;
148
+ }): Promise<void>;
149
+ /**
150
+ * Map the daemon's AGENT NAME — the only identifier the session content path carries — to the
151
+ * stable owner key every document row is scoped by (M14-D5: our own K_local pubkey hex).
152
+ *
153
+ * REQUIRED, and required to be here rather than at the call site, because classification must
154
+ * stay synchronous and must not be done twice: `classify` runs up to four CBOR decodes and this
155
+ * is on every inbound frame.
156
+ *
157
+ * The two identifiers are NOT interchangeable and the store says so. Scoping inbound by name
158
+ * while the delivery sweep scopes by pubkey hex writes every received envelope where no query
159
+ * looks: `pendingDeliveries` returns empty, the sweep reports nothing attempted, and a fully
160
+ * synced document is invisible to both halves with no error on any path. Agent NAME is also
161
+ * mutable and reusable after retirement, so it may never be a join key.
162
+ *
163
+ * Returning null is a refusal, not a fallback — the frame is still consumed, because it IS a
164
+ * document frame and letting it fall through would put CRDT bytes in the operator's transcript.
165
+ */
166
+ ownerKeyFor(agentName: string): string | null;
167
+ logger: Logger;
168
+ }
169
+ export declare class DocumentFrameRouter {
170
+ #private;
171
+ constructor(deps: DocumentFrameRouterDeps);
172
+ /**
173
+ * Classify and dispatch. Returns whether the document layer consumed the frame.
174
+ *
175
+ * NEVER THROWS. A throw here would escape into the session content path and take down message
176
+ * delivery for the whole session — conversation traffic failing because a document frame was
177
+ * malformed. A document frame that cannot be handled is consumed and reported; the session keeps
178
+ * running.
179
+ */
180
+ /**
181
+ * The SYNCHRONOUS half: classify, and start the handling.
182
+ *
183
+ * Returns immediately with what the session layer needs to pick a leaf kind. The handling runs on
184
+ * a per-document queue — see `#handle` — so the caller never waits and frames for one document
185
+ * never overtake each other.
186
+ */
187
+ routeSync(agentName: string, content: Uint8Array, nowMs: number, correlationId: string): FrameClassification;
188
+ /** Classify and handle, awaiting the outcome. The path a caller takes when it wants the verdict. */
189
+ route(ownerAgentId: string, content: Uint8Array, nowMs: number, correlationId: string): Promise<FrameRouting>;
190
+ }
191
+ /**
192
+ * The largest field count a document frame can declare. The update envelope has 10 fields and the
193
+ * ack has 9; the bound is generous so a future field does not silently start routing frames to the
194
+ * transcript, and tight enough that a map header claiming millions of pairs never reaches a decoder.
195
+ */
196
+ export declare const MAX_DOCUMENT_FRAME_FIELDS = 32;
197
+ /**
198
+ * The largest a document frame can legitimately be, and the bound that makes the decode's nesting
199
+ * depth finite.
200
+ *
201
+ * Sized off the payload it has to carry: the gate caps an update at 1 MiB, and everything else in
202
+ * the envelope — two hashes, an agent id, a state vector, a signature — is small. 2 MiB is
203
+ * comfortably above any real frame and far below the size at which nested-array pre-allocation
204
+ * becomes expensive.
205
+ *
206
+ * This is the layer that actually closes the pathological-input class. The decoder's per-container
207
+ * limit reduces amplification; only a bound on the INPUT can bound the depth.
208
+ */
209
+ export declare const MAX_DOCUMENT_FRAME_BYTES: number;
210
+ //# sourceMappingURL=document-frame-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-frame-router.d.ts","sourceRoot":"","sources":["../src/document-frame-router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,396 @@
1
+ /**
2
+ * DOD-DOC-INBOUND-2 — routing an arriving session frame to the document layer.
3
+ *
4
+ * Document traffic and conversation traffic share the session content channel, so something has to
5
+ * decide which is which. That decision has consequences beyond dispatch, and they are the reason
6
+ * this is a unit rather than an `if` at the call site:
7
+ *
8
+ * - A document frame is NOT a transcript message. Recording one would put CRDT bytes into the
9
+ * operator's conversation history, where `cello_receive` would hand them to an agent as
10
+ * something a person said.
11
+ * - A document frame raises NO DOORBELL (§11.3 — doorbell-on-update is parked). A collaborator
12
+ * typing produces a stream of updates, and a doorbell per update would interrupt the operator's
13
+ * agent continuously for something with no deadline.
14
+ * - A document frame is still a LEAF. The seal covers it as a `0x04` doc-op leaf; that is what
15
+ * makes the exchange provable. Consuming a frame means "do not treat this as conversation", not
16
+ * "pretend it did not arrive".
17
+ *
18
+ * ── CLASSIFICATION IS BY DECODE, BEHIND A STRUCTURAL GUARD ────────────────────────────────────
19
+ *
20
+ * A frame is a document frame iff it DECODES as one. No content heuristic, no first-byte guess
21
+ * about what the bytes mean — the same reasoning that made the update envelope carry its encoding
22
+ * explicitly rather than let a decoder infer it. Anything that does not decode is conversation,
23
+ * which is the safe direction: a misrouted document frame lands in a transcript where it is
24
+ * visible, while a misrouted MESSAGE would vanish into the document layer and never reach the
25
+ * operator.
26
+ *
27
+ * A CONVERSATION MESSAGE CANNOT REACH A DECODER AT ALL, and the reason is structural rather than
28
+ * statistical. Both halves verified here, not assumed:
29
+ *
30
+ * - `couldBeDocumentFrame` requires byte 0 in `0xa0`–`0xb9`, and NONE of those can begin a valid
31
+ * UTF-8 sequence — they are all continuation bytes (checked against a fatal `TextDecoder`).
32
+ * - A legitimate message IS UTF-8, by construction rather than by convention: the send path
33
+ * encodes it with `new TextEncoder().encode(...)` (`session-content-handlers.ts`), and there is
34
+ * no binary-content path. Its first byte is therefore ASCII (`< 0x80`) or a LEAD byte
35
+ * (`0xc2`–`0xf4`) — never a continuation byte. Note it is NOT always `< 0x80`: a message
36
+ * beginning in French, Arabic or Chinese starts at `0xc2` or above and is still clear of the
37
+ * admitted range. Getting that wrong would invite someone to widen the range on a false premise.
38
+ *
39
+ * So no text an operator types, and no text an attacker persuades them to send, is ever classified
40
+ * as a document frame. (A hostile PEER can put raw non-UTF-8 bytes on the channel and have them
41
+ * classified as document traffic — but that only suppresses their own message.)
42
+ *
43
+ * ── THE HEADER GUARD IS A FAST PATH, NOT THE SECURITY BOUNDARY ────────────────────────────────
44
+ *
45
+ * Handing hostile bytes to a CBOR decoder is a denial of service — measured at seconds and
46
+ * gigabytes for a few bytes — and `classify` runs on EVERY inbound frame, so a peer could stall or
47
+ * OOM the daemon's whole content path and stop the operator's ordinary MESSAGES.
48
+ *
49
+ * `couldBeDocumentFrame` was written as the fix and IS NOT ONE. It inspects the header, and the cost
50
+ * lives in structures NESTED inside it: `a1 9f` — a one-pair map whose first key is an
51
+ * indefinite-length array — is three bytes, passes any header check, and cost 9.6 seconds and
52
+ * 1.1 GB. Prefixing the real frames' own `b9 000a` does the same. No header-shaped guard can be
53
+ * sound here.
54
+ *
55
+ * The decoder limit in `cbor.ts` cuts the per-byte amplification by ~43,000×, and it is still not
56
+ * the whole answer: cbor-x pre-allocates `new Array(declaredCount)` before reading any element, so
57
+ * NESTED arrays each under that cap still allocate — measured, 15 KB of them costs ~230 ms and
58
+ * ~2.3 GB. What makes the nesting depth finite is an INPUT LENGTH CAP, which is why one is applied
59
+ * here, at the peer-bytes boundary, before anything else looks at the frame.
60
+ *
61
+ * So the three layers, in the order they run and with what each is actually for:
62
+ * MAX_DOCUMENT_FRAME_BYTES — bounds the nesting depth. The one that closes the class.
63
+ * couldBeDocumentFrame — a cheap fast path; keeps every conversation message out of the
64
+ * decoder, and is what the UTF-8 argument above rests on.
65
+ * cbor.ts size limits — bounds a single container. Reduces amplification; not a boundary.
66
+ *
67
+ * The guard must never again be described as what makes hostile input safe. It was, and it was not.
68
+ */
69
+ import { decodeCbor, decodeDocumentUpdateEnvelope, decodeDocumentAck, decodeDocumentProposal, decodeDocumentRejection, decodeDocumentProposalAck, decodeDocumentControl, } from "@cello-protocol/protocol-types";
70
+ export class DocumentFrameRouter {
71
+ #d;
72
+ /** One promise chain per owning agent — the serialization point. See `#enqueue`. */
73
+ #queues = new Map();
74
+ constructor(deps) {
75
+ this.#d = deps;
76
+ }
77
+ /**
78
+ * Classify and dispatch. Returns whether the document layer consumed the frame.
79
+ *
80
+ * NEVER THROWS. A throw here would escape into the session content path and take down message
81
+ * delivery for the whole session — conversation traffic failing because a document frame was
82
+ * malformed. A document frame that cannot be handled is consumed and reported; the session keeps
83
+ * running.
84
+ */
85
+ /**
86
+ * The SYNCHRONOUS half: classify, and start the handling.
87
+ *
88
+ * Returns immediately with what the session layer needs to pick a leaf kind. The handling runs on
89
+ * a per-document queue — see `#handle` — so the caller never waits and frames for one document
90
+ * never overtake each other.
91
+ */
92
+ routeSync(agentName, content, nowMs, correlationId) {
93
+ const kind = classify(content);
94
+ if (kind === "unshaped")
95
+ return { consumed: false };
96
+ if (kind === "undecodable") {
97
+ // ANOMALY, worth a line. A frame that passed the header guard and then decoded as NOTHING is
98
+ // not an ordinary conversation message — the guard's whole argument is that operator text
99
+ // cannot begin with a CBOR map header (see the UTF-8 reasoning above). So this is either a
100
+ // peer running a frame type this build does not know, or our own encoder and decoder
101
+ // disagreeing, and both are invisible today: the frame silently becomes a transcript entry.
102
+ //
103
+ // This was the missing diagnostic when a live proposal reached its peer, passed the content
104
+ // hash cross-check byte for byte, and never reached the document layer — every log on the
105
+ // receiving side said "an ordinary message arrived", because that is what the fall-through
106
+ // makes it.
107
+ this.#d.logger.warn("document.frame.undecodable", {
108
+ bytes: content.length,
109
+ header: content[0],
110
+ correlationId,
111
+ });
112
+ return { consumed: false };
113
+ }
114
+ // Resolved AFTER classification and BEFORE handling: a frame that is not document traffic never
115
+ // pays for the lookup, and one that is never reaches the store under the wrong scope.
116
+ const ownerAgentId = this.#d.ownerKeyFor(agentName);
117
+ if (ownerAgentId === null) {
118
+ this.#d.logger.error("document.frame.owner_unresolved", { agentName, kind, correlationId });
119
+ // Consumed anyway. This IS a document frame; the only alternative is the conversation path,
120
+ // which would hand an operator's agent a CBOR envelope as something a person said.
121
+ return { consumed: true, kind };
122
+ }
123
+ void this.#enqueue(ownerAgentId, content, nowMs, correlationId, kind);
124
+ return { consumed: true, kind };
125
+ }
126
+ /**
127
+ * SERIALIZED PER DOCUMENT-OWNER, because the chain check is order-dependent even though Yjs is
128
+ * not. An envelope's `doc_prev_hash` must find its predecessor already stored, so two frames
129
+ * handled concurrently can have the second refuse with `document_chain_broken` purely because the
130
+ * first has not finished writing — a self-inflicted fork that looks like a peer fault.
131
+ *
132
+ * Keyed by owner rather than by document: the document id is inside the frame, and reading it
133
+ * requires the decode this queue exists to schedule.
134
+ */
135
+ #enqueue(ownerAgentId, content, nowMs, correlationId, kind) {
136
+ const previous = this.#queues.get(ownerAgentId) ?? Promise.resolve();
137
+ const next = previous
138
+ .catch(() => {
139
+ // A previous frame's failure must not cancel this one's turn. It was already reported.
140
+ })
141
+ .then(async () => {
142
+ // `kind` is passed through rather than re-derived: `classify` does up to two full CBOR
143
+ // decodes, and this runs on every inbound frame.
144
+ const outcome = await this.#dispatch(ownerAgentId, content, nowMs, correlationId, kind);
145
+ if (outcome.consumed && !outcome.ok) {
146
+ this.#d.logger.warn("document.frame.refused", {
147
+ kind: outcome.kind,
148
+ reason: outcome.reason,
149
+ correlationId,
150
+ });
151
+ }
152
+ });
153
+ this.#queues.set(ownerAgentId, next);
154
+ // Drop the entry once it is the tail, so the map does not grow one promise per agent forever.
155
+ void next.finally(() => {
156
+ if (this.#queues.get(ownerAgentId) === next)
157
+ this.#queues.delete(ownerAgentId);
158
+ });
159
+ return next;
160
+ }
161
+ /** Classify and handle, awaiting the outcome. The path a caller takes when it wants the verdict. */
162
+ async route(ownerAgentId, content, nowMs, correlationId) {
163
+ const kind = classify(content);
164
+ // Both non-kinds mean "not ours" to this async entry point. `routeSync` is the path that
165
+ // distinguishes them, because it is the one that decides what the session does with the frame.
166
+ if (kind === "unshaped" || kind === "undecodable")
167
+ return { consumed: false };
168
+ return this.#dispatch(ownerAgentId, content, nowMs, correlationId, kind);
169
+ }
170
+ async #dispatch(ownerAgentId, content, nowMs, correlationId, kind) {
171
+ try {
172
+ if (kind === "update") {
173
+ const res = await this.#d.inbound.receive(ownerAgentId, content, nowMs, correlationId);
174
+ if (res.ok && res.admitted) {
175
+ // REWRITE THE FILE. The document has moved; the operator's editor has not been told.
176
+ // Without this the file surface is write-only — an operator publishes their edits and
177
+ // never sees the peer's, which is worse than no file surface at all, because the stale
178
+ // file then looks like the document and gets published back over their work.
179
+ //
180
+ // Not awaited, and not allowed to fail the admission: the content is already in the
181
+ // document, which is the source of truth. A failed rewrite is a stale projection, and
182
+ // `publish` refuses loudly on a stale baseline rather than diffing against it.
183
+ void this.#d.rewriteFile(ownerAgentId, content).catch((err) => {
184
+ this.#d.logger.warn("document.file.rewrite_threw", {
185
+ correlationId,
186
+ reason: err instanceof Error ? err.message : String(err),
187
+ });
188
+ });
189
+ }
190
+ if (res.ok) {
191
+ // ANSWER THE SENDER. Nothing did, and `encodeDocumentAck` had no production caller at all
192
+ // — the frame type, the preimage and the receiving half all existed and no code path ever
193
+ // produced one. The layer's own header names this as a distinct silent failure: an
194
+ // inbound path with no ack producer leaves the peer retrying until their document stalls
195
+ // at the unacked ceiling, and every redelivery re-triggers their gate.
196
+ //
197
+ // A REJECTION IS AN ACK. `admitted: false` says the peer has decided, so the sender stops
198
+ // retrying and supersedes instead — modelling it as "no ack" is what makes a refused
199
+ // envelope redeliver forever.
200
+ //
201
+ // A DUPLICATE IS ACKED TOO, and that is the case that matters most here: a redelivery is
202
+ // usually evidence the first ack was lost, so staying silent about it guarantees the loop
203
+ // never ends.
204
+ // NOT AWAITED, deliberately. This runs inside the per-owner queue, and that queue exists
205
+ // to serialize the CHAIN CHECK — an envelope's predecessor must be stored before the next
206
+ // is examined. It does not exist to serialize network I/O. Awaiting a dial here means one
207
+ // slow or unreachable peer stalls every subsequent inbound frame for that agent, and the
208
+ // symptom is not "the ack was slow": it is documents failing to converge, minutes later,
209
+ // for no reason visible at the point of failure. Measured — it turned a 7-second live run
210
+ // into a 124-second timeout.
211
+ //
212
+ // The ack is best-effort by contract anyway: it cannot fail the content we have already
213
+ // admitted, and a lost one is recovered by the sender's redelivery, which is acked in
214
+ // turn.
215
+ // THE SIGNED REFUSAL ITSELF, when there is one. The ack says "refused"; this frame is what
216
+ // advances the SENDER's retry round, and their entire supersede-then-stall protocol is
217
+ // driven by receiving it. Without it their counter never leaves zero and a peer whose
218
+ // every update is refused republishes forever, with its own surface reporting `active`.
219
+ //
220
+ // Absent on a repeat refusal by design — re-sending would advance their round for a retry
221
+ // that never happened and stall the document early.
222
+ if (!res.admitted && res.rejectionWire) {
223
+ void this.#d
224
+ .sendFrameToPeer(ownerAgentId, content, res.rejectionWire)
225
+ .catch((err) => {
226
+ this.#d.logger.warn("document.rejection.send_threw", {
227
+ correlationId,
228
+ reason: err instanceof Error ? err.message : String(err),
229
+ });
230
+ });
231
+ }
232
+ void this.#d.sendAck(ownerAgentId, content, {
233
+ envelopeHash: res.envelopeHash,
234
+ admitted: res.admitted,
235
+ ...(res.admitted ? {} : { rejectionReason: res.rejectionReason }),
236
+ correlationId,
237
+ }).catch((err) => {
238
+ // Contained: an unhandled rejection out of a fire-and-forget send takes the daemon down,
239
+ // and the one thing an ack must never do is cost us content we already hold.
240
+ this.#d.logger.warn("document.ack.send_threw", {
241
+ correlationId,
242
+ reason: err instanceof Error ? err.message : String(err),
243
+ });
244
+ });
245
+ }
246
+ return { consumed: true, kind, ok: res.ok, reason: res.ok ? undefined : res.reason };
247
+ }
248
+ if (kind === "proposal") {
249
+ this.#d.recordProposal(ownerAgentId, content, nowMs);
250
+ return { consumed: true, kind, ok: true };
251
+ }
252
+ if (kind === "rejection") {
253
+ this.#d.recordRejection(ownerAgentId, content, nowMs);
254
+ return { consumed: true, kind, ok: true };
255
+ }
256
+ if (kind === "proposal_ack") {
257
+ this.#d.recordProposalAck(ownerAgentId, content, nowMs);
258
+ return { consumed: true, kind, ok: true };
259
+ }
260
+ if (kind === "control") {
261
+ this.#d.recordControl(ownerAgentId, content, nowMs);
262
+ return { consumed: true, kind, ok: true };
263
+ }
264
+ const res = this.#d.ackInbound.receive(ownerAgentId, content, nowMs, correlationId);
265
+ return { consumed: true, kind, ok: res.ok, reason: res.ok ? undefined : res.reason };
266
+ }
267
+ catch (err) {
268
+ // Contained deliberately. The document layer refuses by returning a verdict, so reaching here
269
+ // is a programming fault — and the cost of letting it escape is that a peer could stop the
270
+ // operator's MESSAGES from being delivered by sending one bad document frame.
271
+ this.#d.logger.error("document.frame.handler_threw", {
272
+ kind,
273
+ correlationId,
274
+ reason: err instanceof Error ? err.message : String(err),
275
+ });
276
+ return { consumed: true, kind, ok: false, reason: "document_frame_handler_threw" };
277
+ }
278
+ }
279
+ }
280
+ /**
281
+ * The largest field count a document frame can declare. The update envelope has 10 fields and the
282
+ * ack has 9; the bound is generous so a future field does not silently start routing frames to the
283
+ * transcript, and tight enough that a map header claiming millions of pairs never reaches a decoder.
284
+ */
285
+ export const MAX_DOCUMENT_FRAME_FIELDS = 32;
286
+ /**
287
+ * The largest a document frame can legitimately be, and the bound that makes the decode's nesting
288
+ * depth finite.
289
+ *
290
+ * Sized off the payload it has to carry: the gate caps an update at 1 MiB, and everything else in
291
+ * the envelope — two hashes, an agent id, a state vector, a signature — is small. 2 MiB is
292
+ * comfortably above any real frame and far below the size at which nested-array pre-allocation
293
+ * becomes expensive.
294
+ *
295
+ * This is the layer that actually closes the pathological-input class. The decoder's per-container
296
+ * limit reduces amplification; only a bound on the INPUT can bound the depth.
297
+ */
298
+ export const MAX_DOCUMENT_FRAME_BYTES = 2 * 1024 * 1024;
299
+ /**
300
+ * Could these bytes be a document frame at all — cheaply, and without decoding?
301
+ *
302
+ * The decoders' own first requirement is "a definite-length CBOR map"; this is that requirement,
303
+ * checked on the header instead of after the damage. Note the real frames begin `b9 00 0a` — a
304
+ * NON-MINIMAL two-byte count, which `cbor.ts` documents as this encoder's deliberate behaviour — so
305
+ * the 1- and 2-byte count forms must be admitted. The 4- and 8-byte forms are refused outright: no
306
+ * document frame has four billion fields, and those are precisely the headers that let a few bytes
307
+ * ask for an enormous allocation.
308
+ */
309
+ function couldBeDocumentFrame(b) {
310
+ const header = b[0];
311
+ if (header === undefined)
312
+ return false;
313
+ // 0xa0..0xb7 — map with the pair count inline (0..23).
314
+ if (header >= 0xa0 && header <= 0xb7)
315
+ return true;
316
+ // 0xb8 / 0xb9 — map with a 1- or 2-byte pair count, which is what this encoder emits.
317
+ if (header === 0xb8)
318
+ return b.length >= 2 && b[1] <= MAX_DOCUMENT_FRAME_FIELDS;
319
+ if (header === 0xb9) {
320
+ return b.length >= 3 && ((b[1] << 8) | b[2]) <= MAX_DOCUMENT_FRAME_FIELDS;
321
+ }
322
+ // Everything else — including 0xbf (indefinite-length map) and 0x9f (indefinite-length ARRAY,
323
+ // the header in both measured pathological inputs) — is not a document frame.
324
+ //
325
+ // The upper end of the admitted range must stay at or below 0xbf, or the UTF-8 argument in the
326
+ // header stops holding: 0x80-0xbf are exactly the continuation bytes, and 0xc0 upward CAN begin a
327
+ // valid sequence. A test pins that.
328
+ return false;
329
+ }
330
+ function classify(content) {
331
+ // LENGTH FIRST. See the header: this is what bounds the nesting depth, and it is checked before
332
+ // the frame is looked at in any other way.
333
+ if (content.length > MAX_DOCUMENT_FRAME_BYTES)
334
+ return "unshaped";
335
+ if (!couldBeDocumentFrame(content))
336
+ return "unshaped";
337
+ // DECODE ONCE AND READ THE DISCRIMINATOR, rather than trying every decoder in turn.
338
+ //
339
+ // Each decoder validates its own `type` before anything else, so trying them in sequence meant a
340
+ // frame paid for as many full CBOR decodes as there are frame types ahead of it — and hostile
341
+ // bytes that pass the header guard paid for ALL of them. That cost grows every time this protocol
342
+ // gains a frame, which it did three times this milestone: six decoders where there were four, and
343
+ // the measured pathological inputs went from comfortably inside the DoS budget to over it on a
344
+ // slower machine.
345
+ //
346
+ // The decode itself is already bounded — `cbor.ts` sets process-global size limits, which is the
347
+ // actual boundary; the header guard above is only a fast path. So one decode is safe, and one is
348
+ // all this needs: read `type`, then hand the bytes to exactly the decoder that claims them, which
349
+ // re-validates everything including the type. Two decodes worst case instead of six, and one for
350
+ // anything that is not a CBOR map at all.
351
+ let declared;
352
+ try {
353
+ const decoded = decodeCbor(content);
354
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded))
355
+ return "unshaped";
356
+ declared = decoded["type"];
357
+ }
358
+ catch {
359
+ // Not decodable as CBOR at all. Shaped like a map header and is not one.
360
+ return "unshaped";
361
+ }
362
+ if (typeof declared !== "string")
363
+ return "undecodable";
364
+ try {
365
+ switch (declared) {
366
+ case "document_update":
367
+ decodeDocumentUpdateEnvelope(content);
368
+ return "update";
369
+ case "document_ack":
370
+ decodeDocumentAck(content);
371
+ return "ack";
372
+ case "document_proposal":
373
+ decodeDocumentProposal(content);
374
+ return "proposal";
375
+ case "document_rejection":
376
+ decodeDocumentRejection(content);
377
+ return "rejection";
378
+ case "document_proposal_ack":
379
+ decodeDocumentProposalAck(content);
380
+ return "proposal_ack";
381
+ case "document_control":
382
+ decodeDocumentControl(content);
383
+ return "control";
384
+ default:
385
+ // A frame type this build does not know. Reported by the caller rather than silently
386
+ // treated as conversation — see `routeSync`.
387
+ return "undecodable";
388
+ }
389
+ }
390
+ catch {
391
+ // It NAMED a document frame type and failed that type's own validation. Not conversation:
392
+ // conversation does not carry `type: "document_update"`.
393
+ return "undecodable";
394
+ }
395
+ }
396
+ //# sourceMappingURL=document-frame-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-frame-router.js","sourceRoot":"","sources":["../src/document-frame-router.ts"],"names":[],"mappings":"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"}