@cello-protocol/daemon 0.0.168 → 0.0.170

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 (111) hide show
  1. package/dist/agent-id-migration.d.ts.map +1 -1
  2. package/dist/agent-id-migration.js +16 -0
  3. package/dist/agent-id-migration.js.map +1 -1
  4. package/dist/away-detection.d.ts +62 -15
  5. package/dist/away-detection.d.ts.map +1 -1
  6. package/dist/away-detection.js +77 -20
  7. package/dist/away-detection.js.map +1 -1
  8. package/dist/close-session-handler.d.ts.map +1 -1
  9. package/dist/close-session-handler.js +69 -3
  10. package/dist/close-session-handler.js.map +1 -1
  11. package/dist/daemon.d.ts.map +1 -1
  12. package/dist/daemon.js +406 -303
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/delivery-open-registry.d.ts +92 -0
  15. package/dist/delivery-open-registry.d.ts.map +1 -0
  16. package/dist/delivery-open-registry.js +121 -0
  17. package/dist/delivery-open-registry.js.map +1 -0
  18. package/dist/document-amendment-store.d.ts +94 -46
  19. package/dist/document-amendment-store.d.ts.map +1 -1
  20. package/dist/document-amendment-store.js +217 -102
  21. package/dist/document-amendment-store.js.map +1 -1
  22. package/dist/document-delivery-transport.d.ts +51 -53
  23. package/dist/document-delivery-transport.d.ts.map +1 -1
  24. package/dist/document-delivery-transport.js +16 -157
  25. package/dist/document-delivery-transport.js.map +1 -1
  26. package/dist/document-frame-router.d.ts +13 -17
  27. package/dist/document-frame-router.d.ts.map +1 -1
  28. package/dist/document-frame-router.js +21 -93
  29. package/dist/document-frame-router.js.map +1 -1
  30. package/dist/document-handlers.d.ts +1 -1
  31. package/dist/document-handlers.d.ts.map +1 -1
  32. package/dist/document-handlers.js +462 -511
  33. package/dist/document-handlers.js.map +1 -1
  34. package/dist/document-handshake.d.ts +4 -1
  35. package/dist/document-handshake.d.ts.map +1 -1
  36. package/dist/document-handshake.js +3 -1
  37. package/dist/document-handshake.js.map +1 -1
  38. package/dist/document-inbound.d.ts +18 -5
  39. package/dist/document-inbound.d.ts.map +1 -1
  40. package/dist/document-inbound.js +94 -110
  41. package/dist/document-inbound.js.map +1 -1
  42. package/dist/document-layer.d.ts +49 -67
  43. package/dist/document-layer.d.ts.map +1 -1
  44. package/dist/document-layer.js +688 -565
  45. package/dist/document-layer.js.map +1 -1
  46. package/dist/document-lifecycle.d.ts +11 -157
  47. package/dist/document-lifecycle.d.ts.map +1 -1
  48. package/dist/document-lifecycle.js +26 -568
  49. package/dist/document-lifecycle.js.map +1 -1
  50. package/dist/document-publish.d.ts +13 -0
  51. package/dist/document-publish.d.ts.map +1 -1
  52. package/dist/document-publish.js +19 -11
  53. package/dist/document-publish.js.map +1 -1
  54. package/dist/document-reconcile-engine.d.ts +66 -0
  55. package/dist/document-reconcile-engine.d.ts.map +1 -0
  56. package/dist/document-reconcile-engine.js +225 -0
  57. package/dist/document-reconcile-engine.js.map +1 -0
  58. package/dist/document-reconcile-scheduler.d.ts +124 -0
  59. package/dist/document-reconcile-scheduler.d.ts.map +1 -0
  60. package/dist/document-reconcile-scheduler.js +226 -0
  61. package/dist/document-reconcile-scheduler.js.map +1 -0
  62. package/dist/document-rejection.d.ts +2 -0
  63. package/dist/document-rejection.d.ts.map +1 -1
  64. package/dist/document-rejection.js +5 -1
  65. package/dist/document-rejection.js.map +1 -1
  66. package/dist/document-store.d.ts +18 -247
  67. package/dist/document-store.d.ts.map +1 -1
  68. package/dist/document-store.js +95 -827
  69. package/dist/document-store.js.map +1 -1
  70. package/dist/inbound-sessions.d.ts +7 -0
  71. package/dist/inbound-sessions.d.ts.map +1 -1
  72. package/dist/inbound-sessions.js +27 -6
  73. package/dist/inbound-sessions.js.map +1 -1
  74. package/dist/initiate-session-handler.d.ts +5 -0
  75. package/dist/initiate-session-handler.d.ts.map +1 -1
  76. package/dist/initiate-session-handler.js +1 -0
  77. package/dist/initiate-session-handler.js.map +1 -1
  78. package/dist/notification-handlers.d.ts.map +1 -1
  79. package/dist/notification-handlers.js +39 -2
  80. package/dist/notification-handlers.js.map +1 -1
  81. package/dist/session-content-handlers.d.ts.map +1 -1
  82. package/dist/session-content-handlers.js +134 -8
  83. package/dist/session-content-handlers.js.map +1 -1
  84. package/dist/session-node-manager.d.ts +153 -8
  85. package/dist/session-node-manager.d.ts.map +1 -1
  86. package/dist/session-node-manager.js +1376 -35
  87. package/dist/session-node-manager.js.map +1 -1
  88. package/dist/types.d.ts +64 -2
  89. package/dist/types.d.ts.map +1 -1
  90. package/dist/types.js.map +1 -1
  91. package/package.json +5 -5
  92. package/dist/delivery-sweep-bound.d.ts +0 -79
  93. package/dist/delivery-sweep-bound.d.ts.map +0 -1
  94. package/dist/delivery-sweep-bound.js +0 -109
  95. package/dist/delivery-sweep-bound.js.map +0 -1
  96. package/dist/document-ack-inbound.d.ts +0 -71
  97. package/dist/document-ack-inbound.d.ts.map +0 -1
  98. package/dist/document-ack-inbound.js +0 -210
  99. package/dist/document-ack-inbound.js.map +0 -1
  100. package/dist/document-control-notifier.d.ts +0 -124
  101. package/dist/document-control-notifier.d.ts.map +0 -1
  102. package/dist/document-control-notifier.js +0 -176
  103. package/dist/document-control-notifier.js.map +0 -1
  104. package/dist/document-delivery.d.ts +0 -250
  105. package/dist/document-delivery.d.ts.map +0 -1
  106. package/dist/document-delivery.js +0 -608
  107. package/dist/document-delivery.js.map +0 -1
  108. package/dist/document-join-store.d.ts +0 -80
  109. package/dist/document-join-store.d.ts.map +0 -1
  110. package/dist/document-join-store.js +0 -197
  111. package/dist/document-join-store.js.map +0 -1
package/dist/types.d.ts CHANGED
@@ -138,15 +138,64 @@ export interface DaemonStatusResponse {
138
138
  */
139
139
  active_sessions: ActiveSessionInfo[];
140
140
  }
141
+ /**
142
+ * DOD-M12B-SEAL-STUCK-1 — whether a session can be sealed, as an operator surface.
143
+ *
144
+ * THREE STATES, because there are three answers and collapsing them is how this milestone's
145
+ * defects keep happening.
146
+ *
147
+ * `blocked` carries the two numbers separately, and they are NOT interchangeable:
148
+ * - `heldBehindGap` — messages this side RECEIVED and verified, waiting for an earlier position.
149
+ * Durable since DOD-M12B-STRAND-1, so this half survives a restart.
150
+ * - `awaitingArrival` — positions the relay witnessed for which nothing has arrived at all.
151
+ * Memory-only, so this half does NOT survive a restart, which is why `unknown` exists.
152
+ * - `oldestHeldMs` separates "stuck since this morning" from "in flight 40 ms ago". Without it a
153
+ * healthy mid-conversation window — the relay witnesses a counterparty leaf a moment before the
154
+ * content arrives — reads exactly like a permanently stranded session, and a warning on
155
+ * everything is a warning on nothing.
156
+ *
157
+ * `unknown` is NOT a soft `ready`. It is returned when this process cannot answer: the witness
158
+ * state that would show a never-arrived position is memory-only, so for a session that carries
159
+ * leaves from before this daemon started, "no gap recorded" means "not recorded", not "no gap".
160
+ * Reporting `ready` there would invite a close, and a short chain gets `leaf_count_mismatch` back —
161
+ * which is terminal, and the notarized receipt is gone for good.
162
+ *
163
+ * A `blocked` answer can still clear on its own: the close path drains parked relay content and
164
+ * re-checks before it refuses, which this read does not do. So this surface answers "right now",
165
+ * and a close may succeed against a session shown here as blocked.
166
+ */
167
+ export type SealReadinessView = {
168
+ state: "ready";
169
+ } | {
170
+ state: "blocked";
171
+ awaitingArrival: number;
172
+ heldBehindGap: number;
173
+ oldestHeldMs: number | null;
174
+ } | {
175
+ state: "unknown";
176
+ reason: string;
177
+ };
141
178
  /** One active session's status row (see DaemonStatusResponse.active_sessions). */
142
179
  export interface ActiveSessionInfo {
143
180
  sessionId: string;
144
181
  agentName: string;
145
182
  counterpartyPubkey: string;
146
- /** Direct-path counterparty liveness; 'unknown' before any session-node observation. */
147
- liveness: "alive" | "gone" | "unknown";
183
+ /** Direct-path counterparty liveness; 'unknown' before any session-node observation.
184
+ * DOD-M12B-ACK-1: 'impaired' means the connection is up and our writes on it are failing —
185
+ * distinct from 'gone' (the connection dropped), which is what cello_receive turns into "call
186
+ * cello_close_session". It is daemon-local and is NOT the relay's SessionLiveness wire type. */
187
+ liveness: "alive" | "impaired" | "gone" | "unknown";
148
188
  /** DOD-SESSION-NAME-1: this agent's own label for the session; null when unnamed. */
149
189
  sessionName: string | null;
190
+ /**
191
+ * DOD-M12B-SEAL-STUCK-1 — whether this session can be sealed. See SealReadinessView.
192
+ *
193
+ * A chain with a gap cannot be co-signed, so the close refuses — correctly. But until this field
194
+ * existed the condition was only discoverable by ATTEMPTING a close on each session and reading
195
+ * the refusal, so 25 unsealable sessions accumulated on one daemon with every surface calling
196
+ * them ordinary active sessions, while each held a slot against the per-sender cap.
197
+ */
198
+ sealReadiness: SealReadinessView;
150
199
  /** DOD-FRONTIER-STRAND-1 AC3 — see SessionListEntry.frontierMismatch. Declared here because
151
200
  * buildInterruptedSessions returns THIS type; it typechecked only because TS exempts spread
152
201
  * properties from excess-property checking, so a renderer typed as this could not read it. */
@@ -419,6 +468,19 @@ export interface InterruptedSessionInfo {
419
468
  interruptedAt: string;
420
469
  /** DOD-SESSION-NAME-1: this agent's own label for the session; null when unnamed. */
421
470
  sessionName: string | null;
471
+ /**
472
+ * DOD-M12B-SEAL-STUCK-1 — whether this session can be sealed. See SealReadinessView.
473
+ *
474
+ * An interrupted session can seal (that is what seal-interrupted is for), so it can also be
475
+ * BLOCKED from sealing by a gap. `frontierMismatch` beside this reports a different, later
476
+ * condition: the two sides have already exchanged and disagreed. This one is the gap on OUR side,
477
+ * knowable before any exchange, and it is the more common of the two.
478
+ *
479
+ * A PLAIN PROPERTY, not optional — matching the active list. The optional form was built with a
480
+ * spread, which bypasses excess-property checking, and that is exactly how `frontierMismatch`
481
+ * four lines down came to typecheck while no renderer could read it.
482
+ */
483
+ sealReadiness: SealReadinessView;
422
484
  }
423
485
  /**
424
486
  * cello_sessions: one session in the discovery list for the current agent.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,aAAa,EACb,eAAe,EAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKrE,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAGtC,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,GAC5B,CAAC;AAGF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAIlE,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAID,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAI3D,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;AAIrD;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAG,uBAAgC,CAAC;AAI7E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,cAAc,GACd,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAID,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,cAAc,GAAG,MAAM,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,MAAM,EAAE,SAAS,EAAE,CAAC;IAIpB;;;;OAIG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,oBAAoB,EAAE,sBAAsB,EAAE,CAAC;IAC/C;;;;OAIG;IACH,eAAe,EAAE,iBAAiB,EAAE,CAAC;CACtC;AAED,kFAAkF;AAClF,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACvC,qFAAqF;IACrF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;mGAE+F;IAC/F,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAID,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,qBAAqB,CAAC;IAC7C;;;OAGG;IACH,qBAAqB,CAAC,EAAE,sBAAsB,CAAC;IAC/C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;IAChD;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAChD;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACpE;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,CAAC;IAC7E;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,OAAO,kBAAkB,EAAE,MAAM,CAAC;IAClD;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,eAAe,EAAE,qBAAqB,CAAC;IACvC;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,2BAA2B,EAAE,sBAAsB,CAAC;CACpF;AAID;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,0BAA0B,GAI1B,WAAW,CAAC;AAEhB,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAEF,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAID,eAAO,MAAM,UAAU;;;;;;;;;;CAUb,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgFH,gCAAgC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,uBAAgC,CAAC;AAyU7E,6BAA6B;AAE7B;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAmIpC,sBAAsB;AAEtB,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,yBAAyB,EAAE,2BAA2B;IACtD,yBAAyB,EAAE,2BAA2B;IACtD,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,sBAAsB;IAC5C,6BAA6B,EAAE,+BAA+B;IAC9D,8BAA8B,EAAE,gCAAgC;IAChE,oBAAoB,EAAE,sBAAsB;IAC5C,4BAA4B,EAAE,8BAA8B;IAC5D,6BAA6B,EAAE,+BAA+B;CACtD,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgFH,gCAAgC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,uBAAgC,CAAC;AAqX7E,6BAA6B;AAE7B;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAgJpC,sBAAsB;AAEtB,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,yBAAyB,EAAE,2BAA2B;IACtD,yBAAyB,EAAE,2BAA2B;IACtD,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,sBAAsB;IAC5C,6BAA6B,EAAE,+BAA+B;IAC9D,8BAA8B,EAAE,gCAAgC;IAChE,oBAAoB,EAAE,sBAAsB;IAC5C,4BAA4B,EAAE,8BAA8B;IAC5D,6BAA6B,EAAE,+BAA+B;CACtD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cello-protocol/daemon",
3
- "version": "0.0.168",
3
+ "version": "0.0.170",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,10 +35,10 @@
35
35
  "@signalapp/sqlcipher": "^3.3.5",
36
36
  "cbor-x": "^1.6.0",
37
37
  "it-length-prefixed": "^10.0.1",
38
- "@cello-protocol/crypto": "0.0.50",
39
- "@cello-protocol/gateway": "0.0.34",
40
- "@cello-protocol/protocol-types": "0.0.54",
41
- "@cello-protocol/transport": "0.0.56"
38
+ "@cello-protocol/crypto": "0.0.52",
39
+ "@cello-protocol/protocol-types": "0.0.56",
40
+ "@cello-protocol/gateway": "0.0.36",
41
+ "@cello-protocol/transport": "0.0.58"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.6.2",
@@ -1,79 +0,0 @@
1
- /**
2
- * DOD-MP-SWEEP-ALIVE-1 — bound one agent's delivery pass so a hang cannot end delivery.
3
- *
4
- * ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────────────
5
- *
6
- * The daemon's sweep is guarded against overlap by a boolean released in the `finally` of an async
7
- * body. That release runs only when the body SETTLES. A throw is covered — there is a `catch` and
8
- * the `finally` still runs. **An await that never settles is not covered at all.**
9
- *
10
- * So one hung pass wedges the guard for the life of the process: the interval keeps firing, hits
11
- * `if (running) return`, and emits nothing. No log line, no error, no counter — while every
12
- * document edit sits undelivered and the document reports healthy on both sides.
13
- *
14
- * Observed live 2026-08-13: last completed sweep `17:06:28`; delivery work carrying a `dlv-`
15
- * correlation id at `17:07:38`; no sweep ever completed again. The peer daemon, same build, one
16
- * agent, swept on schedule throughout — so the hang is state-dependent, not a general code break.
17
- *
18
- * ── WHY IT BOUNDS THE CLASS, NOT THE INSTANCE ─────────────────────────────────────────────────
19
- *
20
- * `acquireSession` ends in `await deps.openSession(...)` with no timeout of its own, which makes it
21
- * the candidate. It is only a candidate: nothing in the log records which await hung. Closing just
22
- * that one would be a guess wearing a fix's clothes, and the wedge would survive anywhere else it
23
- * can happen. Bounding the pass holds however the hang arises.
24
- *
25
- * The bound does NOT cancel the hung work — it cannot; a promise has no abort. It stops the SWEEP
26
- * from waiting on it, so the remaining agents are swept and the next tick proceeds. A wedged agent
27
- * degrades alone and loudly, which is the fan-out-availability doctrine applied to the sweep
28
- * itself: one unreachable participant must never take the others down with it.
29
- *
30
- * ── THE BOUND ALONE IS NOT ENOUGH ─────────────────────────────────────────────────────────────
31
- *
32
- * `DocumentDelivery.tick` carries the IDENTICAL defect one layer down: it caches its pass in
33
- * `#inFlight` and clears it in a `finally`. So after a hang, every later tick for that agent
34
- * returns the SAME already-hung promise — the agent never delivers again, and each pass re-races it
35
- * for the full bound, which doubles the sweep interval for every healthy agent on the daemon.
36
- * Bounding without evicting therefore trades a silent outage for a slower one. `sweepAgentEvicting`
37
- * below drops the wedged worker so the next tick constructs a fresh one and re-enters the pass.
38
- *
39
- * Evicting cannot double-send: the pass claims each row (`recordHolderAttempt`) BEFORE it dials, so
40
- * anything the hung pass was holding is already scheduled into the future and is not due.
41
- *
42
- * ── ONE LOAD-BEARING CONSTRAINT, STATED WHERE IT BINDS ────────────────────────────────────────
43
- *
44
- * `setTimeout` inside a `Promise.race` is safe here ONLY because the daemon's `stop()` does not
45
- * await the in-flight sweep. If anyone restores that await, a race whose timer never fires during a
46
- * draining loop can hang shutdown — see the shutdown note in daemon.ts.
47
- */
48
- export interface BoundedPassDeps {
49
- logger: {
50
- warn(event: string, ctx: Record<string, unknown>): void;
51
- };
52
- /** How long one agent's pass may run before it is declared stuck. */
53
- timeoutMs: number;
54
- /** Named in the log line — an operator needs to know WHICH agent stopped delivering. */
55
- agentName: string;
56
- }
57
- export type BoundedPassResult<T> = {
58
- completed: true;
59
- value: T;
60
- } | {
61
- completed: false;
62
- };
63
- export declare function runAgentPassBounded<T>(deps: BoundedPassDeps, run: () => Promise<T>): Promise<BoundedPassResult<T>>;
64
- /**
65
- * One agent's pass, bounded AND evicted on a wedge.
66
- *
67
- * Separated from `runAgentPassBounded` because eviction needs the worker registry, and separated
68
- * from the daemon because the daemon's sweep cannot be constructed in a test — which is exactly why
69
- * the call site went uncovered while the helper beneath it was well tested.
70
- */
71
- export interface EvictingSweepDeps<T> extends BoundedPassDeps {
72
- /** The per-agent worker registry. The wedged entry is dropped so the next tick builds a fresh one. */
73
- workers: {
74
- delete(agentName: string): unknown;
75
- };
76
- run: () => Promise<T>;
77
- }
78
- export declare function sweepAgentEvicting<T>(deps: EvictingSweepDeps<T>): Promise<BoundedPassResult<T>>;
79
- //# sourceMappingURL=delivery-sweep-bound.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"delivery-sweep-bound.d.ts","sourceRoot":"","sources":["../src/delivery-sweep-bound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;IACpE,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,IAAI,EAAE,eAAe,EACrB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAkD/B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,eAAe;IAC3D,sGAAsG;IACtG,OAAO,EAAE;QAAE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAChD,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;CACvB;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAQ/B"}
@@ -1,109 +0,0 @@
1
- /**
2
- * DOD-MP-SWEEP-ALIVE-1 — bound one agent's delivery pass so a hang cannot end delivery.
3
- *
4
- * ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────────────
5
- *
6
- * The daemon's sweep is guarded against overlap by a boolean released in the `finally` of an async
7
- * body. That release runs only when the body SETTLES. A throw is covered — there is a `catch` and
8
- * the `finally` still runs. **An await that never settles is not covered at all.**
9
- *
10
- * So one hung pass wedges the guard for the life of the process: the interval keeps firing, hits
11
- * `if (running) return`, and emits nothing. No log line, no error, no counter — while every
12
- * document edit sits undelivered and the document reports healthy on both sides.
13
- *
14
- * Observed live 2026-08-13: last completed sweep `17:06:28`; delivery work carrying a `dlv-`
15
- * correlation id at `17:07:38`; no sweep ever completed again. The peer daemon, same build, one
16
- * agent, swept on schedule throughout — so the hang is state-dependent, not a general code break.
17
- *
18
- * ── WHY IT BOUNDS THE CLASS, NOT THE INSTANCE ─────────────────────────────────────────────────
19
- *
20
- * `acquireSession` ends in `await deps.openSession(...)` with no timeout of its own, which makes it
21
- * the candidate. It is only a candidate: nothing in the log records which await hung. Closing just
22
- * that one would be a guess wearing a fix's clothes, and the wedge would survive anywhere else it
23
- * can happen. Bounding the pass holds however the hang arises.
24
- *
25
- * The bound does NOT cancel the hung work — it cannot; a promise has no abort. It stops the SWEEP
26
- * from waiting on it, so the remaining agents are swept and the next tick proceeds. A wedged agent
27
- * degrades alone and loudly, which is the fan-out-availability doctrine applied to the sweep
28
- * itself: one unreachable participant must never take the others down with it.
29
- *
30
- * ── THE BOUND ALONE IS NOT ENOUGH ─────────────────────────────────────────────────────────────
31
- *
32
- * `DocumentDelivery.tick` carries the IDENTICAL defect one layer down: it caches its pass in
33
- * `#inFlight` and clears it in a `finally`. So after a hang, every later tick for that agent
34
- * returns the SAME already-hung promise — the agent never delivers again, and each pass re-races it
35
- * for the full bound, which doubles the sweep interval for every healthy agent on the daemon.
36
- * Bounding without evicting therefore trades a silent outage for a slower one. `sweepAgentEvicting`
37
- * below drops the wedged worker so the next tick constructs a fresh one and re-enters the pass.
38
- *
39
- * Evicting cannot double-send: the pass claims each row (`recordHolderAttempt`) BEFORE it dials, so
40
- * anything the hung pass was holding is already scheduled into the future and is not due.
41
- *
42
- * ── ONE LOAD-BEARING CONSTRAINT, STATED WHERE IT BINDS ────────────────────────────────────────
43
- *
44
- * `setTimeout` inside a `Promise.race` is safe here ONLY because the daemon's `stop()` does not
45
- * await the in-flight sweep. If anyone restores that await, a race whose timer never fires during a
46
- * draining loop can hang shutdown — see the shutdown note in daemon.ts.
47
- */
48
- export async function runAgentPassBounded(deps, run) {
49
- let timer;
50
- /** Set the moment the bound wins — the only state that makes a later rejection 'late'. */
51
- let abandoned = false;
52
- const bound = new Promise((resolve) => {
53
- timer = setTimeout(() => {
54
- abandoned = true;
55
- deps.logger.warn("document.delivery.pass.stuck", {
56
- agentName: deps.agentName,
57
- timeoutMs: deps.timeoutMs,
58
- // ACCURATE ABOUT RECOVERY. This said "until it clears", and it did not clear: the worker
59
- // cached the hung pass and handed it back forever. It is true only because the caller now
60
- // evicts the worker, so say what actually happens next rather than implying patience.
61
- impact: "this agent's delivery pass did not finish, so the sweep stopped waiting for it and " +
62
- "dropped the worker — a fresh pass starts on the next tick. Repeats mean the dial " +
63
- "underneath is hanging, not that the pass is merely slow",
64
- });
65
- resolve({ completed: false });
66
- }, deps.timeoutMs);
67
- // Never hold the process open on account of the bound itself.
68
- timer.unref?.();
69
- });
70
- try {
71
- // A THROW PROPAGATES. Containment lives in the caller, which already logs
72
- // `document.delivery.tick.failed` and releases its guard; swallowing it here would silently
73
- // delete that reporting and make a failing pass indistinguishable from a healthy one.
74
- const attempt = run();
75
- // THE LOSING BRANCH IS STILL WATCHED. If a pass the sweep GAVE UP ON eventually rejects, the
76
- // settled race absorbs it and the error is logged nowhere — discarding the single best piece of
77
- // evidence about WHICH await hung, on a defect whose root cause is explicitly not established.
78
- //
79
- // ONLY WHEN IT IS ACTUALLY LATE. A pass that throws while the race is still pending propagates
80
- // to the caller, which logs it; reporting that here too would double-report an ordinary failure
81
- // and blunt the one event that means something unusual happened.
82
- attempt.catch((err) => {
83
- if (!abandoned)
84
- return;
85
- deps.logger.warn("document.delivery.pass.late_failure", {
86
- agentName: deps.agentName,
87
- reason: err instanceof Error ? err.message : String(err),
88
- impact: "a pass the sweep had already given up on failed afterwards",
89
- });
90
- });
91
- return await Promise.race([attempt.then((value) => ({ completed: true, value })), bound]);
92
- }
93
- finally {
94
- // Cleared on EVERY exit, including the throw. Left armed, each pass would pin a timer per agent
95
- // per tick for the life of a process whose whole purpose is to keep running.
96
- if (timer !== undefined)
97
- clearTimeout(timer);
98
- }
99
- }
100
- export async function sweepAgentEvicting(deps) {
101
- const outcome = await runAgentPassBounded(deps, deps.run);
102
- if (!outcome.completed) {
103
- // WITHOUT THIS the agent is finished: its worker keeps handing back the same hung promise, so
104
- // it never delivers again and every future pass pays the full bound waiting for it.
105
- deps.workers.delete(deps.agentName);
106
- }
107
- return outcome;
108
- }
109
- //# sourceMappingURL=delivery-sweep-bound.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"delivery-sweep-bound.js","sourceRoot":"","sources":["../src/delivery-sweep-bound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAYH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAqB,EACrB,GAAqB;IAErB,IAAI,KAAgD,CAAC;IACrD,0FAA0F;IAC1F,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,OAAO,CAAuB,CAAC,OAAO,EAAE,EAAE;QAC1D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,SAAS,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE;gBAC/C,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,yFAAyF;gBACzF,0FAA0F;gBAC1F,sFAAsF;gBACtF,MAAM,EACJ,qFAAqF;oBACrF,mFAAmF;oBACnF,yDAAyD;aAC5D,CAAC,CAAC;YACH,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAChC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnB,8DAA8D;QAC9D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,0EAA0E;QAC1E,4FAA4F;QAC5F,sFAAsF;QACtF,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;QACtB,6FAA6F;QAC7F,gGAAgG;QAChG,+FAA+F;QAC/F,EAAE;QACF,+FAA+F;QAC/F,gGAAgG;QAChG,iEAAiE;QACjE,OAAO,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YAC7B,IAAI,CAAC,SAAS;gBAAE,OAAO;YACvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE;gBACtD,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACxD,MAAM,EAAE,4DAA4D;aACrE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACrG,CAAC;YAAS,CAAC;QACT,gGAAgG;QAChG,6EAA6E;QAC7E,IAAI,KAAK,KAAK,SAAS;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAeD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAA0B;IAE1B,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1D,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,8FAA8F;QAC9F,oFAAoF;QACpF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,71 +0,0 @@
1
- /**
2
- * DOD-DOC-INBOUND-2 — consuming an arriving ACK. This is what closes DELIVERY-2's loop.
3
- *
4
- * Without it a sent envelope's outcome is `admitted: null` forever: the worker knows the content
5
- * left and nothing more, so it re-sends on the ack timeout and eventually stalls the document at
6
- * the unacked ceiling.
7
- *
8
- * ── EVERY CHECK HERE GUARDS ONE OF TWO IRREVERSIBLE CONSEQUENCES ──────────────────────────────
9
- *
10
- * An ack SETTLES an envelope. Admitted, it stops being redelivered; rejected, the sender rolls back
11
- * local work and supersedes. Both are things an unauthenticated party must not be able to cause, so
12
- * the order is:
13
- *
14
- * 1. DECODE — refuse malformed input before interpreting any of it.
15
- * 2. KNOW the document — an ack for a document we do not hold settles nothing that exists.
16
- * 3. ACKER is the peer — a document is a pairwise agreement; nobody else's answer settles it.
17
- * 4. VERIFY — before any write. An unsigned ack that silenced a delivery would drop
18
- * the content from the pending set with neither operator ever learning it
19
- * was never applied — the silent divergence the two-layer design exists to
20
- * prevent.
21
- * 5. THE ENVELOPE IS OURS — we authored it, and it is in the log. An ack for a peer-authored
22
- * envelope settles a delivery that was never ours to make; an ack for an
23
- * envelope that is not there is a bug or a probe, and recording it would
24
- * put a claim about a nonexistent delivery into a permanent record.
25
- * 6. SETTLE — mark acked; on a rejection also record it durably on the publishing side,
26
- * because an operator needs to know WHY their work was refused after a
27
- * restart, not merely that it was.
28
- */
29
- import type { DocumentStore } from "./document-store.js";
30
- import type { DocumentRejections } from "./document-rejection.js";
31
- import type { Logger } from "./types.js";
32
- export type AckInboundResult = {
33
- ok: true;
34
- admitted: boolean;
35
- envelopeHash: string;
36
- } | {
37
- ok: false;
38
- reason: string;
39
- detail: string;
40
- };
41
- export interface DocumentAckInboundDeps {
42
- store: DocumentStore;
43
- rejections: DocumentRejections;
44
- logger: Logger;
45
- /**
46
- * Verify the ack signature against `acker_agent_id`. INJECTED and REQUIRED — this module cannot
47
- * resolve an agent id to a public key, and a default would be a default answer to the one
48
- * question that must never have one.
49
- */
50
- verifySignature(ackerAgentId: string, tbs: Uint8Array, signature: Uint8Array): boolean;
51
- /**
52
- * M14B / FANOUT-1 — the CURRENT derived holders, or null when the chain does not derive.
53
- * Null is the bilateral-legacy answer: only then does the genesis-peer clause stand alone.
54
- * When the chain DERIVES, membership is the whole gate — a removed genesis peer's ack must
55
- * refuse (review H2: `doc.peerAgentId` is a genesis fact that outlives the arrangement).
56
- */
57
- currentHolders(ownerAgentId: string, documentId: string): string[] | null;
58
- /**
59
- * Called once an envelope is SETTLED — admitted or rejected, both of which end the delivery.
60
- *
61
- * Exists so the sender can stop holding a session open waiting for an answer that has arrived.
62
- * Optional because settling is complete without it; nothing here depends on anyone listening.
63
- */
64
- onSettled?(ownerAgentId: string, envelopeHash: string, ackerAgentId: string, admitted: boolean): void;
65
- }
66
- export declare class DocumentAckInbound {
67
- #private;
68
- constructor(deps: DocumentAckInboundDeps);
69
- receive(ownerAgentId: string, wire: Uint8Array, nowMs: number, correlationId?: string): AckInboundResult;
70
- }
71
- //# sourceMappingURL=document-ack-inbound.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"document-ack-inbound.d.ts","sourceRoot":"","sources":["../src/document-ack-inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC;IACvF;;;;;OAKG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1E;;;;;OAKG;IACH,SAAS,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;CACvG;AAED,qBAAa,kBAAkB;;gBAGjB,IAAI,EAAE,sBAAsB;IAIxC,OAAO,CACL,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,aAAa,SAAQ,GACpB,gBAAgB;CAwKpB"}
@@ -1,210 +0,0 @@
1
- /**
2
- * DOD-DOC-INBOUND-2 — consuming an arriving ACK. This is what closes DELIVERY-2's loop.
3
- *
4
- * Without it a sent envelope's outcome is `admitted: null` forever: the worker knows the content
5
- * left and nothing more, so it re-sends on the ack timeout and eventually stalls the document at
6
- * the unacked ceiling.
7
- *
8
- * ── EVERY CHECK HERE GUARDS ONE OF TWO IRREVERSIBLE CONSEQUENCES ──────────────────────────────
9
- *
10
- * An ack SETTLES an envelope. Admitted, it stops being redelivered; rejected, the sender rolls back
11
- * local work and supersedes. Both are things an unauthenticated party must not be able to cause, so
12
- * the order is:
13
- *
14
- * 1. DECODE — refuse malformed input before interpreting any of it.
15
- * 2. KNOW the document — an ack for a document we do not hold settles nothing that exists.
16
- * 3. ACKER is the peer — a document is a pairwise agreement; nobody else's answer settles it.
17
- * 4. VERIFY — before any write. An unsigned ack that silenced a delivery would drop
18
- * the content from the pending set with neither operator ever learning it
19
- * was never applied — the silent divergence the two-layer design exists to
20
- * prevent.
21
- * 5. THE ENVELOPE IS OURS — we authored it, and it is in the log. An ack for a peer-authored
22
- * envelope settles a delivery that was never ours to make; an ack for an
23
- * envelope that is not there is a bug or a probe, and recording it would
24
- * put a claim about a nonexistent delivery into a permanent record.
25
- * 6. SETTLE — mark acked; on a rejection also record it durably on the publishing side,
26
- * because an operator needs to know WHY their work was refused after a
27
- * restart, not merely that it was.
28
- */
29
- import { decodeDocumentAck, buildDocumentAckTbs } from "@cello-protocol/protocol-types";
30
- export class DocumentAckInbound {
31
- #d;
32
- constructor(deps) {
33
- this.#d = deps;
34
- }
35
- receive(ownerAgentId, wire, nowMs, correlationId = "ack") {
36
- // 1. DECODE.
37
- let ack;
38
- try {
39
- ack = decodeDocumentAck(wire);
40
- }
41
- catch (err) {
42
- const detail = err instanceof Error ? err.message : String(err);
43
- this.#d.logger.warn("document.ack.malformed", { detail, correlationId });
44
- // One reason code with the upstream message in the detail — the decoder's named refusals and
45
- // raw CBOR errors both arrive here, and building a code out of the message turns the second
46
- // kind into English prose nothing can match on.
47
- return { ok: false, reason: "document_ack_malformed", detail };
48
- }
49
- // 2. KNOW the document.
50
- const doc = this.#d.store.getDocument(ownerAgentId, ack.document_id);
51
- if (!doc) {
52
- return {
53
- ok: false,
54
- reason: "document_unknown",
55
- detail: `no document ${ack.document_id.slice(0, 16)}… for this agent`,
56
- };
57
- }
58
- // 3. The acker must be a party. When the chain DERIVES, derived membership is the WHOLE
59
- // gate — a removed genesis peer's ack refuses like any outsider's (review H2). Only when the
60
- // chain cannot answer (bilateral legacy, no genesis record) does the genesis-peer column
61
- // stand in.
62
- const holders = this.#d.currentHolders(ownerAgentId, ack.document_id);
63
- const isParty = holders === null
64
- ? ack.acker_agent_id === doc.peerAgentId
65
- : holders.includes(ack.acker_agent_id);
66
- if (!isParty) {
67
- this.#d.logger.warn("document.ack.not_peer", {
68
- documentId: ack.document_id,
69
- ackerAgentId: ack.acker_agent_id,
70
- peerAgentId: doc.peerAgentId,
71
- correlationId,
72
- });
73
- return {
74
- ok: false,
75
- reason: "document_ack_not_peer",
76
- // The peer's identity is not disclosed to a party that has not authenticated.
77
- detail: "you are not a party to this document",
78
- };
79
- }
80
- // 4. VERIFY, before any write.
81
- if (!this.#d.verifySignature(ack.acker_agent_id, buildDocumentAckTbs(ack), ack.signature)) {
82
- this.#d.logger.error("document.ack.signature_invalid", {
83
- documentId: ack.document_id,
84
- ackerAgentId: ack.acker_agent_id,
85
- envelopeHash: ack.envelope_hash,
86
- correlationId,
87
- });
88
- return {
89
- ok: false,
90
- reason: "document_ack_signature_invalid",
91
- detail: `the ack claims to come from ${ack.acker_agent_id} but its signature does not verify ` +
92
- `against that agent — nothing was settled`,
93
- };
94
- }
95
- // 5. The envelope must be one WE authored, and it must exist.
96
- const row = this.#d.store
97
- .getEnvelopeLog(ownerAgentId, ack.document_id)
98
- .find((e) => e.envelopeHash === ack.envelope_hash);
99
- if (!row) {
100
- return {
101
- ok: false,
102
- reason: "document_ack_envelope_unknown",
103
- detail: `envelope ${ack.envelope_hash.slice(0, 16)}… is not in this document's log`,
104
- };
105
- }
106
- if (row.senderAgentId !== ownerAgentId) {
107
- return {
108
- ok: false,
109
- reason: "document_ack_not_author",
110
- detail: `envelope ${ack.envelope_hash.slice(0, 16)}… was authored by ${row.senderAgentId}, not by ` +
111
- `you — there is no delivery of ours for this ack to settle`,
112
- };
113
- }
114
- // 6. SETTLE ONCE. The wire type's own header states this contract: nothing binds an ack to the
115
- // acker's chain, so one acker can produce a valid ADMISSION and a valid REJECTION for the same
116
- // envelope. Applying the later one would let a peer that admitted an envelope later claim it
117
- // refused it — and the sender would roll back work the peer already holds. A second,
118
- // CONTRADICTING ack is an error; a second identical one is an ordinary redelivery.
119
- // SETTLE-ONCE, PER ACKER (review M4). The envelope-level column stays null until ALL
120
- // holders answer, so an envelope-scoped check let a holder that ADMITTED later send a
121
- // REJECTION mid-fan-out — processed, rounds advanced, work rolled back that they already
122
- // hold. The acker's own per-holder row is the record; the envelope-level check remains only
123
- // for the zero-row bilateral legacy.
124
- const holderPrior = this.#d.store.holderSettlement(ownerAgentId, ack.envelope_hash, ack.acker_agent_id);
125
- const alreadyAcked = holderPrior !== null || row.ackedAtMs != null;
126
- if (alreadyAcked) {
127
- // ENVELOPE-scoped. The document-scoped reader answers a different question, and using it
128
- // here would make a rejection of any OTHER envelope in this document read as a rejection of
129
- // this one — so an honest redelivered admission would be refused as a contradiction the
130
- // moment anything else in the document had ever been refused.
131
- const previouslyRejected = holderPrior !== null
132
- ? !holderPrior.admitted
133
- : this.#d.store.rejectionReceivedFor(ownerAgentId, ack.document_id, ack.envelope_hash);
134
- const contradicts = ack.admitted === previouslyRejected;
135
- if (contradicts) {
136
- this.#d.logger.error("document.ack.contradiction", {
137
- documentId: ack.document_id,
138
- envelopeHash: ack.envelope_hash,
139
- ackerAgentId: ack.acker_agent_id,
140
- nowSays: ack.admitted ? "admitted" : "rejected",
141
- correlationId,
142
- });
143
- return {
144
- ok: false,
145
- reason: "document_ack_contradiction",
146
- detail: `${ack.acker_agent_id} already settled envelope ${ack.envelope_hash.slice(0, 16)}… and ` +
147
- `now says the opposite — this envelope stays settled as it was, and both claims are ` +
148
- `retained`,
149
- };
150
- }
151
- // An identical redelivery. Nothing to do, and nothing to complain about.
152
- return { ok: true, admitted: ack.admitted, envelopeHash: ack.envelope_hash };
153
- }
154
- // FANOUT-1: the ACKING HOLDER settles THEIR row; the envelope-level record follows the ONE
155
- // deterministic terminal rule (`reconcileEnvelopeSettlement` — review M3), identical from
156
- // every reconciliation site. Bilateral legacy (zero rows) stays byte-identical: the
157
- // reconcile no-ops and markAcked below still runs through it.
158
- this.#d.store.ackHolderDelivery(ownerAgentId, ack.document_id, ack.envelope_hash, ack.acker_agent_id, nowMs);
159
- const before = row.ackedAtMs != null;
160
- this.#d.store.reconcileEnvelopeSettlement(ownerAgentId, ack.document_id, ack.envelope_hash, nowMs);
161
- const legacy = this.#d.store.holderSettlement(ownerAgentId, ack.envelope_hash, ack.acker_agent_id) === null;
162
- const first = legacy
163
- ? this.#d.store.markAcked(ownerAgentId, ack.document_id, ack.envelope_hash, nowMs)
164
- : !before;
165
- // ANNOUNCE THE SETTLE, before the admitted/rejected split — both outcomes end the delivery, and
166
- // a waiter that only heard about admissions would keep a session open through every rejection.
167
- this.#d.onSettled?.(ownerAgentId, ack.envelope_hash, ack.acker_agent_id, ack.admitted);
168
- if (!ack.admitted) {
169
- // Durable on the publishing side. A log line would not survive the restart after which the
170
- // operator asks why their work never landed — and this is also what bounds the retry on the
171
- // side that actually loops.
172
- this.#d.rejections.recordIncomingRejection(ownerAgentId, ack.document_id, {
173
- rejectionEnvelopeHash: ackRecordHash(ack.envelope_hash),
174
- rejectedEnvelopeHash: ack.envelope_hash,
175
- reason: ack.rejection_reason ?? "document_rejected",
176
- fromAgentId: ack.acker_agent_id,
177
- });
178
- return { ok: true, admitted: false, envelopeHash: ack.envelope_hash };
179
- }
180
- this.#d.logger.info("document.ack.admitted", {
181
- documentId: ack.document_id,
182
- envelopeHash: ack.envelope_hash,
183
- firstAck: first,
184
- correlationId,
185
- });
186
- return { ok: true, admitted: true, envelopeHash: ack.envelope_hash };
187
- }
188
- }
189
- /**
190
- * The identity of the RECEIVED-rejection row: the REFUSED ENVELOPE, and nothing else.
191
- *
192
- * ONE REFUSED ENVELOPE IS ONE ROUND, however many acks for it arrive.
193
- *
194
- * This used to mix in the acker's `acked_at_ms` on the reasoning that both fields are signed, so a
195
- * redelivered ack would collapse onto the same row. That reasoning had a false premise: acks are
196
- * not stored and redelivered — `sendAck` builds a fresh `DocumentAck` with `acked_at_ms: Date.now()`
197
- * every time it answers. So two acks for the SAME envelope carried two different timestamps and
198
- * became two rows, advancing the round twice.
199
- *
200
- * That is not hypothetical. Delivery re-sends on a 1s first backoff, so an ordinary in-flight
201
- * overlap — the sender re-sends while the first ack is still on the wire, both land — produced two
202
- * rounds from one refusal. `MAX_REJECTED_ROUNDS` is 3, so routine delivery overlap could stall a
203
- * shared document with no hostility involved. That is the same denial of service the 5b
204
- * already-rejected guard in `document-inbound.ts` was written to close, arriving through the other
205
- * side of the same loop.
206
- */
207
- function ackRecordHash(envelopeHash) {
208
- return envelopeHash;
209
- }
210
- //# sourceMappingURL=document-ack-inbound.js.map