@cello-protocol/daemon 0.0.195 → 0.0.197
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/held-content.d.ts +7 -3
- package/dist/held-content.d.ts.map +1 -1
- package/dist/held-content.js.map +1 -1
- package/dist/session-assignment-parser.d.ts +11 -6
- package/dist/session-assignment-parser.d.ts.map +1 -1
- package/dist/session-assignment-parser.js +11 -6
- package/dist/session-assignment-parser.js.map +1 -1
- package/dist/session-content-context.d.ts +144 -0
- package/dist/session-content-context.d.ts.map +1 -0
- package/dist/session-content-context.js +2 -0
- package/dist/session-content-context.js.map +1 -0
- package/dist/session-content-ingest.d.ts +208 -0
- package/dist/session-content-ingest.d.ts.map +1 -0
- package/dist/session-content-ingest.js +2216 -0
- package/dist/session-content-ingest.js.map +1 -0
- package/dist/session-content-send.d.ts +184 -0
- package/dist/session-content-send.d.ts.map +1 -0
- package/dist/session-content-send.js +1300 -0
- package/dist/session-content-send.js.map +1 -0
- package/dist/session-lifecycle.d.ts +303 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +1643 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/session-node-manager.d.ts +67 -766
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +966 -7940
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-node-types.d.ts +22 -0
- package/dist/session-node-types.d.ts.map +1 -1
- package/dist/session-node-types.js.map +1 -1
- package/dist/session-relay.d.ts +361 -0
- package/dist/session-relay.d.ts.map +1 -0
- package/dist/session-relay.js +1471 -0
- package/dist/session-relay.js.map +1 -0
- package/dist/session-salts.d.ts +13 -0
- package/dist/session-salts.d.ts.map +1 -1
- package/dist/session-salts.js +13 -0
- package/dist/session-salts.js.map +1 -1
- package/dist/session-seal.d.ts +336 -0
- package/dist/session-seal.d.ts.map +1 -0
- package/dist/session-seal.js +948 -0
- package/dist/session-seal.js.map +1 -0
- package/package.json +5 -5
|
@@ -21,27 +21,29 @@ import { type CapacityReason } from "./refusal-reasons.js";
|
|
|
21
21
|
import { type SaltAgreementFrame } from "./session-salt-agreement.js";
|
|
22
22
|
import { type DaemonDatabase } from "./sqlcipher-db.js";
|
|
23
23
|
import { type RefusalKind } from "./refusal-reasons.js";
|
|
24
|
-
import type {
|
|
25
|
-
import type { Logger, SessionRecord, SealReadinessView } from "./types.js";
|
|
24
|
+
import type { Logger, SessionRecord } from "./types.js";
|
|
26
25
|
import { SessionTree, type WritableSessionTreeLeafKind } from "./session-tree.js";
|
|
27
26
|
import { type CelloNode, type IAutoNatService } from "@cello-protocol/transport";
|
|
28
27
|
import type { KeyProvider } from "@cello-protocol/crypto";
|
|
29
28
|
import { type SessionEphemeral } from "@cello-protocol/crypto";
|
|
30
29
|
import { type ParkEnvelope } from "./park-envelope.js";
|
|
31
30
|
import { AgentRelayClient, type RelayAuthRefusal, type RelayWitnessAlert } from "./session-relay-client.js";
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
31
|
+
import { SessionContentSender } from "./session-content-send.js";
|
|
32
|
+
import { SessionContentIngest } from "./session-content-ingest.js";
|
|
33
|
+
import { SessionSeal } from "./session-seal.js";
|
|
34
|
+
import { SessionRelay } from "./session-relay.js";
|
|
35
|
+
import { SessionLifecycle } from "./session-lifecycle.js";
|
|
36
|
+
import { RelayReceiptStore } from "./relay-receipt-store.js";
|
|
37
|
+
import { SessionSealLeafStore } from "./session-seal-leaf-store.js";
|
|
34
38
|
import { SessionOwnChainStore } from "./session-own-chain-store.js";
|
|
35
|
-
import type { SealUpgradeReadiness } from "./seal-upgrade.js";
|
|
36
39
|
import type { SealFrontierLeaf } from "./seal-frontier-verify.js";
|
|
37
|
-
import { type QuarantineFrameMeta } from "./quarantine-framing.js";
|
|
38
40
|
import { type SecurityGatewayClient } from "@cello-protocol/gateway";
|
|
39
41
|
/**
|
|
40
42
|
* One row in an agent's witness-alert list — DOD-M15-CORROBORATE-1 review F1. Deduped on
|
|
41
43
|
* `(witness relay, session)`, so a repeated observation raises `occurrences` rather than taking
|
|
42
44
|
* another slot in a bounded list.
|
|
43
45
|
*/
|
|
44
|
-
import { type
|
|
46
|
+
import { type ISessionNodeFactory, type ParkedDrainReason, type QuarantinedRecord, type RefusalNotice, type SessionImpairment, type TranscriptEntry, type WitnessAlertNotice } from "./session-node-types.js";
|
|
45
47
|
export { ABUSE_MAX_SESSIONS_PER_UNKNOWN_SENDER, ABUSE_MAX_SESSION_RECEIVED_BYTES, ABUSE_MAX_UNKNOWN_SESSIONS_GLOBAL, type AbandonNoticeResult, type AckHashReason, CAP_INTERRUPTED_TTL_MS, type ISessionNodeFactory, LEAF_FETCH_GRACE_MS, MAX_TERMINAL_REFUSALS_PER_SESSION, type ParkedDrainReason, type QuarantinedRecord, RELAY_QUARANTINE_MS, REVIVAL_BOUND_SWEEP_MS, REVIVAL_WINDOW_MS, REVIVE_RESERVATION_CANDIDATES, REVIVE_RESERVATION_TIMEOUT_MS, type RefusalNotice, type RelayConnectParams, SR_RESERVATION_MAX_RETRIES, type SentAuthorship, type SessionImpairment, type SessionNodeConfig, TERMINAL_REFUSAL_READ_RETRY_MS, TERMINAL_REFUSAL_REASONS, type TranscriptEntry, type WitnessAlertNotice, } from "./session-node-types.js";
|
|
46
48
|
export declare class SessionNodeManager {
|
|
47
49
|
#private;
|
|
@@ -199,6 +201,21 @@ export declare class SessionNodeManager {
|
|
|
199
201
|
wasSessionRefused(agentName: string, sessionId: string): boolean;
|
|
200
202
|
countReceivedMessages(agentName: string, sessionId: string): number;
|
|
201
203
|
markInterruptedByCounterpartyForTest(agentName: string, sessionId: string): void;
|
|
204
|
+
/**
|
|
205
|
+
* M7-SESSION-004 (AC-005): persist the seal certificate's legibility object with the
|
|
206
|
+
* sealed record. Stored as a JSON string (hex-encoded pubkeys) so it round-trips a
|
|
207
|
+
* daemon restart and is returned intact on the cert-read surface. The caller normalises
|
|
208
|
+
* the raw wire legibility (Uint8Array pubkeys) into a JSON-safe shape before storing.
|
|
209
|
+
* Best-effort: a session row may not yet exist (the seal arrived before the row was
|
|
210
|
+
* persisted); in that case we no-op rather than throw — the cert still flows through the
|
|
211
|
+
* live return path. The legibility content is identical regardless of delivery timing.
|
|
212
|
+
*
|
|
213
|
+
* ⚠️ THIS BLOCK WAS STRANDED WHEN THE METHOD BECAME A DELEGATOR. It stayed behind and ended up
|
|
214
|
+
* stacked on top of `markSealed`'s own doc block, so that method showed two descriptions and the
|
|
215
|
+
* first one described a different method entirely — and the seal split then carried it into
|
|
216
|
+
* `session-seal.ts`, where `recordSealCertificate` does not appear at all. Returned to the call
|
|
217
|
+
* it describes. The row-writing itself lives in `session-queries.ts`.
|
|
218
|
+
*/
|
|
202
219
|
recordSealCertificate(agentName: string, sessionId: string, sealedRootHex: string, legibilityJson: string): void;
|
|
203
220
|
recordCounterpartyPrimary(agentName: string, sessionId: string, primaryPubkeyHex: string): void;
|
|
204
221
|
/**
|
|
@@ -435,76 +452,6 @@ export declare class SessionNodeManager {
|
|
|
435
452
|
* encrypted DB file.
|
|
436
453
|
*/
|
|
437
454
|
getDb(): DaemonDatabase;
|
|
438
|
-
/**
|
|
439
|
-
* RELAYSIG-1: the durably-stored, signature-verified relay ordering-record receipts for an agent
|
|
440
|
-
* (optionally a single session). Empty when no receipts have been recorded yet. Read-only.
|
|
441
|
-
*/
|
|
442
|
-
getRelayReceipts(agentPubkeyHex: string, sessionIdHex?: string): RelayReceipt[];
|
|
443
|
-
/**
|
|
444
|
-
* FED-OPTIONB-SEAL-001: the complete ordered leaf chain (both parties) a UNILATERAL seal carries to the
|
|
445
|
-
* directory for the OFFLINE tree rebuild. Empty when no leaves were logged (e.g. a direct-only session
|
|
446
|
-
* with no relay witness) — the caller then has nothing to carry and the seal stays bilateral/pending.
|
|
447
|
-
*/
|
|
448
|
-
/**
|
|
449
|
-
* REBUILD THE CERTIFIED ROOT FROM THIS DAEMON'S OWN LEAVES — `DOD-M15-SEALWIRE-1` bullet 2.
|
|
450
|
-
*
|
|
451
|
-
* The receipt used to prove only that the directory signed SOMETHING: the client took the sealed
|
|
452
|
-
* root off the wire, confirmed the directory's signature over those bytes, stored it, and threw
|
|
453
|
-
* away the root it had computed a step earlier. At co-signing time that means **your key signs a
|
|
454
|
-
* root you never checked.**
|
|
455
|
-
*
|
|
456
|
-
* Bullet 1 moved the certified root into the content-hash domain, which is the domain this daemon
|
|
457
|
-
* can actually rebuild — each carry leaf's `content_hash` is the leaf hash (RFC 6962 §2.1 "hash"
|
|
458
|
-
* leaves are used as-is), and the carry is ordered by the relay's canonical `sequence_number`,
|
|
459
|
-
* which is the order the directory rebuilds in.
|
|
460
|
-
*
|
|
461
|
-
* ─── Why this returns "cannot judge" instead of always answering ───────────────────────────
|
|
462
|
-
*
|
|
463
|
-
* A root comparison that is WRONG makes every session unsealable, and force-abandon — no receipt —
|
|
464
|
-
* becomes the only exit. That failure is worse than the one being guarded, and this file already
|
|
465
|
-
* carries two comments saying so about other gates.
|
|
466
|
-
*
|
|
467
|
-
* The carry is this daemon's view, and it is not guaranteed complete at the instant a certificate
|
|
468
|
-
* arrives: the counterparty's SEAL ctrl leaf is what TRIGGERS the seal, so it may not have been
|
|
469
|
-
* witnessed here yet. So completeness is checked FIRST, against the certificate's own leaf count.
|
|
470
|
-
* A short carry means this daemon cannot judge — which is a different answer from "the roots
|
|
471
|
-
* disagree", and conflating them would turn a local timing gap into an accusation.
|
|
472
|
-
*/
|
|
473
|
-
verifyCertifiedRoot(agentPubkeyHex: string, sessionIdHex: string, certifiedRoot: Uint8Array, certifiedLeafCount: number): {
|
|
474
|
-
verdict: "match";
|
|
475
|
-
} | {
|
|
476
|
-
verdict: "mismatch";
|
|
477
|
-
ownRootHex: string | null;
|
|
478
|
-
detail: string;
|
|
479
|
-
} | {
|
|
480
|
-
verdict: "cannot_judge";
|
|
481
|
-
reason: string;
|
|
482
|
-
};
|
|
483
|
-
/**
|
|
484
|
-
* WHERE THE MUTUALLY-SIGNED PREFIX ENDS, DERIVED FROM THIS DAEMON'S OWN LEAVES —
|
|
485
|
-
* `DOD-M15-UNILATERAL-1`, review F2.
|
|
486
|
-
*
|
|
487
|
-
* ⚠️ **THE FIRST VERSION COMPUTED THIS FROM THE CERTIFICATE'S OWN PARTICIPANT LIST, AND CALLED
|
|
488
|
-
* THAT "recomputed, cannot be steered".** It could be steered. On the SOLO path the certificate's
|
|
489
|
-
* TBS binds no legibility at all, and the client verifies only the *live* party's frontier — so
|
|
490
|
-
* the absent party's `content_frontier_seq` and every `last_authored_seq` arrived unchecked. One
|
|
491
|
-
* directory node could publish the absent party's frontier as 3 and the receipt would say
|
|
492
|
-
* "mutually signed through 3" over a transcript that party never signed for. That is the precise
|
|
493
|
-
* conflation this field exists to prevent, reintroduced by the field itself.
|
|
494
|
-
*
|
|
495
|
-
* The carry answers it without trusting anybody. This daemon holds the counterparty's own leaves,
|
|
496
|
-
* each carrying, inside the bytes THEY signed, both the sequence they authored and the
|
|
497
|
-
* `last_seen_seq` they acknowledged. So a party's commitment reaches
|
|
498
|
-
* `max(highest sequence they authored, highest sequence they acknowledged)`, and the transcript is
|
|
499
|
-
* mutually signed only as far as the LEAST-committed party reaches.
|
|
500
|
-
*
|
|
501
|
-
* Fewer than two distinct authors ⇒ `0`: nobody countersigned anything, which is the honest floor
|
|
502
|
-
* for a conversation where the other side only ever received. `null` when the carry is empty or
|
|
503
|
-
* unreadable — the caller must then publish NO boundary rather than fall back to a number
|
|
504
|
-
* somebody else supplied.
|
|
505
|
-
*/
|
|
506
|
-
countersignedThroughSeqFromCarry(agentPubkeyHex: string, sessionIdHex: string): number | null;
|
|
507
|
-
getSealCarry(agentPubkeyHex: string, sessionIdHex: string): SealCarryLeaf[];
|
|
508
455
|
/** DOD-TIER-4: the DISPLAY/relationship check — is this counterparty a genuine contact (KNOWN or
|
|
509
456
|
* above)? Replaces the old binary `isContact` for behaviour that keyed on "we have a relationship"
|
|
510
457
|
* (e.g. the away-response wording). An UNKNOWN-tier contact (a mere row) is NOT known. */
|
|
@@ -520,22 +467,6 @@ export declare class SessionNodeManager {
|
|
|
520
467
|
* four-level resolution TOTAL. A pure read; the resolved text is screened on the outbound path by
|
|
521
468
|
* the caller like any content (SI — it does not bypass the gateway). */
|
|
522
469
|
resolveAwayMessage(agentName: string, pubkey: string): string | null;
|
|
523
|
-
/**
|
|
524
|
-
* DOD-M15-REFUSEDEVIDENCE-1 — retain a message refused OUTSIDE `ingestReceivedContent`.
|
|
525
|
-
*
|
|
526
|
-
* Review F6. The park drain terminally-blocks a message that arrived for an already-committed
|
|
527
|
-
* session, then confirm-deletes the relay copy — the one other route in the tree that discarded
|
|
528
|
-
* refused content, and the highest-suspicion combination in the product: hostile bytes aimed at a
|
|
529
|
-
* conversation somebody has already sealed. Shipped guidance now tells every operator that
|
|
530
|
-
* refused messages are kept, so this is made true rather than the promise narrowed.
|
|
531
|
-
*
|
|
532
|
-
* A thin delegate, not a second implementation: the bound, the dedup, the sequence allocation and
|
|
533
|
-
* the logging are the ones every other refusal uses.
|
|
534
|
-
*/
|
|
535
|
-
quarantineRefusedInbound(agentName: string, sessionId: string, reason: string, content: Uint8Array, contentHashHex: string, senderPubkeyHex: string | null, correlationId?: string): number | null;
|
|
536
|
-
/** The metadata half of a framed quarantine read — everything known ABOUT the message, none of it
|
|
537
|
-
* taken from the message. Split out so the framing module never touches the database. */
|
|
538
|
-
quarantineFrameMeta(agentName: string, sessionId: string, rec: QuarantinedRecord): QuarantineFrameMeta;
|
|
539
470
|
/** M8C-ABUSE-1 + DOD-TIER-2/3: is a NEW inbound session from this counterparty within the
|
|
540
471
|
* acceptance bounds? The per-sender cap is now the sender's TIER cap (DEFAULT_TIER_BOUNDS), not a
|
|
541
472
|
* flat "3 for strangers, unbounded for contacts". This is where DOD-TIER-3 falls out for free: a
|
|
@@ -550,43 +481,11 @@ export declare class SessionNodeManager {
|
|
|
550
481
|
ok: false;
|
|
551
482
|
reason: CapacityReason;
|
|
552
483
|
};
|
|
553
|
-
/**
|
|
554
|
-
* The current standing receiver node's session-transport coordinates (peer id +
|
|
555
|
-
* listen multiaddrs), or null if it is not ready. These are the addresses a local
|
|
556
|
-
* SessionNegotiator advertises as this node's counterparty endpoint so the initiator
|
|
557
|
-
* can dial it, and the value an inbound session_assignment carries in its
|
|
558
|
-
* counterparty_session_* fields. Read-only — does NOT consume the standing receiver
|
|
559
|
-
* (unlike acceptSession, which hands it off).
|
|
560
|
-
*/
|
|
561
|
-
/**
|
|
562
|
-
* 032-RELAYSPREAD — would this receiver ADMIT an inbound dial from this relay?
|
|
563
|
-
*
|
|
564
|
-
* The gater's inbound carve-out is the security-sensitive half of the spread: only relays whose
|
|
565
|
-
* own reservation is confirmed held earn it, so a directory that merely NAMES a relay cannot dial
|
|
566
|
-
* in behind the gate. Nothing could observe that from outside the manager, and the review found
|
|
567
|
-
* the consequence: substituting the CANDIDATE list for the held list at the `setReservedRelayPeers`
|
|
568
|
-
* call kept every test in the unit green while shipping exactly that hole. A guard whose wiring
|
|
569
|
-
* cannot be observed is a guard nothing can test.
|
|
570
|
-
*
|
|
571
|
-
* Reads the live gater rather than a copy, so it cannot drift from what the gate actually does.
|
|
572
|
-
*/
|
|
573
|
-
isRelayCarvedOutInbound(agentName: string, relayPeerId: string): boolean;
|
|
574
|
-
/**
|
|
575
|
-
* The libp2p Peer ID of an active session's node (N_A for an initiated session), or
|
|
576
|
-
* null if no active node exists for it. This is the initiator's session peer id that an
|
|
577
|
-
* inbound session_assignment must carry to the counterparty (so the counterparty gates
|
|
578
|
-
* its handed-off receiver to it). Read-only.
|
|
579
|
-
*/
|
|
580
|
-
getSessionNodePeerId(agentName: string, sessionId: string): string | null;
|
|
581
484
|
/**
|
|
582
485
|
* M7-SESSION-001 (M-1 PUSH): register the session-state-change callback.
|
|
583
486
|
* Called by the composition root (daemon.ts) after the NotificationDispatcher
|
|
584
487
|
* exists. Setter injection avoids a construction-order/circular dependency.
|
|
585
488
|
*/
|
|
586
|
-
/** Fix #1 EXTENSION: inject the broker-connection opener. Setter injection, same construction-order reason. */
|
|
587
|
-
setEnsureSealBroker(cb: (agentName: string, sessionId: string) => Promise<{
|
|
588
|
-
stop: (reason: string) => Promise<void>;
|
|
589
|
-
} | null>): void;
|
|
590
489
|
setOnSessionStateChanged(cb: (agentName: string, sessionId: string, state: string, counterpartyPubkey: string | null) => void): void;
|
|
591
490
|
/**
|
|
592
491
|
* M8C-MSGWAKE-1: inject the content-arrival callback (daemon.ts → NotificationDispatcher.
|
|
@@ -607,17 +506,6 @@ export declare class SessionNodeManager {
|
|
|
607
506
|
* an unresolvable name — see #requireAgentId for why null is not an option.
|
|
608
507
|
*/
|
|
609
508
|
resolveAgentId(agentName: string): string;
|
|
610
|
-
/**
|
|
611
|
-
* Create a new outbound session node.
|
|
612
|
-
* Called during cello_initiate_session.
|
|
613
|
-
*
|
|
614
|
-
* @param sessionId Unique session ID (hex string)
|
|
615
|
-
* @param agentName Name of the initiating agent
|
|
616
|
-
* @param counterpartyPubkey Counterparty's K_local public key (hex)
|
|
617
|
-
* @param counterpartyPeerId Counterparty's session-layer Peer ID (for gater)
|
|
618
|
-
* @param correlationId Correlation ID minted at session initiation
|
|
619
|
-
*/
|
|
620
|
-
createSessionNode(sessionId: string, agentName: string, counterpartyPubkey: string, counterpartyPeerId: string, correlationId: string, reuseStandingReceiver?: boolean, relay?: RelayConnectParams): Promise<CreateSessionResult>;
|
|
621
509
|
/**
|
|
622
510
|
* DOD-M12B-ACK-1 — live `/cello/content/1.0.0` stream counts on a session's direct path, or null
|
|
623
511
|
* when the session has no active node.
|
|
@@ -631,40 +519,6 @@ export declare class SessionNodeManager {
|
|
|
631
519
|
inbound: number;
|
|
632
520
|
outbound: number;
|
|
633
521
|
} | null;
|
|
634
|
-
/**
|
|
635
|
-
* DOD-M12B-ABANDON-NOTIFY-1 — drive the REAL inbound content handler with one framed message and
|
|
636
|
-
* a claimed peer identity.
|
|
637
|
-
*
|
|
638
|
-
* The handler is registered on a live libp2p node, so without this the only way to reach its
|
|
639
|
-
* branches is a full two-node transport fixture — which is why the session-abandoned branch and
|
|
640
|
-
* its peer pinning had no coverage at all. This feeds the same function the protocol handler
|
|
641
|
-
* calls, including the authentication check, rather than a copy of its logic.
|
|
642
|
-
*/
|
|
643
|
-
handleContentFrameForTest(agentName: string, sessionId: string, framedBytes: Uint8Array, remotePeerId?: string): Promise<void>;
|
|
644
|
-
/**
|
|
645
|
-
* Hand the standing receiver to an inbound session.
|
|
646
|
-
* Called during cello_await_session.
|
|
647
|
-
*
|
|
648
|
-
* CRITICAL (AC-015): gater.setAllowedPeer() is called BEFORE returning
|
|
649
|
-
* the node's multiaddr to the caller. This closes the window where an
|
|
650
|
-
* unexpected peer could connect during the hand-off.
|
|
651
|
-
*/
|
|
652
|
-
acceptSession(sessionId: string, agentName: string, counterpartyPubkey: string, initiatorPeerId: string, correlationId: string, relay?: RelayConnectParams): Promise<CreateSessionResult>;
|
|
653
|
-
/**
|
|
654
|
-
* Destroy a session node after seal or on error teardown.
|
|
655
|
-
* Status written to SQLite.
|
|
656
|
-
*/
|
|
657
|
-
destroySessionNode(agentName: string, sessionId: string, reason: "sealed" | "interrupted" | "error"): Promise<void>;
|
|
658
|
-
/**
|
|
659
|
-
* round-2 finding #5: retire a session's live libp2p node WITHOUT changing its
|
|
660
|
-
* DB status. Used after the active-session bilateral seal commitment has already
|
|
661
|
-
* advanced the row to 'seal_interrupted_pending': the session is frozen, so we
|
|
662
|
-
* stop the node and unregister its /cello/content handler (no more inbound leaves,
|
|
663
|
-
* no leaked node per active close) but must NOT overwrite the pending/sealed status
|
|
664
|
-
* the way destroySessionNode would. The durable tree stays in SQLite (getSessionTree
|
|
665
|
-
* reloads it); the in-memory plaintext buffer is evicted.
|
|
666
|
-
*/
|
|
667
|
-
retireSessionNode(agentName: string, sessionId: string): Promise<void>;
|
|
668
522
|
gracefulShutdown(): Promise<void>;
|
|
669
523
|
/**
|
|
670
524
|
* DOD-M12B-REVIVAL-BOUND-1 — close every session the revival window has expired.
|
|
@@ -687,88 +541,46 @@ export declare class SessionNodeManager {
|
|
|
687
541
|
* cello_list_sessions). Classification + filtering + the count limit are applied by the caller.
|
|
688
542
|
*/
|
|
689
543
|
getAllSessions(): SessionRecord[];
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* M7-SESSION-001: Mark a session as interrupted with message count and timestamp.
|
|
732
|
-
* Called when a relay session_interrupted frame arrives or a relay stream closes.
|
|
733
|
-
* Also tears down the in-memory session node if one exists for this sessionId.
|
|
734
|
-
*
|
|
735
|
-
* @param sessionId The hex session ID from the relay frame
|
|
736
|
-
* @param messageCount Number of message leaves at interruption
|
|
737
|
-
* @param source 'relay_frame' | 'stream_close'
|
|
738
|
-
*/
|
|
739
|
-
markInterruptedWithDetails(agentName: string, sessionId: string, messageCount: number,
|
|
740
|
-
/**
|
|
741
|
-
* WHAT ACTUALLY HAPPENED, and it is written to the row — review F3.
|
|
742
|
-
*
|
|
743
|
-
* `key_refused` is its own source rather than a borrowed `stream_close`, because the row's
|
|
744
|
-
* `interrupted_by` is what an operator reads days later: labelling a key-authentication refusal
|
|
745
|
-
* `relay_stream_close` sends them to the relay fleet for a fault in the payload.
|
|
746
|
-
*/
|
|
747
|
-
source: "relay_frame" | "stream_close" | "key_refused"): Promise<boolean>;
|
|
748
|
-
/**
|
|
749
|
-
* M7-SESSION-001 (H-1): persist a verified bilateral SEAL-INTERRUPTED
|
|
750
|
-
* commitment and transition the session to 'seal_interrupted_pending'.
|
|
751
|
-
*
|
|
752
|
-
* This is NOT a seal. It records that both parties produced and exchanged
|
|
753
|
-
* K_local-signed SEAL-INTERRUPTED leaves over the same {leafCount, merkleRoot}.
|
|
754
|
-
* The FROST threshold notarization is a separate, currently-unwired step (see
|
|
755
|
-
* daemon.ts handleSealInterruptedFlow H-1 note), which is precisely why the
|
|
756
|
-
* status is 'seal_interrupted_pending' and never 'sealed'.
|
|
757
|
-
*
|
|
758
|
-
* The status update is guarded so it only advances a session out of the
|
|
759
|
-
* 'interrupted' state — it will not overwrite a 'sealed' row.
|
|
760
|
-
*
|
|
761
|
-
* @returns true if the session row was advanced to seal_interrupted_pending.
|
|
762
|
-
*/
|
|
763
|
-
persistSealInterruptedCommitment(opts: {
|
|
764
|
-
agentName: string;
|
|
765
|
-
sessionId: string;
|
|
766
|
-
role: "initiator" | "responder";
|
|
767
|
-
ownLeaf: unknown;
|
|
768
|
-
counterpartyLeaf: unknown;
|
|
769
|
-
merkleRoot: string;
|
|
770
|
-
nonce: string;
|
|
771
|
-
}): boolean;
|
|
544
|
+
createSessionNode(...args: Parameters<SessionLifecycle["createSessionNode"]>): ReturnType<SessionLifecycle["createSessionNode"]>;
|
|
545
|
+
acceptSession(...args: Parameters<SessionLifecycle["acceptSession"]>): ReturnType<SessionLifecycle["acceptSession"]>;
|
|
546
|
+
destroySessionNode(...args: Parameters<SessionLifecycle["destroySessionNode"]>): ReturnType<SessionLifecycle["destroySessionNode"]>;
|
|
547
|
+
retireSessionNode(...args: Parameters<SessionLifecycle["retireSessionNode"]>): ReturnType<SessionLifecycle["retireSessionNode"]>;
|
|
548
|
+
markInterruptedWithDetails(...args: Parameters<SessionLifecycle["markInterruptedWithDetails"]>): ReturnType<SessionLifecycle["markInterruptedWithDetails"]>;
|
|
549
|
+
connectToCounterparty(...args: Parameters<SessionLifecycle["connectToCounterparty"]>): ReturnType<SessionLifecycle["connectToCounterparty"]>;
|
|
550
|
+
notifyCounterpartyAbandon(...args: Parameters<SessionLifecycle["notifyCounterpartyAbandon"]>): ReturnType<SessionLifecycle["notifyCounterpartyAbandon"]>;
|
|
551
|
+
retireOnCounterpartyAbandon(...args: Parameters<SessionLifecycle["retireOnCounterpartyAbandon"]>): ReturnType<SessionLifecycle["retireOnCounterpartyAbandon"]>;
|
|
552
|
+
reviveSessionNode(...args: Parameters<SessionLifecycle["reviveSessionNode"]>): ReturnType<SessionLifecycle["reviveSessionNode"]>;
|
|
553
|
+
reviveIfNeededForSend(...args: Parameters<SessionLifecycle["reviveIfNeededForSend"]>): ReturnType<SessionLifecycle["reviveIfNeededForSend"]>;
|
|
554
|
+
reviveIfNeededForRead(...args: Parameters<SessionLifecycle["reviveIfNeededForRead"]>): ReturnType<SessionLifecycle["reviveIfNeededForRead"]>;
|
|
555
|
+
abandonSession(...args: Parameters<SessionLifecycle["abandonSession"]>): ReturnType<SessionLifecycle["abandonSession"]>;
|
|
556
|
+
getSessionNodePeerId(...args: Parameters<SessionLifecycle["getSessionNodePeerId"]>): ReturnType<SessionLifecycle["getSessionNodePeerId"]>;
|
|
557
|
+
setDirectoryRelayEndpoints(...args: Parameters<SessionRelay["setDirectoryRelayEndpoints"]>): ReturnType<SessionRelay["setDirectoryRelayEndpoints"]>;
|
|
558
|
+
registerRelayStream(...args: Parameters<SessionRelay["registerRelayStream"]>): ReturnType<SessionRelay["registerRelayStream"]>;
|
|
559
|
+
isRelayCarvedOutInbound(...args: Parameters<SessionRelay["isRelayCarvedOutInbound"]>): ReturnType<SessionRelay["isRelayCarvedOutInbound"]>;
|
|
560
|
+
isRelayQuarantined(...args: Parameters<SessionRelay["isRelayQuarantined"]>): ReturnType<SessionRelay["isRelayQuarantined"]>;
|
|
561
|
+
quarantineRefusedInbound(...args: Parameters<SessionRelay["quarantineRefusedInbound"]>): ReturnType<SessionRelay["quarantineRefusedInbound"]>;
|
|
562
|
+
quarantineFrameMeta(...args: Parameters<SessionRelay["quarantineFrameMeta"]>): ReturnType<SessionRelay["quarantineFrameMeta"]>;
|
|
563
|
+
getRelayReceipts(...args: Parameters<SessionRelay["getRelayReceipts"]>): ReturnType<SessionRelay["getRelayReceipts"]>;
|
|
564
|
+
patchRelayClientForTest(...args: Parameters<SessionRelay["patchRelayClientForTest"]>): ReturnType<SessionRelay["patchRelayClientForTest"]>;
|
|
565
|
+
getSessionRelayForTest(...args: Parameters<SessionRelay["getSessionRelayForTest"]>): ReturnType<SessionRelay["getSessionRelayForTest"]>;
|
|
566
|
+
submitSealLeaf(...args: Parameters<SessionSeal["submitSealLeaf"]>): ReturnType<SessionSeal["submitSealLeaf"]>;
|
|
567
|
+
sealReadiness(...args: Parameters<SessionSeal["sealReadiness"]>): ReturnType<SessionSeal["sealReadiness"]>;
|
|
568
|
+
sealReadinessView(...args: Parameters<SessionSeal["sealReadinessView"]>): ReturnType<SessionSeal["sealReadinessView"]>;
|
|
569
|
+
verifyCertifiedRoot(...args: Parameters<SessionSeal["verifyCertifiedRoot"]>): ReturnType<SessionSeal["verifyCertifiedRoot"]>;
|
|
570
|
+
getSealCarry(...args: Parameters<SessionSeal["getSealCarry"]>): ReturnType<SessionSeal["getSealCarry"]>;
|
|
571
|
+
markSealed(...args: Parameters<SessionSeal["markSealed"]>): ReturnType<SessionSeal["markSealed"]>;
|
|
572
|
+
recordSealCertificateEnsuringRow(...args: Parameters<SessionSeal["recordSealCertificateEnsuringRow"]>): ReturnType<SessionSeal["recordSealCertificateEnsuringRow"]>;
|
|
573
|
+
persistSealInterruptedCommitment(...args: Parameters<SessionSeal["persistSealInterruptedCommitment"]>): ReturnType<SessionSeal["persistSealInterruptedCommitment"]>;
|
|
574
|
+
getSealUpgradeReadiness(...args: Parameters<SessionSeal["getSealUpgradeReadiness"]>): ReturnType<SessionSeal["getSealUpgradeReadiness"]>;
|
|
575
|
+
countersignedThroughSeqFromCarry(...args: Parameters<SessionSeal["countersignedThroughSeqFromCarry"]>): ReturnType<SessionSeal["countersignedThroughSeqFromCarry"]>;
|
|
576
|
+
setEnsureSealBroker(...args: Parameters<SessionSeal["setEnsureSealBroker"]>): ReturnType<SessionSeal["setEnsureSealBroker"]>;
|
|
577
|
+
ingestReceivedContent(...args: Parameters<SessionContentIngest["ingestReceivedContent"]>): ReturnType<SessionContentIngest["ingestReceivedContent"]>;
|
|
578
|
+
sendContent(...args: Parameters<SessionContentSender["sendContent"]>): ReturnType<SessionContentSender["sendContent"]>;
|
|
579
|
+
placeOwnLeaf(...args: Parameters<SessionContentSender["placeOwnLeaf"]>): ReturnType<SessionContentSender["placeOwnLeaf"]>;
|
|
580
|
+
takeReceivedContent(...args: Parameters<SessionContentIngest["takeReceivedContent"]>): ReturnType<SessionContentIngest["takeReceivedContent"]>;
|
|
581
|
+
recordWitnessedSequence(...args: Parameters<SessionContentIngest["recordWitnessedSequence"]>): ReturnType<SessionContentIngest["recordWitnessedSequence"]>;
|
|
582
|
+
getUndeliverableSeqs(...args: Parameters<SessionContentIngest["getUndeliverableSeqs"]>): ReturnType<SessionContentIngest["getUndeliverableSeqs"]>;
|
|
583
|
+
handleContentFrameForTest(...args: Parameters<SessionContentIngest["handleContentFrameForTest"]>): ReturnType<SessionContentIngest["handleContentFrameForTest"]>;
|
|
772
584
|
/** Loaded from SQLite on first access so it survives a restart (AC-007). NEVER null: an unknown session yields an EMPTY tree. */
|
|
773
585
|
getSessionTree(agentName: string, sessionId: string): SessionTree;
|
|
774
586
|
/** Current daemon-owned tree root for a session, as hex. */
|
|
@@ -783,319 +595,9 @@ export declare class SessionNodeManager {
|
|
|
783
595
|
leafIndex: number;
|
|
784
596
|
newRootHex: string;
|
|
785
597
|
};
|
|
786
|
-
/**
|
|
787
|
-
* SEAM 1b (dialer ⇄ session-node reconciliation): dial the counterparty THROUGH
|
|
788
|
-
* this session's OWN node, so the session node N_A holds the connection its content
|
|
789
|
-
* newStream actually rides. TRANSPORT-001's transport selector dialed on a separate
|
|
790
|
-
* (composition-root) node whose connection N_A could not use — the per-session node
|
|
791
|
-
* must be the dialer. Direct mode only here (the default content path, Part 4 D-a);
|
|
792
|
-
* relay-circuit + dcutr strategy via N_A is a later seam. Tries each addr in turn;
|
|
793
|
-
* succeeds on the first connection, returns a named failure if none connect.
|
|
794
|
-
*/
|
|
795
|
-
connectToCounterparty(agentName: string, sessionId: string, addrs: string[]): Promise<{
|
|
796
|
-
ok: true;
|
|
797
|
-
} | {
|
|
798
|
-
ok: false;
|
|
799
|
-
reason: string;
|
|
800
|
-
error: string;
|
|
801
|
-
}>;
|
|
802
|
-
/**
|
|
803
|
-
* Send content over the session node's direct P2P content stream.
|
|
804
|
-
* On a dead/missing stream this returns a NAMED, diagnosable failure — never a silent success
|
|
805
|
-
* (which desyncs the two sides). Do not swallow a send error here.
|
|
806
|
-
*
|
|
807
|
-
* SCOPE / findings #3 + #4 — what this send path does and does NOT do today:
|
|
808
|
-
* - #4: it delivers the content over the direct /cello/content/1.0.0 P2P
|
|
809
|
-
* stream only. It does NOT also submit a K_local-SIGNED content_hash leaf to
|
|
810
|
-
* the RELAY on /cello/relay/1.0.0 (EARS behavior #1). That relay hash-submit
|
|
811
|
-
* is MSG-001's scope; AC-001's "relay log shows a hash_submit" evidence is
|
|
812
|
-
* produced once MSG-001 lands.
|
|
813
|
-
* - #3: because there is no relay yet, the sequence number cello_send returns
|
|
814
|
-
* is the LOCAL leaf index, not a relay-assigned canonical global sequence.
|
|
815
|
-
* Each daemon appends leaves in its own LOCAL observation order, so two
|
|
816
|
-
* daemons' roots agree only under perfectly ping-ponged traffic. Canonical
|
|
817
|
-
* cross-process ordering (and thus AC-002 root agreement under concurrent
|
|
818
|
-
* bidirectional traffic) requires the relay-assigned sequence from MSG-001.
|
|
819
|
-
*/
|
|
820
|
-
sendContent(agentName: string, sessionId: string, content: Uint8Array, contentHash: Uint8Array,
|
|
821
|
-
/**
|
|
822
|
-
* Required alongside the two below — every production caller already passes one, and an optional
|
|
823
|
-
* parameter in front of a required one is what TypeScript refuses. Making it explicit costs
|
|
824
|
-
* nothing and removes the last place a positional argument can silently shift.
|
|
825
|
-
*/
|
|
826
|
-
correlationId: string | undefined,
|
|
827
|
-
/**
|
|
828
|
-
* The DOMAIN this content belongs to, as the relay and the directory will see it. Defaults to
|
|
829
|
-
* MESSAGE so `cello_send` is unchanged; the document path passes 0x04/0x05. Not cosmetic — the
|
|
830
|
-
* directory computes `final_message` and `answered` from the witnessed kind, and both of its
|
|
831
|
-
* document exclusions were dead while every document leaf arrived here as a message.
|
|
832
|
-
*
|
|
833
|
-
* ⚠️ ALSO REQUIRED NOW, and for the same reason as `contentHashAlg` below — this parameter is the
|
|
834
|
-
* precedent, not a bystander. It defaulted to MESSAGE, the document adapter in `daemon.ts`
|
|
835
|
-
* silently dropped it, and the wire was wrong for a whole release: *"0.0.145 shipped the fix
|
|
836
|
-
* everywhere except here."* A default that matches the common case makes the omission invisible
|
|
837
|
-
* at every call site and at typecheck. Every caller states its kind now.
|
|
838
|
-
*/
|
|
839
|
-
leafKind: number,
|
|
840
|
-
/**
|
|
841
|
-
* `DOD-M15-SEALWIRE-1` part B2b — the algorithm `contentHash` was produced under, taken from
|
|
842
|
-
* `contentHashForSession` by the caller that computed the hash.
|
|
843
|
-
*
|
|
844
|
-
* Passed rather than re-derived HERE, deliberately: re-deriving would ask "how would this
|
|
845
|
-
* session hash something now?", and the answer can differ from how THIS message was actually
|
|
846
|
-
* hashed. A hash and its label must travel together or the peer refuses a message nobody touched.
|
|
847
|
-
*
|
|
848
|
-
* ⚠️ REQUIRED, NOT DEFAULTED — review B2b-1 F4, and the default is what made four mutants
|
|
849
|
-
* unfalsifiable. It was `= CONTENT_HASH_ALGS.SHA256`, which equals the only value in play today,
|
|
850
|
-
* so DROPPING THE ARGUMENT AT ANY OF THE FIVE HOPS produced byte-identical output and the whole
|
|
851
|
-
* 2,800-test daemon suite stayed green. Measured at all four send sites individually.
|
|
852
|
-
*
|
|
853
|
-
* A default that equals the current value makes every threading edit invisible until the value
|
|
854
|
-
* changes — and the day it changes, the dropped argument mislabels the message and every peer
|
|
855
|
-
* refuses it as a tamper. Required makes a dropped argument a TYPECHECK failure instead of a
|
|
856
|
-
* test question nobody can answer.
|
|
857
|
-
*/
|
|
858
|
-
contentHashAlg: string): Promise<{
|
|
859
|
-
ok: true;
|
|
860
|
-
delivered: true;
|
|
861
|
-
sequenceNumber?: number;
|
|
862
|
-
authorship?: SentAuthorship;
|
|
863
|
-
} | {
|
|
864
|
-
ok: true;
|
|
865
|
-
delivered: false;
|
|
866
|
-
parked: true;
|
|
867
|
-
sequenceNumber?: number;
|
|
868
|
-
authorship?: SentAuthorship;
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* ⚠️ `authorship` RIDES THE FAILURE PATH TOO — review pass 2, and it is the same reasoning that
|
|
872
|
-
* already put `sequenceNumber` here.
|
|
873
|
-
*
|
|
874
|
-
* A DURABLY QUEUED message was witnessed and SIGNED before delivery was attempted; only the
|
|
875
|
-
* direct hand-off failed. Omitting the proof here meant every relay-degraded-but-alive send —
|
|
876
|
-
* the common case — wrote a transcript row indistinguishable from one the relay never saw, while
|
|
877
|
-
* the signature for it sat in scope and was discarded. The position survives a failed delivery
|
|
878
|
-
* for exactly this reason; so does the proof.
|
|
879
|
-
*/
|
|
880
|
-
| {
|
|
881
|
-
ok: false;
|
|
882
|
-
reason: string;
|
|
883
|
-
error: string;
|
|
884
|
-
durable: boolean;
|
|
885
|
-
cause?: string;
|
|
886
|
-
guidance?: string;
|
|
887
|
-
sequenceNumber?: number;
|
|
888
|
-
authorship?: SentAuthorship;
|
|
889
|
-
}>;
|
|
890
|
-
/**
|
|
891
|
-
* M7 DOD-SPINE-7: submit THIS party's SEAL ctrl leaf (0x02) to the relay witness.
|
|
892
|
-
* Structure: content_hash = SHA-256(0x02 || encodeSealPayload({session_id, final_root,
|
|
893
|
-
* close_timestamp, "PENDING"})), where final_root is the daemon's OWN tree root. Two
|
|
894
|
-
* distinct-sender SEAL leaves in the relay's log trigger the relay's #maybeProcessSeal
|
|
895
|
-
* → directory processSeal (rebuild + verify the signed chain) → FROST notarization →
|
|
896
|
-
* session_sealed. Requires an active relay client; the caller falls back to the
|
|
897
|
-
* directory-mediated path when this returns relay_unavailable.
|
|
898
|
-
*/
|
|
899
|
-
submitSealLeaf(agentName: string, sessionId: string, correlationId?: string): Promise<{
|
|
900
|
-
ok: true;
|
|
901
|
-
sequenceNumber: number;
|
|
902
|
-
reportedRootHex: string;
|
|
903
|
-
} | {
|
|
904
|
-
ok: false;
|
|
905
|
-
reason: string;
|
|
906
|
-
reportedRootHex?: string;
|
|
907
|
-
sequenceNumber?: number;
|
|
908
|
-
}>;
|
|
909
|
-
/**
|
|
910
|
-
* CELLO-M7-UPGRADE-001 (DOD-UP-1): readiness of a session for B to RATIFY a unilateral seal
|
|
911
|
-
* (the returning absent party). This is the SAME verifiability bar as the UP-2 auto-ack gate:
|
|
912
|
-
*
|
|
913
|
-
* - `known`: the session exists locally with its content (B has a transcript to ratify). After a
|
|
914
|
-
* restart B reloads it from SQLite, and autoRecoverForAgent re-pulls any parked content first.
|
|
915
|
-
* - `tampered`: the content cross-check flagged a content_hash mismatch (#contentDesynced) — B
|
|
916
|
-
* must NEVER ratify content it could not integrity-verify (the KERNEL refusal, AC-003).
|
|
917
|
-
*
|
|
918
|
-
* The directory separately verifies B's ack signature is genuine; B separately verifies the
|
|
919
|
-
* unilateral cert signature (R1 is authentic). NOTE: a full "B's frontier covers R1's tail"
|
|
920
|
-
* completeness check (the `desynced` reason) requires the deferred MSG-001-3b canonical-sequence
|
|
921
|
-
* reconciliation — same documented limitation as the UP-2 gate above.
|
|
922
|
-
*/
|
|
923
|
-
getSealUpgradeReadiness(agentName: string, sessionId: string): SealUpgradeReadiness;
|
|
924
|
-
ingestReceivedContent(agentName: string, sessionId: string, content: Uint8Array, contentHash: Uint8Array, correlationId?: string,
|
|
925
|
-
/**
|
|
926
|
-
* DOD-FRONTIER-STRAND-1 AC1: the relay-assigned canonical position for THIS message, taken from
|
|
927
|
-
* the verified ordering record by the caller. Passed EXPLICITLY rather than recovered from
|
|
928
|
-
* `#witnessedSeq`, because that map is keyed by content hash — so two byte-identical messages
|
|
929
|
-
* collapse in it before dedup is ever consulted, which is the whole defect. Absent when the
|
|
930
|
-
* session has no relay witness (relay-degraded): see the announced fallback below.
|
|
931
|
-
*/
|
|
932
|
-
canonicalSeqIn?: number,
|
|
933
|
-
/**
|
|
934
|
-
* DOD-M15-SEALWIRE-1 part B1 — the algorithm the SENDER named on the frame, verbatim.
|
|
935
|
-
*
|
|
936
|
-
* `undefined` means the frame carried no name, which is a peer that predates the field and is
|
|
937
|
-
* the one case we may safely assume `sha256` for. It is threaded through rather than read off
|
|
938
|
-
* the session, because whether a hash is salted is a fact about the FRAME and its sender, never
|
|
939
|
-
* about what this side happens to hold.
|
|
940
|
-
*/
|
|
941
|
-
contentHashAlgIn?: string | null,
|
|
942
|
-
/**
|
|
943
|
-
* DOD-M15-SEALWIRE-1 bullet 5: the VERIFIED authorship proof for this message, when the caller
|
|
944
|
-
* has one. The caller is the only place that has it — `#verifyAuthorshipClaim` verifies the
|
|
945
|
-
* signature the frame carries beside the sender's own signed bytes, against the key inside those
|
|
946
|
-
* bytes, and matches the signer to this session's counterparty. That result reaches here or
|
|
947
|
-
* nowhere.
|
|
948
|
-
*
|
|
949
|
-
* ⚠️ IT USED TO NAME `#recordFrameOrdering`, and that was accurate until
|
|
950
|
-
* `DOD-M15-AUTHORSHIP-ABSENT-1`: the signature arrived only inside the RELAY's Structure 2, so
|
|
951
|
-
* checking authorship needed a relay record. It does not now, and the old name sends a reader to
|
|
952
|
-
* a method that answers a different question. Rewritten, not deleted — that dependence is the
|
|
953
|
-
* defect the unit removed.
|
|
954
|
-
*
|
|
955
|
-
* Optional, because the PARK route ingests without it: recovered mail proves its sender by the
|
|
956
|
-
* mailbox envelope instead. The row records which it was, so absence is never silent.
|
|
957
|
-
*/
|
|
958
|
-
verifiedAuthorship?: {
|
|
959
|
-
senderPubkey: Uint8Array;
|
|
960
|
-
senderSig: Uint8Array;
|
|
961
|
-
},
|
|
962
|
-
/**
|
|
963
|
-
* 024-ORPHANTRIAGE — the key whose signature VERIFIED on a frame we could not tie to a session.
|
|
964
|
-
*
|
|
965
|
-
* Read by the orphan branch below and NOWHERE ELSE. It exists because the daemon establishes,
|
|
966
|
-
* cryptographically, that the sender holds a private key — and then discarded that the instant
|
|
967
|
-
* the session lookup came back empty, leaving the operator advised to go and make contact with
|
|
968
|
-
* whoever sent a message for a conversation that does not exist.
|
|
969
|
-
*
|
|
970
|
-
* Absent on the park-recovery caller, which cannot reach the orphan branch at all:
|
|
971
|
-
* `authenticateParkedEntry` refuses `counterparty_unknown` from the same missing record first.
|
|
972
|
-
*/
|
|
973
|
-
verifiedSignerUnmatched?: Uint8Array): Promise<{
|
|
974
|
-
ok: true;
|
|
975
|
-
leafIndex: number;
|
|
976
|
-
sequenceNumber: number;
|
|
977
|
-
held?: boolean;
|
|
978
|
-
appendedCount?: number;
|
|
979
|
-
screenedOut?: boolean;
|
|
980
|
-
} | {
|
|
981
|
-
ok: false;
|
|
982
|
-
reason: string;
|
|
983
|
-
}>;
|
|
984
|
-
/**
|
|
985
|
-
* DOD-MSG-4: record the relay-witnessed canonical sequence for a content hash. The relay is the
|
|
986
|
-
* ordering authority (Structure 2): it assigns each message a sequence from its hash and delivers
|
|
987
|
-
* B the (content_hash -> sequence) binding via leaf_deliver. The strict-in-order gate orders the
|
|
988
|
-
* transcript by THIS — never a sender-stamped field. Also advances the per-session high-water mark
|
|
989
|
-
* (the largest witnessed sequence) reserved for the future catch-up-before-live increment. Idempotent.
|
|
990
|
-
*/
|
|
991
|
-
recordWitnessedSequence(agentName: string, sessionId: string, contentHashHex: string, sequenceNumber: number): void;
|
|
992
598
|
/** DOD-M12B-LEAF-TRIGGERS-FETCH-1 test seams. */
|
|
993
599
|
setLeafFetchGraceMsForTest(ms: number): void;
|
|
994
600
|
markContentPresentForTest(agentName: string, sessionId: string, contentHashHex: string): void;
|
|
995
|
-
/**
|
|
996
|
-
* DOD-MSG-4: the relay's high-water canonical sequence for this session (largest witnessed leaf),
|
|
997
|
-
* or -1 if none. The relay is the ordering authority, so this is the outside view of how far the
|
|
998
|
-
* session has actually progressed — which is why it is the right input to a catch-up-before-live
|
|
999
|
-
* gate. Consumed by `sealReadiness` (M12-P14) for REPORTING only: the missing-leaf decision is made
|
|
1000
|
-
* from `#witnessedSeq`, because this counts the relay's sequence space (which includes ctrl leaves)
|
|
1001
|
-
* and the tree does not. Maintained by `recordWitnessedSequence`.
|
|
1002
|
-
*/
|
|
1003
|
-
/**
|
|
1004
|
-
* DOD-COATTEND-1 (review F2): leaf sequences whose plaintext failed to reach the transcript and
|
|
1005
|
-
* are therefore undeliverable. Empty is the overwhelmingly normal case.
|
|
1006
|
-
*/
|
|
1007
|
-
getUndeliverableSeqs(agentName: string, sessionId: string): readonly number[];
|
|
1008
|
-
/**
|
|
1009
|
-
* M12-P14: is this side's chain COMPLETE enough to be sealed?
|
|
1010
|
-
*
|
|
1011
|
-
* A seal is a bilateral signature over the same conversation, so a side that is missing a leaf
|
|
1012
|
-
* cannot produce a signable one — the counterparty compares frontiers and refuses with
|
|
1013
|
-
* `leaf_count_mismatch`. That refusal is correct and it is also terminal: there is no backfill
|
|
1014
|
-
* request in the protocol, so the only exit is a force-abandon, which yields NO notarized receipt.
|
|
1015
|
-
* Measured 2026-08-05 on two sessions that died exactly this way (initiator 2 leaves, responder 3).
|
|
1016
|
-
*
|
|
1017
|
-
* The cheap prevention is to notice BEFORE asking. Two local signals already exist and, until now,
|
|
1018
|
-
* nothing read either of them at close time:
|
|
1019
|
-
* - `#highWaterSeq` — the largest canonical sequence the RELAY has witnessed for this session.
|
|
1020
|
-
* The relay is the ordering authority, so a high-water above our own frontier is proof that a
|
|
1021
|
-
* leaf exists which we have not appended. (Its own doc comment called it "reserved … NOT yet
|
|
1022
|
-
* consumed by the gate" — this is that consumer.)
|
|
1023
|
-
* - `#heldContent` — content we HAVE received and verified but cannot append because it sits
|
|
1024
|
-
* behind a gap. Holding content and sealing anyway would seal a chain we know is short.
|
|
1025
|
-
*
|
|
1026
|
-
* Deliberately NOT a network call: it must work when the counterparty is unreachable, which is
|
|
1027
|
-
* the whole situation a seal-interrupted exists for.
|
|
1028
|
-
*
|
|
1029
|
-
* KNOWN LIMIT, stated rather than hidden: both maps are in-memory and cleared on teardown, so
|
|
1030
|
-
* after a daemon restart this returns ready for a session whose gap predates the restart — which
|
|
1031
|
-
* is the shape of the 2026-08-05 incident itself. Closing that needs the mailbox drained (or the
|
|
1032
|
-
* high-water persisted) before the check; tracked with M12-P14, not claimed here.
|
|
1033
|
-
*/
|
|
1034
|
-
sealReadiness(agentName: string, sessionId: string): {
|
|
1035
|
-
ready: boolean;
|
|
1036
|
-
treeSize: number;
|
|
1037
|
-
highWaterSeq: number;
|
|
1038
|
-
heldCount: number;
|
|
1039
|
-
missingLeaves: number;
|
|
1040
|
-
/** DOD-M12B-INDEX-1: of `heldCount`, how many are THIS side's own sends versus the
|
|
1041
|
-
* counterparty's. They block a seal identically and mean completely different things. */
|
|
1042
|
-
heldOwn: number;
|
|
1043
|
-
heldReceived: number;
|
|
1044
|
-
/**
|
|
1045
|
-
* DOD-M15-DIVERGE-1 — this tree and the relay's counter have PROVABLY parted.
|
|
1046
|
-
*
|
|
1047
|
-
* The other two counters both measure the same direction: positions the relay committed that
|
|
1048
|
-
* this tree has not appended. This is the OPPOSITE direction — this tree holds a leaf at a
|
|
1049
|
-
* position the relay assigned to something else — and it is the direction an injected or forged
|
|
1050
|
-
* leaf appears in. Without it `ready` was asymmetric, and a diverged session read as perfectly
|
|
1051
|
-
* sealable right up until the counterparty answered `leaf_count_mismatch`, which is terminal.
|
|
1052
|
-
*/
|
|
1053
|
-
diverged: boolean;
|
|
1054
|
-
};
|
|
1055
|
-
/**
|
|
1056
|
-
* DOD-M12B-SEAL-STUCK-1 — the operator-facing answer to "can this session be closed?".
|
|
1057
|
-
*
|
|
1058
|
-
* THREE STATES, because there are three answers. `sealReadiness` above returns a boolean plus raw
|
|
1059
|
-
* counters, and both of its counters are easy to misread on a surface:
|
|
1060
|
-
*
|
|
1061
|
-
* - `missingLeaves` is `#witnessedSeq.size`, which is every position the relay witnessed that
|
|
1062
|
-
* this tree has not appended — and a HELD frame keeps its witness entry. So it INCLUDES the
|
|
1063
|
-
* held ones. Reporting it beside `heldCount` counts the same message twice and labels one copy
|
|
1064
|
-
* "never received" when it is sitting on our own disk. Split here into what each actually is.
|
|
1065
|
-
* - Neither counter survives a restart on its own: `#witnessedSeq` is memory-only. Held content
|
|
1066
|
-
* is durable since DOD-M12B-STRAND-1, but a position the relay witnessed for content that
|
|
1067
|
-
* never arrived leaves no trace. So for a session carrying leaves this process did not watch
|
|
1068
|
-
* arrive, "clean" is unknowable — and saying `ready` there invites a close that gets
|
|
1069
|
-
* `leaf_count_mismatch` back, which is terminal and costs the receipt for good.
|
|
1070
|
-
*/
|
|
1071
|
-
sealReadinessView(agentName: string, sessionId: string): SealReadinessView;
|
|
1072
|
-
/**
|
|
1073
|
-
* DOD-M12B-ABANDON-NOTIFY-1 — tell the counterparty we have hung up. Best effort, never blocking.
|
|
1074
|
-
*
|
|
1075
|
-
* A force-abandon marks the session terminal HERE and did nothing else, so the other side kept
|
|
1076
|
-
* its half live, kept retrying delivery into it, and kept trying to re-establish — forever,
|
|
1077
|
-
* because nothing would ever answer. That is what produced the 2026-08-17 notification storm:
|
|
1078
|
-
* surviving halves calling continuously while the operator saw connection requests from agents
|
|
1079
|
-
* nobody was driving.
|
|
1080
|
-
*
|
|
1081
|
-
* BEST EFFORT, and every caller must treat it that way. A peer that is offline cannot be told, so
|
|
1082
|
-
* this is an improvement on silence rather than a guarantee — and it must never delay or fail the
|
|
1083
|
-
* abandon, which is the operator's escape hatch out of a session that can never seal.
|
|
1084
|
-
*/
|
|
1085
|
-
notifyCounterpartyAbandon(agentName: string, sessionId: string, correlationId?: string): Promise<AbandonNoticeResult>;
|
|
1086
|
-
/**
|
|
1087
|
-
* DOD-M12B-ABANDON-NOTIFY-1 — the receiving half: our counterparty has abandoned, so retire.
|
|
1088
|
-
*
|
|
1089
|
-
* RETIRING IS NOT DELETING. The counterparty walking away forfeits the notarized receipt; it must
|
|
1090
|
-
* not also cost the operator the record of what was actually said. The transcript and the tree
|
|
1091
|
-
* stay exactly as they are.
|
|
1092
|
-
*
|
|
1093
|
-
* Only an `active` or `interrupted` session moves. A SEALED session has a notarized receipt and
|
|
1094
|
-
* must never be turned into an abandoned one by a late or duplicated notice — that would destroy
|
|
1095
|
-
* the artifact this protocol exists to produce. An unknown session is refused rather than
|
|
1096
|
-
* created: an authenticated stream proves who is speaking, not that a session exists.
|
|
1097
|
-
*/
|
|
1098
|
-
retireOnCounterpartyAbandon(agentName: string, sessionId: string, correlationId?: string): Promise<boolean>;
|
|
1099
601
|
/**
|
|
1100
602
|
* DOD-CAP-SELF-HEAL-1 — the numbers behind a cap refusal, for the OPERATOR'S alarm only.
|
|
1101
603
|
*
|
|
@@ -1111,79 +613,12 @@ export declare class SessionNodeManager {
|
|
|
1111
613
|
mustClear: number;
|
|
1112
614
|
blocked: boolean;
|
|
1113
615
|
};
|
|
1114
|
-
/**
|
|
1115
|
-
* DOD-M12B-INDEX-1 — commit THIS agent's own leaf at the position the relay assigned it.
|
|
1116
|
-
*
|
|
1117
|
-
* The receiver has always enforced "leaf index === canonical position": content witnessed ahead
|
|
1118
|
-
* of the next expected leaf is held, not appended out of order. The sender never did. It had the
|
|
1119
|
-
* position in hand — the relay answers about 4 ms before the append — and called a push-only
|
|
1120
|
-
* append that puts the leaf at the tail whatever the tail happens to be. While its own tree has
|
|
1121
|
-
* no gap the two agree and nothing shows; the first gap puts its leaf at someone else's index,
|
|
1122
|
-
* parts its root from the counterparty's, and the next seal gets `leaf_count_mismatch`, which is
|
|
1123
|
-
* terminal.
|
|
1124
|
-
*
|
|
1125
|
-
* DELIVERY IS NOT DEFERRED BY THIS. The caller has already put the bytes on the wire; only the
|
|
1126
|
-
* leaf waits for its slot, exactly as a received message does. Holding our own send is only
|
|
1127
|
-
* affordable because holds are durable (DOD-M12B-STRAND-1) — before that it would have risked
|
|
1128
|
-
* losing the message outright.
|
|
1129
|
-
*
|
|
1130
|
-
* `assignedSeq` absent means no ordering authority answered. That is the documented degradation
|
|
1131
|
-
* and it appends in arrival order as before: with no position there is no discipline to enforce,
|
|
1132
|
-
* and refusing would take messaging down whenever the relay is unreachable.
|
|
1133
|
-
*/
|
|
1134
|
-
placeOwnLeaf(agentName: string, sessionId: string, contentHashHex: string, sentBytes: Uint8Array, assignedSeq: number | undefined, correlationId: string | undefined,
|
|
1135
|
-
/**
|
|
1136
|
-
* ⚠️ NO DEFAULT, for the same reason `authorship` has none.
|
|
1137
|
-
*
|
|
1138
|
-
* `kind = "msg"` meant a caller writing a `doc` or a `ctrl` leaf got a `msg` leaf by saying
|
|
1139
|
-
* nothing, and the tree recorded a leaf kind the author never chose. TypeScript also forbids a
|
|
1140
|
-
* required parameter after a defaulted one, so leaving the default here would have forced
|
|
1141
|
-
* `authorship` back to optional — which is the defect above. Every one of the seven call sites
|
|
1142
|
-
* already passed a kind or wanted "msg"; making it explicit cost nothing and removes a second
|
|
1143
|
-
* silent answer from the same signature.
|
|
1144
|
-
*/
|
|
1145
|
-
kind: WritableSessionTreeLeafKind,
|
|
1146
|
-
/**
|
|
1147
|
-
* DOD-M15-SEALWIRE-1 bullet 5 — the proof for THIS send, so a held row keeps it.
|
|
1148
|
-
*
|
|
1149
|
-
* ⚠️ REQUIRED, AND `undefined` IS A VALID ANSWER — the two are not the same thing.
|
|
1150
|
-
*
|
|
1151
|
-
* This was `authorship?:` for exactly one review cycle, and in that cycle THREE of the seven
|
|
1152
|
-
* call sites omitted it: `daemon.ts` 1440, 1671, 1685 — the away-reply path, which is the
|
|
1153
|
-
* highest-traffic sent-writer in the daemon and the one with no human watching it. All three
|
|
1154
|
-
* had the proof **already in a local variable one line below**, handed to
|
|
1155
|
-
* `recordTranscriptMessage` and not to this method. Nothing went red, because an optional
|
|
1156
|
-
* parameter's whole behaviour on omission is to look deliberate.
|
|
1157
|
-
*
|
|
1158
|
-
* An unwitnessed send genuinely has no proof, so absence must stay expressible. Requiring the
|
|
1159
|
-
* parameter keeps that while making the caller SAY it: omission is now a type error, and
|
|
1160
|
-
* `undefined` is a claim the author made rather than one the signature made for them.
|
|
1161
|
-
*/
|
|
1162
|
-
authorship: SentAuthorship | undefined): {
|
|
1163
|
-
placed: true;
|
|
1164
|
-
leafIndex: number;
|
|
1165
|
-
diverged?: true;
|
|
1166
|
-
unwitnessed?: true;
|
|
1167
|
-
} | {
|
|
1168
|
-
placed: false;
|
|
1169
|
-
heldAt: number;
|
|
1170
|
-
};
|
|
1171
|
-
/** DAEMON-004: pop the oldest verified received content for cello_receive. */
|
|
1172
|
-
takeReceivedContent(agentName: string, sessionId: string): ReceivedContentEntry | null;
|
|
1173
616
|
/** DOD-AWAY-WRAP-1: peek at the hex of the most-recently buffered (last) received message without
|
|
1174
617
|
* consuming it. Used by sendAwayResponse to detect [[WRAP]]-signalled messages and skip the away
|
|
1175
|
-
* reply. Returning the last entry (not the first) is intentional —
|
|
618
|
+
* reply. Returning the last entry (not the first) is intentional — the ingest file's
|
|
619
|
+
* appendVerifiedContent always
|
|
1176
620
|
* pushes to the tail, so the tail is the message that just triggered onContentArrived. */
|
|
1177
621
|
peekLatestReceivedContentHex(agentName: string, sessionId: string): string | null;
|
|
1178
|
-
/**
|
|
1179
|
-
* TEST-ONLY (M8C-INBOX-1 reviewer F1): buffer a received message + persist its transcript row,
|
|
1180
|
-
* exactly as the real inbound path (#appendVerifiedContent) does, WITHOUT standing up a session
|
|
1181
|
-
* tree — so a test can drive a live cello_receive that advances the read watermark (the N3
|
|
1182
|
-
* "delivery marks read" coupling). Only reachable via the CELLO_ENV=test IPC hook.
|
|
1183
|
-
*/
|
|
1184
|
-
/** CELLO_ENV=test only: patch a relay client and session-id bytes onto an existing active node entry
|
|
1185
|
-
* so submitSealLeaf succeeds without a real relay handshake (used by the oneshot relay-path test). */
|
|
1186
|
-
patchRelayClientForTest(agentName: string, sessionId: string, relayClient: AgentRelayClient, relaySessionIdBytes: Uint8Array): void;
|
|
1187
622
|
pushReceivedContentForTest(agentName: string, sessionId: string, seq: number, content: string, senderPubkey: string): void;
|
|
1188
623
|
/**
|
|
1189
624
|
* F1-b: the terminal answer for a session that sealed while a blocking receive was (or could be)
|
|
@@ -1235,15 +670,6 @@ export declare class SessionNodeManager {
|
|
|
1235
670
|
observeInboundContentFramesForTest(cb: (frame: Record<string, unknown>) => void): void;
|
|
1236
671
|
/** Injected by the daemon once its per-agent key providers exist. See `#keyProviderResolver`. */
|
|
1237
672
|
setKeyProviderResolver(resolver: (agentName: string) => KeyProvider | undefined): void;
|
|
1238
|
-
/**
|
|
1239
|
-
* Test seam: force this session's own salt half, so the LOCAL-defect path is reachable.
|
|
1240
|
-
*
|
|
1241
|
-
* `generateSaltContribution` cannot produce a degenerate half, which is the point of it — so the
|
|
1242
|
-
* only way to exercise "our own random source is broken" end-to-end is to stand in for the broken
|
|
1243
|
-
* source. Named `…ForTest` like every other seam in this file, and it writes the same map
|
|
1244
|
-
* production writes rather than a parallel one, so a test cannot pass against state the daemon
|
|
1245
|
-
* never reads.
|
|
1246
|
-
*/
|
|
1247
673
|
/**
|
|
1248
674
|
* Test seam: run the auto-acknowledge gate, exactly as the counterparty's SEAL ctrl leaf does.
|
|
1249
675
|
*
|
|
@@ -1255,7 +681,7 @@ export declare class SessionNodeManager {
|
|
|
1255
681
|
* `content_verification_unavailable` branch had no coverage anywhere in the repo and two mutants
|
|
1256
682
|
* on it survived the full gate.
|
|
1257
683
|
*
|
|
1258
|
-
* It calls the REAL private
|
|
684
|
+
* It calls the REAL method (public on `SessionSeal` since the split, private before it) rather than reproducing its logic, so a test cannot pass
|
|
1259
685
|
* against a decision production does not make.
|
|
1260
686
|
*/
|
|
1261
687
|
runAutoAcknowledgeGateForTest(agentName: string, sessionId: string, correlationId?: string): void;
|
|
@@ -1314,22 +740,6 @@ export declare class SessionNodeManager {
|
|
|
1314
740
|
salt: null;
|
|
1315
741
|
reason: "none" | "unreadable";
|
|
1316
742
|
};
|
|
1317
|
-
/**
|
|
1318
|
-
* M7-SESSION-001 AC-004/AC-005: Register a relay stream for an active session.
|
|
1319
|
-
* Starts a background reader that watches for session_interrupted frames and
|
|
1320
|
-
* stream close events. Both detection paths call markInterruptedWithDetails().
|
|
1321
|
-
*
|
|
1322
|
-
* The reader runs for the lifetime of the relay stream. If the stream closes
|
|
1323
|
-
* without delivering a session_interrupted frame (AC-005 / 'stream_close' path),
|
|
1324
|
-
* the session is still marked interrupted.
|
|
1325
|
-
*
|
|
1326
|
-
* @param sessionId The hex session ID
|
|
1327
|
-
* @param stream The relay stream to monitor
|
|
1328
|
-
* @param messageCount Number of message leaves at the time of registration
|
|
1329
|
-
* (used as the count at interruption — best effort since exact count at frame
|
|
1330
|
-
* receipt may differ, but this is the value available at stream setup time)
|
|
1331
|
-
*/
|
|
1332
|
-
registerRelayStream(agentName: string, sessionId: string, stream: Stream, messageCount?: number): void;
|
|
1333
743
|
/**
|
|
1334
744
|
* DOD-M15-RELAYSLOTS-1: accept the directory's online token for an agent. Called on every
|
|
1335
745
|
* signaling connect and reconnect, which is what keeps it fresh.
|
|
@@ -1343,109 +753,6 @@ export declare class SessionNodeManager {
|
|
|
1343
753
|
* directory does not recognise.
|
|
1344
754
|
*/
|
|
1345
755
|
getDirectoryOnlineToken(agentName: string): Uint8Array | undefined;
|
|
1346
|
-
/**
|
|
1347
|
-
* DOD-NAT-REACHABILITY-1 (Phase 2): accept the directory's relay-pool endpoints
|
|
1348
|
-
* for an agent (arrives with signaling_auth_ok, i.e. on every connect AND every
|
|
1349
|
-
* reconnect). If the agent's standing receiver is up but holds NO reservation —
|
|
1350
|
-
* the agent-online ensure raced ahead of auth_ok, or every relay was down at
|
|
1351
|
-
* create time — rebuild it now so the agent becomes dialable without waiting
|
|
1352
|
-
* for a session handoff that (being unreachable) would never come.
|
|
1353
|
-
*/
|
|
1354
|
-
setDirectoryRelayEndpoints(agentName: string, endpoints: Array<{
|
|
1355
|
-
relayPeerId: string;
|
|
1356
|
-
relayAddrs: string[];
|
|
1357
|
-
}>): void;
|
|
1358
|
-
/**
|
|
1359
|
-
* Is this agent currently skipping this relay? The observable half of the failover decision — a
|
|
1360
|
-
* test that asserts only on the classifier's boolean proves nothing about what the daemon does.
|
|
1361
|
-
*/
|
|
1362
|
-
isRelayQuarantined(agentName: string, relayPeerId: string): boolean;
|
|
1363
|
-
/**
|
|
1364
|
-
* DOD-M12B-SESSION-SEED-1 — bring an interrupted session back on the peer id it already has.
|
|
1365
|
-
*
|
|
1366
|
-
* THE DEFECT THIS CLOSES. `markInterruptedWithDetails` and `destroySessionNode` stop the node and
|
|
1367
|
-
* delete it from `#activeNodes`, and until now **nothing anywhere recreated one**. A laptop-close
|
|
1368
|
-
* session stayed stuck even though both processes were alive and both keypairs were still in
|
|
1369
|
-
* memory — the trace on 2026-08-17 found no missing transport capability, just a missing edge.
|
|
1370
|
-
*
|
|
1371
|
-
* TWO THINGS HAVE TO HAPPEN, and doing only one leaves the session exactly as stuck:
|
|
1372
|
-
* 1. the NODE comes back, at the same peer id, or the counterparty can never dial us again;
|
|
1373
|
-
* 2. the STATUS comes back to `active`, or every send still refuses with `session_not_active`.
|
|
1374
|
-
*
|
|
1375
|
-
* **DEMAND-DRIVEN ONLY.** Nothing calls this on a timer. That is the `REDIAL-1` discipline and it
|
|
1376
|
-
* is also Andre's tenet — a background rebuilder would hold a dialable endpoint open for a session
|
|
1377
|
-
* nobody is using, which is the "open connection a malicious agent can farm for" in as many words.
|
|
1378
|
-
*
|
|
1379
|
-
* **TERMINAL IS TERMINAL.** A sealed or abandoned session had its seed zeroed in the same step
|
|
1380
|
-
* that wrote its status, so there is nothing to come back on. This refuses by name rather than
|
|
1381
|
-
* minting a fresh identity — a revival that quietly mints would hand one session a second peer id
|
|
1382
|
-
* and break the invariant while appearing to work.
|
|
1383
|
-
*
|
|
1384
|
-
* Idempotent: a session that already has a live node returns ok without building a second one.
|
|
1385
|
-
*/
|
|
1386
|
-
reviveSessionNode(agentName: string, sessionId: string): Promise<{
|
|
1387
|
-
ok: true;
|
|
1388
|
-
peerId: string;
|
|
1389
|
-
} | {
|
|
1390
|
-
ok: false;
|
|
1391
|
-
reason: string;
|
|
1392
|
-
guidance?: string;
|
|
1393
|
-
}>;
|
|
1394
|
-
/**
|
|
1395
|
-
* DOD-M12B-SESSION-SEED-1 — the DEMAND edge: a send on an interrupted session revives it.
|
|
1396
|
-
*
|
|
1397
|
-
* One of TWO production callers of `reviveSessionNode` — `reviveIfNeededForRead` is the other —
|
|
1398
|
-
* and both are deliberately demand paths rather than timers. The `REDIAL-1` discipline and Andre's tenet say the same thing from two
|
|
1399
|
-
* directions: nothing may re-open on its own, because a background rebuilder would hold a dialable
|
|
1400
|
-
* endpoint open for a session nobody is using — the *"open connection a malicious agent can farm
|
|
1401
|
-
* for"*. The operator sending is the demand; there is no other trigger.
|
|
1402
|
-
*
|
|
1403
|
-
* A no-op for the normal case. An `active` session with a live node returns immediately without
|
|
1404
|
-
* touching it — this sits on the hot path of every send, and replacing a healthy node would be
|
|
1405
|
-
* churn that changes the peer id for no reason.
|
|
1406
|
-
*/
|
|
1407
|
-
reviveIfNeededForSend(agentName: string, sessionId: string): Promise<{
|
|
1408
|
-
ok: true;
|
|
1409
|
-
} | {
|
|
1410
|
-
ok: false;
|
|
1411
|
-
reason: string;
|
|
1412
|
-
guidance?: string;
|
|
1413
|
-
}>;
|
|
1414
|
-
/**
|
|
1415
|
-
* DOD-M12B-SESSION-SEED-1 (case B) — the INBOUND half of the demand edge.
|
|
1416
|
-
*
|
|
1417
|
-
* `reviveIfNeededForSend` covers the operator waking first. Case B's triggers are symmetric — a
|
|
1418
|
-
* wifi hop, a relay restart, a directory node cycling — so half the time the COUNTERPARTY wakes
|
|
1419
|
-
* first. They send; we have no node yet, because revival is demand-driven and we have demanded
|
|
1420
|
-
* nothing. Their content parks at the relay, which is the backstop working as designed.
|
|
1421
|
-
*
|
|
1422
|
-
* Then the operator comes back and READS, and until now that told them nothing: the receive
|
|
1423
|
-
* handler reads the transcript and never gates on status, so it happily reports what is already
|
|
1424
|
-
* stored while messages sit parked, waiting for a node that will not exist until the operator
|
|
1425
|
-
* happens to SEND. An operator who only reads was stuck forever with a surface that looked fine.
|
|
1426
|
-
*
|
|
1427
|
-
* **WHY A READ MAY TRIGGER THIS AND AN INBOUND DIAL MAY NOT.** Andre's tenet is about what a
|
|
1428
|
-
* REMOTE party can cause: *"an open connection that a malicious agent can farm for."* Reviving
|
|
1429
|
-
* because a peer dialled us would hand that lever straight to the peer — a stranger could keep our
|
|
1430
|
-
* endpoints open indefinitely by poking dead sessions. A read is the OPERATOR asking, on their own
|
|
1431
|
-
* machine, for their own session: the same class of demand as a send, and the class the tenet
|
|
1432
|
-
* allows. That distinction is the whole reason this is a separate entry point rather than a
|
|
1433
|
-
* revival triggered from the inbound handler.
|
|
1434
|
-
*/
|
|
1435
|
-
reviveIfNeededForRead(agentName: string, sessionId: string): Promise<{
|
|
1436
|
-
ok: true;
|
|
1437
|
-
} | {
|
|
1438
|
-
ok: false;
|
|
1439
|
-
reason: string;
|
|
1440
|
-
guidance?: string;
|
|
1441
|
-
}>;
|
|
1442
|
-
/** DOD-M12B-REVIVE-PARK-1 test seam: the relay the live entry will park to. Not otherwise
|
|
1443
|
-
* observable — `#activeNodes` is private and the park's own refusal is silent about which of its
|
|
1444
|
-
* four preconditions was missing. */
|
|
1445
|
-
getSessionRelayForTest(agentName: string, sessionId: string): {
|
|
1446
|
-
relayPeerId?: string;
|
|
1447
|
-
relayAddrs?: string[];
|
|
1448
|
-
} | null;
|
|
1449
756
|
/** DOD-M12B-SESSION-SEED-1 test seams: the counterparty addresses a re-dial depends on. Not
|
|
1450
757
|
* otherwise observable — they are set from a signed relay assignment that a fixture cannot mint. */
|
|
1451
758
|
setCounterpartyAddrsForTest(agentName: string, sessionId: string, addrs: string[]): void;
|
|
@@ -1493,11 +800,5 @@ export declare class SessionNodeManager {
|
|
|
1493
800
|
* locked out by the acceptance bound forever.
|
|
1494
801
|
*/
|
|
1495
802
|
countEstablishedReceived(agentName: string, sessionId: string): number;
|
|
1496
|
-
/** CC-5/F21: unilaterally mark a session locally-terminal ("abandoned") — retire its live node and
|
|
1497
|
-
* set the DB status, with NO bilateral seal (a dead half-open handshake has nothing to notarize).
|
|
1498
|
-
* Used by cello_close_session { force } and the dead-half-open reaper. Idempotent: a missing/already-
|
|
1499
|
-
* abandoned session is a no-op. Resolves true iff the status flip was actually written (CC-10
|
|
1500
|
-
* reviewer LOW: callers must not report a reap as successful when the write failed). */
|
|
1501
|
-
abandonSession(agentName: string, sessionId: string): Promise<boolean>;
|
|
1502
803
|
}
|
|
1503
804
|
//# sourceMappingURL=session-node-manager.d.ts.map
|