@cello-protocol/daemon 0.0.211 → 0.0.213
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 +79 -6
- 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 +57 -27
- 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-flag.d.ts +58 -0
- package/dist/document-flag.d.ts.map +1 -0
- package/dist/document-flag.js +70 -0
- package/dist/document-flag.js.map +1 -0
- package/dist/document-gate-wiring.d.ts +61 -0
- package/dist/document-gate-wiring.d.ts.map +1 -0
- package/dist/document-gate-wiring.js +119 -0
- package/dist/document-gate-wiring.js.map +1 -0
- 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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- 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/ipc-server.d.ts.map +1 -1
- package/dist/ipc-server.js +52 -2
- package/dist/ipc-server.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 +75 -17
- package/dist/relay-receipt-store.d.ts.map +1 -1
- package/dist/relay-receipt-store.js +109 -27
- package/dist/relay-receipt-store.js.map +1 -1
- package/dist/seal-carried-close.d.ts +37 -0
- package/dist/seal-carried-close.d.ts.map +1 -0
- package/dist/seal-carried-close.js +179 -0
- package/dist/seal-carried-close.js.map +1 -0
- package/dist/seal-certified-root-check.d.ts +37 -0
- package/dist/seal-certified-root-check.d.ts.map +1 -0
- package/dist/seal-certified-root-check.js +127 -0
- package/dist/seal-certified-root-check.js.map +1 -0
- package/dist/seal-coordinator.d.ts +6 -1
- package/dist/seal-coordinator.d.ts.map +1 -1
- package/dist/seal-coordinator.js +2 -1
- package/dist/seal-coordinator.js.map +1 -1
- package/dist/seal-escalation.d.ts.map +1 -1
- package/dist/seal-escalation.js +54 -0
- package/dist/seal-escalation.js.map +1 -1
- package/dist/seal-local-terminus.d.ts +41 -0
- package/dist/seal-local-terminus.d.ts.map +1 -0
- package/dist/seal-local-terminus.js +170 -0
- package/dist/seal-local-terminus.js.map +1 -0
- package/dist/seal-relay-silence.d.ts +51 -0
- package/dist/seal-relay-silence.d.ts.map +1 -0
- package/dist/seal-relay-silence.js +76 -0
- package/dist/seal-relay-silence.js.map +1 -0
- 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 +28 -1
- package/dist/session-leaf-records.d.ts.map +1 -1
- package/dist/session-leaf-records.js +86 -1
- package/dist/session-leaf-records.js.map +1 -1
- package/dist/session-lifecycle.js +12 -12
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/session-node-manager.d.ts +49 -22
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +113 -42
- 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 +380 -41
- 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 +23 -8
- 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 +30 -32
- package/dist/session-seal.d.ts.map +1 -1
- package/dist/session-seal.js +145 -116
- 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/dist/vocabulary.d.ts +22 -4
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +66 -21
- package/dist/vocabulary.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CELLO Daemon — RelayReceiptStore (M8B DOD-RELAYSIG-1)
|
|
2
|
+
* CELLO Daemon — RelayReceiptStore (DOD-M15-ORDERPROOF-1, was M8B DOD-RELAYSIG-1)
|
|
3
3
|
*
|
|
4
4
|
* The relay is the ordering/witness authority: it assigns a canonical sequence number to each submitted
|
|
5
|
-
* content-hash leaf and signs an
|
|
5
|
+
* content-hash leaf and signs an ATTESTATION over it (relay-node `buildRelayAckTbs`). This store is the
|
|
6
6
|
* client's IMMUTABLE record of those signed attestations — durable evidence that the relay assigned a
|
|
7
7
|
* specific sequence to a specific hash at a specific time. The receipt is what lets the client later prove
|
|
8
8
|
* (or dispute) the relay's ordering, and is the building block the client carries to the directory at seal
|
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Verification is an Ed25519 check that MUST stay byte-compatible with the relay's signer.
|
|
12
12
|
*
|
|
13
|
+
* ⚠️ **THE KEY IT IS CHECKED AGAINST IS THE POINT — 069-ORDERPROOF.** This module used to verify the
|
|
14
|
+
* signature against `relay_id`, a key carried inside the very frame being checked, and its own comment
|
|
15
|
+
* called that "SELF-CONSISTENCY". It is not a check: anyone who can write the frame can mint a key, sign
|
|
16
|
+
* any ordering they like with it, and put the key in the field we verify against. The key now comes from
|
|
17
|
+
* `relay_id` on the **directory-signed `SessionAssignment`**, which is inside the FROST-signed TBS the
|
|
18
|
+
* client already verifies — something neither the relay nor either participant can rewrite.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ **AND ABSENT IS NOT FINE.** Missing, malformed and mismatched are ONE outcome (`refused`), because
|
|
21
|
+
* a party holding no attestation for a message is in exactly the state a corrupted one leaves them in.
|
|
22
|
+
* Whoever can cause the absence must not get a softer answer than whoever can cause the corruption — and
|
|
23
|
+
* the party who can cause the absence is the relay, the party the check exists to constrain.
|
|
24
|
+
*
|
|
13
25
|
* IMMUTABILITY (SI-003): the unique unit of a relay attestation is a POSITION — (agent, session, sequence)
|
|
14
26
|
* — not a content hash. The same plaintext ("ok") legitimately produces the same content hash at DIFFERENT
|
|
15
27
|
* sequences (per-session relay counter; repeated content within a session), so the table is keyed on the
|
|
@@ -20,31 +32,92 @@
|
|
|
20
32
|
* Crypto: Ed25519 (RFC 8032), SHA-256 (FIPS 180-4). TBS = SHA-256(hash_bytes || seq_BE4 || ts_BE8).
|
|
21
33
|
*/
|
|
22
34
|
import { verify, buildRelayAckTbs } from "@cello-protocol/crypto";
|
|
35
|
+
import { decodeStructure1 } from "@cello-protocol/protocol-types";
|
|
23
36
|
/**
|
|
24
|
-
* Verify a relay
|
|
25
|
-
*
|
|
26
|
-
*
|
|
37
|
+
* Verify a relay ordering attestation against a relay pubkey the CALLER supplies — and the caller
|
|
38
|
+
* must have got it from the directory-signed assignment, never from the frame.
|
|
39
|
+
*
|
|
40
|
+
* Returns true iff every field is well-formed and the 64-byte Ed25519 signature verifies over
|
|
41
|
+
* TBS = SHA-256(DOMAIN ‖ session_id ‖ content_hash ‖ seq_BE4 ‖ running_root ‖ ts_BE8) — the same
|
|
42
|
+
* statement the relay signs. A forged sequence, a swapped session or a rewritten prefix all change
|
|
43
|
+
* the TBS, so each fails.
|
|
44
|
+
*
|
|
45
|
+
* A wrong-length field returns FALSE rather than throwing: these bytes come off a wire the relay
|
|
46
|
+
* controls, and a malformed length must refuse this attestation, not take down the frame handler.
|
|
27
47
|
*/
|
|
28
|
-
export function verifyRelayAck(contentHash, sequenceNumber, timestamp, signature, relayPubkey) {
|
|
48
|
+
export function verifyRelayAck(sessionId, contentHash, sequenceNumber, runningRoot, timestamp, signature, relayPubkey) {
|
|
29
49
|
if (signature.length !== 64)
|
|
30
50
|
return false;
|
|
31
|
-
|
|
51
|
+
if (sessionId.length !== 16 || contentHash.length !== 32 || runningRoot.length !== 32)
|
|
52
|
+
return false;
|
|
53
|
+
const tbs = buildRelayAckTbs(sessionId, contentHash, sequenceNumber, runningRoot, timestamp);
|
|
32
54
|
return verify(relayPubkey, tbs, signature);
|
|
33
55
|
}
|
|
34
56
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
57
|
+
* Read back the leaf we just submitted, so the relay's attestation has something to be checked
|
|
58
|
+
* against — the step BEFORE `evaluateRelayAck`, and the one that decides whether the check can run
|
|
59
|
+
* at all.
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ "COULD NOT RUN THE CHECK" IS NOT "THE CHECK PASSED" — 069-ORDERPROOF.
|
|
62
|
+
*
|
|
63
|
+
* Both failures here used to be waved through by the caller, which settled the send `ok` having
|
|
64
|
+
* verified nothing: the absent-versus-verified collapse this unit exists to remove, reproduced at
|
|
65
|
+
* the one point where nothing was left to verify with. These are our OWN just-signed bytes, so a
|
|
66
|
+
* hostile relay cannot steer into either — unreachable in practice, which is a different claim from
|
|
67
|
+
* safe to wave through, and the distinction is the one that decays under later edits.
|
|
68
|
+
*
|
|
69
|
+
* `"none"` is the honest third answer: no submit was made, so no attestation is owed and none is
|
|
70
|
+
* missing. It is the only one of the three that does not reject.
|
|
71
|
+
*/
|
|
72
|
+
export function readSubmittedLeaf(structure1Cbor, seq) {
|
|
73
|
+
if (seq < 0)
|
|
74
|
+
return { kind: "none" };
|
|
75
|
+
if (!structure1Cbor)
|
|
76
|
+
return { kind: "unreadable", event: "relay.receipt.leaf_absent" };
|
|
77
|
+
// Structure 1 = [version, content_hash(32), sender_pubkey(32), session_id(16), last_seen_seq, ts],
|
|
78
|
+
// plus last_seen_hash(32) at index 6 on a v2 claim (020-ACKHASH). content_hash is index 1 and
|
|
79
|
+
// session_id index 3 in both.
|
|
80
|
+
const s1 = decodeStructure1(structure1Cbor);
|
|
81
|
+
// The reason is carried because "we cannot read what we just wrote" and "we wrote a layout we
|
|
82
|
+
// cannot name" are different faults with the same symptom.
|
|
83
|
+
if (!s1.ok)
|
|
84
|
+
return { kind: "unreadable", event: "relay.receipt.undecodable_leaf", reason: s1.reason };
|
|
85
|
+
return { kind: "ok", contentHash: s1.fields.contentHash, sessionId: s1.fields.sessionId };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Evaluate a relay ordering attestation: decide whether it yields a storable, signature-verified
|
|
89
|
+
* receipt. This is the verify-gates-store decision in isolation, so it is unit-testable without the
|
|
90
|
+
* relay stream.
|
|
91
|
+
*
|
|
92
|
+
* `expectedRelayPubkeyHex` is the anchor and it is REQUIRED in substance: `undefined` refuses. It
|
|
93
|
+
* must come from `relay_id` on an assignment whose FROST signature has been verified.
|
|
38
94
|
*/
|
|
39
95
|
export function evaluateRelayAck(params) {
|
|
40
|
-
const { contentHash, sessionIdHex, agentPubkeyHex, relayId, relaySignature, timestamp, sequenceNumber } = params;
|
|
41
|
-
if (!
|
|
42
|
-
return { kind: "
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
96
|
+
const { sessionId, contentHash, runningRoot, sessionIdHex, agentPubkeyHex, expectedRelayPubkeyHex, relayId, relaySignature, timestamp, sequenceNumber, } = params;
|
|
97
|
+
if (!expectedRelayPubkeyHex || !/^[0-9a-fA-F]{64}$/.test(expectedRelayPubkeyHex)) {
|
|
98
|
+
return { kind: "refused", cause: "no_anchor" };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* THE FRAME'S OWN CLAIM IS COMPARED, NEVER TRUSTED. The signature below is verified against the
|
|
102
|
+
* directory's key regardless, so this comparison adds no security on its own — what it adds is a
|
|
103
|
+
* NAMED cause. A relay that has been replaced mid-session produces a valid signature under a key
|
|
104
|
+
* the directory never named, and "this is not the relay you were assigned" is the sentence an
|
|
105
|
+
* operator can act on. `undefined` is not a mismatch: a relay omitting the field does not get to
|
|
106
|
+
* skip the check, it simply gets checked against the anchor with no label.
|
|
107
|
+
*/
|
|
108
|
+
if (relayId !== undefined) {
|
|
109
|
+
if (!/^[0-9a-fA-F]{64}$/.test(relayId))
|
|
110
|
+
return { kind: "refused", cause: "bad_relay_id" };
|
|
111
|
+
if (relayId.toLowerCase() !== expectedRelayPubkeyHex.toLowerCase()) {
|
|
112
|
+
return { kind: "refused", cause: "relay_not_assigned" };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!relaySignature || timestamp === undefined || !runningRoot) {
|
|
116
|
+
return { kind: "refused", cause: "attestation_absent" };
|
|
117
|
+
}
|
|
118
|
+
const relayPubkey = new Uint8Array(Buffer.from(expectedRelayPubkeyHex, "hex"));
|
|
119
|
+
if (!verifyRelayAck(sessionId, contentHash, sequenceNumber, runningRoot, timestamp, relaySignature, relayPubkey)) {
|
|
120
|
+
return { kind: "refused", cause: "signature_invalid" };
|
|
48
121
|
}
|
|
49
122
|
return {
|
|
50
123
|
kind: "store",
|
|
@@ -52,11 +125,12 @@ export function evaluateRelayAck(params) {
|
|
|
52
125
|
hashHex: Buffer.from(contentHash).toString("hex"),
|
|
53
126
|
agentPubkeyHex,
|
|
54
127
|
sessionIdHex,
|
|
55
|
-
relayId,
|
|
56
|
-
relayPubkeyHex:
|
|
128
|
+
relayId: expectedRelayPubkeyHex,
|
|
129
|
+
relayPubkeyHex: expectedRelayPubkeyHex,
|
|
57
130
|
sequenceNumber,
|
|
58
131
|
timestamp,
|
|
59
132
|
signatureHex: Buffer.from(relaySignature).toString("hex"),
|
|
133
|
+
runningRootHex: Buffer.from(runningRoot).toString("hex"),
|
|
60
134
|
},
|
|
61
135
|
};
|
|
62
136
|
}
|
|
@@ -71,6 +145,9 @@ const CREATE_RELAY_RECEIPTS_SQL = `
|
|
|
71
145
|
relay_timestamp INTEGER NOT NULL,
|
|
72
146
|
signature_hex TEXT NOT NULL,
|
|
73
147
|
stored_at INTEGER NOT NULL,
|
|
148
|
+
-- 069-ORDERPROOF: the running root the relay signed alongside the position (nullable — rows
|
|
149
|
+
-- written before this order have none, and they stay readable and sealable exactly as they are).
|
|
150
|
+
running_root_hex TEXT,
|
|
74
151
|
-- FED-OPTIONB-SEAL-001: per-leaf carry bytes for the unilateral-seal offline rebuild (nullable).
|
|
75
152
|
structure2_cbor BLOB,
|
|
76
153
|
structure1_cbor BLOB,
|
|
@@ -100,6 +177,10 @@ export class RelayReceiptStore {
|
|
|
100
177
|
["structure2_cbor", "BLOB"],
|
|
101
178
|
["structure1_cbor", "BLOB"],
|
|
102
179
|
["leaf_kind", "INTEGER"],
|
|
180
|
+
// 069-ORDERPROOF. NULLABLE, like the three above: a session that predates this order keeps
|
|
181
|
+
// every receipt it already holds, opens, reads and seals unchanged, and simply has no root
|
|
182
|
+
// recorded for those positions.
|
|
183
|
+
["running_root_hex", "TEXT"],
|
|
103
184
|
]) {
|
|
104
185
|
if (!cols.has(name)) {
|
|
105
186
|
this.#db.exec(`ALTER TABLE relay_ack_receipts ADD COLUMN ${name} ${decl}`);
|
|
@@ -116,11 +197,11 @@ export class RelayReceiptStore {
|
|
|
116
197
|
const existing = this.get(receipt.agentPubkeyHex, receipt.sessionIdHex, receipt.sequenceNumber);
|
|
117
198
|
const info = this.#db
|
|
118
199
|
.prepare(`INSERT OR IGNORE INTO relay_ack_receipts
|
|
119
|
-
(agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, stored_at, structure2_cbor, structure1_cbor, leaf_kind)
|
|
120
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
200
|
+
(agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, stored_at, structure2_cbor, structure1_cbor, leaf_kind, running_root_hex)
|
|
201
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
121
202
|
.run(receipt.agentPubkeyHex, receipt.sessionIdHex, receipt.sequenceNumber, receipt.hashHex, receipt.relayId, receipt.relayPubkeyHex, receipt.timestamp, receipt.signatureHex, storedAtMs,
|
|
122
203
|
// FED-OPTIONB-SEAL-001: carry bytes (null when absent — receipt-only rows).
|
|
123
|
-
receipt.structure2Cbor ? Buffer.from(receipt.structure2Cbor) : null, receipt.structure1Cbor ? Buffer.from(receipt.structure1Cbor) : null, receipt.leafKind ?? null);
|
|
204
|
+
receipt.structure2Cbor ? Buffer.from(receipt.structure2Cbor) : null, receipt.structure1Cbor ? Buffer.from(receipt.structure1Cbor) : null, receipt.leafKind ?? null, receipt.runningRootHex ?? null);
|
|
124
205
|
const wrote = Number(info.changes) > 0;
|
|
125
206
|
if (!wrote && existing && existing.hashHex !== receipt.hashHex) {
|
|
126
207
|
// EQUIVOCATION: a relay re-attesting a DIFFERENT hash at an already-recorded position. The first
|
|
@@ -138,7 +219,7 @@ export class RelayReceiptStore {
|
|
|
138
219
|
/** The stored receipt at an attestation position (agent, session, sequence), or undefined. */
|
|
139
220
|
get(agentPubkeyHex, sessionIdHex, sequenceNumber) {
|
|
140
221
|
const row = this.#db
|
|
141
|
-
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex
|
|
222
|
+
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, running_root_hex
|
|
142
223
|
FROM relay_ack_receipts WHERE agent_pubkey = ? AND session_id = ? AND sequence_number = ?`)
|
|
143
224
|
.get(agentPubkeyHex, sessionIdHex, sequenceNumber);
|
|
144
225
|
return row ? this.#rowToReceipt(row) : undefined;
|
|
@@ -147,11 +228,11 @@ export class RelayReceiptStore {
|
|
|
147
228
|
getAll(agentPubkeyHex, sessionIdHex) {
|
|
148
229
|
const rows = sessionIdHex
|
|
149
230
|
? this.#db
|
|
150
|
-
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex
|
|
231
|
+
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, running_root_hex
|
|
151
232
|
FROM relay_ack_receipts WHERE agent_pubkey = ? AND session_id = ? ORDER BY sequence_number ASC`)
|
|
152
233
|
.all(agentPubkeyHex, sessionIdHex)
|
|
153
234
|
: this.#db
|
|
154
|
-
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex
|
|
235
|
+
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, running_root_hex
|
|
155
236
|
FROM relay_ack_receipts WHERE agent_pubkey = ? ORDER BY session_id, sequence_number ASC`)
|
|
156
237
|
.all(agentPubkeyHex);
|
|
157
238
|
return rows.map((r) => this.#rowToReceipt(r));
|
|
@@ -164,7 +245,7 @@ export class RelayReceiptStore {
|
|
|
164
245
|
*/
|
|
165
246
|
getSealLeaves(agentPubkeyHex, sessionIdHex) {
|
|
166
247
|
const rows = this.#db
|
|
167
|
-
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, structure2_cbor, structure1_cbor, leaf_kind
|
|
248
|
+
.prepare(`SELECT agent_pubkey, session_id, sequence_number, hash_hex, relay_id, relay_pubkey_hex, relay_timestamp, signature_hex, running_root_hex, structure2_cbor, structure1_cbor, leaf_kind
|
|
168
249
|
FROM relay_ack_receipts
|
|
169
250
|
WHERE agent_pubkey = ? AND session_id = ? AND structure2_cbor IS NOT NULL AND structure1_cbor IS NOT NULL AND leaf_kind IS NOT NULL
|
|
170
251
|
ORDER BY sequence_number ASC`)
|
|
@@ -186,6 +267,7 @@ export class RelayReceiptStore {
|
|
|
186
267
|
sequenceNumber: row.sequence_number,
|
|
187
268
|
timestamp: row.relay_timestamp,
|
|
188
269
|
signatureHex: row.signature_hex,
|
|
270
|
+
...(typeof row.running_root_hex === "string" ? { runningRootHex: row.running_root_hex } : {}),
|
|
189
271
|
};
|
|
190
272
|
}
|
|
191
273
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-receipt-store.js","sourceRoot":"","sources":["../src/relay-receipt-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"relay-receipt-store.js","sourceRoot":"","sources":["../src/relay-receipt-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type SealCarryLeaf } from "./session-seal-leaf-store.js";
|
|
2
|
+
import type { SessionSealContext } from "./session-seal.js";
|
|
3
|
+
import type { DaemonDatabase } from "./sqlcipher-db.js";
|
|
4
|
+
/** What the carried close needs from the seal path, and nothing more. */
|
|
5
|
+
export interface CarriedCloseDeps {
|
|
6
|
+
readonly ctx: SessionSealContext;
|
|
7
|
+
readonly db: DaemonDatabase | null;
|
|
8
|
+
readonly getSealCarry: (agentPubkeyHex: string, sessionIdHex: string) => SealCarryLeaf[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* ═══ 070-CARRIEDSEAL — THE CLOSE THAT DOES NOT NEED ANYONE ELSE TO BE AWAKE ═══
|
|
12
|
+
*
|
|
13
|
+
* Returns the escalation values for a seal closed over evidence this side already holds, or
|
|
14
|
+
* `null` when this is not that situation and the caller should fail exactly as it did before.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ `null` IS THE DEFAULT, AND THAT IS THE SAFETY PROPERTY. Every way out of here that is not a
|
|
17
|
+
* successfully written terminus returns `null`, so the caller's original refusal stands with its
|
|
18
|
+
* original reason. A bug in this method costs the receipt this order was written to save; it
|
|
19
|
+
* cannot turn a relay's ruling into a seal.
|
|
20
|
+
*
|
|
21
|
+
* The first gate is the important one: `RELAY_GAVE_NO_ANSWER` separates a relay that was not there
|
|
22
|
+
* from one that considered the leaf and declined it. Routing around a ruling is the thing a trust
|
|
23
|
+
* layer must never do, and "the submit failed" alone does not tell the two apart.
|
|
24
|
+
*/
|
|
25
|
+
export declare function closeOverCarriedEvidence(deps: CarriedCloseDeps, agentName: string, sessionId: string, cause: string, correlationId?: string,
|
|
26
|
+
/**
|
|
27
|
+
* The relay's standing verdict about THIS AGENT, when there is a relay client to ask. The reason
|
|
28
|
+
* string cannot carry it — see `relayRefusedUs` — so it is passed alongside.
|
|
29
|
+
*/
|
|
30
|
+
standingRefusal?: {
|
|
31
|
+
reason: string;
|
|
32
|
+
} | null): Promise<{
|
|
33
|
+
ok: true;
|
|
34
|
+
sequenceNumber: number;
|
|
35
|
+
reportedRootHex: string;
|
|
36
|
+
} | null>;
|
|
37
|
+
//# sourceMappingURL=seal-carried-close.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal-carried-close.d.ts","sourceRoot":"","sources":["../src/seal-carried-close.ts"],"names":[],"mappings":"AAUA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,aAAa,EAAE,CAAC;CAC1F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM;AACtB;;;GAGG;AACH,eAAe,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAC1C,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAuJ/E"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DOD-M15-CARRIEDSEAL-1` — THE CLOSE THAT DOES NOT NEED ANYONE ELSE TO BE AWAKE.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `session-seal.ts` rather than written beside it, for the reason that file's own
|
|
5
|
+
* ratchet exists: a seal path that keeps growing in one place is how the second implementation of a
|
|
6
|
+
* seal gets written next to the first. There is exactly one caller.
|
|
7
|
+
*/
|
|
8
|
+
import { decodeStructure1 } from "@cello-protocol/protocol-types";
|
|
9
|
+
import { buildLocalSealTerminus } from "./seal-local-terminus.js";
|
|
10
|
+
import { RELAY_GAVE_NO_ANSWER, relayRefusedUs } from "./seal-relay-silence.js";
|
|
11
|
+
import { SessionSealLeafStore } from "./session-seal-leaf-store.js";
|
|
12
|
+
/**
|
|
13
|
+
* ═══ 070-CARRIEDSEAL — THE CLOSE THAT DOES NOT NEED ANYONE ELSE TO BE AWAKE ═══
|
|
14
|
+
*
|
|
15
|
+
* Returns the escalation values for a seal closed over evidence this side already holds, or
|
|
16
|
+
* `null` when this is not that situation and the caller should fail exactly as it did before.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ `null` IS THE DEFAULT, AND THAT IS THE SAFETY PROPERTY. Every way out of here that is not a
|
|
19
|
+
* successfully written terminus returns `null`, so the caller's original refusal stands with its
|
|
20
|
+
* original reason. A bug in this method costs the receipt this order was written to save; it
|
|
21
|
+
* cannot turn a relay's ruling into a seal.
|
|
22
|
+
*
|
|
23
|
+
* The first gate is the important one: `RELAY_GAVE_NO_ANSWER` separates a relay that was not there
|
|
24
|
+
* from one that considered the leaf and declined it. Routing around a ruling is the thing a trust
|
|
25
|
+
* layer must never do, and "the submit failed" alone does not tell the two apart.
|
|
26
|
+
*/
|
|
27
|
+
export async function closeOverCarriedEvidence(deps, agentName, sessionId, cause, correlationId,
|
|
28
|
+
/**
|
|
29
|
+
* The relay's standing verdict about THIS AGENT, when there is a relay client to ask. The reason
|
|
30
|
+
* string cannot carry it — see `relayRefusedUs` — so it is passed alongside.
|
|
31
|
+
*/
|
|
32
|
+
standingRefusal) {
|
|
33
|
+
if (!RELAY_GAVE_NO_ANSWER.has(cause))
|
|
34
|
+
return null;
|
|
35
|
+
if (relayRefusedUs(standingRefusal)) {
|
|
36
|
+
/**
|
|
37
|
+
* THE RELAY ANSWERED, AND THE ANSWER WAS NO. Sealing over carried evidence here would be
|
|
38
|
+
* routing around a ruling — the relay authenticated this agent and then refused it, and that
|
|
39
|
+
* refusal is a decision about us rather than an absence of anyone to ask.
|
|
40
|
+
*/
|
|
41
|
+
deps.ctx.logger.warn("session.seal.local_terminus.relay_refused", {
|
|
42
|
+
agentName, sessionId, cause, relayReason: standingRefusal?.reason, correlationId,
|
|
43
|
+
impact: "the relay REFUSED this agent rather than being unreachable, so this side did not sign its own closing leaf. No receipt was produced and the session is untouched — closing around a refusal would put a record on the chain that the witness declined to witness.",
|
|
44
|
+
guidance: "Fix what the relay refused (cello status names it — an expired credential, a rate limit, or a capacity cap), then close again. If the relay is genuinely gone rather than refusing, the close will proceed on its own.",
|
|
45
|
+
});
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const kp = deps.ctx.getKeyProvider(agentName);
|
|
49
|
+
if (!kp) {
|
|
50
|
+
// Not a silent skip: without this agent's key nothing here can be signed, and the operator's
|
|
51
|
+
// action ("start the agent") is different from every other reason a close fails.
|
|
52
|
+
deps.ctx.logger.warn("session.seal.local_terminus.key_unavailable", {
|
|
53
|
+
agentName, sessionId, cause, correlationId,
|
|
54
|
+
impact: "the relay could not be reached and this agent's key is not loaded, so the closing leaf could not be signed here either. No receipt was produced and nothing about the session changed.",
|
|
55
|
+
guidance: "Start the agent (cello_start_agent) and close again.",
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const ownPubkey = new Uint8Array(await kp.getPublicKey());
|
|
60
|
+
const ownPubkeyHex = Buffer.from(ownPubkey).toString("hex");
|
|
61
|
+
const carry = deps.getSealCarry(ownPubkeyHex, sessionId);
|
|
62
|
+
/**
|
|
63
|
+
* The session id comes out of the LEAVES, not out of the caller's argument. The directory checks
|
|
64
|
+
* the terminus against the same session the carried leaves name, so reading it from anywhere
|
|
65
|
+
* else would let the two disagree — and this path exists precisely for the case where no live
|
|
66
|
+
* relay registration is around to be consulted.
|
|
67
|
+
*/
|
|
68
|
+
const first = carry[0] ? decodeStructure1(carry[0].structure1Cbor) : null;
|
|
69
|
+
if (!first?.ok) {
|
|
70
|
+
/**
|
|
71
|
+
* TWO DIFFERENT FACTS, AND THEY SEND THE OPERATOR TO DIFFERENT PLACES. An empty carry means the
|
|
72
|
+
* conversation was never witnessed; an undecodable first leaf means the record IS here and is
|
|
73
|
+
* damaged. Reporting the second as the first sends someone looking for something that is not
|
|
74
|
+
* missing.
|
|
75
|
+
*/
|
|
76
|
+
const empty = carry.length === 0;
|
|
77
|
+
deps.ctx.logger.info("session.seal.local_terminus.no_carry", {
|
|
78
|
+
agentName, sessionId, cause, carryLength: carry.length,
|
|
79
|
+
reason: empty ? "seal_carry_empty" : "seal_carry_unreadable", correlationId,
|
|
80
|
+
impact: empty
|
|
81
|
+
? "no witnessed leaf is held for this session, so there is no record to close over and no receipt to produce."
|
|
82
|
+
: "this session's first recorded leaf is on disk and cannot be decoded, so the record cannot be chained onto. The conversation is NOT missing — cello_transcript still shows it — but no receipt can be produced from this side.",
|
|
83
|
+
});
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* ⚠️ NO GENESIS, NO TERMINUS — and this used to be `?? new Uint8Array(32)`.
|
|
88
|
+
*
|
|
89
|
+
* The genesis is the session's agreed starting point, and it is what BOTH chain links fall back to
|
|
90
|
+
* for a party that has not spoken yet. Substituting thirty-two zero bytes does not fail: the
|
|
91
|
+
* directory's walk skips `prev_own_hash` for a sender's first leaf and never checks
|
|
92
|
+
* `last_seen_hash` at all, so the leaf notarizes and the counterparty — verifying against the real
|
|
93
|
+
* starting point — is the one who finds a link that does not reconcile. A silent substitution
|
|
94
|
+
* whose cost lands on the other party.
|
|
95
|
+
*
|
|
96
|
+
* The send path already refuses for exactly this reason ("no seed means no send… the honest
|
|
97
|
+
* outcome is to refuse rather than sign a chain anchored to nothing"). So does this.
|
|
98
|
+
*/
|
|
99
|
+
const genesis = deps.ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId);
|
|
100
|
+
if (!genesis) {
|
|
101
|
+
deps.ctx.logger.warn("session.seal.local_terminus.no_genesis", {
|
|
102
|
+
agentName, sessionId, cause, correlationId,
|
|
103
|
+
impact: "this session has no recorded starting point on this machine, so a closing leaf written here could not link to anything and its place in the conversation could never be proven. Nothing was signed and the session is untouched.",
|
|
104
|
+
guidance: "Close WITH your counterparty instead — a bilateral close does not need this value. cello_transcript still shows the conversation either way.",
|
|
105
|
+
});
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const built = await buildLocalSealTerminus({
|
|
109
|
+
carry,
|
|
110
|
+
ownPubkeyHex,
|
|
111
|
+
ownPubkey,
|
|
112
|
+
sessionIdBytes: first.fields.sessionId,
|
|
113
|
+
genesis,
|
|
114
|
+
finalRootHex: deps.ctx.getSessionTreeRootHex(agentName, sessionId),
|
|
115
|
+
closeTimestamp: Date.now(),
|
|
116
|
+
sign: async (bytes) => new Uint8Array(await kp.sign(bytes)),
|
|
117
|
+
});
|
|
118
|
+
if (!built.ok) {
|
|
119
|
+
/**
|
|
120
|
+
* ⚠️ AN ALREADY-PLACED TERMINUS IS A SUCCESS BEING REPORTED AS A FAILURE — review MEDIUM-3.
|
|
121
|
+
*
|
|
122
|
+
* `seal_carry_own_ctrl_present` means a previous close already wrote our closing leaf. Returning
|
|
123
|
+
* `null` sent the caller to its original refusal, so an operator who closed successfully and then
|
|
124
|
+
* closed again — or whose reconnect retried for them — was told the RELAY was unavailable, for a
|
|
125
|
+
* session whose closing leaf is on their own disk. The terminus builder's own docblock says the
|
|
126
|
+
* caller must reuse the one that is there; nobody did.
|
|
127
|
+
*
|
|
128
|
+
* Reused rather than rebuilt: the leaf on disk is the one whose bytes were signed, and rebuilding
|
|
129
|
+
* would produce a different timestamp and therefore a different record.
|
|
130
|
+
*/
|
|
131
|
+
if (built.reason === "seal_carry_own_ctrl_present") {
|
|
132
|
+
const existing = carry.find((l) => l.leafKind === 0x02 && l.senderPubkeyHex === ownPubkeyHex);
|
|
133
|
+
const priorS1 = existing ? decodeStructure1(existing.structure1Cbor) : null;
|
|
134
|
+
if (existing && priorS1?.ok) {
|
|
135
|
+
const priorRootHex = deps.ctx.getSessionTree(agentName, sessionId)
|
|
136
|
+
.rootWithAppendedHex(Buffer.from(priorS1.fields.contentHash).toString("hex"));
|
|
137
|
+
deps.ctx.logger.info("session.seal.local_terminus.reused", {
|
|
138
|
+
agentName, sessionId, cause, sequenceNumber: existing.sequenceNumber, correlationId,
|
|
139
|
+
impact: "this side had already signed its closing leaf for this session, so the earlier one is carried again rather than a second being written. A second closing leaf would make the session unsealable by any directory, permanently.",
|
|
140
|
+
});
|
|
141
|
+
return { ok: true, sequenceNumber: existing.sequenceNumber, reportedRootHex: priorRootHex };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
deps.ctx.logger.warn("session.seal.local_terminus.refused", {
|
|
145
|
+
agentName, sessionId, cause, reason: built.reason, correlationId,
|
|
146
|
+
impact: "the relay could not be reached and the record this side holds cannot be closed over as it stands, so no receipt was produced.",
|
|
147
|
+
});
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (!deps.ctx.sealLeafStore && deps.db) {
|
|
151
|
+
deps.ctx.sealLeafStore = new SessionSealLeafStore(deps.db, deps.ctx.logger);
|
|
152
|
+
}
|
|
153
|
+
const wrote = deps.ctx.sealLeafStore?.store(ownPubkeyHex, sessionId, built.leaf, Date.now()) ?? false;
|
|
154
|
+
if (!wrote) {
|
|
155
|
+
/**
|
|
156
|
+
* The store is INSERT OR IGNORE on the position, so a false means something already occupies
|
|
157
|
+
* it. Refusing is the only safe answer: carrying a leaf that is not the one on disk would
|
|
158
|
+
* present a record this daemon does not actually hold.
|
|
159
|
+
*/
|
|
160
|
+
deps.ctx.logger.warn("session.seal.local_terminus.position_taken", {
|
|
161
|
+
agentName, sessionId, sequenceNumber: built.leaf.sequenceNumber, correlationId,
|
|
162
|
+
impact: "a leaf is already recorded at the position the closing leaf would take, so it was not written and no receipt was produced.",
|
|
163
|
+
});
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
// The reported root is the certified-root domain — content hashes, this leaf appended. The same
|
|
167
|
+
// derivation the relay path uses one method down, so both close paths report the same value for
|
|
168
|
+
// the same leaf set.
|
|
169
|
+
const reportedRootHex = deps.ctx.getSessionTree(agentName, sessionId).rootWithAppendedHex(built.contentHashHex);
|
|
170
|
+
deps.ctx.logger.info("session.seal.local_terminus.placed", {
|
|
171
|
+
agentName, sessionId, cause,
|
|
172
|
+
sequenceNumber: built.leaf.sequenceNumber,
|
|
173
|
+
carryLength: carry.length,
|
|
174
|
+
correlationId,
|
|
175
|
+
impact: "the relay did not answer, so this side signed its own closing leaf over the record it already holds. The seal proceeds on carried evidence and needs no relay.",
|
|
176
|
+
});
|
|
177
|
+
return { ok: true, sequenceNumber: built.leaf.sequenceNumber, reportedRootHex };
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=seal-carried-close.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal-carried-close.js","sourceRoot":"","sources":["../src/seal-carried-close.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SealCarryLeaf } from "./session-seal-leaf-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* REBUILD THE CERTIFIED ROOT FROM THIS DAEMON'S OWN LEAVES — `DOD-M15-SEALWIRE-1` bullet 2.
|
|
4
|
+
*
|
|
5
|
+
* The receipt used to prove only that the directory signed SOMETHING: the client took the sealed
|
|
6
|
+
* root off the wire, confirmed the directory's signature over those bytes, stored it, and threw
|
|
7
|
+
* away the root it had computed a step earlier. At co-signing time that means **your key signs a
|
|
8
|
+
* root you never checked.**
|
|
9
|
+
*
|
|
10
|
+
* Bullet 1 moved the certified root into the content-hash domain, which is the domain this daemon
|
|
11
|
+
* can actually rebuild — each carry leaf's `content_hash` is the leaf hash (RFC 6962 §2.1 "hash"
|
|
12
|
+
* leaves are used as-is), and the carry is ordered by the relay's canonical `sequence_number`,
|
|
13
|
+
* which is the order the directory rebuilds in.
|
|
14
|
+
*
|
|
15
|
+
* ─── Why this returns "cannot judge" instead of always answering ───────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* A root comparison that is WRONG makes every session unsealable, and force-abandon — no receipt —
|
|
18
|
+
* becomes the only exit. That failure is worse than the one being guarded, and this file already
|
|
19
|
+
* carries two comments saying so about other gates.
|
|
20
|
+
*
|
|
21
|
+
* The carry is this daemon's view, and it is not guaranteed complete at the instant a certificate
|
|
22
|
+
* arrives: the counterparty's SEAL ctrl leaf is what TRIGGERS the seal, so it may not have been
|
|
23
|
+
* witnessed here yet. So completeness is checked FIRST, against the certificate's own leaf count.
|
|
24
|
+
* A short carry means this daemon cannot judge — which is a different answer from "the roots
|
|
25
|
+
* disagree", and conflating them would turn a local timing gap into an accusation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyCertifiedRoot(getSealCarry: (agentPubkeyHex: string, sessionIdHex: string) => SealCarryLeaf[], agentPubkeyHex: string, sessionIdHex: string, certifiedRoot: Uint8Array, certifiedLeafCount: number): {
|
|
28
|
+
verdict: "match";
|
|
29
|
+
} | {
|
|
30
|
+
verdict: "mismatch";
|
|
31
|
+
ownRootHex: string | null;
|
|
32
|
+
detail: string;
|
|
33
|
+
} | {
|
|
34
|
+
verdict: "cannot_judge";
|
|
35
|
+
reason: string;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=seal-certified-root-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal-certified-root-check.d.ts","sourceRoot":"","sources":["../src/seal-certified-root-check.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,aAAa,EAAE,EAC/E,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,UAAU,EACzB,kBAAkB,EAAE,MAAM,GACzB;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA6FzI"}
|