@cello-protocol/daemon 0.0.183 → 0.0.185
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/close-session-handler.d.ts.map +1 -1
- package/dist/close-session-handler.js +90 -16
- package/dist/close-session-handler.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +365 -44
- package/dist/daemon.js.map +1 -1
- package/dist/frame-values.d.ts.map +1 -1
- package/dist/frame-values.js +14 -0
- package/dist/frame-values.js.map +1 -1
- package/dist/inclusion-proof-handlers.d.ts +43 -0
- package/dist/inclusion-proof-handlers.d.ts.map +1 -0
- package/dist/inclusion-proof-handlers.js +565 -0
- package/dist/inclusion-proof-handlers.js.map +1 -0
- package/dist/inclusion-proof.d.ts +151 -0
- package/dist/inclusion-proof.d.ts.map +1 -0
- package/dist/inclusion-proof.js +228 -0
- package/dist/inclusion-proof.js.map +1 -0
- package/dist/network-directory-node.d.ts +16 -0
- package/dist/network-directory-node.d.ts.map +1 -1
- package/dist/network-directory-node.js +40 -0
- package/dist/network-directory-node.js.map +1 -1
- package/dist/notification-handlers.d.ts.map +1 -1
- package/dist/notification-handlers.js +71 -2
- package/dist/notification-handlers.js.map +1 -1
- package/dist/outbound-sessions.d.ts +6 -0
- package/dist/outbound-sessions.d.ts.map +1 -1
- package/dist/outbound-sessions.js +31 -7
- package/dist/outbound-sessions.js.map +1 -1
- package/dist/seal-coordinator.d.ts +51 -1
- package/dist/seal-coordinator.d.ts.map +1 -1
- package/dist/seal-coordinator.js +248 -1
- package/dist/seal-coordinator.js.map +1 -1
- package/dist/seal-escalation.d.ts.map +1 -1
- package/dist/seal-escalation.js +28 -0
- package/dist/seal-escalation.js.map +1 -1
- package/dist/seal-failure-store.d.ts.map +1 -1
- package/dist/seal-failure-store.js +5 -1
- package/dist/seal-failure-store.js.map +1 -1
- package/dist/sealed-leaf-set.d.ts +92 -0
- package/dist/sealed-leaf-set.d.ts.map +1 -0
- package/dist/sealed-leaf-set.js +129 -0
- package/dist/sealed-leaf-set.js.map +1 -0
- package/dist/session-ceremony.d.ts +21 -1
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +83 -7
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-content-handlers.d.ts +42 -0
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +193 -4
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-node-manager.d.ts +142 -1
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +496 -15
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-relay-client.d.ts +47 -1
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +145 -0
- package/dist/session-relay-client.js.map +1 -1
- package/dist/signal-submission.d.ts +11 -1
- package/dist/signal-submission.d.ts.map +1 -1
- package/dist/signal-submission.js.map +1 -1
- package/dist/submission-retry.d.ts +208 -0
- package/dist/submission-retry.d.ts.map +1 -0
- package/dist/submission-retry.js +505 -0
- package/dist/submission-retry.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vocabulary.d.ts +13 -3
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +14 -3
- package/dist/vocabulary.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-M15-ENDORSE-RETRY-1 — a sealed submission survives the directory node going away.
|
|
3
|
+
*
|
|
4
|
+
* An operator mints a trust signal, the one directory node this daemon holds a signaling stream to
|
|
5
|
+
* is down or restarting, and the command fails and it is over: nothing queues the submission,
|
|
6
|
+
* nothing retries it, and the operator has to notice and run it again. The consortium has three
|
|
7
|
+
* nodes, and surviving one of them being unavailable is the entire reason there are three.
|
|
8
|
+
*
|
|
9
|
+
* THIS IS THE RETRY, NOT THE ROUTING, AND THAT DISTINCTION IS RULED. `sendSealedSubmission` says so
|
|
10
|
+
* in its own header: the daemon holds ONE signaling stream, registration already works this way
|
|
11
|
+
* (the client sends to the connected node and the DIRECTORY fans out), and inventing a client-side
|
|
12
|
+
* multi-node write here would duplicate the SignalingManager's reconnect. So the failover mechanism
|
|
13
|
+
* is that existing reconnect — this module never chooses a node, never holds an endpoint, and never
|
|
14
|
+
* learns which node answered. It waits to be told the stream is up again and sends the same bytes.
|
|
15
|
+
*
|
|
16
|
+
* WHY A RETRY IS SAFE, and it is the load-bearing fact for the whole design: `submission_id` is
|
|
17
|
+
* sha256 of the SIGNED PLAINTEXT body, so the same submission produces the same id however many
|
|
18
|
+
* times it is sent. A node stores it once (`ON CONFLICT DO NOTHING` on the id) and the portal mints
|
|
19
|
+
* once (`processed_submissions`, checked before the mint path). Nothing here may ever re-COMPOSE a
|
|
20
|
+
* submission: the sealed bytes are captured at compose time and re-sent verbatim, because a re-seal
|
|
21
|
+
* is randomised and a re-compose could pick up a different `issued_at` — which changes the id, and
|
|
22
|
+
* a changed id is a second endorsement rather than a retry.
|
|
23
|
+
*
|
|
24
|
+
* SERIAL AND STAGGERED, on the `RestartSealResolver`'s pattern, and one of its lessons is carried
|
|
25
|
+
* over wholesale: a refusal that is a precondition OF OURS is not a verdict on the submission, so it
|
|
26
|
+
* must not spend the attempt budget. There the population was `standing_receiver_unavailable` on a
|
|
27
|
+
* fresh boot; here it is a signaling stream that is reconnecting. Production turns that stream over
|
|
28
|
+
* roughly every 70 seconds, so charging each reconnect an attempt would burn a five-attempt budget
|
|
29
|
+
* in six minutes and give up on a submission whose only problem was that the network moved.
|
|
30
|
+
*
|
|
31
|
+
* IN MEMORY, FOR THE LIFE OF THE PROCESS, and stated rather than implied. The queue holds sealed
|
|
32
|
+
* ciphertext, and persisting it would mean a new table, a client-side migration, and a blob at rest
|
|
33
|
+
* whose retention outlives the intake key it is sealed to. A daemon restart therefore loses the
|
|
34
|
+
* pending retries — the operator sees them disappear from `cello_attestations_issued` rather than
|
|
35
|
+
* seeing a lie, they are NAMED in the log on the way out (`retry.dropped_on_shutdown`), and
|
|
36
|
+
* re-submitting is safe by the same content-derived id.
|
|
37
|
+
*
|
|
38
|
+
* THE INTAKE KEY IS CHECKED BY THE CALLER, not assumed here. An earlier version of this paragraph
|
|
39
|
+
* asserted that the retry window "is far shorter than any manifest validity window, so a queued
|
|
40
|
+
* blob cannot outlive the intake key it was sealed to" — which is only true when the manifest has
|
|
41
|
+
* more than a window left at compose time. The enqueue site in `daemon.ts` holds the manifest and
|
|
42
|
+
* refuses to hold a submission whose intake key would expire inside the window; see
|
|
43
|
+
* `DEFAULT_RETRY_WINDOW_MS` below for what goes wrong when nothing checks.
|
|
44
|
+
*/
|
|
45
|
+
import type { SubmissionOp } from "@cello-protocol/protocol-types";
|
|
46
|
+
import type { SendSubmissionResult, SubmissionSendFailure } from "./signal-submission.js";
|
|
47
|
+
import type { Logger } from "./types.js";
|
|
48
|
+
export declare function isRetryableSendFailure(reason: SubmissionSendFailure): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Why a retry stopped. FOUR names, because they lead to four different operator actions and this is
|
|
51
|
+
* the field a machine consumer branches on.
|
|
52
|
+
*
|
|
53
|
+
* `refused_by_node` and `agent_unloaded` exist because of review H3: a refusal arriving on retry
|
|
54
|
+
* attempt 1 used to be stamped `attempts_exhausted` with `attempts: 0` printed beside it. The
|
|
55
|
+
* node's own words survived in `guidance`, but EVERY machine-readable field said transport — so an
|
|
56
|
+
* operator grepping the log for exhaustion found a quota refusal, and one grepping for refusals
|
|
57
|
+
* found it filed under a transport give-up. The work order's recorded trap is exactly this: *"the
|
|
58
|
+
* operator must be able to tell 'the network is flaky, hold on' from 'the directory said no.'"*
|
|
59
|
+
*/
|
|
60
|
+
export type SubmissionGiveUpReason =
|
|
61
|
+
/** Failures that REACHED a node and got no usable answer, until the budget ran out. */
|
|
62
|
+
"attempts_exhausted"
|
|
63
|
+
/** Only ever local preconditions — the stream never came back inside the window. */
|
|
64
|
+
| "retry_window_elapsed"
|
|
65
|
+
/** A node decoded it, evaluated it, and said no. Nothing about waiting changes that. */
|
|
66
|
+
| "refused_by_node"
|
|
67
|
+
/** This daemon can no longer sign as the submitting agent, so nobody can send these bytes. */
|
|
68
|
+
| "agent_unloaded";
|
|
69
|
+
/** One submission this daemon composed, sealed, and has not yet handed to a directory node. */
|
|
70
|
+
export interface PendingSubmission {
|
|
71
|
+
agentName: string;
|
|
72
|
+
/** The STABLE key. `agentName` is a display label and is reusable after a retire; the surface
|
|
73
|
+
* that lists these is scoped by id. */
|
|
74
|
+
agentId: string;
|
|
75
|
+
/** Content-derived. Never recomputed here — see the header. */
|
|
76
|
+
submissionId: string;
|
|
77
|
+
intakeKeyId: string;
|
|
78
|
+
/** The sealed blob, re-sent verbatim. */
|
|
79
|
+
ciphertext: Uint8Array;
|
|
80
|
+
op: SubmissionOp;
|
|
81
|
+
subject: string;
|
|
82
|
+
}
|
|
83
|
+
export type SubmissionDelivery = {
|
|
84
|
+
state: "retrying";
|
|
85
|
+
attempts: number;
|
|
86
|
+
lastReason: SubmissionSendFailure;
|
|
87
|
+
nextAttemptAt: number;
|
|
88
|
+
guidance: string;
|
|
89
|
+
} | {
|
|
90
|
+
state: "gave_up";
|
|
91
|
+
attempts: number;
|
|
92
|
+
lastReason: SubmissionSendFailure;
|
|
93
|
+
gaveUpBecause: SubmissionGiveUpReason;
|
|
94
|
+
guidance: string;
|
|
95
|
+
};
|
|
96
|
+
export interface PendingSubmissionView {
|
|
97
|
+
submissionId: string;
|
|
98
|
+
op: SubmissionOp;
|
|
99
|
+
subject: string;
|
|
100
|
+
intakeKeyId: string;
|
|
101
|
+
delivery: SubmissionDelivery;
|
|
102
|
+
}
|
|
103
|
+
export interface ScheduledTask {
|
|
104
|
+
cancel: () => void;
|
|
105
|
+
}
|
|
106
|
+
export interface SubmissionRetryQueueDeps {
|
|
107
|
+
logger: Logger;
|
|
108
|
+
/**
|
|
109
|
+
* Re-send THESE bytes on whatever stream the agent's SignalingManager currently holds. The queue
|
|
110
|
+
* does not know or care which node that is — that is the point of the ruling in the header.
|
|
111
|
+
*/
|
|
112
|
+
send: (item: PendingSubmission) => Promise<SendSubmissionResult>;
|
|
113
|
+
/**
|
|
114
|
+
* A retry landed. The caller writes the same local record a first-pass send writes, or a later
|
|
115
|
+
* withdrawal has no handle to name. Called on the retry path only; the first-pass send records
|
|
116
|
+
* its own.
|
|
117
|
+
*/
|
|
118
|
+
onAccepted: (item: PendingSubmission, stored: boolean) => void;
|
|
119
|
+
now?: () => number;
|
|
120
|
+
schedule?: (fn: () => void, ms: number) => ScheduledTask;
|
|
121
|
+
/** Gap between two attempts, so a queue of submissions is not a burst of frames. */
|
|
122
|
+
staggerMs?: number;
|
|
123
|
+
/** Ceiling on attempts that reached a node. */
|
|
124
|
+
maxAttempts?: number;
|
|
125
|
+
/** Wall-clock ceiling for a submission whose failures were all local preconditions. */
|
|
126
|
+
retryWindowMs?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Gap before re-trying a LOCAL PRECONDITION failure. Injectable because it and `baseBackoffMs`
|
|
129
|
+
* were the only two intervals a test could not vary — which is why the live wiring test's green
|
|
130
|
+
* depended on a real reconnect ladder racing a real 5-second timer.
|
|
131
|
+
*/
|
|
132
|
+
localPreconditionRetryMs?: number;
|
|
133
|
+
/** First non-local backoff; doubles from here. */
|
|
134
|
+
baseBackoffMs?: number;
|
|
135
|
+
/** Ceiling on pending submissions held at once, across all agents. */
|
|
136
|
+
queueCap?: number;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* One hour. Chosen against the intake key, not against patience: the queued blob is sealed to the
|
|
140
|
+
* intake key the manifest published at compose time, and a manifest window is measured in weeks.
|
|
141
|
+
*
|
|
142
|
+
* THIS COMMENT USED TO ASSERT THAT AN HOUR "CANNOT OUTLIVE THE KEY", AND THAT WAS AN UNCHECKED
|
|
143
|
+
* CLAIM (review M4). It holds only when the manifest has more than an hour left at compose time.
|
|
144
|
+
* `composeSealedSubmission` refuses an already-expired manifest, but nothing stopped a submission
|
|
145
|
+
* composed one minute before expiry from being held and re-sent for the next fifty-nine — and the
|
|
146
|
+
* portal's rotated-key retention does not cover it either, because that rule keeps a retired key
|
|
147
|
+
* "until no undrained row references it" (M10B-D11) and a HELD submission has no row at any node.
|
|
148
|
+
* The result is exactly the state the expiry gate exists to prevent: a blob the portal cannot open
|
|
149
|
+
* and cannot attribute, arriving as poison with no reply possible — while the operator has been
|
|
150
|
+
* told it is held and needs nothing from them.
|
|
151
|
+
*
|
|
152
|
+
* So the CALLER checks rather than this comment asserting. See the enqueue site in `daemon.ts`,
|
|
153
|
+
* which has the manifest in hand and refuses to hold a submission whose intake key would expire
|
|
154
|
+
* inside this window.
|
|
155
|
+
*/
|
|
156
|
+
export declare const DEFAULT_RETRY_WINDOW_MS: number;
|
|
157
|
+
/**
|
|
158
|
+
* The retry queue. One per daemon; keyed by (agentId, submissionId) because two agents on one
|
|
159
|
+
* daemon can each submit about the same subject and the rows are per-agent facts.
|
|
160
|
+
*/
|
|
161
|
+
export declare class SubmissionRetryQueue {
|
|
162
|
+
#private;
|
|
163
|
+
constructor(deps: SubmissionRetryQueueDeps);
|
|
164
|
+
/**
|
|
165
|
+
* Take ownership of a submission whose send failed for a retryable reason.
|
|
166
|
+
*
|
|
167
|
+
* Returns `false` when the queue is full, and the caller then reports the plain failure — an
|
|
168
|
+
* operator who is told "the daemon has it" about a submission the daemon dropped is worse off
|
|
169
|
+
* than one who is told it failed, because only the second knows to act.
|
|
170
|
+
*/
|
|
171
|
+
enqueue(item: PendingSubmission, lastReason: SubmissionSendFailure): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* The agent's directory signaling stream reached 'connected' — the first connect and every
|
|
174
|
+
* reconnect after a drop. THIS IS THE FAILOVER. Which node it landed on is not this module's
|
|
175
|
+
* business and is deliberately not read.
|
|
176
|
+
*/
|
|
177
|
+
onSignalingConnected(agentName: string): void;
|
|
178
|
+
/**
|
|
179
|
+
* What this agent still has in flight, and what was given up on. Read by `wallet_list_issued`,
|
|
180
|
+
* which is the surface behind `cello_attestations_issued` — the verb whose whole question is
|
|
181
|
+
* "what happened to what I sent?".
|
|
182
|
+
*/
|
|
183
|
+
list(agentId: string): PendingSubmissionView[];
|
|
184
|
+
/**
|
|
185
|
+
* A submission finally landed by some other route — the operator re-issued it, and the
|
|
186
|
+
* first-pass send succeeded. Drop any give-up recorded for it (review L9).
|
|
187
|
+
*
|
|
188
|
+
* The id is content-derived, so re-issuing the SAME words about the SAME subject produces the
|
|
189
|
+
* SAME id: this is precisely the "I wrote it again and it worked" case. Without it the operator
|
|
190
|
+
* sees the old failure and the new accepted row side by side for the life of the daemon, with no
|
|
191
|
+
* way to dismiss the one that is no longer true.
|
|
192
|
+
*/
|
|
193
|
+
clearGaveUp(agentId: string, submissionId: string): void;
|
|
194
|
+
/**
|
|
195
|
+
* Stop, and start nothing further.
|
|
196
|
+
*
|
|
197
|
+
* EVERY DROPPED SUBMISSION IS NAMED (review H2). The queue is in memory by design, but the
|
|
198
|
+
* operator was told in as many words "you do NOT need to run this again" — and before this, a
|
|
199
|
+
* restart left no durable trace anywhere naming what had been lost. A comment in the composition
|
|
200
|
+
* root asserting "in memory by design" is not a record; these lines are.
|
|
201
|
+
*
|
|
202
|
+
* The SUBJECT is logged and the body is not: the subject is a pubkey or a signal hash, and the
|
|
203
|
+
* body is the operator's own words about a third party, which this whole path exists to keep out
|
|
204
|
+
* of the clear on disk.
|
|
205
|
+
*/
|
|
206
|
+
stop(): void;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=submission-retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission-retry.d.ts","sourceRoot":"","sources":["../src/submission-retry.ts"],"names":[],"mappings":"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"}
|