@cello-protocol/daemon 0.0.182 → 0.0.184
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-settings-keys.d.ts.map +1 -1
- package/dist/agent-settings-keys.js +18 -0
- package/dist/agent-settings-keys.js.map +1 -1
- 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/consortium-bootstrap.d.ts.map +1 -1
- package/dist/consortium-bootstrap.js +117 -2
- package/dist/consortium-bootstrap.js.map +1 -1
- package/dist/content-encryption-status.d.ts +97 -0
- package/dist/content-encryption-status.d.ts.map +1 -0
- package/dist/content-encryption-status.js +122 -0
- package/dist/content-encryption-status.js.map +1 -0
- package/dist/content-park-client.d.ts +10 -0
- package/dist/content-park-client.d.ts.map +1 -1
- package/dist/content-park-client.js +84 -3
- package/dist/content-park-client.js.map +1 -1
- package/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +184 -13
- package/dist/content-park.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +671 -67
- package/dist/daemon.js.map +1 -1
- package/dist/document-handlers.d.ts.map +1 -1
- package/dist/document-handlers.js +119 -0
- package/dist/document-handlers.js.map +1 -1
- package/dist/document-layer.d.ts +0 -2
- package/dist/document-layer.d.ts.map +1 -1
- package/dist/document-layer.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/initiate-session-handler.d.ts.map +1 -1
- package/dist/initiate-session-handler.js +34 -3
- package/dist/initiate-session-handler.js.map +1 -1
- package/dist/manifest-deps.d.ts +0 -11
- package/dist/manifest-deps.d.ts.map +1 -1
- package/dist/manifest-deps.js +28 -2
- package/dist/manifest-deps.js.map +1 -1
- package/dist/manifest-validity.d.ts +2 -0
- package/dist/manifest-validity.d.ts.map +1 -1
- package/dist/manifest-validity.js +1 -1
- package/dist/manifest-validity.js.map +1 -1
- 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 +50 -7
- package/dist/outbound-sessions.js.map +1 -1
- package/dist/park-envelope.d.ts +23 -0
- package/dist/park-envelope.d.ts.map +1 -1
- package/dist/park-envelope.js +76 -0
- package/dist/park-envelope.js.map +1 -1
- package/dist/register-handler.d.ts.map +1 -1
- package/dist/register-handler.js +76 -0
- package/dist/register-handler.js.map +1 -1
- package/dist/relay-only.d.ts +133 -0
- package/dist/relay-only.d.ts.map +1 -0
- package/dist/relay-only.js +183 -0
- package/dist/relay-only.js.map +1 -0
- package/dist/retry-queue.d.ts +26 -1
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +18 -1
- package/dist/retry-queue.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 +27 -1
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +109 -7
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-content-handlers.d.ts +12 -0
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +39 -4
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-node-manager.d.ts +387 -4
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +3529 -279
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-read-handlers.d.ts.map +1 -1
- package/dist/session-read-handlers.js +16 -1
- package/dist/session-read-handlers.js.map +1 -1
- package/dist/session-relay-client.d.ts +163 -1
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +540 -6
- package/dist/session-relay-client.js.map +1 -1
- package/dist/session-salt-agreement.d.ts +58 -4
- package/dist/session-salt-agreement.d.ts.map +1 -1
- package/dist/session-salt-agreement.js +110 -3
- package/dist/session-salt-agreement.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/signaling-connect.d.ts +18 -1
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +164 -1
- package/dist/signaling-connect.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 +33 -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/dist/wire-content-hash.d.ts +15 -3
- package/dist/wire-content-hash.d.ts.map +1 -1
- package/dist/wire-content-hash.js +15 -3
- package/dist/wire-content-hash.js.map +1 -1
- package/package.json +5 -5
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type ContentHashAlg } from "./wire-content-hash.js";
|
|
18
18
|
import { type CapacityReason } from "./refusal-reasons.js";
|
|
19
|
+
import { type SaltAgreementFrame } from "./session-salt-agreement.js";
|
|
19
20
|
import { type DaemonDatabase } from "./sqlcipher-db.js";
|
|
20
21
|
import type { Stream } from "@libp2p/interface";
|
|
21
22
|
import type { Logger, SessionRecord, SealReadinessView } from "./types.js";
|
|
@@ -23,11 +24,32 @@ import { SessionConnectionGater } from "./session-connection-gater.js";
|
|
|
23
24
|
import { SessionTree, type WritableSessionTreeLeafKind } from "./session-tree.js";
|
|
24
25
|
import { type CelloNode, type IAutoNatService } from "@cello-protocol/transport";
|
|
25
26
|
import type { KeyProvider } from "@cello-protocol/crypto";
|
|
27
|
+
import { type SessionEphemeral } from "@cello-protocol/crypto";
|
|
26
28
|
import { type ParkEnvelope } from "./park-envelope.js";
|
|
27
|
-
import { AgentRelayClient, type RelayAssignmentCarry } from "./session-relay-client.js";
|
|
29
|
+
import { AgentRelayClient, type RelayAssignmentCarry, type RelayAuthRefusal, type RelayWitnessAlert } from "./session-relay-client.js";
|
|
30
|
+
/**
|
|
31
|
+
* One row in an agent's witness-alert list — DOD-M15-CORROBORATE-1 review F1. Deduped on
|
|
32
|
+
* `(witness relay, session)`, so a repeated observation raises `occurrences` rather than taking
|
|
33
|
+
* another slot in a bounded list.
|
|
34
|
+
*/
|
|
35
|
+
export interface WitnessAlertNotice {
|
|
36
|
+
/** `${relayId}::${sessionIdHex}` — the dedupe key, not shown to anyone. */
|
|
37
|
+
key: string;
|
|
38
|
+
alert: RelayWitnessAlert;
|
|
39
|
+
occurrences: number;
|
|
40
|
+
/**
|
|
41
|
+
* When this witness first said it. Held SEPARATELY from `alert.observedAt`, because a later
|
|
42
|
+
* repeat can replace `alert` (a provable one supersedes an unprovable one) and that must not
|
|
43
|
+
* silently move the first sighting forward — an operator reading "first observed" wants to know
|
|
44
|
+
* when this started, not when the strongest version of it arrived.
|
|
45
|
+
*/
|
|
46
|
+
firstObservedAt: number;
|
|
47
|
+
lastObservedAt: number;
|
|
48
|
+
}
|
|
28
49
|
import { RelayReceiptStore, type RelayReceipt } from "./relay-receipt-store.js";
|
|
29
50
|
import { SessionSealLeafStore, type SealCarryLeaf } from "./session-seal-leaf-store.js";
|
|
30
51
|
import type { SealUpgradeReadiness } from "./seal-upgrade.js";
|
|
52
|
+
import type { SealFrontierLeaf } from "./seal-frontier-verify.js";
|
|
31
53
|
import { type SecurityGatewayClient } from "@cello-protocol/gateway";
|
|
32
54
|
/**
|
|
33
55
|
* DOD-CAP-SELF-HEAL-1 — how long an interrupted session keeps consuming a cap slot.
|
|
@@ -57,6 +79,15 @@ import { type SecurityGatewayClient } from "@cello-protocol/gateway";
|
|
|
57
79
|
* span about 2.5 hours.
|
|
58
80
|
*/
|
|
59
81
|
export declare const SR_RESERVATION_MAX_RETRIES = 5;
|
|
82
|
+
/**
|
|
83
|
+
* DOD-M15-RELAYSLOTS-1 — how long an agent skips a relay that refused it for a relay-side fault.
|
|
84
|
+
*
|
|
85
|
+
* Ten minutes: long enough that the agent is not re-asking a relay that cannot serve it every time
|
|
86
|
+
* its receiver rebuilds, short enough that when someone fixes that relay the agent finds it again
|
|
87
|
+
* without needing its own restart. The fault is on somebody else's machine and nobody tells us when
|
|
88
|
+
* it is fixed, so this has to expire on its own.
|
|
89
|
+
*/
|
|
90
|
+
export declare const RELAY_QUARANTINE_MS: number;
|
|
60
91
|
export declare const CAP_INTERRUPTED_TTL_MS: number;
|
|
61
92
|
/**
|
|
62
93
|
* DOD-M12B-ABANDON-NOTIFY-1 — what happened when we tried to tell the counterparty we hung up.
|
|
@@ -154,6 +185,18 @@ export interface ISessionNodeFactory {
|
|
|
154
185
|
export interface SessionNodeConfig {
|
|
155
186
|
sessionId: string;
|
|
156
187
|
connectionGater?: SessionConnectionGater;
|
|
188
|
+
/**
|
|
189
|
+
* DOD-M15-RELAYONLY-1: this agent has asked never to be directly reachable, so the factory must
|
|
190
|
+
* omit dcutr from the node's service set.
|
|
191
|
+
*
|
|
192
|
+
* ⚠️ NOT a duplicate of filtering the published addresses. Those two things stop DIFFERENT
|
|
193
|
+
* disclosures: the filter controls what the DIRECTORY is told, and dcutr talks to the peer
|
|
194
|
+
* directly. Its whole job is to upgrade a relayed connection into a direct one, and the inbound
|
|
195
|
+
* side starts the upgrade — which is exactly a standing receiver. Leaving it on means the agent
|
|
196
|
+
* routes over the relay precisely as asked and then hole-punches to a direct connection anyway,
|
|
197
|
+
* with every test still green because the leak happens inside libp2p after the assertions.
|
|
198
|
+
*/
|
|
199
|
+
relayOnly?: boolean;
|
|
157
200
|
/**
|
|
158
201
|
* CELLO-M7-TRANSPORT-001: role of the node, forwarded to createNode to tune the
|
|
159
202
|
* libp2p service set (dcutr is included for 'session' dialers, omitted for the
|
|
@@ -257,12 +300,63 @@ export declare const REVIVE_RESERVATION_CANDIDATES = 2;
|
|
|
257
300
|
* the healthy direct latency and far below anything a person would notice.
|
|
258
301
|
*/
|
|
259
302
|
export declare const LEAF_FETCH_GRACE_MS = 2000;
|
|
303
|
+
/**
|
|
304
|
+
* DOD-M15-SEALWIRE-1 bullet 5, SENT half — our own authorship proof for a message we sent.
|
|
305
|
+
*
|
|
306
|
+
* Deliberately the SAME shape as the received half's `verifiedAuthorship`, because the transcript
|
|
307
|
+
* column pair is the same and a second shape would invite a second meaning. What differs is the
|
|
308
|
+
* ATTRIBUTION the row records: a sent row is `self_authored` (we PRODUCED this signature), never
|
|
309
|
+
* `verified_signature` (we CHECKED someone else's). Same bytes, different claim.
|
|
310
|
+
*/
|
|
311
|
+
export interface SentAuthorship {
|
|
312
|
+
senderPubkey: Uint8Array;
|
|
313
|
+
senderSig: Uint8Array;
|
|
314
|
+
}
|
|
260
315
|
export declare class SessionNodeManager {
|
|
261
316
|
#private;
|
|
262
317
|
setDetachedRelayClientBuilder(fn: (agentName: string, relayPeerId: string, relayAddrs: string[], stores: {
|
|
263
318
|
receiptStore?: RelayReceiptStore;
|
|
264
319
|
sealLeafStore?: SessionSealLeafStore;
|
|
320
|
+
onlineToken: () => Uint8Array | undefined;
|
|
265
321
|
}) => AgentRelayClient | undefined): void;
|
|
322
|
+
/**
|
|
323
|
+
* Record what one relay says it saw on one of this agent's sessions, for the operator to read.
|
|
324
|
+
*
|
|
325
|
+
* ⚠️ **IT DOES NOT FREEZE THE SESSION, AND THAT IS THE DESIGN.** A client freezing on its OWN
|
|
326
|
+
* verification is safe: it limits only what that client trusts. Freezing on a REMOTE party's
|
|
327
|
+
* say-so hands any single relay the power to end any conversation it carries, and to write an
|
|
328
|
+
* accusatory record about a counterparty who did nothing. The identity freeze stays where it is —
|
|
329
|
+
* on this daemon's own check of an inbound frame — and this surfaces a second, independent
|
|
330
|
+
* observation next to it. One witness reports; it does not rule.
|
|
331
|
+
*/
|
|
332
|
+
recordRelayWitnessAlert(agentName: string, alert: RelayWitnessAlert): void;
|
|
333
|
+
/** The witness alerts an agent has been told about, oldest first, one row per witness+session. */
|
|
334
|
+
getWitnessAlerts(agentName: string): ReadonlyArray<WitnessAlertNotice>;
|
|
335
|
+
/** Whether this agent's alert list hit its cap, so the inbox can say the list is incomplete. */
|
|
336
|
+
witnessAlertsTruncated(agentName: string): boolean;
|
|
337
|
+
/**
|
|
338
|
+
* Review F7: a relay sent a witness alert this build could not read or could not verify.
|
|
339
|
+
*
|
|
340
|
+
* Recorded so a version skew that silently kills the witness layer is visible to the operator
|
|
341
|
+
* instead of living only in a log file. Carries no session and no party by construction.
|
|
342
|
+
*/
|
|
343
|
+
recordRelayWitnessUnreadable(agentName: string, relayPeerId: string, why: string): void;
|
|
344
|
+
/** Relays whose witness alerts this build could not read, for the agent's inbox. */
|
|
345
|
+
getWitnessUnreadable(agentName: string): ReadonlyArray<{
|
|
346
|
+
relayPeerId: string;
|
|
347
|
+
why: string;
|
|
348
|
+
count: number;
|
|
349
|
+
}>;
|
|
350
|
+
/**
|
|
351
|
+
* Why this agent's standing receiver could not hold a reservation, in words the person running it
|
|
352
|
+
* can act on — or null when the last attempt succeeded or none has been made.
|
|
353
|
+
*
|
|
354
|
+
* This is the surface DoD clause 7 is about: the assertion that matters is what the CLIENT can
|
|
355
|
+
* show someone, not what the relay wrote in its own log.
|
|
356
|
+
*/
|
|
357
|
+
getStandingReceiverRefusal(agentName: string): (RelayAuthRefusal & {
|
|
358
|
+
relayPeerId: string;
|
|
359
|
+
}) | null;
|
|
266
360
|
/**
|
|
267
361
|
* Record that this session's tree and the relay's counter have provably parted.
|
|
268
362
|
*
|
|
@@ -387,6 +481,7 @@ export declare class SessionNodeManager {
|
|
|
387
481
|
ok: false;
|
|
388
482
|
reason: string;
|
|
389
483
|
cause?: string;
|
|
484
|
+
retryAfterMs?: number;
|
|
390
485
|
}>): void;
|
|
391
486
|
/**
|
|
392
487
|
* DOD-PARK-DRAIN-1: inject the parked-mailbox drain (daemon.ts → contentPark.autoRecoverForAgent).
|
|
@@ -420,6 +515,16 @@ export declare class SessionNodeManager {
|
|
|
420
515
|
* encrypted DB file.
|
|
421
516
|
*/
|
|
422
517
|
getDb(): DaemonDatabase;
|
|
518
|
+
/**
|
|
519
|
+
* DOD-M15-RELAYONLY-1: is the settings store readable RIGHT NOW?
|
|
520
|
+
*
|
|
521
|
+
* ⚠️ Exists because `getSetting` cannot answer it. That method returns `null` for BOTH "the key is
|
|
522
|
+
* unset" and "there is no database", and a security setting must tell those apart: unset-means-off
|
|
523
|
+
* is correct, db-gone-means-off publishes the operator's real address during the shutdown window.
|
|
524
|
+
* `getDb()` cannot stand in either — it THROWS when there is no database, which on a catch-less
|
|
525
|
+
* ceremony path is worse than the wrong answer.
|
|
526
|
+
*/
|
|
527
|
+
hasDatabase(): boolean;
|
|
423
528
|
/**
|
|
424
529
|
* RELAYSIG-1: the durably-stored, signature-verified relay ordering-record receipts for an agent
|
|
425
530
|
* (optionally a single session). Empty when no receipts have been recorded yet. Read-only.
|
|
@@ -465,6 +570,30 @@ export declare class SessionNodeManager {
|
|
|
465
570
|
verdict: "cannot_judge";
|
|
466
571
|
reason: string;
|
|
467
572
|
};
|
|
573
|
+
/**
|
|
574
|
+
* WHERE THE MUTUALLY-SIGNED PREFIX ENDS, DERIVED FROM THIS DAEMON'S OWN LEAVES —
|
|
575
|
+
* `DOD-M15-UNILATERAL-1`, review F2.
|
|
576
|
+
*
|
|
577
|
+
* ⚠️ **THE FIRST VERSION COMPUTED THIS FROM THE CERTIFICATE'S OWN PARTICIPANT LIST, AND CALLED
|
|
578
|
+
* THAT "recomputed, cannot be steered".** It could be steered. On the SOLO path the certificate's
|
|
579
|
+
* TBS binds no legibility at all, and the client verifies only the *live* party's frontier — so
|
|
580
|
+
* the absent party's `content_frontier_seq` and every `last_authored_seq` arrived unchecked. One
|
|
581
|
+
* directory node could publish the absent party's frontier as 3 and the receipt would say
|
|
582
|
+
* "mutually signed through 3" over a transcript that party never signed for. That is the precise
|
|
583
|
+
* conflation this field exists to prevent, reintroduced by the field itself.
|
|
584
|
+
*
|
|
585
|
+
* The carry answers it without trusting anybody. This daemon holds the counterparty's own leaves,
|
|
586
|
+
* each carrying, inside the bytes THEY signed, both the sequence they authored and the
|
|
587
|
+
* `last_seen_seq` they acknowledged. So a party's commitment reaches
|
|
588
|
+
* `max(highest sequence they authored, highest sequence they acknowledged)`, and the transcript is
|
|
589
|
+
* mutually signed only as far as the LEAST-committed party reaches.
|
|
590
|
+
*
|
|
591
|
+
* Fewer than two distinct authors ⇒ `0`: nobody countersigned anything, which is the honest floor
|
|
592
|
+
* for a conversation where the other side only ever received. `null` when the carry is empty or
|
|
593
|
+
* unreadable — the caller must then publish NO boundary rather than fall back to a number
|
|
594
|
+
* somebody else supplied.
|
|
595
|
+
*/
|
|
596
|
+
countersignedThroughSeqFromCarry(agentPubkeyHex: string, sessionIdHex: string): number | null;
|
|
468
597
|
getSealCarry(agentPubkeyHex: string, sessionIdHex: string): SealCarryLeaf[];
|
|
469
598
|
/**
|
|
470
599
|
* DOD-LOG-1 / PERSIST-002 (AC-010): append one readable message to the durable transcript, keyed
|
|
@@ -1280,6 +1409,46 @@ export declare class SessionNodeManager {
|
|
|
1280
1409
|
sealed_root: string;
|
|
1281
1410
|
legibility: unknown;
|
|
1282
1411
|
} | null;
|
|
1412
|
+
/**
|
|
1413
|
+
* DOD-M15-INCLUSION-1: keep the leaf set the certificate is signed over, so one message can later
|
|
1414
|
+
* be proved to sit under it.
|
|
1415
|
+
*
|
|
1416
|
+
* REFUSES unless the hashes reproduce `sealedRootHex` — `certifiedLeafSetFrom` does that check and
|
|
1417
|
+
* this method never bypasses it. That is what separates "the leaves the directory sent" from "the
|
|
1418
|
+
* leaves the consortium signed", and only the second is worth storing: a proof built on the first
|
|
1419
|
+
* would inherit whatever the directory chose to say.
|
|
1420
|
+
*
|
|
1421
|
+
* Idempotent (INSERT OR REPLACE keyed on leaf_index) so a re-delivered seal frame, or a unilateral
|
|
1422
|
+
* seal later upgraded to bilateral, rewrites the same rows instead of failing or doubling them.
|
|
1423
|
+
*
|
|
1424
|
+
* @returns whether the set was accepted and stored.
|
|
1425
|
+
*/
|
|
1426
|
+
recordCertifiedLeafSet(agentName: string, sessionId: string, signedLeaves: readonly SealFrontierLeaf[], sealedRootHex: string, correlationId?: string): boolean;
|
|
1427
|
+
/**
|
|
1428
|
+
* Record WHY this session does or does not have a certified leaf set.
|
|
1429
|
+
*
|
|
1430
|
+
* Public for the one case the manager cannot see: a seal frame that carried no signed leaves at
|
|
1431
|
+
* all never reaches `recordCertifiedLeafSet`, and that absence is a permanent fact about the
|
|
1432
|
+
* session for the party that observed it.
|
|
1433
|
+
*/
|
|
1434
|
+
noteCertifiedLeafSetUnavailable(agentName: string, sessionId: string, state: "not_carried_absent_party" | "not_carried_present_party", detail: string): void;
|
|
1435
|
+
/**
|
|
1436
|
+
* The last thing that happened to this session's certified leaf set, or null if nothing has.
|
|
1437
|
+
*
|
|
1438
|
+
* Null here and a null from `getCertifiedLeafSet` together mean "no seal has been processed on
|
|
1439
|
+
* this side yet" — which is a different sentence again from any of the recorded states.
|
|
1440
|
+
*/
|
|
1441
|
+
getCertifiedLeafSetState(agentName: string, sessionId: string): {
|
|
1442
|
+
state: string;
|
|
1443
|
+
detail: string | null;
|
|
1444
|
+
} | null;
|
|
1445
|
+
/**
|
|
1446
|
+
* The certified leaf set, in order, or null when none was stored for this session.
|
|
1447
|
+
*
|
|
1448
|
+
* Null is a REFUSAL upstream, never a fallback to the local tree: the two cover different leaves
|
|
1449
|
+
* and substituting one for the other is how a proof comes to land on a root nobody signed.
|
|
1450
|
+
*/
|
|
1451
|
+
getCertifiedLeafSet(agentName: string, sessionId: string): string[] | null;
|
|
1283
1452
|
/**
|
|
1284
1453
|
* M7-SESSION-001: Mark a session as interrupted with message count and timestamp.
|
|
1285
1454
|
* Called when a relay session_interrupted frame arrives or a relay stream closes.
|
|
@@ -1289,7 +1458,15 @@ export declare class SessionNodeManager {
|
|
|
1289
1458
|
* @param messageCount Number of message leaves at interruption
|
|
1290
1459
|
* @param source 'relay_frame' | 'stream_close'
|
|
1291
1460
|
*/
|
|
1292
|
-
markInterruptedWithDetails(agentName: string, sessionId: string, messageCount: number,
|
|
1461
|
+
markInterruptedWithDetails(agentName: string, sessionId: string, messageCount: number,
|
|
1462
|
+
/**
|
|
1463
|
+
* WHAT ACTUALLY HAPPENED, and it is written to the row — review F3.
|
|
1464
|
+
*
|
|
1465
|
+
* `key_refused` is its own source rather than a borrowed `stream_close`, because the row's
|
|
1466
|
+
* `interrupted_by` is what an operator reads days later: labelling a key-authentication refusal
|
|
1467
|
+
* `relay_stream_close` sends them to the relay fleet for a fault in the payload.
|
|
1468
|
+
*/
|
|
1469
|
+
source: "relay_frame" | "stream_close" | "key_refused"): Promise<boolean>;
|
|
1293
1470
|
/**
|
|
1294
1471
|
* M7-SESSION-001 (H-1): persist a verified bilateral SEAL-INTERRUPTED
|
|
1295
1472
|
* commitment and transition the session to 'seal_interrupted_pending'.
|
|
@@ -1483,12 +1660,25 @@ export declare class SessionNodeManager {
|
|
|
1483
1660
|
ok: true;
|
|
1484
1661
|
delivered: true;
|
|
1485
1662
|
sequenceNumber?: number;
|
|
1663
|
+
authorship?: SentAuthorship;
|
|
1486
1664
|
} | {
|
|
1487
1665
|
ok: true;
|
|
1488
1666
|
delivered: false;
|
|
1489
1667
|
parked: true;
|
|
1490
1668
|
sequenceNumber?: number;
|
|
1491
|
-
|
|
1669
|
+
authorship?: SentAuthorship;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* ⚠️ `authorship` RIDES THE FAILURE PATH TOO — review pass 2, and it is the same reasoning that
|
|
1673
|
+
* already put `sequenceNumber` here.
|
|
1674
|
+
*
|
|
1675
|
+
* A DURABLY QUEUED message was witnessed and SIGNED before delivery was attempted; only the
|
|
1676
|
+
* direct hand-off failed. Omitting the proof here meant every relay-degraded-but-alive send —
|
|
1677
|
+
* the common case — wrote a transcript row indistinguishable from one the relay never saw, while
|
|
1678
|
+
* the signature for it sat in scope and was discarded. The position survives a failed delivery
|
|
1679
|
+
* for exactly this reason; so does the proof.
|
|
1680
|
+
*/
|
|
1681
|
+
| {
|
|
1492
1682
|
ok: false;
|
|
1493
1683
|
reason: string;
|
|
1494
1684
|
error: string;
|
|
@@ -1496,6 +1686,7 @@ export declare class SessionNodeManager {
|
|
|
1496
1686
|
cause?: string;
|
|
1497
1687
|
guidance?: string;
|
|
1498
1688
|
sequenceNumber?: number;
|
|
1689
|
+
authorship?: SentAuthorship;
|
|
1499
1690
|
}>;
|
|
1500
1691
|
/**
|
|
1501
1692
|
* M7 DOD-SPINE-7: submit THIS party's SEAL ctrl leaf (0x02) to the relay witness.
|
|
@@ -1789,7 +1980,35 @@ export declare class SessionNodeManager {
|
|
|
1789
1980
|
* and it appends in arrival order as before: with no position there is no discipline to enforce,
|
|
1790
1981
|
* and refusing would take messaging down whenever the relay is unreachable.
|
|
1791
1982
|
*/
|
|
1792
|
-
placeOwnLeaf(agentName: string, sessionId: string, contentHashHex: string, sentBytes: Uint8Array, assignedSeq: number | undefined, correlationId
|
|
1983
|
+
placeOwnLeaf(agentName: string, sessionId: string, contentHashHex: string, sentBytes: Uint8Array, assignedSeq: number | undefined, correlationId: string | undefined,
|
|
1984
|
+
/**
|
|
1985
|
+
* ⚠️ NO DEFAULT, for the same reason `authorship` has none.
|
|
1986
|
+
*
|
|
1987
|
+
* `kind = "msg"` meant a caller writing a `doc` or a `ctrl` leaf got a `msg` leaf by saying
|
|
1988
|
+
* nothing, and the tree recorded a leaf kind the author never chose. TypeScript also forbids a
|
|
1989
|
+
* required parameter after a defaulted one, so leaving the default here would have forced
|
|
1990
|
+
* `authorship` back to optional — which is the defect above. Every one of the seven call sites
|
|
1991
|
+
* already passed a kind or wanted "msg"; making it explicit cost nothing and removes a second
|
|
1992
|
+
* silent answer from the same signature.
|
|
1993
|
+
*/
|
|
1994
|
+
kind: WritableSessionTreeLeafKind,
|
|
1995
|
+
/**
|
|
1996
|
+
* DOD-M15-SEALWIRE-1 bullet 5 — the proof for THIS send, so a held row keeps it.
|
|
1997
|
+
*
|
|
1998
|
+
* ⚠️ REQUIRED, AND `undefined` IS A VALID ANSWER — the two are not the same thing.
|
|
1999
|
+
*
|
|
2000
|
+
* This was `authorship?:` for exactly one review cycle, and in that cycle THREE of the seven
|
|
2001
|
+
* call sites omitted it: `daemon.ts` 1440, 1671, 1685 — the away-reply path, which is the
|
|
2002
|
+
* highest-traffic sent-writer in the daemon and the one with no human watching it. All three
|
|
2003
|
+
* had the proof **already in a local variable one line below**, handed to
|
|
2004
|
+
* `recordTranscriptMessage` and not to this method. Nothing went red, because an optional
|
|
2005
|
+
* parameter's whole behaviour on omission is to look deliberate.
|
|
2006
|
+
*
|
|
2007
|
+
* An unwitnessed send genuinely has no proof, so absence must stay expressible. Requiring the
|
|
2008
|
+
* parameter keeps that while making the caller SAY it: omission is now a type error, and
|
|
2009
|
+
* `undefined` is a claim the author made rather than one the signature made for them.
|
|
2010
|
+
*/
|
|
2011
|
+
authorship: SentAuthorship | undefined): {
|
|
1793
2012
|
placed: true;
|
|
1794
2013
|
leafIndex: number;
|
|
1795
2014
|
diverged?: true;
|
|
@@ -1882,6 +2101,19 @@ export declare class SessionNodeManager {
|
|
|
1882
2101
|
* record (the recover handler lives in daemon.ts, which has no access to the private method).
|
|
1883
2102
|
*/
|
|
1884
2103
|
recordOrderingRecord(agentName: string, sessionId: string, structure1Cbor: Uint8Array, structure2Cbor: Uint8Array, contentHash: Uint8Array, correlationId?: string): number | null;
|
|
2104
|
+
/**
|
|
2105
|
+
* Test seam: see every decoded inbound content frame, as it arrived.
|
|
2106
|
+
*
|
|
2107
|
+
* Review F4. The "bytes on the wire are ciphertext" claim needs the ACTUAL frame; asserting on a
|
|
2108
|
+
* freshly sealed stand-in tests the crypto primitive and stays green when the send path is
|
|
2109
|
+
* reverted to putting plaintext on the wire. There is no other way to reach the decoded frame from
|
|
2110
|
+
* outside — the handler consumes it and hands ingest the plaintext.
|
|
2111
|
+
*
|
|
2112
|
+
* Read-only by construction: the callback receives the frame and cannot influence routing.
|
|
2113
|
+
*/
|
|
2114
|
+
observeInboundContentFramesForTest(cb: (frame: Record<string, unknown>) => void): void;
|
|
2115
|
+
/** Injected by the daemon once its per-agent key providers exist. See `#keyProviderResolver`. */
|
|
2116
|
+
setKeyProviderResolver(resolver: (agentName: string) => KeyProvider | undefined): void;
|
|
1885
2117
|
/**
|
|
1886
2118
|
* Test seam: force this session's own salt half, so the LOCAL-defect path is reachable.
|
|
1887
2119
|
*
|
|
@@ -1907,6 +2139,97 @@ export declare class SessionNodeManager {
|
|
|
1907
2139
|
*/
|
|
1908
2140
|
runAutoAcknowledgeGateForTest(agentName: string, sessionId: string, correlationId?: string): void;
|
|
1909
2141
|
setSaltContributionForTest(agentName: string, sessionId: string, contribution: Uint8Array): void;
|
|
2142
|
+
/**
|
|
2143
|
+
* Test seams: re-enter the mint path, and read back the PUBLIC half — 006-CRYPTO.
|
|
2144
|
+
*
|
|
2145
|
+
* `#mintSessionEphemeral` is idempotent because a reconnect can re-enter an activation path, and a
|
|
2146
|
+
* second keypair mid-session would leave the two sides deriving against a moving value. Proving
|
|
2147
|
+
* that needs the path called TWICE, and the alternative — driving a real reconnect — drags in node
|
|
2148
|
+
* rebuild and relay reconnection, none of which the property is about. Same justification as
|
|
2149
|
+
* `forgetSaltContributionForTest` above.
|
|
2150
|
+
*
|
|
2151
|
+
* It calls the REAL private method, so a test cannot pass against a decision production does not
|
|
2152
|
+
* make. The reader returns the public half ONLY: a seam that could hand out the secret is a way
|
|
2153
|
+
* for the secret to leave this object, which is the one thing the whole unit is about.
|
|
2154
|
+
*/
|
|
2155
|
+
mintSessionEphemeralForTest(agentName: string, sessionId: string): void;
|
|
2156
|
+
sessionEphemeralPublicForTest(agentName: string, sessionId: string): Uint8Array | null;
|
|
2157
|
+
/**
|
|
2158
|
+
* Test seam: INSTALL a keypair the caller already holds — the only way to prove ZEROING.
|
|
2159
|
+
*
|
|
2160
|
+
* Presence is easy to assert and is not the property. `destroySessionEphemeral` overwrites the
|
|
2161
|
+
* buffer before the entry is dropped, and a mutant that drops without overwriting leaves the
|
|
2162
|
+
* secret wherever the collector last moved it while passing every presence check — which is
|
|
2163
|
+
* exactly what happened: the shutdown zeroing shipped with a surviving mutant, and the transport
|
|
2164
|
+
* seeds four lines above it have the same untested gap today.
|
|
2165
|
+
*
|
|
2166
|
+
* The direction matters. Nothing here HANDS OUT a secret — the test supplies an object it already
|
|
2167
|
+
* owns and then inspects its own reference. A reader that returned the live keypair would be a
|
|
2168
|
+
* path for the secret to leave this object, which is the one thing this unit exists to prevent.
|
|
2169
|
+
*/
|
|
2170
|
+
setSessionEphemeralForTest(agentName: string, sessionId: string, ephemeral: SessionEphemeral): void;
|
|
2171
|
+
/**
|
|
2172
|
+
* Test seam: put a session into the state a COMPLETED exchange leaves it in — 007-CRYPTO.
|
|
2173
|
+
*
|
|
2174
|
+
* A live send now requires an agreed key, because there is no plaintext path to fall back to. In
|
|
2175
|
+
* production the exchange completes on connect, before any send. A fixture with no real peer never
|
|
2176
|
+
* completes it, so without this every content test in the repo would be exercising the refusal
|
|
2177
|
+
* path instead of the thing it was written for.
|
|
2178
|
+
*
|
|
2179
|
+
* ⚠️ IT SHORT-CIRCUITS HOW THE KEY GOT THERE, NEVER WHAT THE KEY IS FOR. The state it produces —
|
|
2180
|
+
* a session holding an agreed content key — is exactly the production state, which is what makes
|
|
2181
|
+
* it legitimate; `setSaltContributionForTest` exists for the same reason. Tests of the EXCHANGE
|
|
2182
|
+
* itself drive the real signed frames and must not use this.
|
|
2183
|
+
*/
|
|
2184
|
+
setSessionContentKeyForTest(agentName: string, sessionId: string, key: Uint8Array): void;
|
|
2185
|
+
/**
|
|
2186
|
+
* Test seam: drop the agreed key while leaving the session up — the state before an exchange
|
|
2187
|
+
* completes, and after a teardown evicts one. Its mirror above is what a completed exchange
|
|
2188
|
+
* leaves; both are needed, or a status field stuck in one position passes either test alone.
|
|
2189
|
+
*/
|
|
2190
|
+
forgetSessionContentKeyForTest(agentName: string, sessionId: string): void;
|
|
2191
|
+
/**
|
|
2192
|
+
* Test seam: produce THIS side's signed ephemeral, using the manager's own identity resolver.
|
|
2193
|
+
*
|
|
2194
|
+
* For harnesses whose connectivity is one-directional — one side dials, so only one announce ever
|
|
2195
|
+
* lands. Carrying the other side's half across with a REAL signature is what completes the
|
|
2196
|
+
* exchange, and it beats seeding a key: a seeded key has no peer half recorded against it, so the
|
|
2197
|
+
* first genuine announce replaces it and the two ends drift apart (which is correct behaviour —
|
|
2198
|
+
* see the re-key guard — and exactly what made seeding fragile here).
|
|
2199
|
+
*
|
|
2200
|
+
* It signs with the same provider production signs with, so a test cannot pass against a signature
|
|
2201
|
+
* production would have refused.
|
|
2202
|
+
*/
|
|
2203
|
+
signOwnEphemeralForTest(agentName: string, sessionId: string): Promise<{
|
|
2204
|
+
ephemeralPublic: Uint8Array;
|
|
2205
|
+
signature: Uint8Array;
|
|
2206
|
+
} | null>;
|
|
2207
|
+
/**
|
|
2208
|
+
* Test seam: deliver a peer's signed ephemeral, exactly as the content-stream decoder does.
|
|
2209
|
+
*
|
|
2210
|
+
* For harnesses whose connectivity is one-directional — one side dials, so only one announce ever
|
|
2211
|
+
* lands — this is what completes the exchange instead of stuffing a key in. It runs the REAL
|
|
2212
|
+
* verification and the REAL derivation, so a test cannot pass against a signature production would
|
|
2213
|
+
* have refused.
|
|
2214
|
+
*/
|
|
2215
|
+
handleEphemeralFrameForTest(agentName: string, sessionId: string, frame: {
|
|
2216
|
+
ephemeralPublic?: Uint8Array;
|
|
2217
|
+
signature?: Uint8Array;
|
|
2218
|
+
}, correlationId?: string): Promise<void>;
|
|
2219
|
+
/**
|
|
2220
|
+
* Test seam: deliver an inbound salt frame, exactly as the content-stream decoder does.
|
|
2221
|
+
*
|
|
2222
|
+
* 006-CRYPTO finding 2. WHICH of the four reasons the peer gave decides what the operator is told,
|
|
2223
|
+
* and reaching that decision from a test otherwise needs a second live daemon that has closed
|
|
2224
|
+
* adoption for a specific reason — which is not something a counterparty can be asked to do on
|
|
2225
|
+
* demand. The four labels are the whole point of the finding, so they need to be reachable.
|
|
2226
|
+
*
|
|
2227
|
+
* It calls the REAL private handler rather than reproducing its routing, so a test cannot pass
|
|
2228
|
+
* against a decision production does not make. It takes the DECODED frame, so it deliberately
|
|
2229
|
+
* does NOT stand in for the decoder above it — the length and vocabulary checks there have their
|
|
2230
|
+
* own tests driving `handleContentFrameForTest`.
|
|
2231
|
+
*/
|
|
2232
|
+
handleSaltFrameForTest(agentName: string, sessionId: string, frame: SaltAgreementFrame, correlationId?: string): Promise<void>;
|
|
1910
2233
|
/**
|
|
1911
2234
|
* Test seam: drop this session's own half while leaving the stored salt in place — the state every
|
|
1912
2235
|
* teardown produces, because `#evictSessionCaches` clears the map and the row survives.
|
|
@@ -1995,7 +2318,49 @@ export declare class SessionNodeManager {
|
|
|
1995
2318
|
* Read-only and cache-backed, so exposing it adds no way to CHANGE the salt from outside — the
|
|
1996
2319
|
* only writer remains `#persistSessionSalt`, behind the one-salt-per-session predicate.
|
|
1997
2320
|
*/
|
|
2321
|
+
/**
|
|
2322
|
+
* IS THIS SESSION ACTUALLY PROTECTED BY ITS SALT RIGHT NOW — pass 2, F3.
|
|
2323
|
+
*
|
|
2324
|
+
* Distinct from `getSessionContentSalt`, which is POSSESSION and is what the verifier needs: a
|
|
2325
|
+
* message parked before suspension was hashed under this salt and must still be checkable against
|
|
2326
|
+
* it, so that accessor must keep answering with the bytes.
|
|
2327
|
+
*
|
|
2328
|
+
* This one answers the OPERATOR's question, and it is a different question. A suspended session
|
|
2329
|
+
* holds a salt it will not use, so every hash it produces is `sha256` — reporting `contentSalted:
|
|
2330
|
+
* true` there is not a gap, it is an affirmatively false security claim on the surface whose own
|
|
2331
|
+
* comment reads *"a security property must not be inferable from a gap."* Same predicate
|
|
2332
|
+
* `#saltForHashing` uses, so the flag cannot drift from the behaviour it describes.
|
|
2333
|
+
*/
|
|
2334
|
+
isContentSaltActive(agentName: string, sessionId: string): boolean;
|
|
1998
2335
|
getSessionContentSalt(agentName: string, sessionId: string): Uint8Array | null;
|
|
2336
|
+
/**
|
|
2337
|
+
* THE SALT, OR WHY THERE ISN'T ONE — `DOD-M15-INCLUSION-1`, fallback-finder finding 2.
|
|
2338
|
+
*
|
|
2339
|
+
* `getSessionContentSalt` above answers `null` for THREE different situations, and a caller that
|
|
2340
|
+
* turns that null into a sentence for an operator gets two of them wrong:
|
|
2341
|
+
*
|
|
2342
|
+
* `none` — no salt was ever agreed. The session really is unsalted.
|
|
2343
|
+
* `unreadable` — a salt row EXISTS and could not be used: the wrong width (corruption on this
|
|
2344
|
+
* operator's own disk), or the read threw.
|
|
2345
|
+
*
|
|
2346
|
+
* The distinction is not cosmetic. `unreadable` means the session's leaves WERE hashed under a
|
|
2347
|
+
* salt, so telling its operator *"this session's content hashes are UNSALTED … start a session
|
|
2348
|
+
* while you are both connected"* is an affirmatively false statement about a security property
|
|
2349
|
+
* their session has, and it points them at their counterparty over damage to their own database.
|
|
2350
|
+
* That is the same defect `#getSessionSalt`'s own F8 note was written to end, re-committed one
|
|
2351
|
+
* surface out.
|
|
2352
|
+
*
|
|
2353
|
+
* ⚠️ IT DELEGATES — there is no second read here. Calling `#getSessionSalt` first means the salt
|
|
2354
|
+
* this reports is the salt the hashing path uses, including its cache and its wrong-width refusal.
|
|
2355
|
+
* A parallel query would be free to disagree with it, which is the whole failure this returns a
|
|
2356
|
+
* reason to prevent.
|
|
2357
|
+
*/
|
|
2358
|
+
getSessionContentSaltState(agentName: string, sessionId: string): {
|
|
2359
|
+
salt: Uint8Array;
|
|
2360
|
+
} | {
|
|
2361
|
+
salt: null;
|
|
2362
|
+
reason: "none" | "unreadable";
|
|
2363
|
+
};
|
|
1999
2364
|
/**
|
|
2000
2365
|
* M7-SESSION-001 AC-004/AC-005: Register a relay stream for an active session.
|
|
2001
2366
|
* Starts a background reader that watches for session_interrupted frames and
|
|
@@ -2012,6 +2377,19 @@ export declare class SessionNodeManager {
|
|
|
2012
2377
|
* receipt may differ, but this is the value available at stream setup time)
|
|
2013
2378
|
*/
|
|
2014
2379
|
registerRelayStream(agentName: string, sessionId: string, stream: Stream, messageCount?: number): void;
|
|
2380
|
+
/**
|
|
2381
|
+
* DOD-M15-RELAYSLOTS-1: accept the directory's online token for an agent. Called on every
|
|
2382
|
+
* signaling connect and reconnect, which is what keeps it fresh.
|
|
2383
|
+
*/
|
|
2384
|
+
setDirectoryOnlineToken(agentName: string, token: Uint8Array): void;
|
|
2385
|
+
setDirectoryOnlineTokenAbsent(agentName: string, reason: "not_registered_here" | "issue_failed" | undefined): void;
|
|
2386
|
+
/**
|
|
2387
|
+
* The current token, or `undefined` when the directory has not issued one — either no directory
|
|
2388
|
+
* connection yet, or this key has no agent profile there. Undefined is a real answer, not a
|
|
2389
|
+
* missing one: the relay refuses without a token, which is the intended outcome for a key the
|
|
2390
|
+
* directory does not recognise.
|
|
2391
|
+
*/
|
|
2392
|
+
getDirectoryOnlineToken(agentName: string): Uint8Array | undefined;
|
|
2015
2393
|
/**
|
|
2016
2394
|
* DOD-NAT-REACHABILITY-1 (Phase 2): accept the directory's relay-pool endpoints
|
|
2017
2395
|
* for an agent (arrives with signaling_auth_ok, i.e. on every connect AND every
|
|
@@ -2024,6 +2402,11 @@ export declare class SessionNodeManager {
|
|
|
2024
2402
|
relayPeerId: string;
|
|
2025
2403
|
relayAddrs: string[];
|
|
2026
2404
|
}>): void;
|
|
2405
|
+
/**
|
|
2406
|
+
* Is this agent currently skipping this relay? The observable half of the failover decision — a
|
|
2407
|
+
* test that asserts only on the classifier's boolean proves nothing about what the daemon does.
|
|
2408
|
+
*/
|
|
2409
|
+
isRelayQuarantined(agentName: string, relayPeerId: string): boolean;
|
|
2027
2410
|
/**
|
|
2028
2411
|
* DOD-M12B-SESSION-SEED-1 — bring an interrupted session back on the peer id it already has.
|
|
2029
2412
|
*
|