@cello-protocol/daemon 0.0.211 → 0.0.212
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-admin-handlers.d.ts.map +1 -1
- package/dist/agent-admin-handlers.js +3 -0
- package/dist/agent-admin-handlers.js.map +1 -1
- package/dist/agent-handlers.d.ts.map +1 -1
- package/dist/agent-handlers.js +44 -0
- package/dist/agent-handlers.js.map +1 -1
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +11 -2
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/assignment-verify.d.ts.map +1 -1
- package/dist/assignment-verify.js +2 -2
- package/dist/assignment-verify.js.map +1 -1
- package/dist/attendance-wiring.d.ts +2 -21
- package/dist/attendance-wiring.d.ts.map +1 -1
- package/dist/attendance-wiring.js +57 -251
- package/dist/attendance-wiring.js.map +1 -1
- package/dist/away-detection.d.ts +16 -21
- package/dist/away-detection.d.ts.map +1 -1
- package/dist/away-detection.js +34 -23
- package/dist/away-detection.js.map +1 -1
- package/dist/boot-agents.d.ts.map +1 -1
- package/dist/close-session-handler.d.ts.map +1 -1
- package/dist/close-session-handler.js +47 -1
- package/dist/close-session-handler.js.map +1 -1
- package/dist/co-attendance.d.ts +1 -1
- package/dist/co-attendance.js +1 -1
- package/dist/consortium-fingerprint.d.ts +115 -0
- package/dist/consortium-fingerprint.d.ts.map +1 -0
- package/dist/consortium-fingerprint.js +175 -0
- package/dist/consortium-fingerprint.js.map +1 -0
- package/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +80 -0
- package/dist/content-park.js.map +1 -1
- package/dist/daemon-handle.d.ts +1 -1
- package/dist/daemon-handle.d.ts.map +1 -1
- package/dist/daemon-status-report.d.ts +10 -2
- package/dist/daemon-status-report.d.ts.map +1 -1
- package/dist/daemon-status-report.js +6 -3
- package/dist/daemon-status-report.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +41 -13
- package/dist/daemon.js.map +1 -1
- package/dist/directory-bootstrap.d.ts +3 -1
- package/dist/directory-bootstrap.d.ts.map +1 -1
- package/dist/directory-bootstrap.js +3 -1
- package/dist/directory-bootstrap.js.map +1 -1
- package/dist/directory-connect.d.ts.map +1 -1
- package/dist/directory-connect.js +10 -2
- package/dist/directory-connect.js.map +1 -1
- package/dist/disconnect-cleanup.d.ts +7 -0
- package/dist/disconnect-cleanup.d.ts.map +1 -1
- package/dist/disconnect-cleanup.js +16 -1
- package/dist/disconnect-cleanup.js.map +1 -1
- package/dist/document-notify.d.ts +0 -1
- package/dist/document-notify.d.ts.map +1 -1
- package/dist/document-notify.js +0 -1
- package/dist/document-notify.js.map +1 -1
- package/dist/document-store.d.ts +0 -1
- package/dist/document-store.d.ts.map +1 -1
- package/dist/document-store.js +0 -1
- package/dist/document-store.js.map +1 -1
- package/dist/document-write-path.d.ts.map +1 -1
- package/dist/document-write-path.js +2 -9
- package/dist/document-write-path.js.map +1 -1
- package/dist/inbound-sessions.d.ts +3 -1
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +12 -4
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/initiate-session-handler.d.ts.map +1 -1
- package/dist/initiate-session-handler.js +5 -1
- package/dist/initiate-session-handler.js.map +1 -1
- package/dist/park-envelope.d.ts +68 -0
- package/dist/park-envelope.d.ts.map +1 -1
- package/dist/park-envelope.js +86 -1
- package/dist/park-envelope.js.map +1 -1
- package/dist/park-recovery.d.ts +46 -0
- package/dist/park-recovery.d.ts.map +1 -1
- package/dist/park-recovery.js +100 -1
- package/dist/park-recovery.js.map +1 -1
- package/dist/relay-only.d.ts.map +1 -1
- package/dist/relay-only.js +7 -4
- package/dist/relay-only.js.map +1 -1
- package/dist/relay-receipt-store.d.ts +48 -17
- package/dist/relay-receipt-store.d.ts.map +1 -1
- package/dist/relay-receipt-store.js +77 -27
- package/dist/relay-receipt-store.js.map +1 -1
- package/dist/seal-escalation.d.ts.map +1 -1
- package/dist/seal-escalation.js +25 -0
- package/dist/seal-escalation.js.map +1 -1
- package/dist/seal-settle.d.ts +38 -0
- package/dist/seal-settle.d.ts.map +1 -0
- package/dist/seal-settle.js +33 -0
- package/dist/seal-settle.js.map +1 -0
- package/dist/session-assignment-parser.d.ts.map +1 -1
- package/dist/session-assignment-parser.js +5 -0
- package/dist/session-assignment-parser.js.map +1 -1
- package/dist/session-ceremony.d.ts +21 -14
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +25 -15
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-content-context.d.ts +11 -2
- package/dist/session-content-context.d.ts.map +1 -1
- package/dist/session-content-ingest.d.ts +0 -3
- package/dist/session-content-ingest.d.ts.map +1 -1
- package/dist/session-content-ingest.js +18 -111
- package/dist/session-content-ingest.js.map +1 -1
- package/dist/session-content-send.d.ts +5 -0
- package/dist/session-content-send.d.ts.map +1 -1
- package/dist/session-content-send.js +58 -0
- package/dist/session-content-send.js.map +1 -1
- package/dist/session-delivery-acks.d.ts +195 -0
- package/dist/session-delivery-acks.d.ts.map +1 -0
- package/dist/session-delivery-acks.js +674 -0
- package/dist/session-delivery-acks.js.map +1 -0
- package/dist/session-leaf-records.d.ts +19 -1
- package/dist/session-leaf-records.d.ts.map +1 -1
- package/dist/session-leaf-records.js +67 -1
- package/dist/session-leaf-records.js.map +1 -1
- package/dist/session-lifecycle.js +3 -3
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/session-node-manager.d.ts +36 -21
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +99 -29
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-node-types.d.ts +18 -12
- package/dist/session-node-types.d.ts.map +1 -1
- package/dist/session-node-types.js +1 -9
- package/dist/session-node-types.js.map +1 -1
- package/dist/session-read-handlers.d.ts.map +1 -1
- package/dist/session-read-handlers.js +125 -4
- package/dist/session-read-handlers.js.map +1 -1
- package/dist/session-records.d.ts +17 -0
- package/dist/session-records.d.ts.map +1 -1
- package/dist/session-records.js +32 -0
- package/dist/session-records.js.map +1 -1
- package/dist/session-relay-client.d.ts +103 -6
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +369 -27
- package/dist/session-relay-client.js.map +1 -1
- package/dist/session-relay.d.ts +24 -2
- package/dist/session-relay.d.ts.map +1 -1
- package/dist/session-relay.js +21 -6
- package/dist/session-relay.js.map +1 -1
- package/dist/session-schema.d.ts.map +1 -1
- package/dist/session-schema.js +43 -0
- package/dist/session-schema.js.map +1 -1
- package/dist/session-seal-leaf-store.d.ts +6 -0
- package/dist/session-seal-leaf-store.d.ts.map +1 -1
- package/dist/session-seal-leaf-store.js +21 -4
- package/dist/session-seal-leaf-store.js.map +1 -1
- package/dist/session-seal.d.ts +12 -0
- package/dist/session-seal.d.ts.map +1 -1
- package/dist/session-seal.js +92 -3
- package/dist/session-seal.js.map +1 -1
- package/dist/session-views.d.ts +1 -0
- package/dist/session-views.d.ts.map +1 -1
- package/dist/session-views.js +81 -1
- package/dist/session-views.js.map +1 -1
- package/dist/signaling-connect.d.ts +1 -0
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +0 -1
- package/dist/signaling-connect.js.map +1 -1
- package/dist/status-handler.d.ts +9 -1
- package/dist/status-handler.d.ts.map +1 -1
- package/dist/status-handler.js +5 -2
- package/dist/status-handler.js.map +1 -1
- package/dist/test-handlers.js +4 -3
- package/dist/test-handlers.js.map +1 -1
- package/dist/trust-signal-store.d.ts +4 -2
- package/dist/trust-signal-store.d.ts.map +1 -1
- package/dist/trust-signal-store.js +4 -2
- package/dist/trust-signal-store.js.map +1 -1
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-relay-client.d.ts","sourceRoot":"","sources":["../src/session-relay-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-relay-client.d.ts","sourceRoot":"","sources":["../src/session-relay-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,9 +32,28 @@ import { createHash } from "node:crypto";
|
|
|
32
32
|
import * as lp from "it-length-prefixed";
|
|
33
33
|
import { decode } from "cbor-x";
|
|
34
34
|
import { encodeCbor, decodeSealPayload, encodeStructure1, decodeStructure1, computeGenesisPrevRoot } from "@cello-protocol/protocol-types";
|
|
35
|
+
import { encodeSessionLivenessQuery, decodeSessionLivenessResponse, encodeSessionAttendanceNotice, } from "@cello-protocol/protocol-types";
|
|
35
36
|
import { verify } from "@cello-protocol/crypto";
|
|
36
37
|
import { extractErrorMessage } from "./error-message.js";
|
|
37
38
|
import { evaluateRelayAck } from "./relay-receipt-store.js";
|
|
39
|
+
/**
|
|
40
|
+
* DOD-M15-AWAYSCOPE-1 — what a liveness query answers with.
|
|
41
|
+
*
|
|
42
|
+
* `refused` is kept separate from the values rather than folded into `liveness`, because it means
|
|
43
|
+
* something different: the relay declined to say, most likely because this caller is not a
|
|
44
|
+
* participant of the session it named. The liveness is 'unknown' either way — the operator surface
|
|
45
|
+
* shows the same thing — but a caller that wants to tell "the relay would not tell me" from "the
|
|
46
|
+
* relay does not know" can, and a log line that conflated them would send the next investigation
|
|
47
|
+
* looking at the counterparty instead of at the session id.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* The key an answer is matched to its question by: the two fields the RESPONSE carries back. One
|
|
51
|
+
* relay client serves every session an agent holds there, so two concurrent queries on one stream
|
|
52
|
+
* is ordinary — and an answer routed to whatever was asked last shows the wrong counterparty's row.
|
|
53
|
+
*/
|
|
54
|
+
function livenessKey(sessionId, counterpartyPubkey) {
|
|
55
|
+
return `${Buffer.from(sessionId).toString("hex")}:${Buffer.from(counterpartyPubkey).toString("hex")}`;
|
|
56
|
+
}
|
|
38
57
|
export const RELAY_PROTOCOL_ID = "/cello/relay/1.0.0";
|
|
39
58
|
/**
|
|
40
59
|
* How long to wait before re-proving after a transport-class failure.
|
|
@@ -47,7 +66,6 @@ export const RELAY_PROTOCOL_ID = "/cello/relay/1.0.0";
|
|
|
47
66
|
*/
|
|
48
67
|
export const PROOF_RETRY_DELAY_MS = 1_000;
|
|
49
68
|
export const RELAY_AUTH_DOMAIN = "CELLO-RELAY-AUTH-v1";
|
|
50
|
-
/** Structure 1 leaf kind: 0x00 = message, 0x02 = control (matches the relay). */
|
|
51
69
|
/**
|
|
52
70
|
* DOD-WITNESS-STALL-1 — relay refusals that can NEVER resolve.
|
|
53
71
|
*
|
|
@@ -218,6 +236,7 @@ export function classifyRelayAuthRefusal(reason, extra = {}) {
|
|
|
218
236
|
...(extra.retryAfterMs !== undefined ? { retryAfterMs: extra.retryAfterMs } : {}),
|
|
219
237
|
};
|
|
220
238
|
}
|
|
239
|
+
/** Structure 1 leaf kind — these values match the relay's. Message leaf (the `cello_send` default). */
|
|
221
240
|
export const LEAF_KIND_MSG = 0x00;
|
|
222
241
|
/** Control leaf (SEAL etc.) — two distinct-sender ctrl leaves trigger directory notarization. */
|
|
223
242
|
export const LEAF_KIND_CTRL = 0x02;
|
|
@@ -384,9 +403,9 @@ export class AgentRelayClient {
|
|
|
384
403
|
/**
|
|
385
404
|
* PER-SESSION acknowledgement state (session_id hex → the position AND the content at it).
|
|
386
405
|
*
|
|
387
|
-
* `seq` is the highest relay-assigned sequence
|
|
388
|
-
* rejects `last_seen_seq > seq_counter`, so each session's
|
|
389
|
-
* high-water mark — NOT an agent-global one (which would make a newer session's first submit look
|
|
406
|
+
* `seq` is the highest relay-assigned sequence, advanced by an ack or a deliver. The relay's
|
|
407
|
+
* `seq_counter` is per session and it rejects `last_seen_seq > seq_counter`, so each session's
|
|
408
|
+
* submit MUST carry that session's own high-water mark — NOT an agent-global one (which would make a newer session's first submit look
|
|
390
409
|
* ahead and get rejected).
|
|
391
410
|
*
|
|
392
411
|
* ⚠️ `hash` IS THE SAME FACT AS `seq`, WHICH IS WHY THEY LIVE IN ONE ENTRY — 033-ACKEMIT.
|
|
@@ -431,6 +450,18 @@ export class AgentRelayClient {
|
|
|
431
450
|
* serialized on the shared stream, exactly like `#pendingRecord`, whose ack also carries no id.
|
|
432
451
|
*/
|
|
433
452
|
#pendingRelease = null;
|
|
453
|
+
/**
|
|
454
|
+
* DOD-M15-AWAYSCOPE-1 — in-flight liveness queries, keyed `sessionIdHex:counterpartyHex`.
|
|
455
|
+
*
|
|
456
|
+
* ⚠️ A MAP, NOT A SLOT, and the first version was a slot. One `AgentRelayClient` serves EVERY
|
|
457
|
+
* session an agent holds on a relay and both callers fan out with `Promise.all`, so `cello status`
|
|
458
|
+
* with two open conversations fired two queries on one stream: the second overwrote the first's
|
|
459
|
+
* resolver, the first answer resolved the WRONG session's promise, and the other waited the full
|
|
460
|
+
* submit timeout — blowing the caller's budget so every row came back unenriched. With more than
|
|
461
|
+
* one session the feature did nothing, and when it did something it could show the wrong
|
|
462
|
+
* counterparty. The key is what the RESPONSE carries, so an answer is matched to its question.
|
|
463
|
+
*/
|
|
464
|
+
#pendingLiveness = new Map();
|
|
434
465
|
/** Serializes submits so only one is in flight at a time across all sessions. */
|
|
435
466
|
#submitChain = Promise.resolve();
|
|
436
467
|
/** session_id hex → { the live node to (re)dial from, inbound leaf handler, Option-B assignment to present }. */
|
|
@@ -488,14 +519,28 @@ export class AgentRelayClient {
|
|
|
488
519
|
* point has nothing for them to anchor to and every submit on it is refused by name. Registering
|
|
489
520
|
* without a seed is therefore a real fault, not a degraded mode.
|
|
490
521
|
*/
|
|
491
|
-
genesisPrevRoot
|
|
522
|
+
genesisPrevRoot,
|
|
523
|
+
/**
|
|
524
|
+
* 069-ORDERPROOF — the relay key this session's ordering attestations verify under, from
|
|
525
|
+
* `relay_id` on the directory-signed assignment. Supplied by the caller because
|
|
526
|
+
* `session-node-manager` is where the durable copy lives, exactly as with the genesis above.
|
|
527
|
+
*
|
|
528
|
+
* ⚠️ ABSENT IS NOT A DEGRADED MODE. A session with no anchor refuses every attestation that
|
|
529
|
+
* arrives on it, by name, because the only alternative is checking a signature against a key
|
|
530
|
+
* the signer handed us.
|
|
531
|
+
*/
|
|
532
|
+
relayAnchorHex) {
|
|
492
533
|
const existing = this.#sessions.get(sessionIdHex);
|
|
493
534
|
const carriedAssignment = assignment ?? existing?.assignment;
|
|
535
|
+
// The anchor is carried forward for the same reason the assignment is: a re-registration with
|
|
536
|
+
// nothing in hand must not silently strip a session of the key it verifies against.
|
|
537
|
+
const carriedAnchor = relayAnchorHex ?? carriedAssignment?.relayPubkeyHex ?? existing?.relayAnchorHex;
|
|
494
538
|
this.#sessions.set(sessionIdHex, {
|
|
495
539
|
node,
|
|
496
540
|
onLeafDeliver: onLeafDeliver ?? (() => { }),
|
|
497
541
|
// Carry the assignment forward across re-registration; never lose a recorded flag on re-register.
|
|
498
542
|
assignment: carriedAssignment,
|
|
543
|
+
...(carriedAnchor ? { relayAnchorHex: carriedAnchor } : {}),
|
|
499
544
|
recorded: existing?.recorded ?? false,
|
|
500
545
|
recordRejected: existing?.recordRejected ?? false,
|
|
501
546
|
recordTimedOut: existing?.recordTimedOut ?? false,
|
|
@@ -560,6 +605,10 @@ export class AgentRelayClient {
|
|
|
560
605
|
const sess = this.#sessions.get(sessionIdHex);
|
|
561
606
|
if (!sess || !sess.assignment)
|
|
562
607
|
return true;
|
|
608
|
+
// Nothing to PRESENT. The carry may exist only to name the relay this session's attestations
|
|
609
|
+
// verify against; there is no frame to send and nothing to wait for, so the submit proceeds.
|
|
610
|
+
if (!sess.assignment.assignmentSignature)
|
|
611
|
+
return true;
|
|
563
612
|
if (sess.recorded)
|
|
564
613
|
return true;
|
|
565
614
|
// Terminal rejection: a relay that cleanly rejected this assignment will reject it
|
|
@@ -724,11 +773,25 @@ export class AgentRelayClient {
|
|
|
724
773
|
return Buffer.from(s1.fields.senderPubkey).equals(Buffer.from(this.#senderPubkey));
|
|
725
774
|
}
|
|
726
775
|
/**
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
776
|
+
* The relay pubkey the DIRECTORY named for a session — 069-ORDERPROOF, and the only key a relay
|
|
777
|
+
* ordering attestation is ever checked against.
|
|
778
|
+
*
|
|
779
|
+
* It rides the assignment carry, which is built from an assignment whose FROST signature this
|
|
780
|
+
* daemon verified before the session began, so it is anchored to something the relay does not
|
|
781
|
+
* control. `undefined` means no such key was recorded, and every attestation on that session is
|
|
782
|
+
* then refused for want of an anchor — never accepted against whatever key the frame supplies.
|
|
783
|
+
*/
|
|
784
|
+
#expectedRelayPubkeyHex(sessionIdHex) {
|
|
785
|
+
return this.#sessions.get(sessionIdHex)?.relayAnchorHex;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Verify a relay `hash_submit_ack`'s ordering attestation and durably store the receipt.
|
|
789
|
+
*
|
|
790
|
+
* The relay signs the session, the content hash, the position it assigned and the running root of
|
|
791
|
+
* the tree after this leaf. We verify it **against the relay key on the directory-signed
|
|
792
|
+
* assignment**, and refuse otherwise — missing, malformed and mismatched all take the one path.
|
|
793
|
+
*
|
|
794
|
+
* Returns true to REJECT the submit. A send must not settle ok on a position nothing witnessed.
|
|
732
795
|
*/
|
|
733
796
|
#captureReceipt(frame, structure1Cbor, seq) {
|
|
734
797
|
if (seq < 0 || !structure1Cbor)
|
|
@@ -746,30 +809,40 @@ export class AgentRelayClient {
|
|
|
746
809
|
}
|
|
747
810
|
const contentHash = s1.fields.contentHash;
|
|
748
811
|
const sessionId = s1.fields.sessionId;
|
|
812
|
+
const sessionIdHex = Buffer.from(sessionId).toString("hex");
|
|
749
813
|
const ev = evaluateRelayAck({
|
|
814
|
+
sessionId,
|
|
750
815
|
contentHash,
|
|
751
|
-
|
|
816
|
+
runningRoot: frame["running_root"] instanceof Uint8Array ? frame["running_root"] : undefined,
|
|
817
|
+
sessionIdHex,
|
|
752
818
|
agentPubkeyHex: this.senderPubkeyHex,
|
|
819
|
+
expectedRelayPubkeyHex: this.#expectedRelayPubkeyHex(sessionIdHex),
|
|
753
820
|
relayId: typeof frame["relay_id"] === "string" ? frame["relay_id"] : undefined,
|
|
754
821
|
relaySignature: frame["relay_signature"] instanceof Uint8Array ? frame["relay_signature"] : undefined,
|
|
755
822
|
timestamp: typeof frame["timestamp"] === "number" ? frame["timestamp"] : undefined,
|
|
756
823
|
sequenceNumber: seq,
|
|
757
824
|
});
|
|
758
825
|
switch (ev.kind) {
|
|
759
|
-
case "
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
826
|
+
case "refused":
|
|
827
|
+
/**
|
|
828
|
+
* ONE OUTCOME, and the cause is in the log rather than in the control flow.
|
|
829
|
+
*
|
|
830
|
+
* The submit is rejected for all five causes. A message whose position no assigned relay
|
|
831
|
+
* attested has no ordering evidence behind it, and letting the send settle ok would report
|
|
832
|
+
* a witnessed message that nothing witnessed. `attestation_absent` takes this path with the
|
|
833
|
+
* rest deliberately: the party who can produce the absence is the relay, which is the party
|
|
834
|
+
* the check exists to constrain, so a softer answer for absence is a mute button with our
|
|
835
|
+
* own name on it.
|
|
836
|
+
*/
|
|
837
|
+
this.#logger.warn("relay.attestation.refused", {
|
|
838
|
+
seq,
|
|
839
|
+
session: sessionIdHex,
|
|
840
|
+
cause: ev.cause,
|
|
841
|
+
hashShort: Buffer.from(contentHash).toString("hex").slice(0, 16),
|
|
842
|
+
impact: "the relay's ordering attestation for this message was refused, so this send did not " +
|
|
843
|
+
"settle and no evidence was stored. This agent holds no proof of where this message " +
|
|
844
|
+
"sits in the conversation, and nothing was recorded that could later be presented as one.",
|
|
845
|
+
});
|
|
773
846
|
return true;
|
|
774
847
|
case "store": {
|
|
775
848
|
if (!this.#receiptStore)
|
|
@@ -821,6 +894,7 @@ export class AgentRelayClient {
|
|
|
821
894
|
relayId: ev.receipt.relayId,
|
|
822
895
|
relayTimestamp: ev.receipt.timestamp,
|
|
823
896
|
relaySignatureHex: ev.receipt.signatureHex,
|
|
897
|
+
...(ev.receipt.runningRootHex ? { relayRunningRootHex: ev.receipt.runningRootHex } : {}),
|
|
824
898
|
}
|
|
825
899
|
: {}),
|
|
826
900
|
}, Date.now());
|
|
@@ -857,11 +931,77 @@ export class AgentRelayClient {
|
|
|
857
931
|
// signed-but-INVALID ACK rejects the submit so the send does not settle ok on an unverified sequence.
|
|
858
932
|
const rejectSubmit = this.#captureReceipt(frame, structure1Cbor, seq);
|
|
859
933
|
this.#settlePending(rejectSubmit
|
|
860
|
-
|
|
934
|
+
/**
|
|
935
|
+
* ONE REASON for every refusal cause — 069-ORDERPROOF Done When 3. A distinct reason per
|
|
936
|
+
* cause would be observable, and the clause requires that a missing attestation and a
|
|
937
|
+
* malformed one look the same from outside. The cause is named in the log line above.
|
|
938
|
+
*/
|
|
939
|
+
? { ok: false, reason: "relay_ack_unverified" }
|
|
861
940
|
: seq >= 0
|
|
862
941
|
? { ok: true, sequence_number: seq, structure1_cbor: structure1Cbor, structure2_cbor: structure2Cbor, sender_signature: senderSignature }
|
|
863
942
|
: { ok: false, reason: "relay_ack_malformed" });
|
|
864
943
|
}
|
|
944
|
+
else if (type === "session_liveness_response" || type === "session_liveness_refused") {
|
|
945
|
+
/**
|
|
946
|
+
* DOD-M15-AWAYSCOPE-1 — the relay's answer to "is my counterparty reachable, and is anyone
|
|
947
|
+
* attending it". Decoded through the shared codec rather than read field by field here: that
|
|
948
|
+
* codec is what refuses an attendance paired with a non-alive liveness, and re-reading the
|
|
949
|
+
* fields inline would quietly reintroduce the tolerance branch it exists to prevent.
|
|
950
|
+
*
|
|
951
|
+
* A REFUSAL resolves the waiter rather than dropping it. The relay refuses a query naming a
|
|
952
|
+
* session the caller is not in, and it deliberately does not say which of the two reasons —
|
|
953
|
+
* so the honest local answer is "unknown", not a hang until the timeout.
|
|
954
|
+
*/
|
|
955
|
+
/**
|
|
956
|
+
* ⚠️ A REFUSAL NAMES NOTHING, so it cannot be matched. The relay refuses without echoing the
|
|
957
|
+
* session or the subject — deliberately, because telling "no such session" from "not your
|
|
958
|
+
* session" is the enumeration signal `DOD-M15-RELAYAUTH-1` closed. So there is nothing to key
|
|
959
|
+
* on, and the only sound reading is that every outstanding query on this stream was refused:
|
|
960
|
+
* the refusal is a property of the CALLER, and the caller is the same for all of them.
|
|
961
|
+
*/
|
|
962
|
+
if (type === "session_liveness_refused") {
|
|
963
|
+
for (const r of this.#pendingLiveness.values())
|
|
964
|
+
r({ liveness: "unknown", observedAt: 0, refused: true });
|
|
965
|
+
this.#pendingLiveness.clear();
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
const decoded = decodeSessionLivenessResponse(encodeCbor(frame));
|
|
969
|
+
if (decoded === null) {
|
|
970
|
+
// Malformed fails exactly like missing. A frame this build cannot read is not evidence of
|
|
971
|
+
// anything, and reporting it as 'gone' would invent an observation nobody made. It also
|
|
972
|
+
// cannot be matched to a waiter, so every outstanding query is answered 'unknown' rather
|
|
973
|
+
// than left to time out on a stream that just proved it speaks a shape we cannot read.
|
|
974
|
+
this.#logger.warn("session.relay.liveness.response_malformed", {
|
|
975
|
+
relayPeerId: this.#relayPeerId,
|
|
976
|
+
impact: "the counterparty's reachability is reported as unknown, which is what it is",
|
|
977
|
+
});
|
|
978
|
+
for (const r of this.#pendingLiveness.values())
|
|
979
|
+
r({ liveness: "unknown", observedAt: 0 });
|
|
980
|
+
this.#pendingLiveness.clear();
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
// MATCHED ON WHAT THE ANSWER ITSELF NAMES. Resolving the most recent waiter instead would
|
|
984
|
+
// be right exactly once — when only one query is outstanding — and silently wrong the
|
|
985
|
+
// moment an agent holds two conversations through one relay, which is ordinary.
|
|
986
|
+
const key = livenessKey(decoded.session_id, decoded.counterparty_pubkey);
|
|
987
|
+
const r = this.#pendingLiveness.get(key);
|
|
988
|
+
if (r) {
|
|
989
|
+
this.#pendingLiveness.delete(key);
|
|
990
|
+
r({
|
|
991
|
+
liveness: decoded.liveness,
|
|
992
|
+
observedAt: decoded.observed_at,
|
|
993
|
+
...(decoded.attendance !== undefined ? { attendance: decoded.attendance } : {}),
|
|
994
|
+
...(decoded.attendance_observed_at !== undefined ? { attendanceObservedAt: decoded.attendance_observed_at } : {}),
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
// A late answer to a query that already timed out, or one nobody asked for. Dropped —
|
|
999
|
+
// handing it to a different session's waiter is exactly the defect this key exists for.
|
|
1000
|
+
this.#logger.debug("session.relay.liveness.response_unmatched", { relayPeerId: this.#relayPeerId });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
865
1005
|
else if (type === "hash_submit_error") {
|
|
866
1006
|
const reason = typeof frame["reason"] === "string" ? frame["reason"] : "relay_rejected";
|
|
867
1007
|
// Carry the relay's `detail` through — see `SubmitResult`. Reading the class and discarding
|
|
@@ -1046,6 +1186,69 @@ export class AgentRelayClient {
|
|
|
1046
1186
|
this.#logger.warn("relay.seal_leaf.counterparty.capture_skipped", { seq, session: sidHex, hasS2: !!structure2Cbor, hasS1: s1.length > 0, hasSender: !!senderHex });
|
|
1047
1187
|
}
|
|
1048
1188
|
}
|
|
1189
|
+
/**
|
|
1190
|
+
* ─── THE RECIPIENT KEEPS THE ORDERING PROOF TOO — 069-ORDERPROOF unit 2 ──────────────────
|
|
1191
|
+
*
|
|
1192
|
+
* Before this, the relay's attestation went to the SENDER alone, on the submit
|
|
1193
|
+
* acknowledgement. The recipient held no proof of where a single message of the conversation
|
|
1194
|
+
* sat. That made one party's copy the only copy, which is the dependency this order exists to
|
|
1195
|
+
* remove wearing a different coat.
|
|
1196
|
+
*
|
|
1197
|
+
* It is verified here exactly as on the send path — against the relay key the DIRECTORY named
|
|
1198
|
+
* for this session, never `relay_id` on the frame — and an attestation that does not verify is
|
|
1199
|
+
* refused and NOT stored. Nothing else changes: the leaf is still delivered to the session, the
|
|
1200
|
+
* acknowledgement still advances, and no tree, root or leaf count is touched. This is evidence
|
|
1201
|
+
* recorded beside the chain, never in it.
|
|
1202
|
+
*
|
|
1203
|
+
* Our own echoed leaf comes through here too. Storing it is a no-op — the ack path already
|
|
1204
|
+
* wrote the same row at the same position, and the store is INSERT OR IGNORE — but it is not
|
|
1205
|
+
* skipped, because the echo is the only copy a leaf COUNTER-SUBMITTED by the counterparty
|
|
1206
|
+
* ever produces for us.
|
|
1207
|
+
*/
|
|
1208
|
+
if (this.#receiptStore && seq >= 0 && deliveredS1.ok) {
|
|
1209
|
+
const ev = evaluateRelayAck({
|
|
1210
|
+
sessionId: deliveredS1.fields.sessionId,
|
|
1211
|
+
contentHash: deliveredS1.fields.contentHash,
|
|
1212
|
+
runningRoot: frame["running_root"] instanceof Uint8Array ? frame["running_root"] : undefined,
|
|
1213
|
+
sessionIdHex: sidHex,
|
|
1214
|
+
agentPubkeyHex: this.senderPubkeyHex,
|
|
1215
|
+
expectedRelayPubkeyHex: this.#expectedRelayPubkeyHex(sidHex),
|
|
1216
|
+
relayId: typeof frame["relay_id"] === "string" ? frame["relay_id"] : undefined,
|
|
1217
|
+
relaySignature: frame["relay_signature"] instanceof Uint8Array ? frame["relay_signature"] : undefined,
|
|
1218
|
+
timestamp: typeof frame["timestamp"] === "number" ? frame["timestamp"] : undefined,
|
|
1219
|
+
sequenceNumber: seq,
|
|
1220
|
+
});
|
|
1221
|
+
if (ev.kind === "refused") {
|
|
1222
|
+
/**
|
|
1223
|
+
* LOUD, AND THE DELIVERY STILL LANDS. This is the one place the two halves differ, and
|
|
1224
|
+
* deliberately: on the send path a refusal means OUR message has no witness, so the send
|
|
1225
|
+
* must not settle. Here the counterparty's message has already been written and read —
|
|
1226
|
+
* dropping it would let a relay silence a conversation by withholding its own signature,
|
|
1227
|
+
* which hands the party the check constrains a far better weapon than the one it takes
|
|
1228
|
+
* away. So the message arrives and the evidence is recorded as absent.
|
|
1229
|
+
*/
|
|
1230
|
+
this.#logger.warn("relay.attestation.delivered.refused", {
|
|
1231
|
+
seq,
|
|
1232
|
+
session: sidHex,
|
|
1233
|
+
cause: ev.cause,
|
|
1234
|
+
impact: "this message arrived, but the relay's proof of WHERE it sits in the conversation did " +
|
|
1235
|
+
"not, so this agent cannot later show a third party that this message was ordered here. " +
|
|
1236
|
+
"The message itself is unaffected and is in the transcript.",
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
try {
|
|
1241
|
+
this.#receiptStore.store(ev.receipt, Date.now());
|
|
1242
|
+
}
|
|
1243
|
+
catch (err) {
|
|
1244
|
+
// A durable-evidence write failure is LOUD: the relay never re-sends a delivery, so a
|
|
1245
|
+
// swallowed write permanently loses this side's only copy of the ordering proof.
|
|
1246
|
+
this.#logger.error("relay.attestation.delivered.store_failed", {
|
|
1247
|
+
seq, session: sidHex, error: extractErrorMessage(err),
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1049
1252
|
const session = this.#sessions.get(sidHex);
|
|
1050
1253
|
if (session && seq >= 0) {
|
|
1051
1254
|
session.onLeafDeliver({
|
|
@@ -1446,6 +1649,139 @@ export class AgentRelayClient {
|
|
|
1446
1649
|
* ⚠️ **THE FRAME CARRIES NO PEER ID, deliberately.** The relay frees the peer this connection
|
|
1447
1650
|
* authenticated as. A peer id on the wire would let any registered agent free another's.
|
|
1448
1651
|
*/
|
|
1652
|
+
/**
|
|
1653
|
+
* DOD-M15-AWAYSCOPE-1 — tell this relay whether anyone is attending this agent, for one session.
|
|
1654
|
+
*
|
|
1655
|
+
* ── WHY THIS EXISTS AT ALL ──────────────────────────────────────────────────────────────────────
|
|
1656
|
+
*
|
|
1657
|
+
* An unattended agent used to answer inbound messages with its away greeting. That greeting took a
|
|
1658
|
+
* hash-chain leaf inside a live conversation, and on session `e7dd3f43…` it cost two operators a
|
|
1659
|
+
* completed conversation's receipt, permanently. The fact itself is worth telling — a counterparty
|
|
1660
|
+
* mid-exchange genuinely needs to know whether to wait — but it is a fact ABOUT the session, not a
|
|
1661
|
+
* sentence IN it. So it rides here instead, and takes no leaf.
|
|
1662
|
+
*
|
|
1663
|
+
* ── FIRE AND FORGET, AND THAT IS THE DESIGN ─────────────────────────────────────────────────────
|
|
1664
|
+
*
|
|
1665
|
+
* Nothing is awaited and the relay sends no ack. The notice may be dropped, duplicated or arrive
|
|
1666
|
+
* out of order; `observed_at` settles it, last-write-wins. A failure changes nothing the operator
|
|
1667
|
+
* is doing — the counterparty reads "unknown" until the next notice, which is what is true.
|
|
1668
|
+
*
|
|
1669
|
+
* ⚠️ NO RETRY, NO QUEUE. Anything that made this reliable would want ordering, ordering wants a
|
|
1670
|
+
* witness, and a witnessed position is how machine chatter reached the leaf set. A status hint
|
|
1671
|
+
* that is allowed to be lost is the safe shape.
|
|
1672
|
+
*/
|
|
1673
|
+
announceAttendance(node, sessionId, attendance) {
|
|
1674
|
+
if (this.#closed)
|
|
1675
|
+
return;
|
|
1676
|
+
void (async () => {
|
|
1677
|
+
try {
|
|
1678
|
+
if (!(await this.#ensureConnected(node)))
|
|
1679
|
+
return;
|
|
1680
|
+
const stream = this.#stream;
|
|
1681
|
+
if (!stream)
|
|
1682
|
+
return;
|
|
1683
|
+
stream.send(lp.encode.single(encodeSessionAttendanceNotice({
|
|
1684
|
+
type: "session_attendance_notice",
|
|
1685
|
+
session_id: sessionId,
|
|
1686
|
+
attendance,
|
|
1687
|
+
// OUR clock, stated plainly. The relay bounds it against its own and refuses a value too
|
|
1688
|
+
// far ahead rather than clamping — clamping would turn a hostile timestamp into a valid
|
|
1689
|
+
// recent one, and this client is the code an adversary gets to rewrite.
|
|
1690
|
+
observed_at: Date.now(),
|
|
1691
|
+
})));
|
|
1692
|
+
this.#logger.debug("session.relay.attendance.announced", {
|
|
1693
|
+
relayPeerId: this.#relayPeerId,
|
|
1694
|
+
sessionId: Buffer.from(sessionId).toString("hex").slice(0, 16),
|
|
1695
|
+
attendance,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
catch (err) {
|
|
1699
|
+
this.#logger.debug("session.relay.attendance.announce_failed", {
|
|
1700
|
+
relayPeerId: this.#relayPeerId,
|
|
1701
|
+
error: extractErrorMessage(err),
|
|
1702
|
+
impact: "the counterparty reads 'unknown' for this agent until the next notice lands",
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
})();
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* DOD-M15-AWAYSCOPE-1 — ask the relay about the counterparty: reachable, and attended?
|
|
1709
|
+
*
|
|
1710
|
+
* Two facts with two producers, which is why they are separate fields. `liveness` is the RELAY's
|
|
1711
|
+
* observation — it holds that agent's standing connection or it does not. `attendance` is the far
|
|
1712
|
+
* DAEMON's assertion about itself, which the relay was told and repeats; absent whenever
|
|
1713
|
+
* `liveness` is not 'alive', because a daemon nobody can reach asserts nothing.
|
|
1714
|
+
*
|
|
1715
|
+
* ⚠️ EVERY FAILURE ANSWERS 'unknown', never 'gone'. A timeout, a dead stream, a refusal and an
|
|
1716
|
+
* unparseable frame all mean "this side learned nothing"; reporting any as `gone` invents an
|
|
1717
|
+
* observation nobody made, and `gone` is the value that tells an operator their peer has left.
|
|
1718
|
+
*/
|
|
1719
|
+
/**
|
|
1720
|
+
* Test-only seams for the liveness query path, which cannot be reached any other way.
|
|
1721
|
+
*
|
|
1722
|
+
* ⚠️ THEY EXIST BECAUSE A ONE-SESSION FAKE HID A REAL DEFECT. Every other test of this feature
|
|
1723
|
+
* stubs `queryLiveness` outright, so the code that matches an ANSWER to its QUESTION never ran —
|
|
1724
|
+
* and it was wrong: a single resolver slot handed one session's answer to another session's
|
|
1725
|
+
* promise the moment an agent held two conversations through one relay, which is ordinary.
|
|
1726
|
+
* Reaching that code needs a live stream and a dispatched frame, and both are private.
|
|
1727
|
+
*
|
|
1728
|
+
* Named `ForTest` like `__setLastActivityAtForTest` and `patchRelayClientForTest`, and doing
|
|
1729
|
+
* nothing production does not: one installs the stream a dial would have produced, the other
|
|
1730
|
+
* feeds the reader loop a frame the relay would have sent.
|
|
1731
|
+
*/
|
|
1732
|
+
installStreamForTest(stream) { this.#stream = stream; }
|
|
1733
|
+
dispatchForTest(frame) { this.#dispatch(frame); }
|
|
1734
|
+
async queryLiveness(sessionId, counterpartyPubkey) {
|
|
1735
|
+
const unknown = { liveness: "unknown", observedAt: 0 };
|
|
1736
|
+
if (this.#closed)
|
|
1737
|
+
return unknown;
|
|
1738
|
+
/**
|
|
1739
|
+
* ⚠️ IT DOES NOT DIAL — the one place in this client that refuses to connect. Every other caller
|
|
1740
|
+
* here does the operator's work; this one serves a READ (`cello status`, `cello sessions`), and
|
|
1741
|
+
* a status command that opens a connection as a side effect changes what it is reporting on. It
|
|
1742
|
+
* would also make a read of a dozen sessions pay a dial each, against a budget measured in what
|
|
1743
|
+
* a person waits at a terminal. The cost is small and precise: right after a daemon restart a
|
|
1744
|
+
* status read says 'unknown' instead of dialling; the first real message establishes the stream.
|
|
1745
|
+
*/
|
|
1746
|
+
const stream = this.#stream;
|
|
1747
|
+
if (!stream)
|
|
1748
|
+
return unknown;
|
|
1749
|
+
let resolveLiveness;
|
|
1750
|
+
const answer = new Promise((r) => { resolveLiveness = r; });
|
|
1751
|
+
const key = livenessKey(sessionId, counterpartyPubkey);
|
|
1752
|
+
this.#pendingLiveness.set(key, resolveLiveness);
|
|
1753
|
+
try {
|
|
1754
|
+
stream.send(lp.encode.single(encodeSessionLivenessQuery({
|
|
1755
|
+
type: "session_liveness_query",
|
|
1756
|
+
session_id: sessionId,
|
|
1757
|
+
counterparty_pubkey: counterpartyPubkey,
|
|
1758
|
+
})));
|
|
1759
|
+
}
|
|
1760
|
+
catch (err) {
|
|
1761
|
+
this.#pendingLiveness.delete(key);
|
|
1762
|
+
this.#logger.debug("session.relay.liveness.query_failed", {
|
|
1763
|
+
relayPeerId: this.#relayPeerId, error: extractErrorMessage(err),
|
|
1764
|
+
});
|
|
1765
|
+
return unknown;
|
|
1766
|
+
}
|
|
1767
|
+
let timer;
|
|
1768
|
+
const timeout = new Promise((r) => { timer = setTimeout(() => r("timeout"), HASH_SUBMIT_TIMEOUT_MS); });
|
|
1769
|
+
try {
|
|
1770
|
+
const result = await Promise.race([answer, timeout]);
|
|
1771
|
+
if (result === "timeout") {
|
|
1772
|
+
// NOT a stream reset, for the same reason a release timeout is not: a liveness query is not
|
|
1773
|
+
// ordered against anything, so tearing the shared stream down would cost every sibling
|
|
1774
|
+
// session its in-flight submits to tidy up one status read.
|
|
1775
|
+
this.#pendingLiveness.delete(key);
|
|
1776
|
+
this.#logger.debug("session.relay.liveness.query_timeout", { relayPeerId: this.#relayPeerId });
|
|
1777
|
+
return unknown;
|
|
1778
|
+
}
|
|
1779
|
+
return result;
|
|
1780
|
+
}
|
|
1781
|
+
finally {
|
|
1782
|
+
clearTimeout(timer);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1449
1785
|
async releaseReservation(node) {
|
|
1450
1786
|
if (this.#closed)
|
|
1451
1787
|
return false;
|
|
@@ -1761,6 +2097,13 @@ export class AgentRelayClient {
|
|
|
1761
2097
|
if (r)
|
|
1762
2098
|
r(false);
|
|
1763
2099
|
}
|
|
2100
|
+
// DOD-M15-AWAYSCOPE-1: and an in-flight liveness query, so a status read does not wait its
|
|
2101
|
+
// whole timeout on a stream that is already gone. "unknown" is the honest answer.
|
|
2102
|
+
{
|
|
2103
|
+
for (const r of this.#pendingLiveness.values())
|
|
2104
|
+
r({ liveness: "unknown", observedAt: 0 });
|
|
2105
|
+
this.#pendingLiveness.clear();
|
|
2106
|
+
}
|
|
1764
2107
|
// A pure-receiver session issues no submit, so it would never trigger a re-dial
|
|
1765
2108
|
// after the node that owned the stream is torn down. If sessions remain, proactively
|
|
1766
2109
|
// re-establish from any still-live registered session node so queued leaf_delivers
|
|
@@ -2436,7 +2779,6 @@ export class AgentRelayClient {
|
|
|
2436
2779
|
}
|
|
2437
2780
|
}
|
|
2438
2781
|
}
|
|
2439
|
-
/** The highest relay-assigned sequence observed for a given session (ack or deliver). */
|
|
2440
2782
|
/**
|
|
2441
2783
|
* Advance this session's acknowledgement from a message that ARRIVED — 033-ACKEMIT review F1.
|
|
2442
2784
|
*
|