@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.
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +16 -0
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/away-detection.d.ts +62 -15
- package/dist/away-detection.d.ts.map +1 -1
- package/dist/away-detection.js +77 -20
- package/dist/away-detection.js.map +1 -1
- package/dist/close-session-handler.d.ts.map +1 -1
- package/dist/close-session-handler.js +69 -3
- package/dist/close-session-handler.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +406 -303
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-open-registry.d.ts +92 -0
- package/dist/delivery-open-registry.d.ts.map +1 -0
- package/dist/delivery-open-registry.js +121 -0
- package/dist/delivery-open-registry.js.map +1 -0
- package/dist/document-amendment-store.d.ts +94 -46
- package/dist/document-amendment-store.d.ts.map +1 -1
- package/dist/document-amendment-store.js +217 -102
- package/dist/document-amendment-store.js.map +1 -1
- package/dist/document-delivery-transport.d.ts +51 -53
- package/dist/document-delivery-transport.d.ts.map +1 -1
- package/dist/document-delivery-transport.js +16 -157
- package/dist/document-delivery-transport.js.map +1 -1
- package/dist/document-frame-router.d.ts +13 -17
- package/dist/document-frame-router.d.ts.map +1 -1
- package/dist/document-frame-router.js +21 -93
- package/dist/document-frame-router.js.map +1 -1
- package/dist/document-handlers.d.ts +1 -1
- package/dist/document-handlers.d.ts.map +1 -1
- package/dist/document-handlers.js +462 -511
- package/dist/document-handlers.js.map +1 -1
- package/dist/document-handshake.d.ts +4 -1
- package/dist/document-handshake.d.ts.map +1 -1
- package/dist/document-handshake.js +3 -1
- package/dist/document-handshake.js.map +1 -1
- package/dist/document-inbound.d.ts +18 -5
- package/dist/document-inbound.d.ts.map +1 -1
- package/dist/document-inbound.js +94 -110
- package/dist/document-inbound.js.map +1 -1
- package/dist/document-layer.d.ts +49 -67
- package/dist/document-layer.d.ts.map +1 -1
- package/dist/document-layer.js +688 -565
- package/dist/document-layer.js.map +1 -1
- package/dist/document-lifecycle.d.ts +11 -157
- package/dist/document-lifecycle.d.ts.map +1 -1
- package/dist/document-lifecycle.js +26 -568
- package/dist/document-lifecycle.js.map +1 -1
- package/dist/document-publish.d.ts +13 -0
- package/dist/document-publish.d.ts.map +1 -1
- package/dist/document-publish.js +19 -11
- package/dist/document-publish.js.map +1 -1
- package/dist/document-reconcile-engine.d.ts +66 -0
- package/dist/document-reconcile-engine.d.ts.map +1 -0
- package/dist/document-reconcile-engine.js +225 -0
- package/dist/document-reconcile-engine.js.map +1 -0
- package/dist/document-reconcile-scheduler.d.ts +124 -0
- package/dist/document-reconcile-scheduler.d.ts.map +1 -0
- package/dist/document-reconcile-scheduler.js +226 -0
- package/dist/document-reconcile-scheduler.js.map +1 -0
- package/dist/document-rejection.d.ts +2 -0
- package/dist/document-rejection.d.ts.map +1 -1
- package/dist/document-rejection.js +5 -1
- package/dist/document-rejection.js.map +1 -1
- package/dist/document-store.d.ts +18 -247
- package/dist/document-store.d.ts.map +1 -1
- package/dist/document-store.js +95 -827
- package/dist/document-store.js.map +1 -1
- package/dist/inbound-sessions.d.ts +7 -0
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +27 -6
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/initiate-session-handler.d.ts +5 -0
- package/dist/initiate-session-handler.d.ts.map +1 -1
- package/dist/initiate-session-handler.js +1 -0
- package/dist/initiate-session-handler.js.map +1 -1
- package/dist/notification-handlers.d.ts.map +1 -1
- package/dist/notification-handlers.js +39 -2
- package/dist/notification-handlers.js.map +1 -1
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +134 -8
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-node-manager.d.ts +153 -8
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +1376 -35
- package/dist/session-node-manager.js.map +1 -1
- package/dist/types.d.ts +64 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -5
- package/dist/delivery-sweep-bound.d.ts +0 -79
- package/dist/delivery-sweep-bound.d.ts.map +0 -1
- package/dist/delivery-sweep-bound.js +0 -109
- package/dist/delivery-sweep-bound.js.map +0 -1
- package/dist/document-ack-inbound.d.ts +0 -71
- package/dist/document-ack-inbound.d.ts.map +0 -1
- package/dist/document-ack-inbound.js +0 -210
- package/dist/document-ack-inbound.js.map +0 -1
- package/dist/document-control-notifier.d.ts +0 -124
- package/dist/document-control-notifier.d.ts.map +0 -1
- package/dist/document-control-notifier.js +0 -176
- package/dist/document-control-notifier.js.map +0 -1
- package/dist/document-delivery.d.ts +0 -250
- package/dist/document-delivery.d.ts.map +0 -1
- package/dist/document-delivery.js +0 -608
- package/dist/document-delivery.js.map +0 -1
- package/dist/document-join-store.d.ts +0 -80
- package/dist/document-join-store.d.ts.map +0 -1
- package/dist/document-join-store.js +0 -197
- 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
|
-
|
|
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.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
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;
|
|
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.
|
|
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.
|
|
39
|
-
"@cello-protocol/
|
|
40
|
-
"@cello-protocol/
|
|
41
|
-
"@cello-protocol/transport": "0.0.
|
|
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
|