@cello-protocol/daemon 0.0.49 → 0.0.50
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/daemon.d.ts.map +1 -1
- package/dist/daemon.js +66 -15
- package/dist/daemon.js.map +1 -1
- package/dist/park-envelope.d.ts +93 -0
- package/dist/park-envelope.d.ts.map +1 -0
- package/dist/park-envelope.js +172 -0
- package/dist/park-envelope.js.map +1 -0
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +13 -1
- package/dist/retry-queue.js.map +1 -1
- package/dist/session-node-manager.d.ts +31 -13
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +104 -26
- package/dist/session-node-manager.js.map +1 -1
- package/package.json +4 -4
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAQrB,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAQrB,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAuB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAQ/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA0InF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AA2CD,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAE1D,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA6kM7E"}
|
package/dist/daemon.js
CHANGED
|
@@ -48,12 +48,13 @@ import { RegistrationManager } from "./registration-manager.js";
|
|
|
48
48
|
import { DaemonRegistrationContext } from "./registration-context.js";
|
|
49
49
|
import { DbRegistrationPersistence, DbIdentityStore } from "./db-identity-store.js";
|
|
50
50
|
import { DbManifestVersionStore } from "./manifest-version-store-db.js";
|
|
51
|
-
import { verify as ed25519Verify,
|
|
51
|
+
import { verify as ed25519Verify, generateKLocalSeed, InMemoryKeyProvider, hash as cryptoHash } from "@cello-protocol/crypto";
|
|
52
52
|
import { attemptSealUpgrade as attemptSealUpgradeImpl, verifyUpgradeConfirmedCert } from "./seal-upgrade.js";
|
|
53
53
|
import { upgradeAbsentToRecovered, hasAbsentParticipant } from "./seal-receipt-upgrade.js";
|
|
54
54
|
// CELLO-M7-MSG-001 (AC-013/AC-018): the single application content-size cap, enforced
|
|
55
55
|
// at the send point here (the receive point lives in the transport content decode).
|
|
56
56
|
import { MAX_CONTENT_BYTES, computeGenesisPrevRoot, buildAgentRevocationTbs, MONIKER_RE, validateMoniker } from "@cello-protocol/protocol-types";
|
|
57
|
+
import { sealParkEnvelope } from "./park-envelope.js";
|
|
57
58
|
import { resolveCelloEnv, createTransportSelector, isProductionVariant, } from "./transport-composition.js";
|
|
58
59
|
import { selectAdvertisedAddress } from "./transport-selector.js";
|
|
59
60
|
import { parseSessionAssignment, sessionRequestErrorReason, parseDiscoveryLookupResult, discoveryLookupErrorReason, extractOfferedMoniker, resolveOutboundMoniker } from "./session-assignment-parser.js";
|
|
@@ -1508,7 +1509,7 @@ export async function startDaemon(config) {
|
|
|
1508
1509
|
// the session's relay endpoint; we seal the content to the recipient (E2E — the relay never sees
|
|
1509
1510
|
// plaintext, INV-3) and deposit it to that relay's store-and-forward mailbox via the standing
|
|
1510
1511
|
// receiver node. The recipient pulls + recovers it at the witnessed sequence (R1) on next online.
|
|
1511
|
-
sessionNodeManager.setContentParkHook(async ({ sessionId, recipientPubkeyHex, relayPeerId, relayAddrs, contentHashHex, content, structure1Cbor, structure2Cbor }) => {
|
|
1512
|
+
sessionNodeManager.setContentParkHook(async ({ agentName, sessionId, recipientPubkeyHex, relayPeerId, relayAddrs, contentHashHex, content, structure1Cbor, structure2Cbor }) => {
|
|
1512
1513
|
const node = sessionNodeManager.getStandingReceiverNode();
|
|
1513
1514
|
if (!node) {
|
|
1514
1515
|
const reason = "standing_receiver_unavailable";
|
|
@@ -1519,10 +1520,32 @@ export async function startDaemon(config) {
|
|
|
1519
1520
|
// parked when nothing was ever deposited.
|
|
1520
1521
|
return { ok: false, reason };
|
|
1521
1522
|
}
|
|
1523
|
+
// SEC-1: sign the entry as the SENDING agent. Without a key we cannot produce an envelope the
|
|
1524
|
+
// recipient will accept, so fail LOUD rather than depositing something that will be refused on
|
|
1525
|
+
// recovery (a deposit the recipient must reject is worse than no deposit — it looks delivered).
|
|
1526
|
+
const senderKp = keyProviders.get(agentName);
|
|
1527
|
+
if (!senderKp) {
|
|
1528
|
+
const reason = "signing_key_unavailable";
|
|
1529
|
+
logger.warn("content.park.deposit.failed", { agentName, sessionId, contentHash: contentHashHex, reason });
|
|
1530
|
+
return { ok: false, reason };
|
|
1531
|
+
}
|
|
1522
1532
|
const recipientPubkey = Buffer.from(recipientPubkeyHex, "hex");
|
|
1533
|
+
const contentHashBytes = Buffer.from(contentHashHex, "hex");
|
|
1534
|
+
logger.info("content.park.signed", { agentName, sessionId, contentHash: contentHashHex });
|
|
1523
1535
|
// DOD-MSG-4 (2b): seal the ORDERING ENVELOPE (content + the relay's signed Structure2), not bare
|
|
1524
1536
|
// content, so the parked entry is self-ordering on recover. The relay still holds only ciphertext.
|
|
1525
|
-
|
|
1537
|
+
// SEC-1: sealParkEnvelope is the SOLE producer — it signs (sender's K_local, over the
|
|
1538
|
+
// session/recipient/content binding) and seals in one place, so the two park sites cannot drift
|
|
1539
|
+
// apart on what gets signed.
|
|
1540
|
+
const ciphertext = await sealParkEnvelope({
|
|
1541
|
+
signer: senderKp,
|
|
1542
|
+
sessionIdHex: sessionId,
|
|
1543
|
+
recipientPubkey,
|
|
1544
|
+
contentHash: contentHashBytes,
|
|
1545
|
+
content,
|
|
1546
|
+
structure1Cbor,
|
|
1547
|
+
structure2Cbor,
|
|
1548
|
+
});
|
|
1526
1549
|
const client = new ContentParkClient({ relayPeerId, relayAddrs: [...relayAddrs], logger });
|
|
1527
1550
|
const res = await client.deposit(node, {
|
|
1528
1551
|
recipientPubkey,
|
|
@@ -1573,11 +1596,29 @@ export async function startDaemon(config) {
|
|
|
1573
1596
|
const node = sessionNodeManager.getStandingReceiverNode(record.agent_name);
|
|
1574
1597
|
if (!node)
|
|
1575
1598
|
return { parked: false, error: "standing_receiver_unavailable" };
|
|
1599
|
+
// SEC-1: the crash backstop must sign too — an unsigned re-park would be REFUSED on recovery,
|
|
1600
|
+
// which would turn the message-loss backstop into a message-loss cause. This is why the SEC-1
|
|
1601
|
+
// signature binds to the sender's own K_local and NOT to the relay's ordering record: the
|
|
1602
|
+
// ordering record is not persisted in retry_queue (see below) and so cannot be reproduced here,
|
|
1603
|
+
// but the KEY can — the owning agent still holds it after a crash. Hence: NO schema migration.
|
|
1604
|
+
const senderKp = keyProviders.get(ownerName);
|
|
1605
|
+
if (!senderKp)
|
|
1606
|
+
return { parked: false, error: "signing_key_unavailable" };
|
|
1576
1607
|
const recipientPubkey = Buffer.from(record.counterparty_pubkey, "hex");
|
|
1608
|
+
const contentHashBytes = Buffer.from(entry.contentHashHex, "hex");
|
|
1609
|
+
logger.info("content.park.signed", { agentName: ownerName, sessionId: entry.sessionId, contentHash: entry.contentHashHex, source: "startup_flush" });
|
|
1577
1610
|
// DOD-MSG-4 (2b): seal the envelope shape too (content only — the durable awaiting queue does not
|
|
1578
1611
|
// persist the ordering record, so a crash-backstop re-park recovers in arrival order; the common
|
|
1579
1612
|
// live-park path above carries the full Structure2). Keeps ONE envelope format on the recover side.
|
|
1580
|
-
|
|
1613
|
+
// SEC-1: same sole producer as the live hook — the backstop signs from the persisted
|
|
1614
|
+
// (sessionId, recipient, contentHash), which is why it survives a crash with NO schema migration.
|
|
1615
|
+
const ciphertext = await sealParkEnvelope({
|
|
1616
|
+
signer: senderKp,
|
|
1617
|
+
sessionIdHex: entry.sessionId,
|
|
1618
|
+
recipientPubkey,
|
|
1619
|
+
contentHash: contentHashBytes,
|
|
1620
|
+
content: entry.contentBlob,
|
|
1621
|
+
});
|
|
1581
1622
|
const client = new ContentParkClient({ relayPeerId: ep.relayPeerId, relayAddrs: [...ep.relayAddrs], logger });
|
|
1582
1623
|
const res = await client.deposit(node, {
|
|
1583
1624
|
recipientPubkey,
|
|
@@ -3706,22 +3747,28 @@ export async function startDaemon(config) {
|
|
|
3706
3747
|
const client = new ContentParkClient({ relayPeerId, relayAddrs, logger });
|
|
3707
3748
|
const entries = await client.pull(node, Buffer.from(recipientPubkey, "hex"), kp);
|
|
3708
3749
|
let recovered = 0;
|
|
3750
|
+
// SEC-1 / review M2: a REFUSED entry must not vanish behind `ok:true`. Report it — an operator
|
|
3751
|
+
// seeing {ok:true, pulled:3, recovered:0} would otherwise have three messages evaporate with the
|
|
3752
|
+
// only explanation buried in the daemon log. This is both the injection signal and, given the
|
|
3753
|
+
// deliberate no-tolerant-window rollout, the lagging-peer signal.
|
|
3754
|
+
const refusals = [];
|
|
3755
|
+
// AC7: one correlationId per recover flow, threaded through every event it emits.
|
|
3756
|
+
const correlationId = randomUUID();
|
|
3709
3757
|
for (const e of entries) {
|
|
3710
3758
|
const unsealed = await kp.openContentSeal(e.ciphertext);
|
|
3711
3759
|
if (!unsealed) {
|
|
3712
3760
|
logger.warn("content.recover.unseal_failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex });
|
|
3713
3761
|
continue;
|
|
3714
3762
|
}
|
|
3715
|
-
//
|
|
3716
|
-
//
|
|
3717
|
-
//
|
|
3718
|
-
//
|
|
3719
|
-
|
|
3763
|
+
// SEC-1: authenticate THEN ingest — fused in recoverParkedEntry, which is now the ONLY way
|
|
3764
|
+
// parked content can enter the transcript. It verifies the sender's signature over
|
|
3765
|
+
// (session_id, recipient_pubkey, content_hash) and that the signer is this session's
|
|
3766
|
+
// counterparty, and it still verifies + records the DOD-MSG-4 ordering record when present.
|
|
3767
|
+
// Relay deposit is unauthenticated and the seal is anonymous, so WITHOUT this gate anyone
|
|
3768
|
+
// holding the recipient's public key — the relay above all — could inject content that gets
|
|
3769
|
+
// attributed to the honest counterparty and then notarized by the bilateral seal.
|
|
3720
3770
|
const contentHashBytes = Buffer.from(e.contentHashHex, "hex");
|
|
3721
|
-
|
|
3722
|
-
sessionNodeManager.recordOrderingRecord(recipientAgent.name, e.sessionIdHex, env.structure1Cbor, env.structure2Cbor, contentHashBytes);
|
|
3723
|
-
}
|
|
3724
|
-
const ingest = await sessionNodeManager.ingestReceivedContent(recipientAgent.name, e.sessionIdHex, env.content, contentHashBytes);
|
|
3771
|
+
const ingest = await sessionNodeManager.recoverParkedEntry(recipientAgent.name, e.sessionIdHex, Buffer.from(recipientPubkey, "hex"), unsealed, contentHashBytes, correlationId);
|
|
3725
3772
|
if (ingest.ok && ingest.held) {
|
|
3726
3773
|
// DOD-MSG-4 (review finding #4): a held entry is NOT yet an appended leaf — its sequence is
|
|
3727
3774
|
// the FUTURE canonical index, not a completed recovery. Do not count it as recovered; log it
|
|
@@ -3758,10 +3805,14 @@ export async function startDaemon(config) {
|
|
|
3758
3805
|
}
|
|
3759
3806
|
}
|
|
3760
3807
|
else {
|
|
3761
|
-
|
|
3808
|
+
// SEC-1 (M2): carry the refusal out to the caller, not just to the log. Deliberately still
|
|
3809
|
+
// NOT confirm-deleted — a forgery must not be able to evict itself from the mailbox, and a
|
|
3810
|
+
// genuine v1-peer message must not be destroyed by our refusing it.
|
|
3811
|
+
refusals.push({ contentHash: e.contentHashHex, sessionId: e.sessionIdHex, reason: ingest.reason });
|
|
3812
|
+
logger.warn("content.recover.ingest_failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, reason: ingest.reason, correlationId });
|
|
3762
3813
|
}
|
|
3763
3814
|
}
|
|
3764
|
-
return { ok: true, recovered, pulled: entries.length };
|
|
3815
|
+
return { ok: true, recovered, pulled: entries.length, refused: refusals.length, refusals };
|
|
3765
3816
|
}
|
|
3766
3817
|
// DOD-MSG-4 (auto-recover-on-reconnect): when an agent comes online, drain its parked mailbox from
|
|
3767
3818
|
// every relay it has sessions on — symmetric to the SENDER's flushAwaitingContent. Without this,
|