@cello-protocol/daemon 0.0.181 → 0.0.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-id-migration.d.ts +9 -0
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +73 -1
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/agent-selection.d.ts +73 -0
- package/dist/agent-selection.d.ts.map +1 -0
- package/dist/agent-selection.js +80 -0
- package/dist/agent-selection.js.map +1 -0
- package/dist/assignment-verify.d.ts +62 -0
- package/dist/assignment-verify.d.ts.map +1 -0
- package/dist/assignment-verify.js +185 -0
- package/dist/assignment-verify.js.map +1 -0
- package/dist/backup-restore.d.ts +102 -0
- package/dist/backup-restore.d.ts.map +1 -0
- package/dist/backup-restore.js +340 -0
- package/dist/backup-restore.js.map +1 -0
- package/dist/bin/cello-daemon.js +91 -49
- package/dist/bin/cello-daemon.js.map +1 -1
- package/dist/close-commitment.d.ts +67 -0
- package/dist/close-commitment.d.ts.map +1 -0
- package/dist/close-commitment.js +78 -0
- package/dist/close-commitment.js.map +1 -0
- package/dist/close-session-handler.d.ts +20 -0
- package/dist/close-session-handler.d.ts.map +1 -1
- package/dist/close-session-handler.js +351 -58
- package/dist/close-session-handler.js.map +1 -1
- package/dist/consortium-bootstrap.d.ts +2 -0
- package/dist/consortium-bootstrap.d.ts.map +1 -1
- package/dist/consortium-bootstrap.js +65 -7
- package/dist/consortium-bootstrap.js.map +1 -1
- package/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +141 -10
- package/dist/content-park.js.map +1 -1
- package/dist/daemon.d.ts +13 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +866 -122
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-session-suspects.d.ts.map +1 -1
- package/dist/delivery-session-suspects.js +5 -0
- package/dist/delivery-session-suspects.js.map +1 -1
- package/dist/directory-auth-posture.d.ts +87 -0
- package/dist/directory-auth-posture.d.ts.map +1 -0
- package/dist/directory-auth-posture.js +134 -0
- package/dist/directory-auth-posture.js.map +1 -0
- package/dist/directory-bootstrap.d.ts +66 -3
- package/dist/directory-bootstrap.d.ts.map +1 -1
- package/dist/directory-bootstrap.js +133 -7
- package/dist/directory-bootstrap.js.map +1 -1
- package/dist/document-delivery-transport.d.ts +22 -2
- package/dist/document-delivery-transport.d.ts.map +1 -1
- package/dist/document-delivery-transport.js +11 -3
- package/dist/document-delivery-transport.js.map +1 -1
- package/dist/inbound-sessions.d.ts +31 -1
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +276 -16
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc-server.d.ts +5 -1
- package/dist/ipc-server.d.ts.map +1 -1
- package/dist/ipc-server.js +19 -3
- package/dist/ipc-server.js.map +1 -1
- package/dist/manifest-validity.d.ts +151 -0
- package/dist/manifest-validity.d.ts.map +1 -0
- package/dist/manifest-validity.js +268 -0
- package/dist/manifest-validity.js.map +1 -0
- package/dist/notification-handlers.d.ts.map +1 -1
- package/dist/notification-handlers.js +15 -0
- package/dist/notification-handlers.js.map +1 -1
- package/dist/outbound-sessions.d.ts +31 -0
- package/dist/outbound-sessions.d.ts.map +1 -1
- package/dist/outbound-sessions.js +198 -17
- package/dist/outbound-sessions.js.map +1 -1
- package/dist/park-envelope.d.ts +72 -1
- package/dist/park-envelope.d.ts.map +1 -1
- package/dist/park-envelope.js +119 -4
- package/dist/park-envelope.js.map +1 -1
- package/dist/refusal-reasons.d.ts +72 -0
- package/dist/refusal-reasons.d.ts.map +1 -0
- package/dist/refusal-reasons.js +69 -0
- package/dist/refusal-reasons.js.map +1 -0
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +20 -1
- package/dist/registration-manager.js.map +1 -1
- package/dist/restart-seal-resolver.d.ts.map +1 -1
- package/dist/restart-seal-resolver.js +6 -0
- package/dist/restart-seal-resolver.js.map +1 -1
- package/dist/retry-queue.d.ts +23 -1
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +37 -9
- package/dist/retry-queue.js.map +1 -1
- package/dist/roster-freshness.d.ts +169 -0
- package/dist/roster-freshness.d.ts.map +1 -0
- package/dist/roster-freshness.js +249 -0
- package/dist/roster-freshness.js.map +1 -0
- package/dist/seal-certificate-pull.d.ts +17 -1
- package/dist/seal-certificate-pull.d.ts.map +1 -1
- package/dist/seal-certificate-pull.js +37 -3
- package/dist/seal-certificate-pull.js.map +1 -1
- package/dist/seal-coordinator.d.ts +17 -1
- package/dist/seal-coordinator.d.ts.map +1 -1
- package/dist/seal-coordinator.js +71 -0
- package/dist/seal-coordinator.js.map +1 -1
- package/dist/seal-failure-store.d.ts +104 -0
- package/dist/seal-failure-store.d.ts.map +1 -0
- package/dist/seal-failure-store.js +126 -0
- package/dist/seal-failure-store.js.map +1 -0
- package/dist/seal-flows.d.ts.map +1 -1
- package/dist/seal-flows.js +35 -6
- package/dist/seal-flows.js.map +1 -1
- package/dist/seal-upgrade.d.ts +20 -5
- package/dist/seal-upgrade.d.ts.map +1 -1
- package/dist/seal-upgrade.js +28 -5
- package/dist/seal-upgrade.js.map +1 -1
- package/dist/session-assignment-parser.d.ts +41 -5
- package/dist/session-assignment-parser.d.ts.map +1 -1
- package/dist/session-assignment-parser.js +77 -14
- package/dist/session-assignment-parser.js.map +1 -1
- package/dist/session-ceremony.d.ts +29 -0
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +71 -0
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-connection-gater.d.ts +46 -7
- package/dist/session-connection-gater.d.ts.map +1 -1
- package/dist/session-connection-gater.js +163 -12
- package/dist/session-connection-gater.js.map +1 -1
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +110 -4
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-node-manager.d.ts +406 -36
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +3025 -245
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-read-handlers.d.ts +27 -0
- package/dist/session-read-handlers.d.ts.map +1 -1
- package/dist/session-read-handlers.js +112 -6
- package/dist/session-read-handlers.js.map +1 -1
- package/dist/session-relay-client.d.ts +61 -2
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +170 -11
- package/dist/session-relay-client.js.map +1 -1
- package/dist/session-salt-agreement.d.ts +284 -0
- package/dist/session-salt-agreement.d.ts.map +1 -0
- package/dist/session-salt-agreement.js +371 -0
- package/dist/session-salt-agreement.js.map +1 -0
- package/dist/signal-submission.d.ts.map +1 -1
- package/dist/signal-submission.js +10 -1
- package/dist/signal-submission.js.map +1 -1
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +42 -5
- package/dist/signaling-connect.js.map +1 -1
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +10 -0
- package/dist/vocabulary.js.map +1 -1
- package/dist/wire-content-hash.d.ts +56 -0
- package/dist/wire-content-hash.d.ts.map +1 -1
- package/dist/wire-content-hash.js +71 -0
- package/dist/wire-content-hash.js.map +1 -1
- package/package.json +5 -5
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Interrupted-session detection runs BEFORE the IPC socket opens, so a client cannot observe a
|
|
15
15
|
* stale 'active' row from a previous process.
|
|
16
16
|
*/
|
|
17
|
+
import { type ContentHashAlg } from "./wire-content-hash.js";
|
|
18
|
+
import { type CapacityReason } from "./refusal-reasons.js";
|
|
17
19
|
import { type DaemonDatabase } from "./sqlcipher-db.js";
|
|
18
20
|
import type { Stream } from "@libp2p/interface";
|
|
19
21
|
import type { Logger, SessionRecord, SealReadinessView } from "./types.js";
|
|
@@ -25,6 +27,7 @@ import { type ParkEnvelope } from "./park-envelope.js";
|
|
|
25
27
|
import { AgentRelayClient, type RelayAssignmentCarry } from "./session-relay-client.js";
|
|
26
28
|
import { RelayReceiptStore, type RelayReceipt } from "./relay-receipt-store.js";
|
|
27
29
|
import { SessionSealLeafStore, type SealCarryLeaf } from "./session-seal-leaf-store.js";
|
|
30
|
+
import type { SealUpgradeReadiness } from "./seal-upgrade.js";
|
|
28
31
|
import { type SecurityGatewayClient } from "@cello-protocol/gateway";
|
|
29
32
|
/**
|
|
30
33
|
* DOD-CAP-SELF-HEAL-1 — how long an interrupted session keeps consuming a cap slot.
|
|
@@ -260,6 +263,15 @@ export declare class SessionNodeManager {
|
|
|
260
263
|
receiptStore?: RelayReceiptStore;
|
|
261
264
|
sealLeafStore?: SessionSealLeafStore;
|
|
262
265
|
}) => AgentRelayClient | undefined): void;
|
|
266
|
+
/**
|
|
267
|
+
* Record that this session's tree and the relay's counter have provably parted.
|
|
268
|
+
*
|
|
269
|
+
* Idempotent, and deliberately does NOT touch `updated_at`: that column drives the inbox's
|
|
270
|
+
* last-spoke ordering, and divergence is not activity.
|
|
271
|
+
*/
|
|
272
|
+
markSessionDiverged(agentName: string, sessionId: string): void;
|
|
273
|
+
/** Whether this session has provably parted from the relay's ordering. */
|
|
274
|
+
isSessionDiverged(agentName: string, sessionId: string): boolean;
|
|
263
275
|
/** Arm the park-deposit fault. Returns the count now armed. */
|
|
264
276
|
injectParkFault(count: number, cause?: string): number;
|
|
265
277
|
/** Arm the direct-send fault — makes the next N sends take the dial-failure path. */
|
|
@@ -336,8 +348,8 @@ export declare class SessionNodeManager {
|
|
|
336
348
|
*/
|
|
337
349
|
setAwaitingAckHooks(hooks: {
|
|
338
350
|
onPersisted?: (agentName: string, sessionId: string, contentHashHex: string) => void;
|
|
339
|
-
onTtf?: (agentName: string, sessionId: string, contentHashHex: string, content: Uint8Array, structure1Cbor?: Uint8Array, structure2Cbor?: Uint8Array) => void;
|
|
340
|
-
onParkFailed?: (agentName: string, sessionId: string, contentHashHex: string, content: Uint8Array, structure1Cbor?: Uint8Array, structure2Cbor?: Uint8Array) => boolean;
|
|
351
|
+
onTtf?: (agentName: string, sessionId: string, contentHashHex: string, content: Uint8Array, structure1Cbor?: Uint8Array, structure2Cbor?: Uint8Array, contentHashAlg?: string) => void;
|
|
352
|
+
onParkFailed?: (agentName: string, sessionId: string, contentHashHex: string, content: Uint8Array, structure1Cbor?: Uint8Array, structure2Cbor?: Uint8Array, contentHashAlg?: string) => boolean;
|
|
341
353
|
}): void;
|
|
342
354
|
/**
|
|
343
355
|
* DOD-RETRYQ-STRAND-1: wire the disposition of durable state a session can no longer drain.
|
|
@@ -368,6 +380,7 @@ export declare class SessionNodeManager {
|
|
|
368
380
|
content: Uint8Array;
|
|
369
381
|
structure1Cbor?: Uint8Array;
|
|
370
382
|
structure2Cbor?: Uint8Array;
|
|
383
|
+
contentHashAlg: string | undefined;
|
|
371
384
|
}) => Promise<{
|
|
372
385
|
ok: true;
|
|
373
386
|
} | {
|
|
@@ -417,6 +430,41 @@ export declare class SessionNodeManager {
|
|
|
417
430
|
* directory for the OFFLINE tree rebuild. Empty when no leaves were logged (e.g. a direct-only session
|
|
418
431
|
* with no relay witness) — the caller then has nothing to carry and the seal stays bilateral/pending.
|
|
419
432
|
*/
|
|
433
|
+
/**
|
|
434
|
+
* REBUILD THE CERTIFIED ROOT FROM THIS DAEMON'S OWN LEAVES — `DOD-M15-SEALWIRE-1` bullet 2.
|
|
435
|
+
*
|
|
436
|
+
* The receipt used to prove only that the directory signed SOMETHING: the client took the sealed
|
|
437
|
+
* root off the wire, confirmed the directory's signature over those bytes, stored it, and threw
|
|
438
|
+
* away the root it had computed a step earlier. At co-signing time that means **your key signs a
|
|
439
|
+
* root you never checked.**
|
|
440
|
+
*
|
|
441
|
+
* Bullet 1 moved the certified root into the content-hash domain, which is the domain this daemon
|
|
442
|
+
* can actually rebuild — each carry leaf's `content_hash` is the leaf hash (RFC 6962 §2.1 "hash"
|
|
443
|
+
* leaves are used as-is), and the carry is ordered by the relay's canonical `sequence_number`,
|
|
444
|
+
* which is the order the directory rebuilds in.
|
|
445
|
+
*
|
|
446
|
+
* ─── Why this returns "cannot judge" instead of always answering ───────────────────────────
|
|
447
|
+
*
|
|
448
|
+
* A root comparison that is WRONG makes every session unsealable, and force-abandon — no receipt —
|
|
449
|
+
* becomes the only exit. That failure is worse than the one being guarded, and this file already
|
|
450
|
+
* carries two comments saying so about other gates.
|
|
451
|
+
*
|
|
452
|
+
* The carry is this daemon's view, and it is not guaranteed complete at the instant a certificate
|
|
453
|
+
* arrives: the counterparty's SEAL ctrl leaf is what TRIGGERS the seal, so it may not have been
|
|
454
|
+
* witnessed here yet. So completeness is checked FIRST, against the certificate's own leaf count.
|
|
455
|
+
* A short carry means this daemon cannot judge — which is a different answer from "the roots
|
|
456
|
+
* disagree", and conflating them would turn a local timing gap into an accusation.
|
|
457
|
+
*/
|
|
458
|
+
verifyCertifiedRoot(agentPubkeyHex: string, sessionIdHex: string, certifiedRoot: Uint8Array, certifiedLeafCount: number): {
|
|
459
|
+
verdict: "match";
|
|
460
|
+
} | {
|
|
461
|
+
verdict: "mismatch";
|
|
462
|
+
ownRootHex: string | null;
|
|
463
|
+
detail: string;
|
|
464
|
+
} | {
|
|
465
|
+
verdict: "cannot_judge";
|
|
466
|
+
reason: string;
|
|
467
|
+
};
|
|
420
468
|
getSealCarry(agentPubkeyHex: string, sessionIdHex: string): SealCarryLeaf[];
|
|
421
469
|
/**
|
|
422
470
|
* DOD-LOG-1 / PERSIST-002 (AC-010): append one readable message to the durable transcript, keyed
|
|
@@ -429,7 +477,19 @@ export declare class SessionNodeManager {
|
|
|
429
477
|
* lost row only cost the unread count. Delivery reads the transcript now, so a swallowed received
|
|
430
478
|
* row is TOTAL content loss and the caller has to know.
|
|
431
479
|
*/
|
|
432
|
-
recordTranscriptMessage(agentName: string, sessionId: string, sequence: number, direction: "sent" | "received", plaintext: Uint8Array, correlationId?: string
|
|
480
|
+
recordTranscriptMessage(agentName: string, sessionId: string, sequence: number, direction: "sent" | "received", plaintext: Uint8Array, correlationId?: string,
|
|
481
|
+
/**
|
|
482
|
+
* DOD-M15-SEALWIRE-1 bullet 5: the VERIFIED authorship proof, when there is one.
|
|
483
|
+
*
|
|
484
|
+
* Optional because there legitimately is not always one — the ordering decode can fail SOFT and
|
|
485
|
+
* the message is still ingested via hash-dedup. Optional is NOT the same as unremarked: absence
|
|
486
|
+
* is written into the row as `attribution = 'local_session_state'`, so a reader can tell a row
|
|
487
|
+
* whose author was proven from one whose author was assumed. That distinction is the bullet.
|
|
488
|
+
*/
|
|
489
|
+
authorship?: {
|
|
490
|
+
senderPubkey: Uint8Array;
|
|
491
|
+
senderSig: Uint8Array;
|
|
492
|
+
}): boolean;
|
|
433
493
|
/**
|
|
434
494
|
* DOD-LOG-1: read a session's durable transcript back (after a restart), decrypted and ordered by
|
|
435
495
|
* canonical sequence then direction. A blob that fails to decrypt (tamper/wrong key) is skipped
|
|
@@ -621,6 +681,24 @@ export declare class SessionNodeManager {
|
|
|
621
681
|
clearRenameNotice(agentName: string, pubkey: string): void;
|
|
622
682
|
/** M8C-CONTACT-1: known stays known until explicitly removed. */
|
|
623
683
|
removeContact(agentName: string, pubkey: string): boolean;
|
|
684
|
+
/**
|
|
685
|
+
* Forget the pinned threshold group key for a counterparty, so the next session re-pins.
|
|
686
|
+
*
|
|
687
|
+
* DOD-M15-OFFER-SIGNED-1 review F2 — WITHOUT THIS THE REFUSAL WAS PERMANENT. The identity-change
|
|
688
|
+
* check refuses a counterparty whose group key differs from the one recorded in an earlier
|
|
689
|
+
* session, and its guidance told the operator to confirm out of band and then remove the contact
|
|
690
|
+
* so the new identity is pinned afresh. `removeContact` deleted a row in `contacts`; the pin lives
|
|
691
|
+
* in `sessions.counterparty_primary_pubkey`, and nothing in the daemon ever cleared it.
|
|
692
|
+
*
|
|
693
|
+
* So an operator who did exactly as instructed — called their counterparty, confirmed the
|
|
694
|
+
* re-registration was genuine, removed the contact, retried — got the identical refusal, with no
|
|
695
|
+
* way out short of editing the database. A security control that cannot be reset by the person it
|
|
696
|
+
* protects is a lockout, and the printed remedy made it worse by reading as though it worked.
|
|
697
|
+
*
|
|
698
|
+
* Nulls the column rather than deleting the session rows: those rows are the transcript record,
|
|
699
|
+
* and a re-pin is not a reason to lose them.
|
|
700
|
+
*/
|
|
701
|
+
clearPinnedCounterpartyPrimary(agentName: string, counterpartyPubkeyHex: string): number;
|
|
624
702
|
/** MONIKER-4: the operator's pet name for a pubkey (whoLabel's top tier), or null. Read-only
|
|
625
703
|
* and tolerant of a not-yet-open DB (a missing label degrades the doorbell, never blocks it). */
|
|
626
704
|
getContactMoniker(agentName: string, pubkey: string): string | null;
|
|
@@ -666,7 +744,7 @@ export declare class SessionNodeManager {
|
|
|
666
744
|
ok: true;
|
|
667
745
|
} | {
|
|
668
746
|
ok: false;
|
|
669
|
-
reason:
|
|
747
|
+
reason: CapacityReason;
|
|
670
748
|
};
|
|
671
749
|
/** M8C-TGDOOR-1: the daemon-wide Telegram bot settings, or null if never configured. */
|
|
672
750
|
getTelegramSettings(): {
|
|
@@ -737,10 +815,85 @@ export declare class SessionNodeManager {
|
|
|
737
815
|
addrs: string[];
|
|
738
816
|
} | null;
|
|
739
817
|
/**
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
818
|
+
* DOD-M15-ASSIGN-1 — name the one peer allowed to dial this agent's standing receiver, at the
|
|
819
|
+
* moment the directory's `session_offer` says who is coming.
|
|
820
|
+
*
|
|
821
|
+
* This is what makes the receiver's deny-by-default safe. The offer names
|
|
822
|
+
* `initiator_session_peer_id`, and the responder answers it by advertising its OWN address in
|
|
823
|
+
* `session_offer_accept`. Narrowing here — BEFORE that answer goes out — means the door opens to
|
|
824
|
+
* exactly one peer at the same instant the address that reaches them is published, and never
|
|
825
|
+
* before. The initiator cannot know where to dial until the accept it triggers has been sent.
|
|
826
|
+
*
|
|
827
|
+
* Returns WHICH failure it was, never a bare false (review F6). The caller reports a distinct
|
|
828
|
+
* reason per cause: "no receiver" and "the directory named nobody" are different subsystems, and
|
|
829
|
+
* collapsing them sent the operator to the directory for a local problem. This method never
|
|
830
|
+
* widens the gate to compensate.
|
|
831
|
+
*
|
|
832
|
+
* Narrows INBOUND ONLY. The receiver is still the daemon's general-purpose dialer at this point
|
|
833
|
+
* — no assignment exists yet — so revoking its outbound latitude here would break content
|
|
834
|
+
* parking and restart-seal submission (review F2).
|
|
835
|
+
*/
|
|
836
|
+
admitOfferedDialer(agentName: string, initiatorSessionPeerId: string, sessionIdHex: string): "narrowed" | "no_receiver" | "no_peer_named";
|
|
837
|
+
/**
|
|
838
|
+
* What the UNSIGNED offer claimed, so the SIGNED assignment can be checked against it.
|
|
839
|
+
*
|
|
840
|
+
* DOD-M15-OFFER-SIGNED-1. Decision 2 rules that the listening socket is "gated on the
|
|
841
|
+
* assignment", and the gate is narrowed from `session_offer` — a frame carrying no signature —
|
|
842
|
+
* because that is the only thing that arrives early enough. Timing forced the offer; it does not
|
|
843
|
+
* excuse trusting it.
|
|
844
|
+
*
|
|
845
|
+
* Keeping what the offer said turns the two frames into a CHECK ON EACH OTHER. The assignment is
|
|
846
|
+
* FROST-signed by the initiator's own threshold group, which no single directory can produce, and
|
|
847
|
+
* it names the same peer id. A directory that says one peer in the offer and another in the
|
|
848
|
+
* assignment is naming two different dialers for one session — which a truthful directory never
|
|
849
|
+
* does, and which is exactly the move a compromised one would make to slip a peer past the gate
|
|
850
|
+
* before the signed document arrives.
|
|
851
|
+
*/
|
|
852
|
+
getOfferedDialer(agentName: string, sessionIdHex: string): string | null;
|
|
853
|
+
/**
|
|
854
|
+
* Which peer this agent's standing receiver is currently admitting INBOUND — `null` for nobody.
|
|
855
|
+
*
|
|
856
|
+
* Read-only, and it answers a question the daemon otherwise cannot: *"whose dial would this
|
|
857
|
+
* receiver accept right now?"* The gate is narrowed and re-closed from several paths (an offer
|
|
858
|
+
* arrives, an assignment is refused, a session is promoted), and until now the only way to know
|
|
859
|
+
* where it had ended up was to reproduce the sequence in your head.
|
|
860
|
+
*
|
|
861
|
+
* Added for `DOD-M15-RESPONDER-VERIFY-1`, where a refusal for one session was closing the gate a
|
|
862
|
+
* DIFFERENT session had narrowed — a defect with no observable symptom short of the second
|
|
863
|
+
* session's initiator being refused with "nothing invited it".
|
|
864
|
+
*/
|
|
865
|
+
getStandingReceiverAllowedPeer(agentName: string): string | null;
|
|
866
|
+
/** Forget the offered dialer for ONE session — called on BOTH the claim and the refusal paths. */
|
|
867
|
+
clearOfferedDialer(agentName: string, sessionIdHex: string): void;
|
|
868
|
+
/**
|
|
869
|
+
* RE-CLOSE the standing receiver — but ONLY if this session is still the one holding it.
|
|
870
|
+
*
|
|
871
|
+
* DOD-M15-OFFER-SIGNED-1 review F4, then N1. The first version closed the gate unconditionally,
|
|
872
|
+
* and that was worse than the defect it fixed: an agent has ONE standing receiver with ONE allowed
|
|
873
|
+
* peer, so a refusal for session P closed the gate that offer Q had narrowed. Q's initiator —
|
|
874
|
+
* invited, legitimate — was then refused with *"nothing invited it"*, which this daemon had.
|
|
875
|
+
*
|
|
876
|
+
* That is the same cross-session interference F1 was written to remove, moved one method along,
|
|
877
|
+
* and triggerable the same way: one bogus offer/assignment pair collapses a concurrent real
|
|
878
|
+
* session.
|
|
879
|
+
*
|
|
880
|
+
* So the gate is closed only when it still names the peer THIS session opened it to. If a later
|
|
881
|
+
* offer has already re-narrowed it, that offer owns the receiver and its narrowing stands.
|
|
882
|
+
*
|
|
883
|
+
* NO EVICTION SWEEP, deliberately (N4). The sweep evicts by "not the allowed peer", and
|
|
884
|
+
* `getConnections()` returns OUTBOUND connections too — including the content-park and
|
|
885
|
+
* restart-seal dials this node makes as the daemon's general-purpose dialer, whose targets are on
|
|
886
|
+
* no allowlist by construction. Sweeping here hung those up, and the failure surfaced as
|
|
887
|
+
* `relay_unavailable`: a transport label for a local decision, which is the exact substitution
|
|
888
|
+
* that comment was written to prevent. The load-bearing control is `DOD-M15-FRAME-1`'s frame gate,
|
|
889
|
+
* which refuses what an unauthorised peer sends; closing the door is enough here.
|
|
890
|
+
*/
|
|
891
|
+
revokeOfferedDialer(agentName: string, sessionIdHex: string, offeredPeerId: string | null): void;
|
|
892
|
+
/**
|
|
893
|
+
* The standing receiver's libp2p node — a general-purpose node usable for OUTBOUND dials that
|
|
894
|
+
* are not session-scoped (e.g. the content-park deposit/pull to the relay, MSG-001-3b). Its
|
|
895
|
+
* gater admits nobody INBOUND until a session names them (DOD-M15-ASSIGN-1), but leaves these
|
|
896
|
+
* outbound errands open. Returns null until the receiver is ready.
|
|
744
897
|
*/
|
|
745
898
|
getStandingReceiverNode(agentName?: string): CelloNode | null;
|
|
746
899
|
/**
|
|
@@ -1040,14 +1193,6 @@ export declare class SessionNodeManager {
|
|
|
1040
1193
|
* escalates to a unilateral seal. This column only withdraws the session from AUTOMATIC retries.
|
|
1041
1194
|
*/
|
|
1042
1195
|
markRestartSealGaveUp(agentName: string, sessionId: string, reason: string): void;
|
|
1043
|
-
/**
|
|
1044
|
-
* cello_list_sessions: every persisted session for one agent, regardless of
|
|
1045
|
-
* status (active, interrupted, sealed, seal_interrupted_pending). Ordered most
|
|
1046
|
-
* recently updated first so the live session surfaces at the top. This is the
|
|
1047
|
-
* discovery surface that the by-id reads (cello_get_transcript /
|
|
1048
|
-
* cello_get_sealed_receipt) depend on — without it an agent has no way to learn
|
|
1049
|
-
* its own session ids after a restart or from a fresh MCP connection.
|
|
1050
|
-
*/
|
|
1051
1196
|
getSessionsForAgent(agentName: string): SessionRecord[];
|
|
1052
1197
|
/**
|
|
1053
1198
|
* Every persisted session across ALL agents, most-recently-updated first. Backs the daemon-wide
|
|
@@ -1102,6 +1247,26 @@ export declare class SessionNodeManager {
|
|
|
1102
1247
|
* signature locally. Best-effort — a missing row (race) is a no-op; the seal then falls back to
|
|
1103
1248
|
* accept-without-verify (still sound: the live frame arrives over the authenticated Noise channel).
|
|
1104
1249
|
*/
|
|
1250
|
+
/**
|
|
1251
|
+
* The counterparty's threshold group key as this agent has seen it BEFORE — trust on first use.
|
|
1252
|
+
*
|
|
1253
|
+
* DOD-M15-OFFER-SIGNED-1 / RESPONDER-VERIFY-1. The responder does not verify the assignment's
|
|
1254
|
+
* signature (deferred to SESSION-004), so every field in it is whatever the directory said. That
|
|
1255
|
+
* makes a same-frame check circular: a compromised directory just says the same thing twice.
|
|
1256
|
+
*
|
|
1257
|
+
* This is the one anchor the responder holds that a directory CANNOT retroactively change — its
|
|
1258
|
+
* own memory of previous sessions with this counterparty. A directory that names a different
|
|
1259
|
+
* threshold group key for someone you have already talked to is either substituting an identity
|
|
1260
|
+
* or has been compromised since; neither is a session to accept quietly.
|
|
1261
|
+
*
|
|
1262
|
+
* THE BOUND, stated rather than glossed: this is worth nothing on FIRST contact, which is the
|
|
1263
|
+
* definition of trust-on-first-use. It hardens every session after it, which is where a long-lived
|
|
1264
|
+
* counterparty relationship actually lives.
|
|
1265
|
+
*
|
|
1266
|
+
* Keyed on `counterparty_pubkey` — the K_local IDENTITY, which is the stable thing — not on a
|
|
1267
|
+
* session id or a display name.
|
|
1268
|
+
*/
|
|
1269
|
+
getPinnedCounterpartyPrimary(agentName: string, counterpartyPubkeyHex: string): string | null;
|
|
1105
1270
|
recordCounterpartyPrimary(agentName: string, sessionId: string, primaryPubkeyHex: string): void;
|
|
1106
1271
|
/**
|
|
1107
1272
|
* M7-SESSION-004 (AC-005/AC-006): read the persisted seal certificate for a session.
|
|
@@ -1276,14 +1441,45 @@ export declare class SessionNodeManager {
|
|
|
1276
1441
|
* cross-process ordering (and thus AC-002 root agreement under concurrent
|
|
1277
1442
|
* bidirectional traffic) requires the relay-assigned sequence from MSG-001.
|
|
1278
1443
|
*/
|
|
1279
|
-
sendContent(agentName: string, sessionId: string, content: Uint8Array, contentHash: Uint8Array,
|
|
1444
|
+
sendContent(agentName: string, sessionId: string, content: Uint8Array, contentHash: Uint8Array,
|
|
1445
|
+
/**
|
|
1446
|
+
* Required alongside the two below — every production caller already passes one, and an optional
|
|
1447
|
+
* parameter in front of a required one is what TypeScript refuses. Making it explicit costs
|
|
1448
|
+
* nothing and removes the last place a positional argument can silently shift.
|
|
1449
|
+
*/
|
|
1450
|
+
correlationId: string | undefined,
|
|
1280
1451
|
/**
|
|
1281
1452
|
* The DOMAIN this content belongs to, as the relay and the directory will see it. Defaults to
|
|
1282
1453
|
* MESSAGE so `cello_send` is unchanged; the document path passes 0x04/0x05. Not cosmetic — the
|
|
1283
1454
|
* directory computes `final_message` and `answered` from the witnessed kind, and both of its
|
|
1284
1455
|
* document exclusions were dead while every document leaf arrived here as a message.
|
|
1456
|
+
*
|
|
1457
|
+
* ⚠️ ALSO REQUIRED NOW, and for the same reason as `contentHashAlg` below — this parameter is the
|
|
1458
|
+
* precedent, not a bystander. It defaulted to MESSAGE, the document adapter in `daemon.ts`
|
|
1459
|
+
* silently dropped it, and the wire was wrong for a whole release: *"0.0.145 shipped the fix
|
|
1460
|
+
* everywhere except here."* A default that matches the common case makes the omission invisible
|
|
1461
|
+
* at every call site and at typecheck. Every caller states its kind now.
|
|
1462
|
+
*/
|
|
1463
|
+
leafKind: number,
|
|
1464
|
+
/**
|
|
1465
|
+
* `DOD-M15-SEALWIRE-1` part B2b — the algorithm `contentHash` was produced under, taken from
|
|
1466
|
+
* `contentHashForSession` by the caller that computed the hash.
|
|
1467
|
+
*
|
|
1468
|
+
* Passed rather than re-derived HERE, deliberately: re-deriving would ask "how would this
|
|
1469
|
+
* session hash something now?", and the answer can differ from how THIS message was actually
|
|
1470
|
+
* hashed. A hash and its label must travel together or the peer refuses a message nobody touched.
|
|
1471
|
+
*
|
|
1472
|
+
* ⚠️ REQUIRED, NOT DEFAULTED — review B2b-1 F4, and the default is what made four mutants
|
|
1473
|
+
* unfalsifiable. It was `= CONTENT_HASH_ALGS.SHA256`, which equals the only value in play today,
|
|
1474
|
+
* so DROPPING THE ARGUMENT AT ANY OF THE FIVE HOPS produced byte-identical output and the whole
|
|
1475
|
+
* 2,800-test daemon suite stayed green. Measured at all four send sites individually.
|
|
1476
|
+
*
|
|
1477
|
+
* A default that equals the current value makes every threading edit invisible until the value
|
|
1478
|
+
* changes — and the day it changes, the dropped argument mislabels the message and every peer
|
|
1479
|
+
* refuses it as a tamper. Required makes a dropped argument a TYPECHECK failure instead of a
|
|
1480
|
+
* test question nobody can answer.
|
|
1285
1481
|
*/
|
|
1286
|
-
|
|
1482
|
+
contentHashAlg: string): Promise<{
|
|
1287
1483
|
ok: true;
|
|
1288
1484
|
delivered: true;
|
|
1289
1485
|
sequenceNumber?: number;
|
|
@@ -1334,27 +1530,55 @@ export declare class SessionNodeManager {
|
|
|
1334
1530
|
* completeness check (the `desynced` reason) requires the deferred MSG-001-3b canonical-sequence
|
|
1335
1531
|
* reconciliation — same documented limitation as the UP-2 gate above.
|
|
1336
1532
|
*/
|
|
1337
|
-
getSealUpgradeReadiness(agentName: string, sessionId: string):
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1533
|
+
getSealUpgradeReadiness(agentName: string, sessionId: string): SealUpgradeReadiness;
|
|
1534
|
+
/** Record an inbound refusal for the operator. First of its kind per session is the signal. */
|
|
1535
|
+
noteContentRefusal(agentName: string, sessionId: string, reason: string, detail?: {
|
|
1536
|
+
impact?: string;
|
|
1537
|
+
guidance?: string;
|
|
1538
|
+
}): void;
|
|
1341
1539
|
/**
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1344
|
-
*
|
|
1540
|
+
* Drain the refusals a GIVEN CONSUMER has not been shown yet, and remember what it was shown.
|
|
1541
|
+
*
|
|
1542
|
+
* ─── Why this is keyed by connection, and not by a single flag ─────────────────────────────────
|
|
1345
1543
|
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
* the
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
* lands, a malicious sender that sends matching (content, hash) in one frame is
|
|
1354
|
-
* not detected here — only the relay-relayed signed leaf closes that gap.
|
|
1544
|
+
* It used to set one `surfaced: boolean` on the notice. Two MCP windows attending the same agent
|
|
1545
|
+
* is the ordinary case, and under that flag whoever read FIRST consumed the notice — the second
|
|
1546
|
+
* window was told nothing, permanently. **That is the same defect `takeReceivedContent` had**, and
|
|
1547
|
+
* the comment above the delivery loop in `session-content-handlers.ts` spells out why it was
|
|
1548
|
+
* removed: *"reading is non-destructive by construction. Nothing one consumer does mutates state
|
|
1549
|
+
* another consumer reads."* The whole `taken_by_sibling` apparatus exists because this was paid
|
|
1550
|
+
* for once already; re-introducing it on a different surface makes it no less true.
|
|
1355
1551
|
*
|
|
1356
|
-
*
|
|
1552
|
+
* ─── Why the count now has a reader ────────────────────────────────────────────────────────────
|
|
1553
|
+
*
|
|
1554
|
+
* The old docstring claimed *"count still grows underneath, so a later reader can ask how many
|
|
1555
|
+
* without being told again."* **There was no later reader.** After the first surfacing the count
|
|
1556
|
+
* incremented under a flag the drain skipped unconditionally, so 3 refusals became 903 and nothing
|
|
1557
|
+
* anywhere could say so — while the comment asserted the opposite.
|
|
1558
|
+
*
|
|
1559
|
+
* So a reason RE-ANNOUNCES to a consumer when its count has grown by an order of magnitude since
|
|
1560
|
+
* that consumer last saw it (1 → 10 → 100 → …), marked `repeat: true`. That keeps the first
|
|
1561
|
+
* refusal the signal and the ninetieth silent, which is the dedup's point, while still making a
|
|
1562
|
+
* skew that has swallowed hundreds of messages visible — at a handful of announcements per
|
|
1563
|
+
* session, not one per message.
|
|
1357
1564
|
*/
|
|
1565
|
+
takeContentRefusals(agentName: string, sessionId: string,
|
|
1566
|
+
/**
|
|
1567
|
+
* REQUIRED, deliberately — no default.
|
|
1568
|
+
*
|
|
1569
|
+
* It had one (`"default"`), and a default is the defect this method was rewritten to remove,
|
|
1570
|
+
* lying in wait: any future call site that omits the argument silently shares ONE bucket across
|
|
1571
|
+
* every window, the first reader consumes the notice for all the others, and nothing fails to
|
|
1572
|
+
* compile and no test goes red. The parameter existing is not the protection; being unable to
|
|
1573
|
+
* forget it is.
|
|
1574
|
+
*/
|
|
1575
|
+
consumerId: string): Array<{
|
|
1576
|
+
reason: string;
|
|
1577
|
+
impact?: string;
|
|
1578
|
+
guidance?: string;
|
|
1579
|
+
count: number;
|
|
1580
|
+
repeat?: boolean;
|
|
1581
|
+
}>;
|
|
1358
1582
|
ingestReceivedContent(agentName: string, sessionId: string, content: Uint8Array, contentHash: Uint8Array, correlationId?: string,
|
|
1359
1583
|
/**
|
|
1360
1584
|
* DOD-FRONTIER-STRAND-1 AC1: the relay-assigned canonical position for THIS message, taken from
|
|
@@ -1363,7 +1587,29 @@ export declare class SessionNodeManager {
|
|
|
1363
1587
|
* collapse in it before dedup is ever consulted, which is the whole defect. Absent when the
|
|
1364
1588
|
* session has no relay witness (relay-degraded): see the announced fallback below.
|
|
1365
1589
|
*/
|
|
1366
|
-
canonicalSeqIn?: number
|
|
1590
|
+
canonicalSeqIn?: number,
|
|
1591
|
+
/**
|
|
1592
|
+
* DOD-M15-SEALWIRE-1 part B1 — the algorithm the SENDER named on the frame, verbatim.
|
|
1593
|
+
*
|
|
1594
|
+
* `undefined` means the frame carried no name, which is a peer that predates the field and is
|
|
1595
|
+
* the one case we may safely assume `sha256` for. It is threaded through rather than read off
|
|
1596
|
+
* the session, because whether a hash is salted is a fact about the FRAME and its sender, never
|
|
1597
|
+
* about what this side happens to hold.
|
|
1598
|
+
*/
|
|
1599
|
+
contentHashAlgIn?: string | null,
|
|
1600
|
+
/**
|
|
1601
|
+
* DOD-M15-SEALWIRE-1 bullet 5: the VERIFIED authorship proof for this message, when the caller
|
|
1602
|
+
* has one. The caller is the only place that has it — `#recordFrameOrdering` verifies the
|
|
1603
|
+
* signature against the key inside the sender's own signed bytes and matches the signer to this
|
|
1604
|
+
* session's counterparty, and that result reaches here or nowhere.
|
|
1605
|
+
*
|
|
1606
|
+
* Optional, because the soft decode-failure path ingests without it. The row records which it
|
|
1607
|
+
* was, so absence is never silent.
|
|
1608
|
+
*/
|
|
1609
|
+
verifiedAuthorship?: {
|
|
1610
|
+
senderPubkey: Uint8Array;
|
|
1611
|
+
senderSig: Uint8Array;
|
|
1612
|
+
}): Promise<{
|
|
1367
1613
|
ok: true;
|
|
1368
1614
|
leafIndex: number;
|
|
1369
1615
|
sequenceNumber: number;
|
|
@@ -1435,6 +1681,16 @@ export declare class SessionNodeManager {
|
|
|
1435
1681
|
* counterparty's. They block a seal identically and mean completely different things. */
|
|
1436
1682
|
heldOwn: number;
|
|
1437
1683
|
heldReceived: number;
|
|
1684
|
+
/**
|
|
1685
|
+
* DOD-M15-DIVERGE-1 — this tree and the relay's counter have PROVABLY parted.
|
|
1686
|
+
*
|
|
1687
|
+
* The other two counters both measure the same direction: positions the relay committed that
|
|
1688
|
+
* this tree has not appended. This is the OPPOSITE direction — this tree holds a leaf at a
|
|
1689
|
+
* position the relay assigned to something else — and it is the direction an injected or forged
|
|
1690
|
+
* leaf appears in. Without it `ready` was asymmetric, and a diverged session read as perfectly
|
|
1691
|
+
* sealable right up until the counterparty answered `leaf_count_mismatch`, which is terminal.
|
|
1692
|
+
*/
|
|
1693
|
+
diverged: boolean;
|
|
1438
1694
|
};
|
|
1439
1695
|
/**
|
|
1440
1696
|
* DOD-M12B-SEAL-STUCK-1 — the operator-facing answer to "can this session be closed?".
|
|
@@ -1626,6 +1882,120 @@ export declare class SessionNodeManager {
|
|
|
1626
1882
|
* record (the recover handler lives in daemon.ts, which has no access to the private method).
|
|
1627
1883
|
*/
|
|
1628
1884
|
recordOrderingRecord(agentName: string, sessionId: string, structure1Cbor: Uint8Array, structure2Cbor: Uint8Array, contentHash: Uint8Array, correlationId?: string): number | null;
|
|
1885
|
+
/**
|
|
1886
|
+
* Test seam: force this session's own salt half, so the LOCAL-defect path is reachable.
|
|
1887
|
+
*
|
|
1888
|
+
* `generateSaltContribution` cannot produce a degenerate half, which is the point of it — so the
|
|
1889
|
+
* only way to exercise "our own random source is broken" end-to-end is to stand in for the broken
|
|
1890
|
+
* source. Named `…ForTest` like every other seam in this file, and it writes the same map
|
|
1891
|
+
* production writes rather than a parallel one, so a test cannot pass against state the daemon
|
|
1892
|
+
* never reads.
|
|
1893
|
+
*/
|
|
1894
|
+
/**
|
|
1895
|
+
* Test seam: run the auto-acknowledge gate, exactly as the counterparty's SEAL ctrl leaf does.
|
|
1896
|
+
*
|
|
1897
|
+
* `DOD-M15-SEALWIRE-1` part B1, review F-B. The gate has ONE production call site — inside the
|
|
1898
|
+
* relay leaf handler, behind `leaf_kind === CTRL && !authored_by_us` — so reaching it from a test
|
|
1899
|
+
* needs a live relay client delivering a real ctrl leaf. The consequence was measured: my
|
|
1900
|
+
* "tampered never downgrades" test wrapped its decisive assertion in
|
|
1901
|
+
* `if (skipped.length > 0)`, which was ALWAYS FALSE, so the whole `content_tamper` vs
|
|
1902
|
+
* `content_verification_unavailable` branch had no coverage anywhere in the repo and two mutants
|
|
1903
|
+
* on it survived the full gate.
|
|
1904
|
+
*
|
|
1905
|
+
* It calls the REAL private method rather than reproducing its logic, so a test cannot pass
|
|
1906
|
+
* against a decision production does not make.
|
|
1907
|
+
*/
|
|
1908
|
+
runAutoAcknowledgeGateForTest(agentName: string, sessionId: string, correlationId?: string): void;
|
|
1909
|
+
setSaltContributionForTest(agentName: string, sessionId: string, contribution: Uint8Array): void;
|
|
1910
|
+
/**
|
|
1911
|
+
* Test seam: drop this session's own half while leaving the stored salt in place — the state every
|
|
1912
|
+
* teardown produces, because `#evictSessionCaches` clears the map and the row survives.
|
|
1913
|
+
*
|
|
1914
|
+
* It clears the SAME map the eviction clears rather than a stand-in, so a test cannot pass against
|
|
1915
|
+
* a state the daemon never reaches. Reproducing it through a real teardown/revive would also drag
|
|
1916
|
+
* in node rebuild and relay reconnection, none of which this is about.
|
|
1917
|
+
*/
|
|
1918
|
+
forgetSaltContributionForTest(agentName: string, sessionId: string): void;
|
|
1919
|
+
/**
|
|
1920
|
+
* THE ONE PLACE THAT DECIDES HOW A SESSION'S OUTBOUND CONTENT IS HASHED —
|
|
1921
|
+
* `DOD-M15-SEALWIRE-1` part B2b.
|
|
1922
|
+
*
|
|
1923
|
+
* Returns the hash AND the algorithm that produced it, together, because the two must not be
|
|
1924
|
+
* decided separately. `wire-content-hash.ts` exists for exactly this reason and says so in its own
|
|
1925
|
+
* header: the expression was written out at five call sites, the two added last got it wrong, and
|
|
1926
|
+
* the failure was invisible — *"the send succeeds, `parked: false`, the sender's log says the frame
|
|
1927
|
+
* left, and the receiver discards it at the authenticity check."* It took two real daemons.
|
|
1928
|
+
*
|
|
1929
|
+
* There are FOUR outbound sites (`session-content-handlers.ts`, two in `daemon.ts`,
|
|
1930
|
+
* `document-delivery-transport.ts`). Once salting is switchable, each of them independently
|
|
1931
|
+
* deciding whether to salt is that defect again with a worse failure mode — a message hashed one
|
|
1932
|
+
* way and LABELLED another is refused by every peer, including a correct one.
|
|
1933
|
+
*
|
|
1934
|
+
* ⚠️ ASYNC, AND THAT IS THE POINT — B2b-2 constraint 2, not an implementation detail.
|
|
1935
|
+
*
|
|
1936
|
+
* The agreement is in flight while the operator composes their first message. Hash without waiting
|
|
1937
|
+
* and it comes out unsalted, and that first unsalted hash closes adoption for the LIFE of the
|
|
1938
|
+
* session (Decision #8, unit 1). Every session would fall back permanently while every log line
|
|
1939
|
+
* about it stayed true — the feature present, wired, tested, and never once reached.
|
|
1940
|
+
*
|
|
1941
|
+
* The wait lives HERE rather than at the four call sites for the same reason `contentHashAlg` is a
|
|
1942
|
+
* required parameter rather than a defaulted one: a site that forgets it must fail to compile. A
|
|
1943
|
+
* caller that drops the `await` gets a `Promise` where bytes belong, which is a typecheck error;
|
|
1944
|
+
* a caller that forgot to call a separate `awaitSaltSettled()` would silently send unsalted.
|
|
1945
|
+
*/
|
|
1946
|
+
contentHashForSession(agentName: string, sessionId: string, content: Uint8Array): Promise<{
|
|
1947
|
+
hash: Uint8Array;
|
|
1948
|
+
alg: ContentHashAlg;
|
|
1949
|
+
}>;
|
|
1950
|
+
/**
|
|
1951
|
+
* THIS SESSION'S UNSALTED HASH NEVER BECAME A MESSAGE — release the permanent closure it caused.
|
|
1952
|
+
*
|
|
1953
|
+
* ⚠️ REVIEW FINDING 3, and it is the opposite of the direction the flag was written to defend.
|
|
1954
|
+
* `#hashedWithoutSalt` closes adoption at hash time, because for a session's first message the
|
|
1955
|
+
* leaf is a network round trip away and every frontier count reads zero in between. Correct — but
|
|
1956
|
+
* `cello_send` has three paths that compute the hash and then produce NOTHING: a sibling send
|
|
1957
|
+
* holding the in-flight claim, the frontier moving under the send, and a non-durable send failure
|
|
1958
|
+
* whose bytes go to a queue with no production consumer.
|
|
1959
|
+
*
|
|
1960
|
+
* In all three the session was permanently unsalted for a message that exists nowhere: no leaf, no
|
|
1961
|
+
* wire, no copy at the peer. And B2b-2 made two of them MORE likely on a first message, because
|
|
1962
|
+
* the five-second wait widens the very window the frontier re-check is watching.
|
|
1963
|
+
*
|
|
1964
|
+
* Only safe because it is called on paths that provably sent nothing. It deliberately does NOT
|
|
1965
|
+
* clear `#unsaltedAnnounced`: the announcement was true when it fired and re-announcing on the
|
|
1966
|
+
* retry would be the per-message flood Decision #15 forbids.
|
|
1967
|
+
*
|
|
1968
|
+
* ─── THREE OTHER SITES HASH AND MAY SEND NOTHING, AND ARE EXEMPT ON PURPOSE (pass 2, F8) ──────
|
|
1969
|
+
*
|
|
1970
|
+
* `daemon.ts`'s one-shot rejection and away reply, and `document-delivery-transport.ts`'s frame
|
|
1971
|
+
* send, can all fail after hashing. None of them needs to abandon, and the reason is the same in
|
|
1972
|
+
* each: every one is a REPLY. The inbound message that triggered it has already been leafed on
|
|
1973
|
+
* this side, so `#saltAdoptionClosed` is already closed by the leaf count and would stay closed
|
|
1974
|
+
* whatever this flag said. Calling abandon there would be a no-op that looks like a guarantee.
|
|
1975
|
+
*
|
|
1976
|
+
* Written down rather than left to be re-derived: the next reader's first question is why the
|
|
1977
|
+
* list is three and not six.
|
|
1978
|
+
*/
|
|
1979
|
+
abandonUnsaltedHash(agentName: string, sessionId: string): void;
|
|
1980
|
+
/**
|
|
1981
|
+
* TEST SEAM — put a session into the state a real one is in between announcing and being answered.
|
|
1982
|
+
*
|
|
1983
|
+
* Reaching that state for real needs a live counterparty connection, which the daemon-level
|
|
1984
|
+
* fixtures do not have; without a seam the wait could only be tested by not testing it. It calls
|
|
1985
|
+
* the same private registration the announce path calls, so it cannot drift from it.
|
|
1986
|
+
*/
|
|
1987
|
+
markSaltAgreementPendingForTest(agentName: string, sessionId: string, boundMs?: number): void;
|
|
1988
|
+
/**
|
|
1989
|
+
* PUBLIC read of a session's agreed salt — `DOD-M15-SEALWIRE-1` part B2a.
|
|
1990
|
+
*
|
|
1991
|
+
* `content-park.ts` runs a SECOND, independent content-hash verifier (the park signature does not
|
|
1992
|
+
* cover the envelope content, so it checks before `ingestReceivedContent` is ever reached), and it
|
|
1993
|
+
* hardcoded `sha256`. It needs the salt to verify a v3 envelope, and it is outside this class.
|
|
1994
|
+
*
|
|
1995
|
+
* Read-only and cache-backed, so exposing it adds no way to CHANGE the salt from outside — the
|
|
1996
|
+
* only writer remains `#persistSessionSalt`, behind the one-salt-per-session predicate.
|
|
1997
|
+
*/
|
|
1998
|
+
getSessionContentSalt(agentName: string, sessionId: string): Uint8Array | null;
|
|
1629
1999
|
/**
|
|
1630
2000
|
* M7-SESSION-001 AC-004/AC-005: Register a relay stream for an active session.
|
|
1631
2001
|
* Starts a background reader that watches for session_interrupted frames and
|