@adhdev/daemon-core 0.9.82-rc.366 → 0.9.82-rc.368

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.
@@ -734,6 +734,12 @@ async function retryUnresolvedDelegateForwards(components: DaemonComponents): Pr
734
734
  const entries = peekUnresolvedDelegateForwards();
735
735
  if (entries.length === 0) return;
736
736
 
737
+ // Every id-form THIS daemon answers to. A self-addressed outbox entry (coordinator
738
+ // == this daemon) must never be cross-dialled — see the self-route branch below.
739
+ const selfIds = resolveCoordinatorDaemonIds(components);
740
+ const isSelfCoordinatorId = (id: string): boolean =>
741
+ selfIds.some(self => daemonIdsEquivalent(self, id));
742
+
737
743
  for (const entry of entries) {
738
744
  // EVTTRACE correlation context for this outbox entry's retry.
739
745
  const entryTraceCtx = {
@@ -742,6 +748,38 @@ async function retryUnresolvedDelegateForwards(components: DaemonComponents): Pr
742
748
  nodeId: readNonEmptyString(entry.payload.nodeId),
743
749
  event: readNonEmptyString(entry.payload.event),
744
750
  };
751
+
752
+ // Self-addressed forward: the coordinator daemon this entry targets IS this
753
+ // daemon (a self-coordinating / single-node mesh, or a delegate whose coordinator
754
+ // anchor resolved to our own id). A cross-daemon mesh_forward_event to our own id
755
+ // is REFUSED by the dispatch self-dial guard ("Refusing to send ... to this
756
+ // daemon's own id; route via the local router instead") on every retry, so the
757
+ // entry can never be acked and loops forever (~every tick), spamming the log and
758
+ // pinning the outbox row permanently undrained. Honour the guard's own advice:
759
+ // route the event straight through the local receiver (handleMeshForwardEvent —
760
+ // the same path the coordinator runs on receiving a remote push), then ack it.
761
+ // We drain regardless of the local result: a cross-daemon dispatch could not have
762
+ // resolved it either (the guard rejects before the receiver ever runs), so leaving
763
+ // it queued only re-spams. handleMeshForwardEvent has the BEST recovery chance —
764
+ // this daemon hosts the mesh, so its workspace/nodeId → meshId recovery applies.
765
+ if (isSelfCoordinatorId(entry.coordinatorDaemonId)) {
766
+ let localResult: any;
767
+ try {
768
+ traceMeshEventStage('forward_send', entryTraceCtx, `self → local router (${entry.coordinatorDaemonId})`);
769
+ localResult = handleMeshForwardEvent(components, entry.payload);
770
+ } catch (e: any) {
771
+ LOG.warn('MeshReconcile', `Local route of self-addressed forward to ${entry.coordinatorDaemonId} threw: ${e?.message || e} — draining anyway to break the retry loop`);
772
+ }
773
+ ackUnresolvedDelegateForward(entry.id);
774
+ if (localResult && localResult.success === false) {
775
+ LOG.warn('MeshReconcile', `Self-addressed unresolved-delegate ${readNonEmptyString(entry.payload.event)} rejected by local router (${readNonEmptyString(localResult.error) || 'no reason'}) — drained to break the self-forward retry loop`);
776
+ traceMeshEventDrop('self_forward_local_rejected', entryTraceCtx, readNonEmptyString(localResult.error) || 'no reason');
777
+ } else {
778
+ LOG.info('MeshReconcile', `Self-addressed unresolved-delegate ${readNonEmptyString(entry.payload.event)} routed via local router (coordinator ${entry.coordinatorDaemonId} is self) — drained`);
779
+ }
780
+ continue;
781
+ }
782
+
745
783
  let result: any;
746
784
  try {
747
785
  traceMeshEventStage('forward_send', entryTraceCtx, `retry → ${entry.coordinatorDaemonId}`);