@cello-protocol/daemon 0.0.166 → 0.0.167

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.
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAMrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAmC,KAAK,UAAU,EAAsB,MAAM,iBAAiB,CAAC;AAEvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAO/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAiB7G,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAsB,MAAM,2BAA2B,CAAC;AAS5G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA6BnF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAWlD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;IACrC;;OAEG;IACH,eAAe,IAAI,YAAY,CAAC;IAChC;;;;;;;;OAQG;IACH,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACxC;AAYD,qBAAa,4BAA6B,YAAW,mBAAmB;IAChE,UAAU,CAAC,MAAM,EAAE,iBAAiB;CA8B3C;AAID,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAS7E"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAMrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAmC,KAAK,UAAU,EAAsB,MAAM,iBAAiB,CAAC;AAEvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAO/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAiB7G,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAsB,MAAM,2BAA2B,CAAC;AAS5G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA8BnF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAWlD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;IACrC;;OAEG;IACH,eAAe,IAAI,YAAY,CAAC;IAChC;;;;;;;;OAQG;IACH,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACxC;AAYD,qBAAa,4BAA6B,YAAW,mBAAmB;IAChE,UAAU,CAAC,MAAM,EAAE,iBAAiB;CA8B3C;AAID,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAS7E"}
package/dist/daemon.js CHANGED
@@ -72,6 +72,7 @@ import { wireContentHash } from "./wire-content-hash.js";
72
72
  import { DocumentPublish } from "./document-publish.js";
73
73
  import { createDocumentDeliveryTransport } from "./document-delivery-transport.js";
74
74
  import { DocumentDelivery } from "./document-delivery.js";
75
+ import { sweepAgentEvicting } from "./delivery-sweep-bound.js";
75
76
  import { encodeDocumentUpdateEnvelope, DOCUMENT_UPDATE_ENCODING_V1 } from "@cello-protocol/protocol-types";
76
77
  import { createSealFlows } from "./seal-flows.js";
77
78
  import { registerCloseSessionHandler } from "./close-session-handler.js";
@@ -3678,6 +3679,15 @@ async function startDaemonHoldingLock(config, singletonLock) {
3678
3679
  // Floored, so a misread env cannot turn the sweep into a busy loop against the directory.
3679
3680
  const tickOverride = Number(process.env["CELLO_DOCUMENT_DELIVERY_TICK_MS"]);
3680
3681
  const DOCUMENT_DELIVERY_TICK_MS = Number.isFinite(tickOverride) && tickOverride >= 250 ? tickOverride : 60_000;
3682
+ // DOD-MP-SWEEP-ALIVE-1 — how long ONE agent's pass may run before the sweep stops waiting for
3683
+ // it. Generous relative to real work (the ack-grace budget for a whole pass is 30s) and finite,
3684
+ // which is the only property that matters: the defect is an unbounded wait, not a slow one.
3685
+ // Generous relative to what a HEALTHY pass can legitimately spend: the discovery lookup is
3686
+ // bounded at 10s per (document, holder), the whole-pass ack grace is 30s, and only the dial is
3687
+ // unbounded. Six pairs against a slow directory is already ~90s, so a 120s bound would warn on
3688
+ // the normal case — and a signal that fires when nothing is wrong teaches an operator to ignore
3689
+ // the one that matters. Scales with the tick so the fast-tick test path does not sit out minutes.
3690
+ const DOCUMENT_DELIVERY_PASS_TIMEOUT_MS = Math.max(180_000, DOCUMENT_DELIVERY_TICK_MS * 3);
3681
3691
  let documentDeliveryRunning = false;
3682
3692
  let documentDeliveryStopping = false;
3683
3693
  let documentDeliveryInFlight = null;
@@ -3691,6 +3701,7 @@ async function startDaemonHoldingLock(config, singletonLock) {
3691
3701
  documentDeliveryInFlight = (async () => {
3692
3702
  try {
3693
3703
  let agentsSwept = 0;
3704
+ let stuck = 0;
3694
3705
  let attempted = 0;
3695
3706
  let delivered = 0;
3696
3707
  let failed = 0;
@@ -3706,23 +3717,48 @@ async function startDaemonHoldingLock(config, singletonLock) {
3706
3717
  // and leaves a fully synced document invisible with no error on any path.
3707
3718
  if (ownerAgentId === null)
3708
3719
  continue;
3709
- agentsSwept++;
3720
+ // COUNTED AFTER THE PASS, not before — an agent whose pass never finished was not swept,
3721
+ // and saying it was is what made the stall indistinguishable from an idle daemon.
3710
3722
  // FANOUT-1: targets are the DERIVED holders minus ourselves — never per-sender
3711
3723
  // config, never the genesis peer column (§7-1's silent-divergence hazard).
3712
3724
  const holdersFor = (documentId) => {
3713
3725
  const holders = documentLayer.holdersFor(ownerAgentId, documentId);
3714
3726
  return holders === null ? null : holders.filter((h) => h !== ownerAgentId);
3715
3727
  };
3716
- const result = await documentDeliveryFor(agentName).tick(ownerAgentId, holdersFor, Date.now(), {
3717
- senderAgentId: ownerAgentId,
3728
+ // DOD-MP-SWEEP-ALIVE-1 BOUNDED. The `finally` below releases the overlap guard only
3729
+ // when this body settles, so an await that never settles ends document delivery for the
3730
+ // life of the process, silently: the interval keeps firing, returns at the guard, and
3731
+ // logs nothing. Observed live 2026-08-13. One wedged agent now degrades alone and loudly
3732
+ // instead of taking every other agent's documents down with it.
3733
+ const bounded = await sweepAgentEvicting({
3734
+ logger,
3735
+ timeoutMs: DOCUMENT_DELIVERY_PASS_TIMEOUT_MS,
3736
+ agentName,
3737
+ // Evicting the wedged worker is what makes the recovery real: `DocumentDelivery.tick`
3738
+ // caches its pass and would otherwise hand back the same hung promise forever, so the
3739
+ // agent would never deliver again AND every later pass would pay the full bound
3740
+ // waiting for it — a 2x sweep-interval regression for every healthy agent.
3741
+ workers: documentDeliveryWorkers,
3742
+ run: () => documentDeliveryFor(agentName).tick(ownerAgentId, holdersFor, Date.now(), {
3743
+ senderAgentId: ownerAgentId,
3744
+ }),
3718
3745
  });
3746
+ if (!bounded.completed) {
3747
+ stuck += 1;
3748
+ continue;
3749
+ }
3750
+ agentsSwept++;
3751
+ const result = bounded.value;
3719
3752
  attempted += result.attempted;
3720
3753
  delivered += result.delivered;
3721
3754
  failed += result.failed;
3722
3755
  }
3723
3756
  // H3: a sweep that delivers nothing was byte-for-byte identical to a healthy idle one, which
3724
3757
  // is what made every scoping bug above invisible. One line per tick, always.
3725
- logger.debug("document.delivery.sweep", { agents: agentsSwept, attempted, delivered, failed });
3758
+ // `stuck` IS PART OF THE LINE. Counting a wedged agent as swept reported `agents: 3,
3759
+ // attempted: 0` — byte-for-byte a healthy idle sweep, which is the exact shape the comment
3760
+ // below exists to prevent, on the one occasion it matters most.
3761
+ logger.debug("document.delivery.sweep", { agents: agentsSwept, stuck, attempted, delivered, failed });
3726
3762
  if (agentsSwept === 0 && documentLayer.store.anyDocumentExists()) {
3727
3763
  logger.warn("document.delivery.sweep_empty", {
3728
3764
  reason: "documents exist but no agent was swept — nothing will ever be delivered",