@cello-protocol/daemon 0.0.22 → 0.0.24

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.
@@ -100,7 +100,11 @@ async function main() {
100
100
  // discovers the directory endpoint via GET ${CELLO_DIRECTORY_URL}/bootstrap (the
101
101
  // proven M6 path); startDaemon builds the real signalingConnect from it + the
102
102
  // primary agent identity.
103
- const directoryEndpointResolver = createDirectoryEndpointResolver({ logger });
103
+ // FINDING-4: this resolver is wrapped by the daemon's roster-aware failover resolver, which owns
104
+ // ALL fallback semantics (roster + sticky). staleFallback:false makes it report a dead primary as
105
+ // null on a fresh /bootstrap failure — WITHOUT it, the wrapper would keep receiving the stale dead
106
+ // endpoint and never fail over (the exact live kill-primary bug). The roster is the real fallback.
107
+ const directoryEndpointResolver = createDirectoryEndpointResolver({ logger, staleFallback: false });
104
108
  // M7 J-AUTH (DOD-AUTH-1/2): the consortium-manifest hardening is OPT-IN. When the
105
109
  // operator (or the live harness) configures a manifest, the daemon loads + verifies
106
110
  // it (threshold officer signatures + validity window) and verifies the directory's
@@ -1 +1 @@
1
- {"version":3,"file":"cello-daemon.js","sourceRoot":"","sources":["../../src/bin/cello-daemon.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cello-daemon.js","sourceRoot":"","sources":["../../src/bin/cello-daemon.ts"],"names":[],"mappings":"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"}
@@ -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,EASrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAqB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAM/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAqInF,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;AAyCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAwjI7E"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EASrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAM/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;AAyCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAwwI7E"}
package/dist/daemon.js CHANGED
@@ -45,6 +45,7 @@ import { DbRegistrationPersistence, DbIdentityStore } from "./db-identity-store.
45
45
  import { DbManifestVersionStore } from "./manifest-version-store-db.js";
46
46
  import { verify as ed25519Verify, sealToRecipient, generateKLocalSeed, InMemoryKeyProvider, hash as cryptoHash } from "@cello-protocol/crypto";
47
47
  import { attemptSealUpgrade as attemptSealUpgradeImpl, verifyUpgradeConfirmedCert } from "./seal-upgrade.js";
48
+ import { upgradeAbsentToRecovered, hasAbsentParticipant } from "./seal-receipt-upgrade.js";
48
49
  // CELLO-M7-MSG-001 (AC-013/AC-018): the single application content-size cap, enforced
49
50
  // at the send point here (the receive point lives in the transport content decode).
50
51
  import { MAX_CONTENT_BYTES, computeGenesisPrevRoot, buildAgentRevocationTbs } from "@cello-protocol/protocol-types";
@@ -52,10 +53,10 @@ import { resolveCelloEnv, createTransportSelector, isProductionVariant, } from "
52
53
  import { selectAdvertisedAddress } from "./transport-selector.js";
53
54
  import { parseSessionAssignment, sessionRequestErrorReason } from "./session-assignment-parser.js";
54
55
  import { wireSessionCeremonyHandler, wireSessionOfferHandler, wireSealCeremonyHandler, verifyUnilateralCertificate, verifyBilateralSealCertificate, runAgentRefresh } from "./session-ceremony.js";
55
- import { reDeriveFrontiers, findInflatedFrontier } from "./seal-frontier-verify.js";
56
+ import { reDeriveFrontiers, findInflatedFrontier, checkUnilateralFrontier } from "./seal-frontier-verify.js";
56
57
  import { LocalAutoNatStub } from "@cello-protocol/transport";
57
58
  import { startHttpManifestPoll } from "./http-manifest-poll.js";
58
- import { resolveDirectoryUrl, manifestNodesToEndpoints } from "./directory-bootstrap.js";
59
+ import { resolveDirectoryUrl, manifestNodesToEndpoints, createRosterAwareEndpointResolver, } from "./directory-bootstrap.js";
59
60
  /**
60
61
  * M7-SESSION-001 (H-1): canonical byte encoding of a SEAL-INTERRUPTED leaf for
61
62
  * Ed25519 signing/verification. Field order is fixed and deterministic. Both the
@@ -330,6 +331,25 @@ export async function startDaemon(config) {
330
331
  const m = manifestProvider?.getCurrentManifest();
331
332
  return m ? await manifestNodesToEndpoints(m.nodes, { logger }) : null;
332
333
  };
334
+ // FINDING-4: roster-aware directory failover (bootstrap SPOF fix). The injected
335
+ // directoryEndpointResolver only ever probes the single configured node (CELLO_DIRECTORY_URL),
336
+ // so a down primary stranded the client at startup even though it held the other nodes'
337
+ // addresses. Wrap it with the consortium roster so the signaling dialer — and the ceremony
338
+ // endpoint that shares this same instance — route AROUND a down primary (primary-first,
339
+ // sticky-until-fail, randomized fallback), honoring the sovereign-node REDUNDANCY invariant.
340
+ // ONE instance → shared sticky state so signaling + ceremonies stay on the SAME directory node
341
+ // and fail over together. Undefined on the in-process test path (no directoryEndpointResolver),
342
+ // where the shared manager uses an injected signalingConnect instead.
343
+ const failoverEndpointResolver = directoryEndpointResolver
344
+ ? createRosterAwareEndpointResolver({
345
+ primaryResolver: directoryEndpointResolver,
346
+ getConsortiumRoster: resolveConsortiumRoster,
347
+ logger,
348
+ })
349
+ : undefined;
350
+ // Null-safe accessor for the ceremony/handler getDirectoryEndpoint sites (mirrors the old
351
+ // `directoryEndpointResolver ? ... : null` wrapper, now routed through the failover resolver).
352
+ const getFailoverEndpoint = async () => failoverEndpointResolver ? await failoverEndpointResolver() : null;
333
353
  // CELLO-M7-CONN-001 (DOD-CONN-3): start the daemon-level HTTP manifest poll. It fetches
334
354
  // ${directoryHttpUrl}/manifest, verifies the threshold signature against the locally-pinned
335
355
  // root keys, applies anti-rollback + expiry, and adopts a newer manifest — independent of any
@@ -484,7 +504,10 @@ export async function startDaemon(config) {
484
504
  if (!directoryEndpointResolver) {
485
505
  throw new Error("getAgentSignaling: no directory endpoint resolver configured (and no shared manager)");
486
506
  }
487
- const resolver = directoryEndpointResolver;
507
+ // FINDING-4: dial through the roster-aware failover resolver so this agent's signaling
508
+ // stream routes around a down primary node. (failoverEndpointResolver is defined here
509
+ // because it is built iff directoryEndpointResolver is — guarded non-null just above.)
510
+ const resolver = failoverEndpointResolver ?? directoryEndpointResolver;
488
511
  let nodeRef = null;
489
512
  const connect = createSignalingConnect({
490
513
  getDirectoryEndpoint: resolver,
@@ -513,7 +536,7 @@ export async function startDaemon(config) {
513
536
  persistence: getPersistence(agentName),
514
537
  agentPubkeyHex,
515
538
  getNode: entry.getNode,
516
- getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
539
+ getDirectoryEndpoint: getFailoverEndpoint,
517
540
  getConsortiumEndpoints: resolveConsortiumRoster,
518
541
  signaling: mgr,
519
542
  logger,
@@ -524,7 +547,7 @@ export async function startDaemon(config) {
524
547
  persistence: getPersistence(agentName),
525
548
  agentPubkeyHex,
526
549
  getNode: entry.getNode,
527
- getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
550
+ getDirectoryEndpoint: getFailoverEndpoint,
528
551
  getConsortiumEndpoints: resolveConsortiumRoster,
529
552
  signaling: mgr,
530
553
  logger,
@@ -591,7 +614,7 @@ export async function startDaemon(config) {
591
614
  persistence: getPersistence(agent.name),
592
615
  agentPubkeyHex: agent.pubkey,
593
616
  getNode: getDirectoryNode,
594
- getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
617
+ getDirectoryEndpoint: getFailoverEndpoint,
595
618
  getConsortiumEndpoints: resolveConsortiumRoster,
596
619
  signaling: mgr,
597
620
  logger,
@@ -601,7 +624,7 @@ export async function startDaemon(config) {
601
624
  persistence: getPersistence(agent.name),
602
625
  agentPubkeyHex: agent.pubkey,
603
626
  getNode: getDirectoryNode,
604
- getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
627
+ getDirectoryEndpoint: getFailoverEndpoint,
605
628
  getConsortiumEndpoints: resolveConsortiumRoster,
606
629
  signaling: mgr,
607
630
  logger,
@@ -1444,9 +1467,16 @@ export async function startDaemon(config) {
1444
1467
  registrationInProgress = true;
1445
1468
  try {
1446
1469
  // Resolve the directory endpoint once for this registration (the context's
1447
- // getDirectoryEndpoint is synchronous; the daemon's resolver is async with a
1448
- // last-known-good fallback). The endpoint is stable for the duration of one
1449
- // registrationif it changed mid-flow the DKG streams would break anyway.
1470
+ // getDirectoryEndpoint is synchronous; the daemon's resolver is async).
1471
+ // FINDING-4 scope note: this uses the PRIMARY resolver directly (not the roster-aware
1472
+ // failover resolver registration is deliberately out of FINDING-4's "signaling +
1473
+ // ceremony" scope). Because that primary is now built with staleFallback:false (so the
1474
+ // failover wrapper sees a dead primary as null), a transient /bootstrap blip here resolves
1475
+ // to null → directory_unreachable rather than riding through on a stale last-known-good;
1476
+ // registration is a rare, manual, retryable op, so failing fast (retry) is acceptable. With
1477
+ // a manifest configured the DKG fans out over the independently-probed roster regardless of
1478
+ // this endpoint. The endpoint is stable for one registration — if it changed mid-flow the
1479
+ // DKG streams would break anyway.
1450
1480
  const ep = await directoryEndpointResolver();
1451
1481
  if (!ep || !ep.multiaddr) {
1452
1482
  // FROST DKG must dial the directory's /cello/frost/1.0.0 — a dialable
@@ -1759,8 +1789,118 @@ export async function startDaemon(config) {
1759
1789
  return;
1760
1790
  }
1761
1791
  logger.info("session.unilateral.certificate.verified", { sessionId: sidHex, signatureType: sigType, party: "present" });
1792
+ // M8B FINDING-3 (cascade-2): normalise + PERSIST the legibility certificate the directory
1793
+ // now ships on this frame — the SAME store the bilateral session_sealed handler writes
1794
+ // (recordSealCertificate → read back by cello_get_sealed_receipt). Without this a unilateral
1795
+ // close returned ok+root but sealed_receipt_not_found forever: the whole point of sealing
1796
+ // when your counterparty vanished is walking away with a DURABLE, RETRIEVABLE receipt.
1797
+ // Persist BEFORE destroying the node (bilateral ordering) so the sessions row still exists
1798
+ // for the UPDATE. undefined legibility (a pre-cascade-2 directory) → nothing persisted; the
1799
+ // seal still completes — surfaced loudly so this can never masquerade as a produced receipt.
1800
+ const rootHex = Buffer.from(sealedRoot).toString("hex");
1801
+ const legibility = normalizeLegibility(frame["legibility"]);
1802
+ // M8B FINDING-5 (SI-002): before persisting, INDEPENDENTLY re-derive each CLIENT-VERIFIABLE
1803
+ // ('live', the present party) frontier from the signed frontier_leaves and OVERRIDE an inflated
1804
+ // directory-published value DOWN to the provable one (the directory cannot forge signed leaves,
1805
+ // so the re-derived value is truth). We OVERRIDE, never reject — the unilateral seal has a
1806
+ // directory-side dedup guard that makes a client rejection unrecoverable (a retry close is
1807
+ // silently ignored → no receipt ever, worse than FINDING-3; cascade-2 reviewer Critical 2). The
1808
+ // absent party's frontier stays directory-attested (its remainder is not re-derivable here). No
1809
+ // frontier_leaves (pre-FINDING-5 directory) → the cert stays directory-attested (FINDING-3).
1810
+ // A receipt is ALWAYS persisted; the close never dead-ends here.
1811
+ if (legibility !== undefined) {
1812
+ const rawParticipants = legibility.participants ?? [];
1813
+ const guardParticipants = rawParticipants
1814
+ .filter((p) => typeof p.pubkey === "string" && typeof p.attestation_mode === "string")
1815
+ .map((p) => ({
1816
+ pubkey: p.pubkey,
1817
+ content_frontier_seq: typeof p.content_frontier_seq === "number" ? p.content_frontier_seq : 0,
1818
+ attestation_mode: p.attestation_mode,
1819
+ }));
1820
+ const frontierLeavesRaw = frame["frontier_leaves"];
1821
+ const toU8f = (v) => (v instanceof Uint8Array ? v : new Uint8Array(v));
1822
+ const parsedFrontierLeaves = Array.isArray(frontierLeavesRaw) && frontierLeavesRaw.length > 0
1823
+ ? frontierLeavesRaw.map((l) => {
1824
+ const o = l;
1825
+ return {
1826
+ structure1_cbor: toU8f(o["structure1_cbor"]),
1827
+ sender_pubkey: toU8f(o["sender_pubkey"]),
1828
+ sender_signature: toU8f(o["sender_signature"]),
1829
+ };
1830
+ })
1831
+ : undefined;
1832
+ const check = checkUnilateralFrontier(guardParticipants, parsedFrontierLeaves, sessionId);
1833
+ // Apply any frontier corrections to the object we persist — override inflated 'live'
1834
+ // frontier(s) DOWN to the provable value, so the stored receipt never claims more than the
1835
+ // signed leaves support. Runs for both 'corrected' (valid leaves) and 'leaves_invalid'
1836
+ // (forged leaves → corrected to 0). Never rejects → the close never dead-ends.
1837
+ if (check.corrections.size > 0) {
1838
+ for (const p of rawParticipants) {
1839
+ if (typeof p.pubkey === "string" && check.corrections.has(p.pubkey.toLowerCase())) {
1840
+ p.content_frontier_seq = check.corrections.get(p.pubkey.toLowerCase());
1841
+ }
1842
+ }
1843
+ }
1844
+ if (check.status === "corrected") {
1845
+ logger.error("seal.certificate.frontier.overridden", {
1846
+ sessionId: sidHex,
1847
+ corrections: [...check.corrections.entries()].map(([party, seq]) => ({ party, correctedTo: seq })),
1848
+ path: "unilateral",
1849
+ });
1850
+ }
1851
+ else if (check.status === "verified") {
1852
+ logger.info("seal.certificate.frontier.verified", {
1853
+ sessionId: sidHex,
1854
+ parties: guardParticipants.filter((p) => p.attestation_mode === "live").length,
1855
+ path: "unilateral",
1856
+ });
1857
+ }
1858
+ else if (check.status === "leaves_invalid") {
1859
+ // Forged / cross-session leaves — a tamper signal. The 'live' frontier(s) have been
1860
+ // corrected to 0 above (zero trustworthy evidence); persist that (never reject → never
1861
+ // dead-end). Surfaced loudly for audit.
1862
+ logger.error("seal.certificate.frontier.leaves_invalid", {
1863
+ sessionId: sidHex,
1864
+ reason: check.reason,
1865
+ corrections: [...check.corrections.entries()].map(([party, seq]) => ({ party, correctedTo: seq })),
1866
+ path: "unilateral",
1867
+ });
1868
+ }
1869
+ else {
1870
+ // directory_attested: no frontier_leaves shipped (pre-FINDING-5 directory). Frontiers stay
1871
+ // DIRECTORY-attested (already marked per-participant) — visible/auditable, never silently
1872
+ // presented as client-verified.
1873
+ logger.warn("seal.certificate.frontier.directory_attested", {
1874
+ sessionId: sidHex,
1875
+ reason: "no_frontier_leaves",
1876
+ path: "unilateral",
1877
+ });
1878
+ }
1879
+ }
1880
+ if (legibility !== undefined) {
1881
+ try {
1882
+ sessionNodeManager.recordSealCertificate(agentName, sidHex, rootHex, JSON.stringify(legibility));
1883
+ logger.info("session.unilateral.receipt.persisted", {
1884
+ sessionId: sidHex,
1885
+ sealedRoot: rootHex,
1886
+ finalMessageAnswered: legibility && typeof legibility === "object" && "final_message" in legibility
1887
+ ? legibility.final_message?.answered
1888
+ : undefined,
1889
+ });
1890
+ }
1891
+ catch (error) {
1892
+ logger.warn("seal.certificate.persist.failed", {
1893
+ sessionId: sidHex,
1894
+ reason: error instanceof Error ? error.message : String(error),
1895
+ });
1896
+ }
1897
+ }
1898
+ else {
1899
+ // The seal is valid, but no receipt is retrievable — a directory that predates cascade-2.
1900
+ logger.warn("session.unilateral.receipt.absent", { sessionId: sidHex, reason: "no_legibility_on_frame" });
1901
+ }
1762
1902
  void sessionNodeManager.destroySessionNode(agentName, sidHex, "sealed");
1763
- waiter({ ok: true, sealedRootHex: Buffer.from(sealedRoot).toString("hex") });
1903
+ waiter({ ok: true, sealedRootHex: rootHex, legibility });
1764
1904
  })();
1765
1905
  });
1766
1906
  }
@@ -1787,7 +1927,7 @@ export async function startDaemon(config) {
1787
1927
  return;
1788
1928
  sealUpgradeInFlight.add(key);
1789
1929
  try {
1790
- await attemptSealUpgradeImpl({
1930
+ const result = await attemptSealUpgradeImpl({
1791
1931
  logger, agentName, agentPubkeyHex,
1792
1932
  getReadiness: (a, s) => sessionNodeManager.getSealUpgradeReadiness(a, s),
1793
1933
  getContentLeafCount: (a, s) => sessionNodeManager.getSessionTree(a, s).size(),
@@ -1795,6 +1935,44 @@ export async function startDaemon(config) {
1795
1935
  getKeyProvider: (a) => keyProviders.get(a),
1796
1936
  sendRaw: (f) => signaling.sendRaw(f),
1797
1937
  }, sidHex, frame);
1938
+ // M8B FINDING-6 (3b): the ABSENT party (B) persists its UNILATERAL receipt from the
1939
+ // notification's legibility — but ONLY after the KERNEL content-recovery/verify gate passed.
1940
+ // `result.sent` is true iff attemptSealUpgradeImpl recovered + integrity-verified the content
1941
+ // behind R1 and sent the ratification request; a tampered/unrecoverable/incomplete case returns
1942
+ // sent:false → NO receipt (never a receipt for content B could not verify). B may hold no local
1943
+ // `sessions` row (recordSealCertificateEnsuringRow inserts a stub, counterparty = A's pubkey).
1944
+ // The notification carries no frontier_leaves (FINDING-5 ships those only on the present party's
1945
+ // confirm frame), so B's legibility is directory-attested — consistent with FINDING-5's
1946
+ // directory_attested path; B trusts its own KERNEL-verified content, not a re-derivation.
1947
+ if (result.sent) {
1948
+ try {
1949
+ // ONE-WAY RATCHET (cascade-2 FINDING-6 review): a re-delivered seal_unilateral_notification
1950
+ // (reconnect burst) re-runs this path and would re-persist the notification's ORIGINAL
1951
+ // legibility (counterparty 'absent'). If a prior upgrade already flipped the stored receipt
1952
+ // to 'recovered' (no 'absent' participant), re-persisting would REGRESS it — and no later
1953
+ // event restores 'recovered' (the directory dedups the duplicate as already_bilateral →
1954
+ // seal_upgrade_rejected, which only logs). So skip the re-persist once the cert is upgraded.
1955
+ const existing = sessionNodeManager.getSealCertificate(agentName, sidHex);
1956
+ if (existing && !hasAbsentParticipant(existing.legibility)) {
1957
+ logger.debug("session.unilateral.receipt.persist.skipped", { sessionId: sidHex, reason: "already_upgraded" });
1958
+ }
1959
+ else {
1960
+ const legibility = normalizeLegibility(frame["legibility"]);
1961
+ const rootHex = frameValueToHex(frame["sealed_root"]);
1962
+ const counterpartyHex = frameValueToHex(frame["present_pubkey"]); // A — the present party
1963
+ if (legibility !== undefined && rootHex && counterpartyHex) {
1964
+ sessionNodeManager.recordSealCertificateEnsuringRow(agentName, sidHex, counterpartyHex, rootHex, JSON.stringify(legibility));
1965
+ logger.info("session.unilateral.receipt.persisted", { sessionId: sidHex, sealedRoot: rootHex, party: "absent" });
1966
+ }
1967
+ else if (legibility === undefined) {
1968
+ logger.warn("session.unilateral.receipt.absent", { sessionId: sidHex, reason: "no_legibility_on_notification" });
1969
+ }
1970
+ }
1971
+ }
1972
+ catch (error) {
1973
+ logger.warn("seal.certificate.persist.failed", { sessionId: sidHex, reason: error instanceof Error ? error.message : String(error) });
1974
+ }
1975
+ }
1798
1976
  }
1799
1977
  finally {
1800
1978
  // Clear the guard so a later reconnect can retry if the request never reached the directory;
@@ -1812,6 +1990,24 @@ export async function startDaemon(config) {
1812
1990
  if (!result.ok)
1813
1991
  return; // cert.invalid already logged inside; do NOT accept as bilateral.
1814
1992
  logger.info("session.seal.upgraded", { sessionId: sidHex, agentName, party: result.party });
1993
+ // M8B FINDING-6 (3a): the seal is now BILATERAL (verified) — upgrade THIS party's own stored
1994
+ // receipt so the counterparty recorded 'absent' becomes 'recovered'. Client-side: the directory
1995
+ // ships no bilateral legibility at upgrade time (the seal leaves aren't persisted there), so each
1996
+ // party rebuilds from the cert it already holds — the present party's from its unilateral close
1997
+ // (FINDING-3), the returning party's from the notification it persisted after the KERNEL gate
1998
+ // (3b). The seal signatures do not bind the (unsigned) legibility, so mutating it is sound; only
1999
+ // the attestation flips (frontiers unchanged — never overstated). No-op if no cert is stored yet.
2000
+ const stored = sessionNodeManager.getSealCertificate(agentName, sidHex);
2001
+ if (stored) {
2002
+ try {
2003
+ const upgraded = upgradeAbsentToRecovered(stored.legibility);
2004
+ sessionNodeManager.recordSealCertificate(agentName, sidHex, stored.sealed_root, JSON.stringify(upgraded));
2005
+ logger.info("session.seal.receipt.upgraded", { sessionId: sidHex, agentName, party: result.party });
2006
+ }
2007
+ catch (error) {
2008
+ logger.warn("seal.certificate.persist.failed", { sessionId: sidHex, reason: error instanceof Error ? error.message : String(error) });
2009
+ }
2010
+ }
1815
2011
  void sessionNodeManager.destroySessionNode(agentName, sidHex, "sealed");
1816
2012
  }
1817
2013
  /**
@@ -1869,7 +2065,7 @@ export async function startDaemon(config) {
1869
2065
  persistence: getPersistence(agentName),
1870
2066
  agentPubkeyHex: loaded.pubkey,
1871
2067
  getNode: entry.getNode,
1872
- getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
2068
+ getDirectoryEndpoint: getFailoverEndpoint,
1873
2069
  getConsortiumEndpoints: resolveConsortiumRoster,
1874
2070
  signaling: entry.signaling,
1875
2071
  logger,
@@ -2280,7 +2476,15 @@ export async function startDaemon(config) {
2280
2476
  pendingUnilateralWaiters.delete(sessionId);
2281
2477
  if (uniResult.ok) {
2282
2478
  logger.info("session.seal.completed", { sessionId, sealedRoot: uniResult.sealedRootHex, role: "unilateral", correlationId });
2283
- return { ok: true, sealed_root: uniResult.sealedRootHex, seal_type: "unilateral" };
2479
+ // M8B FINDING-3 (cascade-2): return the legibility certificate inline — same shape as the
2480
+ // bilateral close (AC-006) — so the operator gets the receipt on the surface that proves
2481
+ // the seal, not just a bare root. It is also persisted (above) for cello_get_sealed_receipt.
2482
+ return {
2483
+ ok: true,
2484
+ sealed_root: uniResult.sealedRootHex,
2485
+ seal_type: "unilateral",
2486
+ ...(uniResult.legibility !== undefined ? { legibility: uniResult.legibility } : {}),
2487
+ };
2284
2488
  }
2285
2489
  if (uniResult.reason === "seal_unilateral_too_early") {
2286
2490
  // F20: tell the operator WHEN the unilateral seal becomes available, not just "later".