@cello-protocol/daemon 0.0.227 → 0.0.229
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 +56 -0
- package/dist/agent-admin-handlers.d.ts.map +1 -0
- package/dist/agent-admin-handlers.js +73 -0
- package/dist/agent-admin-handlers.js.map +1 -0
- package/dist/agent-handlers.d.ts +72 -0
- package/dist/agent-handlers.d.ts.map +1 -0
- package/dist/agent-handlers.js +604 -0
- package/dist/agent-handlers.js.map +1 -0
- package/dist/agent-id-migration.d.ts +70 -0
- package/dist/agent-id-migration.d.ts.map +1 -0
- package/dist/agent-id-migration.js +468 -0
- package/dist/agent-id-migration.js.map +1 -0
- package/dist/agent-loader.d.ts +37 -0
- package/dist/agent-loader.d.ts.map +1 -0
- package/dist/agent-loader.js +37 -0
- package/dist/agent-loader.js.map +1 -0
- package/dist/agent-selection-root.d.ts +29 -0
- package/dist/agent-selection-root.d.ts.map +1 -0
- package/dist/agent-selection-root.js +126 -0
- package/dist/agent-selection-root.js.map +1 -0
- package/dist/agent-selection.d.ts +73 -0
- package/dist/agent-selection.d.ts.map +1 -0
- package/dist/agent-selection.js +80 -0
- package/dist/agent-selection.js.map +1 -0
- package/dist/agent-settings-keys.d.ts +52 -0
- package/dist/agent-settings-keys.d.ts.map +1 -0
- package/dist/agent-settings-keys.js +114 -0
- package/dist/agent-settings-keys.js.map +1 -0
- package/dist/agent-state.d.ts +67 -0
- package/dist/agent-state.d.ts.map +1 -0
- package/dist/agent-state.js +66 -0
- package/dist/agent-state.js.map +1 -0
- package/dist/assignment-verify.d.ts +90 -0
- package/dist/assignment-verify.d.ts.map +1 -0
- package/dist/assignment-verify.js +301 -0
- package/dist/assignment-verify.js.map +1 -0
- package/dist/attendance-wiring.d.ts +23 -0
- package/dist/attendance-wiring.d.ts.map +1 -0
- package/dist/attendance-wiring.js +248 -0
- package/dist/attendance-wiring.js.map +1 -0
- package/dist/attestation-body.d.ts +47 -0
- package/dist/attestation-body.d.ts.map +1 -0
- package/dist/attestation-body.js +71 -0
- package/dist/attestation-body.js.map +1 -0
- package/dist/authorship-verification.d.ts +91 -0
- package/dist/authorship-verification.d.ts.map +1 -0
- package/dist/authorship-verification.js +532 -0
- package/dist/authorship-verification.js.map +1 -0
- package/dist/away-detection.d.ts +139 -0
- package/dist/away-detection.d.ts.map +1 -0
- package/dist/away-detection.js +186 -0
- package/dist/away-detection.js.map +1 -0
- package/dist/away-inbox-oneshot.d.ts +40 -0
- package/dist/away-inbox-oneshot.d.ts.map +1 -0
- package/dist/away-inbox-oneshot.js +311 -0
- package/dist/away-inbox-oneshot.js.map +1 -0
- package/dist/backup-restore-handlers.d.ts +10 -0
- package/dist/backup-restore-handlers.d.ts.map +1 -0
- package/dist/backup-restore-handlers.js +82 -0
- package/dist/backup-restore-handlers.js.map +1 -0
- package/dist/backup-restore.d.ts +102 -0
- package/dist/backup-restore.d.ts.map +1 -0
- package/dist/backup-restore.js +341 -0
- package/dist/backup-restore.js.map +1 -0
- package/dist/bin/cello-daemon.d.ts +13 -0
- package/dist/bin/cello-daemon.d.ts.map +1 -0
- package/dist/bin/cello-daemon.js.map +1 -0
- package/dist/boot-agents.d.ts +64 -0
- package/dist/boot-agents.d.ts.map +1 -0
- package/dist/boot-agents.js +241 -0
- package/dist/boot-agents.js.map +1 -0
- package/dist/boot-connection-state.d.ts +38 -0
- package/dist/boot-connection-state.d.ts.map +1 -0
- package/dist/boot-connection-state.js +95 -0
- package/dist/boot-connection-state.js.map +1 -0
- package/dist/boot-core.d.ts +36 -0
- package/dist/boot-core.d.ts.map +1 -0
- package/dist/boot-core.js +293 -0
- package/dist/boot-core.js.map +1 -0
- package/dist/boot-parked-content.d.ts +34 -0
- package/dist/boot-parked-content.d.ts.map +1 -0
- package/dist/boot-parked-content.js +528 -0
- package/dist/boot-parked-content.js.map +1 -0
- package/dist/boot-sweeps.d.ts +22 -0
- package/dist/boot-sweeps.d.ts.map +1 -0
- package/dist/boot-sweeps.js +51 -0
- package/dist/boot-sweeps.js.map +1 -0
- package/dist/bundled-consortium-manifest.d.ts +47 -0
- package/dist/bundled-consortium-manifest.d.ts.map +1 -0
- package/dist/bundled-consortium-manifest.js +116 -0
- package/dist/bundled-consortium-manifest.js.map +1 -0
- package/dist/cbor-blob-migration.d.ts +8 -0
- package/dist/cbor-blob-migration.d.ts.map +1 -0
- package/dist/cbor-blob-migration.js +116 -0
- package/dist/cbor-blob-migration.js.map +1 -0
- package/dist/challenge-verifier.d.ts +12 -0
- package/dist/challenge-verifier.d.ts.map +1 -0
- package/dist/challenge-verifier.js +11 -0
- package/dist/challenge-verifier.js.map +1 -0
- package/dist/channel-log-store.d.ts +80 -0
- package/dist/channel-log-store.d.ts.map +1 -0
- package/dist/channel-log-store.js +290 -0
- package/dist/channel-log-store.js.map +1 -0
- package/dist/close-commitment.d.ts +67 -0
- package/dist/close-commitment.d.ts.map +1 -0
- package/dist/close-commitment.js +78 -0
- package/dist/close-commitment.js.map +1 -0
- package/dist/close-session-handler.d.ts +94 -0
- package/dist/close-session-handler.d.ts.map +1 -0
- package/dist/close-session-handler.js +1418 -0
- package/dist/close-session-handler.js.map +1 -0
- package/dist/co-attendance.d.ts +29 -0
- package/dist/co-attendance.d.ts.map +1 -0
- package/dist/co-attendance.js +32 -0
- package/dist/co-attendance.js.map +1 -0
- package/dist/column-birth.d.ts +40 -0
- package/dist/column-birth.d.ts.map +1 -0
- package/dist/column-birth.js +61 -0
- package/dist/column-birth.js.map +1 -0
- package/dist/connect-or-start.d.ts +25 -0
- package/dist/connect-or-start.d.ts.map +1 -0
- package/dist/connect-or-start.js +175 -0
- package/dist/connect-or-start.js.map +1 -0
- package/dist/connection-agents.d.ts +16 -0
- package/dist/connection-agents.d.ts.map +1 -0
- package/dist/connection-agents.js +68 -0
- package/dist/connection-agents.js.map +1 -0
- package/dist/consent-migration.d.ts +49 -0
- package/dist/consent-migration.d.ts.map +1 -0
- package/dist/consent-migration.js +128 -0
- package/dist/consent-migration.js.map +1 -0
- package/dist/consortium-bootstrap.d.ts +138 -0
- package/dist/consortium-bootstrap.d.ts.map +1 -0
- package/dist/consortium-bootstrap.js +339 -0
- package/dist/consortium-bootstrap.js.map +1 -0
- 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/contact-handlers.d.ts +59 -0
- package/dist/contact-handlers.d.ts.map +1 -0
- package/dist/contact-handlers.js +343 -0
- package/dist/contact-handlers.js.map +1 -0
- package/dist/contact-pubkey-case.d.ts +65 -0
- package/dist/contact-pubkey-case.d.ts.map +1 -0
- package/dist/contact-pubkey-case.js +136 -0
- package/dist/contact-pubkey-case.js.map +1 -0
- package/dist/contacts-tier-migration.d.ts +90 -0
- package/dist/contacts-tier-migration.d.ts.map +1 -0
- package/dist/contacts-tier-migration.js +150 -0
- package/dist/contacts-tier-migration.js.map +1 -0
- package/dist/content-encryption-status.d.ts +111 -0
- package/dist/content-encryption-status.d.ts.map +1 -0
- package/dist/content-encryption-status.js +158 -0
- package/dist/content-encryption-status.js.map +1 -0
- package/dist/content-park-client.d.ts +90 -0
- package/dist/content-park-client.d.ts.map +1 -0
- package/dist/content-park-client.js +362 -0
- package/dist/content-park-client.js.map +1 -0
- package/dist/content-park.d.ts +52 -0
- package/dist/content-park.d.ts.map +1 -0
- package/dist/content-park.js +1309 -0
- package/dist/content-park.js.map +1 -0
- package/dist/cross-node-negotiation.d.ts +44 -0
- package/dist/cross-node-negotiation.d.ts.map +1 -0
- package/dist/cross-node-negotiation.js +34 -0
- package/dist/cross-node-negotiation.js.map +1 -0
- package/dist/daemon-handle.d.ts +57 -0
- package/dist/daemon-handle.d.ts.map +1 -0
- package/dist/daemon-handle.js +2 -0
- package/dist/daemon-handle.js.map +1 -0
- package/dist/daemon-status-report.d.ts +50 -0
- package/dist/daemon-status-report.d.ts.map +1 -0
- package/dist/daemon-status-report.js +81 -0
- package/dist/daemon-status-report.js.map +1 -0
- package/dist/daemon.d.ts +45 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +1177 -0
- package/dist/daemon.js.map +1 -0
- package/dist/db-identity-store.d.ts +159 -0
- package/dist/db-identity-store.d.ts.map +1 -0
- package/dist/db-identity-store.js +502 -0
- package/dist/db-identity-store.js.map +1 -0
- package/dist/delivery-open-registry.d.ts +92 -0
- package/dist/delivery-open-registry.d.ts.map +1 -0
- package/dist/delivery-open-registry.js +121 -0
- package/dist/delivery-open-registry.js.map +1 -0
- package/dist/delivery-session-suspects.d.ts +56 -0
- package/dist/delivery-session-suspects.d.ts.map +1 -0
- package/dist/delivery-session-suspects.js +94 -0
- package/dist/delivery-session-suspects.js.map +1 -0
- package/dist/directory-auth-posture.d.ts +87 -0
- package/dist/directory-auth-posture.d.ts.map +1 -0
- package/dist/directory-auth-posture.js +134 -0
- package/dist/directory-auth-posture.js.map +1 -0
- package/dist/directory-bootstrap.d.ts +310 -0
- package/dist/directory-bootstrap.d.ts.map +1 -0
- package/dist/directory-bootstrap.js +557 -0
- package/dist/directory-bootstrap.js.map +1 -0
- package/dist/directory-connect.d.ts +27 -0
- package/dist/directory-connect.d.ts.map +1 -0
- package/dist/directory-connect.js +106 -0
- package/dist/directory-connect.js.map +1 -0
- package/dist/disconnect-cleanup.d.ts +51 -0
- package/dist/disconnect-cleanup.d.ts.map +1 -0
- package/dist/disconnect-cleanup.js +72 -0
- package/dist/disconnect-cleanup.js.map +1 -0
- package/dist/document-amendment-store.d.ts +120 -0
- package/dist/document-amendment-store.d.ts.map +1 -0
- package/dist/document-amendment-store.js +266 -0
- package/dist/document-amendment-store.js.map +1 -0
- package/dist/document-delivery-transport.d.ts +168 -0
- package/dist/document-delivery-transport.d.ts.map +1 -0
- package/dist/document-delivery-transport.js +206 -0
- package/dist/document-delivery-transport.js.map +1 -0
- package/dist/document-engine.d.ts +134 -0
- package/dist/document-engine.d.ts.map +1 -0
- package/dist/document-engine.js +282 -0
- package/dist/document-engine.js.map +1 -0
- 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-frame-router.d.ts +245 -0
- package/dist/document-frame-router.d.ts.map +1 -0
- package/dist/document-frame-router.js +397 -0
- package/dist/document-frame-router.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 +125 -0
- package/dist/document-gate-wiring.js.map +1 -0
- package/dist/document-gate.d.ts +149 -0
- package/dist/document-gate.d.ts.map +1 -0
- package/dist/document-gate.js +509 -0
- package/dist/document-gate.js.map +1 -0
- package/dist/document-handlers.d.ts +47 -0
- package/dist/document-handlers.d.ts.map +1 -0
- package/dist/document-handlers.js +2203 -0
- package/dist/document-handlers.js.map +1 -0
- package/dist/document-handshake.d.ts +176 -0
- package/dist/document-handshake.d.ts.map +1 -0
- package/dist/document-handshake.js +452 -0
- package/dist/document-handshake.js.map +1 -0
- package/dist/document-inbound.d.ts +162 -0
- package/dist/document-inbound.d.ts.map +1 -0
- package/dist/document-inbound.js +530 -0
- package/dist/document-inbound.js.map +1 -0
- package/dist/document-json.d.ts +120 -0
- package/dist/document-json.d.ts.map +1 -0
- package/dist/document-json.js +191 -0
- package/dist/document-json.js.map +1 -0
- package/dist/document-layer.d.ts +215 -0
- package/dist/document-layer.d.ts.map +1 -0
- package/dist/document-layer.js +1025 -0
- package/dist/document-layer.js.map +1 -0
- package/dist/document-lifecycle.d.ts +52 -0
- package/dist/document-lifecycle.d.ts.map +1 -0
- package/dist/document-lifecycle.js +134 -0
- package/dist/document-lifecycle.js.map +1 -0
- package/dist/document-live-docs.d.ts +58 -0
- package/dist/document-live-docs.d.ts.map +1 -0
- package/dist/document-live-docs.js +126 -0
- package/dist/document-live-docs.js.map +1 -0
- package/dist/document-notify.d.ts +228 -0
- package/dist/document-notify.d.ts.map +1 -0
- package/dist/document-notify.js +580 -0
- package/dist/document-notify.js.map +1 -0
- package/dist/document-profile.d.ts +61 -0
- package/dist/document-profile.d.ts.map +1 -0
- package/dist/document-profile.js +112 -0
- package/dist/document-profile.js.map +1 -0
- package/dist/document-publish.d.ts +87 -0
- package/dist/document-publish.d.ts.map +1 -0
- package/dist/document-publish.js +173 -0
- package/dist/document-publish.js.map +1 -0
- package/dist/document-reachability.d.ts +42 -0
- package/dist/document-reachability.d.ts.map +1 -0
- package/dist/document-reachability.js +80 -0
- package/dist/document-reachability.js.map +1 -0
- package/dist/document-reconcile-engine.d.ts +66 -0
- package/dist/document-reconcile-engine.d.ts.map +1 -0
- package/dist/document-reconcile-engine.js +225 -0
- package/dist/document-reconcile-engine.js.map +1 -0
- package/dist/document-reconcile-scheduler.d.ts +163 -0
- package/dist/document-reconcile-scheduler.d.ts.map +1 -0
- package/dist/document-reconcile-scheduler.js +303 -0
- package/dist/document-reconcile-scheduler.js.map +1 -0
- package/dist/document-rejection.d.ts +251 -0
- package/dist/document-rejection.d.ts.map +1 -0
- package/dist/document-rejection.js +435 -0
- package/dist/document-rejection.js.map +1 -0
- package/dist/document-screen.d.ts +114 -0
- package/dist/document-screen.d.ts.map +1 -0
- package/dist/document-screen.js +223 -0
- package/dist/document-screen.js.map +1 -0
- package/dist/document-store.d.ts +372 -0
- package/dist/document-store.d.ts.map +1 -0
- package/dist/document-store.js +931 -0
- package/dist/document-store.js.map +1 -0
- package/dist/document-surface.d.ts +32 -0
- package/dist/document-surface.d.ts.map +1 -0
- package/dist/document-surface.js +151 -0
- package/dist/document-surface.js.map +1 -0
- package/dist/document-types.d.ts +94 -0
- package/dist/document-types.d.ts.map +1 -0
- package/dist/document-types.js +90 -0
- package/dist/document-types.js.map +1 -0
- package/dist/document-watch.d.ts +69 -0
- package/dist/document-watch.d.ts.map +1 -0
- package/dist/document-watch.js +108 -0
- package/dist/document-watch.js.map +1 -0
- package/dist/document-wiring.d.ts +53 -0
- package/dist/document-wiring.d.ts.map +1 -0
- package/dist/document-wiring.js +323 -0
- package/dist/document-wiring.js.map +1 -0
- package/dist/document-write-guard.d.ts +66 -0
- package/dist/document-write-guard.d.ts.map +1 -0
- package/dist/document-write-guard.js +98 -0
- package/dist/document-write-guard.js.map +1 -0
- package/dist/document-write-path.d.ts +94 -0
- package/dist/document-write-path.d.ts.map +1 -0
- package/dist/document-write-path.js +530 -0
- package/dist/document-write-path.js.map +1 -0
- package/dist/error-message.d.ts +7 -0
- package/dist/error-message.d.ts.map +1 -0
- package/dist/error-message.js +19 -0
- package/dist/error-message.js.map +1 -0
- package/dist/file-manifest-provider.d.ts +37 -0
- package/dist/file-manifest-provider.d.ts.map +1 -0
- package/dist/file-manifest-provider.js +105 -0
- package/dist/file-manifest-provider.js.map +1 -0
- package/dist/frame-values.d.ts +3 -0
- package/dist/frame-values.d.ts.map +1 -0
- package/dist/frame-values.js +84 -0
- package/dist/frame-values.js.map +1 -0
- package/dist/frontier-mismatch.d.ts +73 -0
- package/dist/frontier-mismatch.d.ts.map +1 -0
- package/dist/frontier-mismatch.js +89 -0
- package/dist/frontier-mismatch.js.map +1 -0
- package/dist/gateway-config-handlers.d.ts +26 -0
- package/dist/gateway-config-handlers.d.ts.map +1 -0
- package/dist/gateway-config-handlers.js +454 -0
- package/dist/gateway-config-handlers.js.map +1 -0
- package/dist/held-content.d.ts +145 -0
- package/dist/held-content.d.ts.map +1 -0
- package/dist/held-content.js +389 -0
- package/dist/held-content.js.map +1 -0
- package/dist/http-manifest-poll.d.ts +67 -0
- package/dist/http-manifest-poll.d.ts.map +1 -0
- package/dist/http-manifest-poll.js +150 -0
- package/dist/http-manifest-poll.js.map +1 -0
- package/dist/identity-migration.d.ts +40 -0
- package/dist/identity-migration.d.ts.map +1 -0
- package/dist/identity-migration.js +461 -0
- package/dist/identity-migration.js.map +1 -0
- package/dist/inbound-refusals.d.ts +283 -0
- package/dist/inbound-refusals.d.ts.map +1 -0
- package/dist/inbound-refusals.js +919 -0
- package/dist/inbound-refusals.js.map +1 -0
- package/dist/inbound-seal-request.d.ts +32 -0
- package/dist/inbound-seal-request.d.ts.map +1 -0
- package/dist/inbound-seal-request.js +228 -0
- package/dist/inbound-seal-request.js.map +1 -0
- package/dist/inbound-sessions.d.ts +291 -0
- package/dist/inbound-sessions.d.ts.map +1 -0
- package/dist/inbound-sessions.js +1609 -0
- package/dist/inbound-sessions.js.map +1 -0
- package/dist/inclusion-proof-handlers.d.ts +43 -0
- package/dist/inclusion-proof-handlers.d.ts.map +1 -0
- package/dist/inclusion-proof-handlers.js +565 -0
- package/dist/inclusion-proof-handlers.js.map +1 -0
- package/dist/inclusion-proof.d.ts +151 -0
- package/dist/inclusion-proof.d.ts.map +1 -0
- package/dist/inclusion-proof.js +228 -0
- package/dist/inclusion-proof.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/initiate-session-handler.d.ts +55 -0
- package/dist/initiate-session-handler.d.ts.map +1 -0
- package/dist/initiate-session-handler.js +310 -0
- package/dist/initiate-session-handler.js.map +1 -0
- package/dist/ipc-client.d.ts +31 -0
- package/dist/ipc-client.d.ts.map +1 -0
- package/dist/ipc-client.js +113 -0
- package/dist/ipc-client.js.map +1 -0
- package/dist/ipc-server.d.ts +63 -0
- package/dist/ipc-server.d.ts.map +1 -0
- package/dist/ipc-server.js +429 -0
- package/dist/ipc-server.js.map +1 -0
- package/dist/ipc-surface.d.ts +44 -0
- package/dist/ipc-surface.d.ts.map +1 -0
- package/dist/ipc-surface.js +107 -0
- package/dist/ipc-surface.js.map +1 -0
- package/dist/line-lcs.d.ts +51 -0
- package/dist/line-lcs.d.ts.map +1 -0
- package/dist/line-lcs.js +71 -0
- package/dist/line-lcs.js.map +1 -0
- package/dist/lock-file.d.ts +39 -0
- package/dist/lock-file.d.ts.map +1 -0
- package/dist/lock-file.js +120 -0
- package/dist/lock-file.js.map +1 -0
- package/dist/log-collapse.d.ts +66 -0
- package/dist/log-collapse.d.ts.map +1 -0
- package/dist/log-collapse.js +244 -0
- package/dist/log-collapse.js.map +1 -0
- package/dist/log-rotate.d.ts +67 -0
- package/dist/log-rotate.d.ts.map +1 -0
- package/dist/log-rotate.js +134 -0
- package/dist/log-rotate.js.map +1 -0
- package/dist/manifest-deps.d.ts +25 -0
- package/dist/manifest-deps.d.ts.map +1 -0
- package/dist/manifest-deps.js +151 -0
- package/dist/manifest-deps.js.map +1 -0
- package/dist/manifest-poll-scheduler.d.ts +31 -0
- package/dist/manifest-poll-scheduler.d.ts.map +1 -0
- package/dist/manifest-poll-scheduler.js +59 -0
- package/dist/manifest-poll-scheduler.js.map +1 -0
- package/dist/manifest-validity.d.ts +153 -0
- package/dist/manifest-validity.d.ts.map +1 -0
- package/dist/manifest-validity.js +268 -0
- package/dist/manifest-validity.js.map +1 -0
- package/dist/manifest-version-store-db.d.ts +24 -0
- package/dist/manifest-version-store-db.d.ts.map +1 -0
- package/dist/manifest-version-store-db.js +58 -0
- package/dist/manifest-version-store-db.js.map +1 -0
- package/dist/manifest-version-store.d.ts +16 -0
- package/dist/manifest-version-store.d.ts.map +1 -0
- package/dist/manifest-version-store.js +15 -0
- package/dist/manifest-version-store.js.map +1 -0
- package/dist/network-directory-node.d.ts +136 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +810 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/nonce-dedup.d.ts +68 -0
- package/dist/nonce-dedup.d.ts.map +1 -0
- package/dist/nonce-dedup.js +205 -0
- package/dist/nonce-dedup.js.map +1 -0
- package/dist/notification-dispatcher.d.ts +92 -0
- package/dist/notification-dispatcher.d.ts.map +1 -0
- package/dist/notification-dispatcher.js +210 -0
- package/dist/notification-dispatcher.js.map +1 -0
- package/dist/notification-handlers.d.ts +53 -0
- package/dist/notification-handlers.d.ts.map +1 -0
- package/dist/notification-handlers.js +461 -0
- package/dist/notification-handlers.js.map +1 -0
- package/dist/onboarding-guidance.d.ts +79 -0
- package/dist/onboarding-guidance.d.ts.map +1 -0
- package/dist/onboarding-guidance.js +95 -0
- package/dist/onboarding-guidance.js.map +1 -0
- package/dist/operator-guidance.d.ts +25 -0
- package/dist/operator-guidance.d.ts.map +1 -0
- package/dist/operator-guidance.js +50 -0
- package/dist/operator-guidance.js.map +1 -0
- package/dist/orphan-triage.d.ts +130 -0
- package/dist/orphan-triage.d.ts.map +1 -0
- package/dist/orphan-triage.js +207 -0
- package/dist/orphan-triage.js.map +1 -0
- package/dist/outbound-sessions.d.ts +126 -0
- package/dist/outbound-sessions.d.ts.map +1 -0
- package/dist/outbound-sessions.js +1052 -0
- package/dist/outbound-sessions.js.map +1 -0
- package/dist/park-envelope.d.ts +329 -0
- package/dist/park-envelope.d.ts.map +1 -0
- package/dist/park-envelope.js +509 -0
- package/dist/park-envelope.js.map +1 -0
- package/dist/park-recovery.d.ts +273 -0
- package/dist/park-recovery.d.ts.map +1 -0
- package/dist/park-recovery.js +717 -0
- package/dist/park-recovery.js.map +1 -0
- package/dist/park-refusals.d.ts +147 -0
- package/dist/park-refusals.d.ts.map +1 -0
- package/dist/park-refusals.js +331 -0
- package/dist/park-refusals.js.map +1 -0
- package/dist/quarantine-framing.d.ts +92 -0
- package/dist/quarantine-framing.d.ts.map +1 -0
- package/dist/quarantine-framing.js +111 -0
- package/dist/quarantine-framing.js.map +1 -0
- package/dist/reconnect-drain.d.ts +22 -0
- package/dist/reconnect-drain.d.ts.map +1 -0
- package/dist/reconnect-drain.js +65 -0
- package/dist/reconnect-drain.js.map +1 -0
- package/dist/recovered-position.d.ts +14 -0
- package/dist/recovered-position.d.ts.map +1 -0
- package/dist/recovered-position.js +59 -0
- package/dist/recovered-position.js.map +1 -0
- package/dist/refusal-notices.d.ts +196 -0
- package/dist/refusal-notices.d.ts.map +1 -0
- package/dist/refusal-notices.js +516 -0
- package/dist/refusal-notices.js.map +1 -0
- package/dist/refusal-reasons.d.ts +245 -0
- package/dist/refusal-reasons.d.ts.map +1 -0
- package/dist/refusal-reasons.js +363 -0
- package/dist/refusal-reasons.js.map +1 -0
- package/dist/register-handler.d.ts +36 -0
- package/dist/register-handler.d.ts.map +1 -0
- package/dist/register-handler.js +285 -0
- package/dist/register-handler.js.map +1 -0
- package/dist/registration-context.d.ts +72 -0
- package/dist/registration-context.d.ts.map +1 -0
- package/dist/registration-context.js +126 -0
- package/dist/registration-context.js.map +1 -0
- package/dist/registration-manager.d.ts +94 -0
- package/dist/registration-manager.d.ts.map +1 -0
- package/dist/registration-manager.js +585 -0
- package/dist/registration-manager.js.map +1 -0
- package/dist/registration-persistence.d.ts +183 -0
- package/dist/registration-persistence.d.ts.map +1 -0
- package/dist/registration-persistence.js +263 -0
- package/dist/registration-persistence.js.map +1 -0
- package/dist/registry-poll.d.ts +52 -0
- package/dist/registry-poll.d.ts.map +1 -0
- package/dist/registry-poll.js +140 -0
- package/dist/registry-poll.js.map +1 -0
- package/dist/registry-version-store-db.d.ts +22 -0
- package/dist/registry-version-store-db.d.ts.map +1 -0
- package/dist/registry-version-store-db.js +50 -0
- package/dist/registry-version-store-db.js.map +1 -0
- package/dist/relay-endpoints.d.ts +18 -0
- package/dist/relay-endpoints.d.ts.map +1 -0
- package/dist/relay-endpoints.js +9 -0
- package/dist/relay-endpoints.js.map +1 -0
- package/dist/relay-only.d.ts +140 -0
- package/dist/relay-only.d.ts.map +1 -0
- package/dist/relay-only.js +193 -0
- package/dist/relay-only.js.map +1 -0
- package/dist/relay-receipt-store.d.ts +155 -0
- package/dist/relay-receipt-store.d.ts.map +1 -0
- package/dist/relay-receipt-store.js +284 -0
- package/dist/relay-receipt-store.js.map +1 -0
- package/dist/relay-reconnect.d.ts +32 -0
- package/dist/relay-reconnect.d.ts.map +1 -0
- package/dist/relay-reconnect.js +29 -0
- package/dist/relay-reconnect.js.map +1 -0
- package/dist/reply-lag.d.ts +11 -0
- package/dist/reply-lag.d.ts.map +1 -0
- package/dist/reply-lag.js +46 -0
- package/dist/reply-lag.js.map +1 -0
- package/dist/resolve-named-agent.d.ts +49 -0
- package/dist/resolve-named-agent.d.ts.map +1 -0
- package/dist/resolve-named-agent.js +77 -0
- package/dist/resolve-named-agent.js.map +1 -0
- package/dist/restart-seal-resolver.d.ts +110 -0
- package/dist/restart-seal-resolver.d.ts.map +1 -0
- package/dist/restart-seal-resolver.js +353 -0
- package/dist/restart-seal-resolver.js.map +1 -0
- package/dist/resume-last-seen.d.ts +15 -0
- package/dist/resume-last-seen.d.ts.map +1 -0
- package/dist/resume-last-seen.js +44 -0
- package/dist/resume-last-seen.js.map +1 -0
- package/dist/retry-queue.d.ts +203 -0
- package/dist/retry-queue.d.ts.map +1 -0
- package/dist/retry-queue.js +701 -0
- package/dist/retry-queue.js.map +1 -0
- package/dist/roster-freshness.d.ts +160 -0
- package/dist/roster-freshness.d.ts.map +1 -0
- package/dist/roster-freshness.js +250 -0
- package/dist/roster-freshness.js.map +1 -0
- package/dist/screening-status.d.ts +15 -0
- package/dist/screening-status.d.ts.map +1 -0
- package/dist/screening-status.js +65 -0
- package/dist/screening-status.js.map +1 -0
- 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-certificate-pull.d.ts +79 -0
- package/dist/seal-certificate-pull.d.ts.map +1 -0
- package/dist/seal-certificate-pull.js +186 -0
- package/dist/seal-certificate-pull.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 +118 -0
- package/dist/seal-coordinator.d.ts.map +1 -0
- package/dist/seal-coordinator.js +927 -0
- package/dist/seal-coordinator.js.map +1 -0
- package/dist/seal-escalation.d.ts +80 -0
- package/dist/seal-escalation.d.ts.map +1 -0
- package/dist/seal-escalation.js +283 -0
- package/dist/seal-escalation.js.map +1 -0
- package/dist/seal-evidence-root-check.d.ts +70 -0
- package/dist/seal-evidence-root-check.d.ts.map +1 -0
- package/dist/seal-evidence-root-check.js +199 -0
- package/dist/seal-evidence-root-check.js.map +1 -0
- package/dist/seal-failure-store.d.ts +139 -0
- package/dist/seal-failure-store.d.ts.map +1 -0
- package/dist/seal-failure-store.js +207 -0
- package/dist/seal-failure-store.js.map +1 -0
- package/dist/seal-flows.d.ts +107 -0
- package/dist/seal-flows.d.ts.map +1 -0
- package/dist/seal-flows.js +638 -0
- package/dist/seal-flows.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +103 -0
- package/dist/seal-frontier-verify.d.ts.map +1 -0
- package/dist/seal-frontier-verify.js +143 -0
- package/dist/seal-frontier-verify.js.map +1 -0
- package/dist/seal-leaf.d.ts +58 -0
- package/dist/seal-leaf.d.ts.map +1 -0
- package/dist/seal-leaf.js +112 -0
- package/dist/seal-leaf.js.map +1 -0
- package/dist/seal-legibility-tbs.d.ts +25 -0
- package/dist/seal-legibility-tbs.d.ts.map +1 -0
- package/dist/seal-legibility-tbs.js +77 -0
- package/dist/seal-legibility-tbs.js.map +1 -0
- 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-receipt-upgrade.d.ts +30 -0
- package/dist/seal-receipt-upgrade.d.ts.map +1 -0
- package/dist/seal-receipt-upgrade.js +49 -0
- package/dist/seal-receipt-upgrade.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/seal-upgrade.d.ts +107 -0
- package/dist/seal-upgrade.d.ts.map +1 -0
- package/dist/seal-upgrade.js +200 -0
- package/dist/seal-upgrade.js.map +1 -0
- package/dist/sealed-conversation.d.ts +36 -0
- package/dist/sealed-conversation.d.ts.map +1 -0
- package/dist/sealed-conversation.js +171 -0
- package/dist/sealed-conversation.js.map +1 -0
- package/dist/sealed-leaf-set.d.ts +92 -0
- package/dist/sealed-leaf-set.d.ts.map +1 -0
- package/dist/sealed-leaf-set.js +122 -0
- package/dist/sealed-leaf-set.js.map +1 -0
- package/dist/send-claims.d.ts +56 -0
- package/dist/send-claims.d.ts.map +1 -0
- package/dist/send-claims.js +50 -0
- package/dist/send-claims.js.map +1 -0
- package/dist/session-assignment-parser.d.ts +104 -0
- package/dist/session-assignment-parser.d.ts.map +1 -0
- package/dist/session-assignment-parser.js +345 -0
- package/dist/session-assignment-parser.js.map +1 -0
- package/dist/session-category.d.ts +19 -0
- package/dist/session-category.d.ts.map +1 -0
- package/dist/session-category.js +14 -0
- package/dist/session-category.js.map +1 -0
- package/dist/session-ceremony.d.ts +297 -0
- package/dist/session-ceremony.d.ts.map +1 -0
- package/dist/session-ceremony.js +964 -0
- package/dist/session-ceremony.js.map +1 -0
- package/dist/session-closed.d.ts +79 -0
- package/dist/session-closed.d.ts.map +1 -0
- package/dist/session-closed.js +189 -0
- package/dist/session-closed.js.map +1 -0
- package/dist/session-connection-gater.d.ts +148 -0
- package/dist/session-connection-gater.d.ts.map +1 -0
- package/dist/session-connection-gater.js +332 -0
- package/dist/session-connection-gater.js.map +1 -0
- package/dist/session-content-context.d.ts +162 -0
- package/dist/session-content-context.d.ts.map +1 -0
- package/dist/session-content-context.js +2 -0
- package/dist/session-content-context.js.map +1 -0
- package/dist/session-content-handlers.d.ts +82 -0
- package/dist/session-content-handlers.d.ts.map +1 -0
- package/dist/session-content-handlers.js +1382 -0
- package/dist/session-content-handlers.js.map +1 -0
- package/dist/session-content-ingest.d.ts +206 -0
- package/dist/session-content-ingest.d.ts.map +1 -0
- package/dist/session-content-ingest.js +2167 -0
- package/dist/session-content-ingest.js.map +1 -0
- package/dist/session-content-send.d.ts +191 -0
- package/dist/session-content-send.d.ts.map +1 -0
- package/dist/session-content-send.js +1360 -0
- package/dist/session-content-send.js.map +1 -0
- package/dist/session-delivery-acks.d.ts +169 -0
- package/dist/session-delivery-acks.d.ts.map +1 -0
- package/dist/session-delivery-acks.js +569 -0
- package/dist/session-delivery-acks.js.map +1 -0
- package/dist/session-ephemerals.d.ts +279 -0
- package/dist/session-ephemerals.d.ts.map +1 -0
- package/dist/session-ephemerals.js +591 -0
- package/dist/session-ephemerals.js.map +1 -0
- package/dist/session-leaf-records.d.ts +159 -0
- package/dist/session-leaf-records.d.ts.map +1 -0
- package/dist/session-leaf-records.js +408 -0
- package/dist/session-leaf-records.js.map +1 -0
- package/dist/session-lifecycle.d.ts +303 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +1679 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/session-liveness.d.ts +135 -0
- package/dist/session-liveness.d.ts.map +1 -0
- package/dist/session-liveness.js +347 -0
- package/dist/session-liveness.js.map +1 -0
- package/dist/session-name.d.ts +35 -0
- package/dist/session-name.d.ts.map +1 -0
- package/dist/session-name.js +60 -0
- package/dist/session-name.js.map +1 -0
- package/dist/session-node-factory.d.ts +18 -0
- package/dist/session-node-factory.d.ts.map +1 -0
- package/dist/session-node-factory.js +182 -0
- package/dist/session-node-factory.js.map +1 -0
- package/dist/session-node-manager.d.ts +877 -0
- package/dist/session-node-manager.d.ts.map +1 -0
- package/dist/session-node-manager.js +2998 -0
- package/dist/session-node-manager.js.map +1 -0
- package/dist/session-node-types.d.ts +1057 -0
- package/dist/session-node-types.d.ts.map +1 -0
- package/dist/session-node-types.js +657 -0
- package/dist/session-node-types.js.map +1 -0
- package/dist/session-notify.d.ts +46 -0
- package/dist/session-notify.d.ts.map +1 -0
- package/dist/session-notify.js +116 -0
- package/dist/session-notify.js.map +1 -0
- package/dist/session-own-chain-store.d.ts +65 -0
- package/dist/session-own-chain-store.d.ts.map +1 -0
- package/dist/session-own-chain-store.js +75 -0
- package/dist/session-own-chain-store.js.map +1 -0
- package/dist/session-queries.d.ts +476 -0
- package/dist/session-queries.d.ts.map +1 -0
- package/dist/session-queries.js +1007 -0
- package/dist/session-queries.js.map +1 -0
- package/dist/session-read-handlers.d.ts +87 -0
- package/dist/session-read-handlers.d.ts.map +1 -0
- package/dist/session-read-handlers.js +689 -0
- package/dist/session-read-handlers.js.map +1 -0
- package/dist/session-records.d.ts +341 -0
- package/dist/session-records.d.ts.map +1 -0
- package/dist/session-records.js +858 -0
- package/dist/session-records.js.map +1 -0
- package/dist/session-relay-client.d.ts +659 -0
- package/dist/session-relay-client.d.ts.map +1 -0
- package/dist/session-relay-client.js +2876 -0
- package/dist/session-relay-client.js.map +1 -0
- package/dist/session-relay.d.ts +397 -0
- package/dist/session-relay.d.ts.map +1 -0
- package/dist/session-relay.js +1636 -0
- package/dist/session-relay.js.map +1 -0
- package/dist/session-salt-agreement.d.ts +331 -0
- package/dist/session-salt-agreement.d.ts.map +1 -0
- package/dist/session-salt-agreement.js +472 -0
- package/dist/session-salt-agreement.js.map +1 -0
- package/dist/session-salts.d.ts +432 -0
- package/dist/session-salts.d.ts.map +1 -0
- package/dist/session-salts.js +1540 -0
- package/dist/session-salts.js.map +1 -0
- package/dist/session-schema.d.ts +30 -0
- package/dist/session-schema.d.ts.map +1 -0
- package/dist/session-schema.js +877 -0
- package/dist/session-schema.js.map +1 -0
- package/dist/session-seal-leaf-store.d.ts +70 -0
- package/dist/session-seal-leaf-store.d.ts.map +1 -0
- package/dist/session-seal-leaf-store.js +105 -0
- package/dist/session-seal-leaf-store.js.map +1 -0
- package/dist/session-seal.d.ts +334 -0
- package/dist/session-seal.d.ts.map +1 -0
- package/dist/session-seal.js +1017 -0
- package/dist/session-seal.js.map +1 -0
- package/dist/session-terminal-refusal.d.ts +65 -0
- package/dist/session-terminal-refusal.d.ts.map +1 -0
- package/dist/session-terminal-refusal.js +87 -0
- package/dist/session-terminal-refusal.js.map +1 -0
- package/dist/session-tree.d.ts +110 -0
- package/dist/session-tree.d.ts.map +1 -0
- package/dist/session-tree.js +144 -0
- package/dist/session-tree.js.map +1 -0
- package/dist/session-views.d.ts +47 -0
- package/dist/session-views.d.ts.map +1 -0
- package/dist/session-views.js +278 -0
- package/dist/session-views.js.map +1 -0
- package/dist/signal-handlers.d.ts +63 -0
- package/dist/signal-handlers.d.ts.map +1 -0
- package/dist/signal-handlers.js +980 -0
- package/dist/signal-handlers.js.map +1 -0
- package/dist/signal-requirement-policy.d.ts +51 -0
- package/dist/signal-requirement-policy.d.ts.map +1 -0
- package/dist/signal-requirement-policy.js +89 -0
- package/dist/signal-requirement-policy.js.map +1 -0
- package/dist/signal-revocability.d.ts +49 -0
- package/dist/signal-revocability.d.ts.map +1 -0
- package/dist/signal-revocability.js +93 -0
- package/dist/signal-revocability.js.map +1 -0
- package/dist/signal-submission.d.ts +181 -0
- package/dist/signal-submission.d.ts.map +1 -0
- package/dist/signal-submission.js +368 -0
- package/dist/signal-submission.js.map +1 -0
- package/dist/signaling-connect.d.ts +118 -0
- package/dist/signaling-connect.d.ts.map +1 -0
- package/dist/signaling-connect.js +538 -0
- package/dist/signaling-connect.js.map +1 -0
- package/dist/signaling-wiring.d.ts +105 -0
- package/dist/signaling-wiring.d.ts.map +1 -0
- package/dist/signaling-wiring.js +393 -0
- package/dist/signaling-wiring.js.map +1 -0
- package/dist/singleton-lock.d.ts +85 -0
- package/dist/singleton-lock.d.ts.map +1 -0
- package/dist/singleton-lock.js +219 -0
- package/dist/singleton-lock.js.map +1 -0
- package/dist/sqlcipher-db.d.ts +139 -0
- package/dist/sqlcipher-db.d.ts.map +1 -0
- package/dist/sqlcipher-db.js +357 -0
- package/dist/sqlcipher-db.js.map +1 -0
- package/dist/standing-receivers.d.ts +319 -0
- package/dist/standing-receivers.d.ts.map +1 -0
- package/dist/standing-receivers.js +1295 -0
- package/dist/standing-receivers.js.map +1 -0
- package/dist/start-agent.d.ts +64 -0
- package/dist/start-agent.d.ts.map +1 -0
- package/dist/start-agent.js +136 -0
- package/dist/start-agent.js.map +1 -0
- package/dist/status-handler.d.ts +54 -0
- package/dist/status-handler.d.ts.map +1 -0
- package/dist/status-handler.js +61 -0
- package/dist/status-handler.js.map +1 -0
- package/dist/submission-retry.d.ts +208 -0
- package/dist/submission-retry.d.ts.map +1 -0
- package/dist/submission-retry.js +506 -0
- package/dist/submission-retry.js.map +1 -0
- package/dist/telegram-bot-client.d.ts +34 -0
- package/dist/telegram-bot-client.d.ts.map +1 -0
- package/dist/telegram-bot-client.js +36 -0
- package/dist/telegram-bot-client.js.map +1 -0
- package/dist/telegram-doorbell.d.ts +38 -0
- package/dist/telegram-doorbell.d.ts.map +1 -0
- package/dist/telegram-doorbell.js +140 -0
- package/dist/telegram-doorbell.js.map +1 -0
- package/dist/test-handlers.d.ts +62 -0
- package/dist/test-handlers.d.ts.map +1 -0
- package/dist/test-handlers.js +240 -0
- package/dist/test-handlers.js.map +1 -0
- package/dist/testing.d.ts +10 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +10 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport-composition.d.ts +31 -0
- package/dist/transport-composition.d.ts.map +1 -0
- package/dist/transport-composition.js +55 -0
- package/dist/transport-composition.js.map +1 -0
- package/dist/transport-selector.d.ts +202 -0
- package/dist/transport-selector.d.ts.map +1 -0
- package/dist/transport-selector.js +196 -0
- package/dist/transport-selector.js.map +1 -0
- package/dist/trust-signal-pickup-listener.d.ts +43 -0
- package/dist/trust-signal-pickup-listener.d.ts.map +1 -0
- package/dist/trust-signal-pickup-listener.js +48 -0
- package/dist/trust-signal-pickup-listener.js.map +1 -0
- package/dist/trust-signal-store.d.ts +406 -0
- package/dist/trust-signal-store.d.ts.map +1 -0
- package/dist/trust-signal-store.js +939 -0
- package/dist/trust-signal-store.js.map +1 -0
- package/dist/trust-signal-sweep-tick.d.ts +60 -0
- package/dist/trust-signal-sweep-tick.d.ts.map +1 -0
- package/dist/trust-signal-sweep-tick.js +101 -0
- package/dist/trust-signal-sweep-tick.js.map +1 -0
- package/dist/trust-signal-sweep.d.ts +94 -0
- package/dist/trust-signal-sweep.d.ts.map +1 -0
- package/dist/trust-signal-sweep.js +147 -0
- package/dist/trust-signal-sweep.js.map +1 -0
- package/dist/type-registry.d.ts +42 -0
- package/dist/type-registry.d.ts.map +1 -0
- package/dist/type-registry.js +37 -0
- package/dist/type-registry.js.map +1 -0
- package/dist/types.d.ts +746 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/unresolved-nodes-report.d.ts +14 -0
- package/dist/unresolved-nodes-report.d.ts.map +1 -0
- package/dist/unresolved-nodes-report.js +96 -0
- package/dist/unresolved-nodes-report.js.map +1 -0
- package/dist/vocabulary.d.ts +150 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +386 -0
- package/dist/vocabulary.js.map +1 -0
- package/dist/who-label.d.ts +28 -0
- package/dist/who-label.d.ts.map +1 -0
- package/dist/who-label.js +31 -0
- package/dist/who-label.js.map +1 -0
- package/dist/who-resolver.d.ts +15 -0
- package/dist/who-resolver.d.ts.map +1 -0
- package/dist/who-resolver.js +47 -0
- package/dist/who-resolver.js.map +1 -0
- package/dist/wire-content-hash.d.ts +93 -0
- package/dist/wire-content-hash.d.ts.map +1 -0
- package/dist/wire-content-hash.js +116 -0
- package/dist/wire-content-hash.js.map +1 -0
- package/dist/withheld-content.d.ts +13 -0
- package/dist/withheld-content.d.ts.map +1 -0
- package/dist/withheld-content.js +46 -0
- package/dist/withheld-content.js.map +1 -0
- package/dist/witness-alerts.d.ts +40 -0
- package/dist/witness-alerts.d.ts.map +1 -0
- package/dist/witness-alerts.js +102 -0
- package/dist/witness-alerts.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CELLO Daemon — SENDING A MESSAGE
|
|
3
|
+
*
|
|
4
|
+
* The outbound half of the content path, split out of `session-node-manager.ts` with its inbound
|
|
5
|
+
* counterpart in `session-content-ingest.ts`. What happens between an operator calling
|
|
6
|
+
* `cello_send` and the bytes leaving this machine: hashing and labelling the content, having the
|
|
7
|
+
* relay witness its position, signing our own authorship of it, encrypting it, placing our leaf,
|
|
8
|
+
* opening the stream, and arming the timer that parks the message when no acknowledgement returns.
|
|
9
|
+
*
|
|
10
|
+
* **Moved verbatim, comments included.**
|
|
11
|
+
*
|
|
12
|
+
* The two halves are separated because they share almost nothing: outbound reaches nothing on the
|
|
13
|
+
* inbound side at all, and inbound reaches exactly ONE thing here — `resolveAwaitingAck`, when the
|
|
14
|
+
* counterparty's delivery acknowledgement arrives on the stream the receiver is already reading.
|
|
15
|
+
* That single edge is why this class is constructed first and handed to the other one.
|
|
16
|
+
*/
|
|
17
|
+
import * as lp from "it-length-prefixed";
|
|
18
|
+
import { encodeCbor, decodeStructure1, encodeStructure1 } from "@cello-protocol/protocol-types";
|
|
19
|
+
import { verify, sealSessionContent } from "@cello-protocol/crypto";
|
|
20
|
+
import { CELLO_CONTENT_PROTOCOL_ID } from "@cello-protocol/transport";
|
|
21
|
+
import { CONTENT_ENCRYPTION_GUIDANCE, SESSION_CONTENT_ENCRYPTION_V1 } from "./content-encryption-status.js";
|
|
22
|
+
import { REFUSAL_KINDS, noteLocalCredentialRefusal, relayAckHashRefusalNotice } from "./refusal-reasons.js";
|
|
23
|
+
import { parkRefusalGuidance } from "./park-envelope.js";
|
|
24
|
+
import { terminalRelayRefusal } from "./session-terminal-refusal.js";
|
|
25
|
+
import { classifyRelayAuthRefusal, isTerminalRelayRefusal, isLocalCredentialRefusal } from "./session-relay-client.js";
|
|
26
|
+
import { REDIAL_COOLDOWN_MS } from "./session-node-types.js";
|
|
27
|
+
import { extractErrorMessage } from "./error-message.js";
|
|
28
|
+
export class SessionContentSender {
|
|
29
|
+
#ctx;
|
|
30
|
+
constructor(ctx) {
|
|
31
|
+
this.#ctx = ctx;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Send content over the session node's direct P2P content stream.
|
|
35
|
+
* On a dead/missing stream this returns a NAMED, diagnosable failure — never a silent success
|
|
36
|
+
* (which desyncs the two sides). Do not swallow a send error here.
|
|
37
|
+
*
|
|
38
|
+
* SCOPE — what this send path does, and the one thing it still does not:
|
|
39
|
+
*
|
|
40
|
+
* - It delivers content over the direct /cello/content/1.0.0 P2P stream AND submits a
|
|
41
|
+
* K_local-signed content_hash leaf to the relay (`session.relay.hash.submitted` below).
|
|
42
|
+
* Both happen. `witnessed` on the response says whether the relay accepted the leaf.
|
|
43
|
+
*
|
|
44
|
+
* - The `sequence_number` returned is still the LOCAL leaf index, not a relay-assigned global
|
|
45
|
+
* one. Each daemon appends in its own observation order, so two roots agree only under
|
|
46
|
+
* ping-ponged traffic; concurrent bidirectional sends can still diverge.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ STATED SEPARATELY BECAUSE THEY WENT STALE AT DIFFERENT TIMES. This block once said the relay
|
|
49
|
+
* hash-submit did not exist and that the sequence was local BECAUSE of that. The first is false
|
|
50
|
+
* now; the second's CLAIM holds and its REASON did not — correcting both on one verdict would
|
|
51
|
+
* have deleted a live limitation along with a dead one.
|
|
52
|
+
*/
|
|
53
|
+
async sendContent(agentName, sessionId, content, contentHash,
|
|
54
|
+
/**
|
|
55
|
+
* Required alongside the two below — every production caller already passes one, and an optional
|
|
56
|
+
* parameter in front of a required one is what TypeScript refuses. Making it explicit costs
|
|
57
|
+
* nothing and removes the last place a positional argument can silently shift.
|
|
58
|
+
*/
|
|
59
|
+
correlationId,
|
|
60
|
+
/**
|
|
61
|
+
* The DOMAIN this content belongs to, as the relay and the directory will see it. Defaults to
|
|
62
|
+
* MESSAGE so `cello_send` is unchanged; the document path passes 0x04/0x05. Not cosmetic — the
|
|
63
|
+
* directory computes `final_message` and `answered` from the witnessed kind, and both of its
|
|
64
|
+
* document exclusions were dead while every document leaf arrived here as a message.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ ALSO REQUIRED NOW, and for the same reason as `contentHashAlg` below — this parameter is the
|
|
67
|
+
* precedent, not a bystander. It defaulted to MESSAGE, the document adapter in `daemon.ts`
|
|
68
|
+
* silently dropped it, and the wire was wrong for a whole release: *"0.0.145 shipped the fix
|
|
69
|
+
* everywhere except here."* A default that matches the common case makes the omission invisible
|
|
70
|
+
* at every call site and at typecheck. Every caller states its kind now.
|
|
71
|
+
*/
|
|
72
|
+
leafKind,
|
|
73
|
+
/**
|
|
74
|
+
* `DOD-M15-SEALWIRE-1` part B2b — the algorithm `contentHash` was produced under, taken from
|
|
75
|
+
* `contentHashForSession` by the caller that computed the hash.
|
|
76
|
+
*
|
|
77
|
+
* Passed rather than re-derived HERE, deliberately: re-deriving would ask "how would this
|
|
78
|
+
* session hash something now?", and the answer can differ from how THIS message was actually
|
|
79
|
+
* hashed. A hash and its label must travel together or the peer refuses a message nobody touched.
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ REQUIRED, NOT DEFAULTED — review B2b-1 F4, and the default is what made four mutants
|
|
82
|
+
* unfalsifiable. It was `= CONTENT_HASH_ALGS.SHA256`, which equals the only value in play today,
|
|
83
|
+
* so DROPPING THE ARGUMENT AT ANY OF THE FIVE HOPS produced byte-identical output and the whole
|
|
84
|
+
* 2,800-test daemon suite stayed green. Measured at all four send sites individually.
|
|
85
|
+
*
|
|
86
|
+
* A default that equals the current value makes every threading edit invisible until the value
|
|
87
|
+
* changes — and the day it changes, the dropped argument mislabels the message and every peer
|
|
88
|
+
* refuses it as a tamper. Required makes a dropped argument a TYPECHECK failure instead of a
|
|
89
|
+
* test question nobody can answer.
|
|
90
|
+
*/
|
|
91
|
+
contentHashAlg) {
|
|
92
|
+
const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
93
|
+
if (!entry) {
|
|
94
|
+
// M12-P13: no node, so nothing was witnessed and nothing was queued — the caller must NOT
|
|
95
|
+
// commit a leaf for this. `durable` is a required field precisely so a new failure branch
|
|
96
|
+
// cannot be added without answering the question every caller now asks.
|
|
97
|
+
return { ok: false, reason: "session_node_unavailable", error: "no active session node for this session", durable: false };
|
|
98
|
+
}
|
|
99
|
+
// R1 (MSG-001-3b): witness the message-leaf HASH to the relay FIRST, INDEPENDENT of
|
|
100
|
+
// direct delivery. The relay is the ordering authority (Structure 2): it assigns the
|
|
101
|
+
// canonical sequence from the hash whether or not the counterparty is reachable for direct
|
|
102
|
+
// content. So an OFFLINE recipient still gets a sequence, and the parked content is later
|
|
103
|
+
// recovered AT that sequence (DOD-MSG-4 recovery-not-desync). The relay only ever sees the
|
|
104
|
+
// hash (INV-3). Best-effort: a relay miss degrades to local-only sequencing.
|
|
105
|
+
//
|
|
106
|
+
// This MUST run BEFORE the direct send, not after it: an offline recipient never completes a
|
|
107
|
+
// direct send, and sequencing after it would leave their content with no sequence at all.
|
|
108
|
+
//
|
|
109
|
+
// DOD-MSG-4 (self-ordering content frame): the relay's committed ordering record for this leaf,
|
|
110
|
+
// captured from the hash submit so it can be stamped into the content frame (and the parked
|
|
111
|
+
// entry). Undefined if the relay is unreachable / an old relay — the receiver then falls back to
|
|
112
|
+
// the leaf_deliver witness stream / arrival order.
|
|
113
|
+
let orderingS1;
|
|
114
|
+
let orderingS2;
|
|
115
|
+
/**
|
|
116
|
+
* `DOD-M15-AUTHORSHIP-ABSENT-1`: the sender's signature over `orderingS1`, when the relay
|
|
117
|
+
* witnessed this leaf. Undefined when it did not — and the frame builder below then signs a
|
|
118
|
+
* Structure 1 of its own rather than shipping a frame with nothing to check.
|
|
119
|
+
*/
|
|
120
|
+
let orderingSig;
|
|
121
|
+
/**
|
|
122
|
+
* DOD-M15-SEALWIRE-1 bullet 5, SENT half. Our own Ed25519 signature over `orderingS1`.
|
|
123
|
+
*
|
|
124
|
+
* The submit path already computes this — `keyProvider.sign(structure1)` — and puts it on the
|
|
125
|
+
* wire as `sender_signature`. It was simply never handed back, which is the whole of the defect:
|
|
126
|
+
* a RECEIVED row could prove its author to a third party and a SENT row could not, so half the
|
|
127
|
+
* transcript was provable and half was assertion.
|
|
128
|
+
*/
|
|
129
|
+
let sentAuthorship;
|
|
130
|
+
// DOD-M12B-INDEX-1: the relay's answer to "where does this message go", carried to the caller.
|
|
131
|
+
let assignedSeq;
|
|
132
|
+
// DOD-MP-SESSION-RETIRE-1 — the relay's answer SURVIVES to the caller even when the direct send
|
|
133
|
+
// then succeeds. `relay_session_gone` is deliberately not terminal (it also fires for perfectly
|
|
134
|
+
// live sessions whenever the relay restarts, because the relay stores sessions in memory), so
|
|
135
|
+
// this path warns and carries on — and the send returns `ok: true, delivered: true` for a leaf
|
|
136
|
+
// that was never witnessed. The content arrives; the RECORD stops growing, silently.
|
|
137
|
+
//
|
|
138
|
+
// Reporting it does not change success or failure for any existing caller. It lets the document
|
|
139
|
+
// worker, which has no human in the loop, notice that a session's record is dead and route
|
|
140
|
+
// around it. Without this the suspicion counter could never see the one reason it exists for,
|
|
141
|
+
// and the successful direct send actively CLEARED it.
|
|
142
|
+
let relayRefusal;
|
|
143
|
+
if (entry.relayClient && entry.relaySessionIdBytes) {
|
|
144
|
+
try {
|
|
145
|
+
const witnessed = await entry.relayClient.submitMessageHash(entry.node, entry.relaySessionIdBytes, contentHash, leafKind);
|
|
146
|
+
if (witnessed.ok) {
|
|
147
|
+
orderingS1 = witnessed.structure1_cbor;
|
|
148
|
+
orderingS2 = witnessed.structure2_cbor;
|
|
149
|
+
// `DOD-M15-AUTHORSHIP-ABSENT-1`: the signature that goes ON THE FRAME beside `orderingS1`.
|
|
150
|
+
// Captured here, next to the bytes it signs, because a signature assigned anywhere else
|
|
151
|
+
// could end up beside a different Structure 1 — a proof next to the wrong signed bytes is
|
|
152
|
+
// worse than no proof, since it looks checkable and fails.
|
|
153
|
+
orderingSig = witnessed.sender_signature;
|
|
154
|
+
/**
|
|
155
|
+
* PAIRED WITH THE BYTES IT SIGNS, in one place, so the two can never be assigned apart.
|
|
156
|
+
*
|
|
157
|
+
* ⚠️ THE PUBKEY COMES FROM INSIDE `structure1_cbor`, NOT FROM AN AGENT LOOKUP — the same
|
|
158
|
+
* source the RECEIVED half uses. A verifier checks the signature against the key in the
|
|
159
|
+
* signed bytes; storing any other key would produce a row that looks checkable and fails,
|
|
160
|
+
* and a lookup could drift from what was actually signed (a rotated identity, the wrong
|
|
161
|
+
* agent resolved by name). Taking it from the signed bytes makes that class impossible.
|
|
162
|
+
*/
|
|
163
|
+
if (witnessed.structure1_cbor && witnessed.sender_signature) {
|
|
164
|
+
/**
|
|
165
|
+
* ⚠️ THE DROP IS SOFT, BUT IT MUST NOT BE SILENT — review pass 1, F2.
|
|
166
|
+
*
|
|
167
|
+
* My comment here claimed the resulting row is "distinguishable from one that carries a
|
|
168
|
+
* signature". True, and it misses the comparison that matters: **it is byte-identical to
|
|
169
|
+
* an UNWITNESSED send** — `self_authored`, both proof columns NULL. So the record cannot
|
|
170
|
+
* tell "the relay never witnessed this" from "we witnessed it, held the proof, and
|
|
171
|
+
* dropped it decoding our own bytes."
|
|
172
|
+
*
|
|
173
|
+
* And the asymmetry with the received half is the argument. The received half is soft
|
|
174
|
+
* about a missing ORDERING record (`#recordFrameOrdering`) because the COUNTERPARTY
|
|
175
|
+
* supplied those bytes — an absence we cannot resolve. It is not soft about a missing
|
|
176
|
+
* authorship proof any more; `DOD-M15-AUTHORSHIP-ABSENT-1` refuses that outright.
|
|
177
|
+
* Here **we produced them**, in `session-relay-client.ts`, moments earlier. A failure
|
|
178
|
+
* means our own encoder and decoder disagree: an internal invariant break that would
|
|
179
|
+
* strip authorship from every sent row for the life of the process. Soft is still right
|
|
180
|
+
* — throwing would lose a delivered message over a missing attestation — but soft and
|
|
181
|
+
* unannounced is the silent-fallback pattern this milestone exists to find.
|
|
182
|
+
*/
|
|
183
|
+
const dropAuthorship = (reason, error, extra) => {
|
|
184
|
+
this.#ctx.logger.warn("session.sent.authorship.unavailable", {
|
|
185
|
+
sessionId,
|
|
186
|
+
agentName,
|
|
187
|
+
reason,
|
|
188
|
+
// WHICH ROW lost its proof — review pass 2, M2. Without the sequence an operator knows
|
|
189
|
+
// a message is unproven and not which one, in a transcript of hundreds.
|
|
190
|
+
...(witnessed.ok ? { relaySequence: witnessed.sequence_number } : {}),
|
|
191
|
+
...(extra ?? {}),
|
|
192
|
+
...(error === undefined ? {} : { error: extractErrorMessage(error) }),
|
|
193
|
+
impact: "this sent message is recorded with attribution 'self_authored' and NO signature, so the row " +
|
|
194
|
+
"asserts its author rather than proving one. It is indistinguishable in the database from a " +
|
|
195
|
+
"send the relay never witnessed — this log line is the only thing that tells them apart.",
|
|
196
|
+
guidance: "We produced these bytes ourselves, so a decode or shape failure here means this daemon's own " +
|
|
197
|
+
"encoder and decoder disagree. Treat it as an internal invariant break, not a peer problem.",
|
|
198
|
+
correlationId,
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
// Structure 1 = [version, content_hash, sender_pubkey, session_id, last_seen_seq,
|
|
203
|
+
// timestamp] (+ last_seen_hash at 6 on a v2 claim — 020-ACKHASH). The sender pubkey is
|
|
204
|
+
// index 2 in both; the same read `#recordFrameOrdering` does for the received half.
|
|
205
|
+
const s1Decoded = decodeStructure1(witnessed.structure1_cbor);
|
|
206
|
+
// NAMED AT ITS CAUSE. A failed decode falling through to the shape check below would
|
|
207
|
+
// report `pubkey_shape` for bytes that never yielded a pubkey at all, sending the next
|
|
208
|
+
// reader to audit a key when the layout is what disagreed.
|
|
209
|
+
const pk = s1Decoded.ok ? s1Decoded.fields.senderPubkey : undefined;
|
|
210
|
+
// The SIGNATURE is length-checked too (review F2): the guard checked the pubkey's 32
|
|
211
|
+
// bytes and only truthiness on the signature, so a zero-length one would have stored an
|
|
212
|
+
// uncheckable BLOB. Not reachable today — `sign()` returns 64 — and the asymmetry is
|
|
213
|
+
// the kind that stops being unreachable quietly.
|
|
214
|
+
/**
|
|
215
|
+
* ⚠️ VERIFIED BEFORE IT IS STORED, NOT SHAPE-CHECKED — review pass 2, H2, and this is
|
|
216
|
+
* worth more than any test of it.
|
|
217
|
+
*
|
|
218
|
+
* This used to accept the pair on 32 bytes and 64 bytes. A shape check cannot tell a
|
|
219
|
+
* real proof from 96 bytes that resemble one, and **nothing downstream ever checks
|
|
220
|
+
* either**: not at write time, and not at read time, because no production reader of
|
|
221
|
+
* these columns exists yet. So a wrong Structure-1 index, a wrong key, or a pair from
|
|
222
|
+
* two different submits would all have been persisted as a row that **looks checkable
|
|
223
|
+
* to an auditor and fails** — strictly worse than the honest unproven row it replaced.
|
|
224
|
+
*
|
|
225
|
+
* The received half has always done this (`#verifyAuthorshipClaim` verifies before
|
|
226
|
+
* storing and treats a failure as fatal). The sent half did not, and every ingredient
|
|
227
|
+
* was already in scope on this line.
|
|
228
|
+
*
|
|
229
|
+
* What it buys over a test: a wrong index becomes **impossible to persist**. The row
|
|
230
|
+
* gets NULL, the warn below fires, and it happens in production on the machine that
|
|
231
|
+
* caused it — not in a suite someone has to remember to write.
|
|
232
|
+
*
|
|
233
|
+
* NOT fatal, unlike the received half, and the asymmetry is deliberate: there the
|
|
234
|
+
* failure means a COUNTERPARTY sent something that does not verify, which is an
|
|
235
|
+
* identity problem. Here it means our own encoder and decoder disagree — bad, but it
|
|
236
|
+
* must not cost the operator a delivered message.
|
|
237
|
+
*/
|
|
238
|
+
if (!s1Decoded.ok) {
|
|
239
|
+
dropAuthorship("structure1_decode_failed", undefined, { structure1Reason: s1Decoded.reason });
|
|
240
|
+
}
|
|
241
|
+
else if (!(pk instanceof Uint8Array) || pk.length !== 32) {
|
|
242
|
+
dropAuthorship("pubkey_shape", undefined, { pubkeyLen: pk instanceof Uint8Array ? pk.length : -1 });
|
|
243
|
+
}
|
|
244
|
+
else if (witnessed.sender_signature.length !== 64) {
|
|
245
|
+
dropAuthorship("signature_shape", undefined, { sigLen: witnessed.sender_signature.length });
|
|
246
|
+
}
|
|
247
|
+
else if (!verify(pk, witnessed.structure1_cbor, witnessed.sender_signature)) {
|
|
248
|
+
dropAuthorship("pair_does_not_verify");
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
sentAuthorship = { senderPubkey: pk, senderSig: witnessed.sender_signature };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
// NOT a decode failure — review F3. `decodeStructure1` never throws and its failure is
|
|
256
|
+
// handled as the first branch above, with its own reason. What can still throw in here
|
|
257
|
+
// is `verify()` and the Buffer work, so this names that instead of sending the reader
|
|
258
|
+
// to audit a CBOR layout that decoded fine.
|
|
259
|
+
dropAuthorship("authorship_verify_threw", err);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// 1-BASED → 0-BASED. The relay numbers the first leaf of a session 1
|
|
263
|
+
// (`relay-node.ts`: `const seq = state.seq_counter + 1`), and this tree is 0-indexed.
|
|
264
|
+
// Every RECEIVE path in this file normalises with -1 and says so; the send path took the
|
|
265
|
+
// raw number, which puts every comparison against `tree.size()` one position out — so a
|
|
266
|
+
// perfectly healthy first message reads as "ahead of the tail" and is held behind a gap
|
|
267
|
+
// that does not exist. Do not remove this without changing both receive sites too.
|
|
268
|
+
assignedSeq = witnessed.sequence_number - 1;
|
|
269
|
+
/**
|
|
270
|
+
* DOD-M15-SEALPRECOND-1 — FROM HERE UNTIL `placeOwnLeaf`, THIS TREE IS SHORT AND KNOWS IT.
|
|
271
|
+
*
|
|
272
|
+
* The relay has ordered this leaf, so every other party counts it, and this side cannot
|
|
273
|
+
* append it until the caller places it at the assigned position (DOD-M12B-INDEX-1, which
|
|
274
|
+
* is not being changed here). A seal signed inside that span covers a tree one leaf short
|
|
275
|
+
* of the relay's leaf set: `merkle_root_mismatch`, and a receipt lost permanently for
|
|
276
|
+
* BOTH sides — measured on session `e7dd3f43…`, 2026-09-11.
|
|
277
|
+
*
|
|
278
|
+
* Recorded rather than inferred, for the reason `missingLeaves` counts a witness map
|
|
279
|
+
* instead of subtracting `treeSize` from `highWaterSeq`: the relay's sequence space holds
|
|
280
|
+
* CTRL leaves and this tree does not, so arithmetic between the two reads a phantom gap
|
|
281
|
+
* forever after the first seal.
|
|
282
|
+
*/
|
|
283
|
+
this.#noteOwnLeafOrdered(agentName, sessionId, Buffer.from(contentHash).toString("hex"), assignedSeq);
|
|
284
|
+
this.#ctx.logger.info("session.relay.hash.submitted", {
|
|
285
|
+
sessionId,
|
|
286
|
+
// BOTH SPACES, NAMED. The relay's number is 1-based and the leaf index is 0-based, and
|
|
287
|
+
// reading one as the other is the defect this milestone exists to stop — so a log that
|
|
288
|
+
// carries only "sequenceNumber" invites exactly that mistake on the next investigation.
|
|
289
|
+
relaySequence: witnessed.sequence_number,
|
|
290
|
+
leafIndex: assignedSeq,
|
|
291
|
+
correlationId,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
else if (isTerminalRelayRefusal(witnessed.reason)) {
|
|
295
|
+
// TERMINAL — REFUSE THE SEND, AND RETIRE THE SESSION. The relay has ended this session, so
|
|
296
|
+
// this leaf can never enter the record and neither can any leaf after it. Continuing would
|
|
297
|
+
// deliver content the conversation cannot prove it exchanged, and report `delivered: true`
|
|
298
|
+
// while doing it.
|
|
299
|
+
//
|
|
300
|
+
// That is not hypothetical. Measured 2026-08-09: a session whose relay had sealed it after
|
|
301
|
+
// both away-responders fired ran for 68 more minutes and 8 more messages, every send
|
|
302
|
+
// reporting success, against a chain that had stopped growing at six leaves.
|
|
303
|
+
//
|
|
304
|
+
// Refused rather than parked: parking is for content the peer has not received YET. There
|
|
305
|
+
// is no yet.
|
|
306
|
+
//
|
|
307
|
+
// DOD-MP-SESSION-RETIRE-1 — extracted to a function so the behaviour is REACHABLE BY A
|
|
308
|
+
// TEST. Inline, it needed a live `#activeNodes` entry holding a real relay client, which
|
|
309
|
+
// no unit test can construct — so nothing could assert what this daemon DOES about a
|
|
310
|
+
// terminal refusal, and for a long time the answer was "logs it and carries on". The seam
|
|
311
|
+
// that must be substituted is the relay's ANSWER; the thing under test is the response to
|
|
312
|
+
// it. Splitting them makes the second testable without faking the first.
|
|
313
|
+
return terminalRelayRefusal({
|
|
314
|
+
logger: this.#ctx.logger,
|
|
315
|
+
// FULL TEARDOWN, not a status write. Every other terminal path goes through these,
|
|
316
|
+
// and the difference is not bookkeeping: `destroySessionNode` also records the
|
|
317
|
+
// terminal answer for a BLOCKED `cello_receive` (which otherwise hangs to timeout),
|
|
318
|
+
// detaches the relay stream, stops the libp2p node, evicts the plaintext caches, and
|
|
319
|
+
// re-arms the standing receiver — which on a fixed-port deployment is the moment the
|
|
320
|
+
// port is freed. A DB-only flip leaves the corpse in `#activeNodes` holding the very
|
|
321
|
+
// port the replacement session may need, which would defeat this fix's own purpose.
|
|
322
|
+
//
|
|
323
|
+
// THE TWO TERMINAL REASONS ARE NOT THE SAME FACT and must not share a status:
|
|
324
|
+
// session_sealed — the seal really completed; a FROST certificate exists at the
|
|
325
|
+
// directory and `cello_sealed_receipt` pulls it on a local miss.
|
|
326
|
+
// session_not_found — the relay never held it or lost it. There may be no
|
|
327
|
+
// certificate anywhere, so writing "sealed" would be a
|
|
328
|
+
// FABRICATED NOTARIZATION CLAIM: `cello_close_session` would
|
|
329
|
+
// answer "already sealed, view its notarization" while the
|
|
330
|
+
// receipt read answers "not sealed yet" — the two-answers-
|
|
331
|
+
// pointing-at-each-other deadlock `seal-certificate-pull.ts`
|
|
332
|
+
// exists to kill. `abandoned` is the state invented for
|
|
333
|
+
// locally-terminal-with-nothing-to-notarize.
|
|
334
|
+
// ONLY `session_sealed` RETIRES ANYTHING. The other terminal reason,
|
|
335
|
+
// `session_not_found`, is documented THREE FUNCTIONS AWAY as **transient**
|
|
336
|
+
// (DOD-FIRSTMSG-WITNESS-1): the relay does not hold the session YET, and in all 23
|
|
337
|
+
// logged first-message failures the assignment landed 5ms–2.1s after the rejected
|
|
338
|
+
// submit. Retiring on it would destroy a live session seconds old — trading a stuck
|
|
339
|
+
// document for a killed conversation, which is a strictly worse bug than the one this
|
|
340
|
+
// unit fixes. It still refuses the send; it just does not reach for the shovel.
|
|
341
|
+
retireSession: (id) => {
|
|
342
|
+
/**
|
|
343
|
+
* `session_sealed` retires as SEALED. `seal_refused` retires as ABANDONED —
|
|
344
|
+
* `DOD-M15-TERMINAL-REASON-1`, and the distinction is the whole reason that unit
|
|
345
|
+
* exists.
|
|
346
|
+
*
|
|
347
|
+
* A refused seal has NO certificate: a directory read it and rejected it. Writing
|
|
348
|
+
* `sealed` here would be a fabricated notarization claim — `cello_close_session`
|
|
349
|
+
* answering "already sealed, view its notarization" while the receipt read answers
|
|
350
|
+
* "not sealed yet", which is the two-answers-pointing-at-each-other deadlock
|
|
351
|
+
* described above. `abandoned` is the state invented for exactly this:
|
|
352
|
+
* locally terminal with nothing to notarize.
|
|
353
|
+
*/
|
|
354
|
+
if (witnessed.reason === "seal_refused") {
|
|
355
|
+
// `abandonSession`, not a hand-rolled flip-then-teardown: it already does the
|
|
356
|
+
// status write synchronously BEFORE the async teardown yields, which is the
|
|
357
|
+
// ordering the comment above spends a paragraph on. Reimplementing it here would
|
|
358
|
+
// be a second copy of that reasoning, free to drift from the first.
|
|
359
|
+
void this.#ctx.abandonSession(agentName, id);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (witnessed.reason !== "session_sealed")
|
|
363
|
+
return;
|
|
364
|
+
// STATUS FIRST AND SYNCHRONOUS, teardown second — the order `abandonSession` uses,
|
|
365
|
+
// and for a sharper reason here: `destroySessionNode` returns early at its
|
|
366
|
+
// `if (!entry) return` when the node is not in `#activeNodes`, and the status write
|
|
367
|
+
// lives AFTER that guard. There is an entry on this path today (we are mid-send on
|
|
368
|
+
// its relay client), but resting the whole fix on that is resting it on a
|
|
369
|
+
// coincidence — a concurrent teardown would leave the row `active` and the loop
|
|
370
|
+
// would resume. The flip is the load-bearing half; the teardown is what makes the
|
|
371
|
+
// memory agree with it.
|
|
372
|
+
this.#ctx.updateSessionStatus(agentName, id, "sealed");
|
|
373
|
+
void this.#ctx.destroySessionNode(agentName, id, "sealed");
|
|
374
|
+
},
|
|
375
|
+
}, { sessionId, reason: witnessed.reason, ...(witnessed.detail === undefined ? {} : { detail: witnessed.detail }), correlationId });
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
this.#ctx.logger.warn("session.relay.hash.submit.failed", {
|
|
379
|
+
sessionId,
|
|
380
|
+
reason: witnessed.reason,
|
|
381
|
+
// The relay's own words about what happened, when it sent any. `reason` is the class and
|
|
382
|
+
// this is the cause — without it a refusal reaches the operator as a bare code, which is
|
|
383
|
+
// the state this field existed to end and never actually did.
|
|
384
|
+
...(witnessed.detail === undefined ? {} : { detail: witnessed.detail }),
|
|
385
|
+
correlationId,
|
|
386
|
+
});
|
|
387
|
+
/**
|
|
388
|
+
* ─── 033-ACKEMIT: THIS ONE REACHES THE OPERATOR, NOT JUST THE LOG ─────────────────────
|
|
389
|
+
*
|
|
390
|
+
* Every other refusal on this branch is an availability answer — the relay is busy, the
|
|
391
|
+
* session is not recorded, the stream died — and the send degrades to unwitnessed, which
|
|
392
|
+
* is the documented path. These two are not availability. The WITNESS is telling us that
|
|
393
|
+
* the acknowledgement this daemon signed disagrees with the record, and that is a
|
|
394
|
+
* statement about the integrity of the conversation.
|
|
395
|
+
*
|
|
396
|
+
* `logger.warn` followed by a bare assignment is the exact shape Invariant 2's recurring
|
|
397
|
+
* box names — a guard that fires correctly into a file nobody opens. The named surface is
|
|
398
|
+
* `noteContentRefusal`, the same one every inbound refusal in this file uses, so it lands
|
|
399
|
+
* where the operator already looks for "something was rejected and here is why".
|
|
400
|
+
*
|
|
401
|
+
* It does NOT stop the send. The relay refused to witness this leaf, so the message
|
|
402
|
+
* degrades to unwitnessed exactly as any other refusal does and the operator keeps their
|
|
403
|
+
* conversation; what changes is that they are told the record has stopped agreeing with
|
|
404
|
+
* itself, at the moment it happens, instead of discovering it at the seal.
|
|
405
|
+
*/
|
|
406
|
+
if (witnessed.reason === "ack_hash_mismatch" || witnessed.reason === "ack_hash_unverifiable") {
|
|
407
|
+
/**
|
|
408
|
+
* THE SENTENCES LIVE IN `refusal-reasons.ts` — 033-ACKEMIT review F6. They were inline
|
|
409
|
+
* here, behind a real relay answering a real refusal, so nothing could test them; and
|
|
410
|
+
* the one that was wrong (a remedy naming a relay handover this system does not have)
|
|
411
|
+
* was wrong for as long as that lasted.
|
|
412
|
+
*/
|
|
413
|
+
const { impact, guidance } = relayAckHashRefusalNotice(witnessed.reason === "ack_hash_unverifiable", this.#ctx.mailboxRouteAvailable(agentName));
|
|
414
|
+
this.#ctx.logger.error("session.relay.ack_hash.refused", {
|
|
415
|
+
agentName, sessionId, correlationId, reason: witnessed.reason,
|
|
416
|
+
...(witnessed.detail === undefined ? {} : { detail: witnessed.detail }),
|
|
417
|
+
impact, guidance,
|
|
418
|
+
});
|
|
419
|
+
this.#ctx.notices.noteContentRefusal(agentName, sessionId, witnessed.reason, {
|
|
420
|
+
kind: REFUSAL_KINDS.REFUSED, impact, guidance,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
// DOD-M15-TOKENSTALE-1 — about the AGENT, not this message: see `noteLocalCredentialRefusal`.
|
|
424
|
+
if (isLocalCredentialRefusal(witnessed.reason))
|
|
425
|
+
noteLocalCredentialRefusal(this.#ctx, agentName, sessionId, correlationId, witnessed.reason, classifyRelayAuthRefusal(witnessed.reason).advice);
|
|
426
|
+
relayRefusal = witnessed.reason;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
catch (relayErr) {
|
|
430
|
+
this.#ctx.logger.warn("session.relay.hash.submit.failed", { sessionId,
|
|
431
|
+
reason: extractErrorMessage(relayErr),
|
|
432
|
+
correlationId,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Attempt direct peer↔peer content delivery. On success the receiver's `persisted` ACK
|
|
437
|
+
// resolves the awaiting timer; on failure (counterparty offline) the hash is already
|
|
438
|
+
// witnessed above, so the caller / TTF path parks the SEALED content to the relay
|
|
439
|
+
// store-and-forward backstop and the recipient recovers it at the witnessed sequence (2b).
|
|
440
|
+
// Held outside the try so the catch can retire a stream that was opened and then failed to
|
|
441
|
+
// write. Without it every failure leaks the OUTBOUND half of the stream the receiver-side
|
|
442
|
+
// `finally` retires — same defect, other end, other cap (64 outbound per protocol per
|
|
443
|
+
// connection). See the note on #handleContentStream's finally.
|
|
444
|
+
let sendStream;
|
|
445
|
+
// 🔗 `DOD-M15-SELFCHAIN-1`: true when THIS side built the claim, so this side owns advancing its
|
|
446
|
+
// chain once the message has gone. Declared out here because the PARK path — which is also a
|
|
447
|
+
// delivery — runs in the catch below.
|
|
448
|
+
let ownClaimAwaitingSend = false;
|
|
449
|
+
/**
|
|
450
|
+
* 034-CARRYLEAF — HOISTED OUT OF THE TRY so the PARK path in the catch can carry them.
|
|
451
|
+
*
|
|
452
|
+
* The park copy is built when the direct send fails, which is inside the catch below; declaring
|
|
453
|
+
* these in the try left the parked envelope with only the relay-WITNESSED claim, so a message
|
|
454
|
+
* its author deliberately did not witness was parked with no ordering claim at all and could
|
|
455
|
+
* never be witnessed by its recipient.
|
|
456
|
+
*/
|
|
457
|
+
let frameS1;
|
|
458
|
+
let frameSig;
|
|
459
|
+
try {
|
|
460
|
+
/**
|
|
461
|
+
* ─── EVERY FRAME CARRIES ITS OWN PROOF — `DOD-M15-AUTHORSHIP-ABSENT-1` ────────────────────
|
|
462
|
+
*
|
|
463
|
+
* Structure 1 used to be built and signed INSIDE the relay submit, so a send the relay never
|
|
464
|
+
* witnessed put a frame on the wire with nothing on it to check — and the receiver, having no
|
|
465
|
+
* proof to compare, ingested it and attributed it anyway. There was always something to sign;
|
|
466
|
+
* nobody signed it.
|
|
467
|
+
*
|
|
468
|
+
* ⚠️ `structure2_cbor` IS DROPPED WHEN WE BUILD OUR OWN, and that pairing is load-bearing. The
|
|
469
|
+
* relay's record commits its copy of the sender's signature to the EXACT Structure 1 that was
|
|
470
|
+
* submitted; put it beside a Structure 1 built here (different timestamp, different
|
|
471
|
+
* last_seen_seq) and the receiver's cross-check fails against bytes that were never altered —
|
|
472
|
+
* a freeze on an honest message. The two travel together or the relay's half does not travel.
|
|
473
|
+
*
|
|
474
|
+
* ⚠️ SIGNED HERE, BEFORE THE SESSION KEY IS READ, and the order is load-bearing rather than
|
|
475
|
+
* tidy. `sessionKey` below is read once and used to seal the body several `await`s later; a
|
|
476
|
+
* key agreed with the counterparty inside that window leaves this side sealing under the key
|
|
477
|
+
* it captured while the far side has already moved on, and every message is refused as
|
|
478
|
+
* `decrypt_failed`. Signing costs two awaits, and putting them between the read and the seal
|
|
479
|
+
* widened that window enough to break the live two-node round trip. Measured, not reasoned
|
|
480
|
+
* about: seam-3 went red and both daemons logged `session.key.agreed` before the refusal.
|
|
481
|
+
*/
|
|
482
|
+
frameS1 = orderingS1;
|
|
483
|
+
frameSig = orderingSig;
|
|
484
|
+
let frameS2 = orderingS2;
|
|
485
|
+
// A relay-witnessed claim advanced the chain on its ack; this flag covers the other case.
|
|
486
|
+
if (frameS1 === undefined || frameSig === undefined) {
|
|
487
|
+
const own = await this.#signOwnContentClaim(agentName, sessionId, entry, contentHash);
|
|
488
|
+
ownClaimAwaitingSend = true;
|
|
489
|
+
frameS1 = own.structure1;
|
|
490
|
+
frameSig = own.signature;
|
|
491
|
+
frameS2 = undefined;
|
|
492
|
+
/**
|
|
493
|
+
* ⚠️ **AND OUR OWN TRANSCRIPT ROW GETS THE PROOF TOO** — review M3.
|
|
494
|
+
*
|
|
495
|
+
* `sentAuthorship` is set above only when the relay witnessed the leaf, because that was
|
|
496
|
+
* the only path that ever produced a signature. This path produces one — and dropping it
|
|
497
|
+
* here would leave the counterparty's transcript able to prove we wrote the message while
|
|
498
|
+
* ours recorded `self_authored` with a NULL signature. That is exactly the half-provable
|
|
499
|
+
* transcript `DOD-M15-SEALWIRE-1` bullet 5 exists to close, reappearing on the one path
|
|
500
|
+
* that had no proof to lose before this unit and has one now.
|
|
501
|
+
*
|
|
502
|
+
* VERIFIED BEFORE IT IS STORED, the same discipline as the witnessed path: the pubkey comes
|
|
503
|
+
* from INSIDE the bytes we signed, never from an agent lookup, and a pair that does not
|
|
504
|
+
* verify is dropped loudly rather than persisted as a row that looks checkable and fails.
|
|
505
|
+
* A failure here means this daemon's own encoder and decoder disagree.
|
|
506
|
+
*/
|
|
507
|
+
const s1Decoded = decodeStructure1(own.structure1);
|
|
508
|
+
const pk = s1Decoded.ok ? s1Decoded.fields.senderPubkey : undefined;
|
|
509
|
+
if (!s1Decoded.ok) {
|
|
510
|
+
this.#ctx.logger.warn("session.sent.authorship.unavailable", {
|
|
511
|
+
agentName, sessionId, correlationId, reason: "own_structure1_decode_failed",
|
|
512
|
+
structure1Reason: s1Decoded.reason,
|
|
513
|
+
impact: "this sent message is recorded with attribution 'self_authored' and NO signature, so the row asserts its author rather than proving one — even though this side signed the claim it put on the wire.",
|
|
514
|
+
guidance: "We produced these bytes ourselves moments ago, so a decode failure here means this daemon's own encoder and decoder disagree. Treat it as an internal invariant break, not a peer problem.",
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
else if (!verify(pk, own.structure1, own.signature)) {
|
|
518
|
+
this.#ctx.logger.warn("session.sent.authorship.unavailable", {
|
|
519
|
+
agentName, sessionId, correlationId, reason: "own_pair_does_not_verify",
|
|
520
|
+
impact: "this sent message is recorded with attribution 'self_authored' and NO signature. The signature this side just produced does not verify against the key inside the bytes it signed.",
|
|
521
|
+
guidance: "An internal invariant break: the signer and the encoder disagree. The message still went out; only the local proof was dropped.",
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
sentAuthorship = { senderPubkey: pk, senderSig: own.signature };
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* 🚨 NO KEY, NO DIRECT SEND — `DOD-M15-EPHEMERAL-AUTH-1`, and there is no plaintext fallback.
|
|
530
|
+
*
|
|
531
|
+
* Throwing here rather than sending in the open, because the catch below PARKS the content —
|
|
532
|
+
* and the mailbox copy is sealed to the counterparty's long-term identity key, so the message
|
|
533
|
+
* still travels encrypted and still arrives. The failure mode is a delay, never an exposure.
|
|
534
|
+
*
|
|
535
|
+
* A fallback to plaintext would be a thing an attacker steers a session into: strip the key
|
|
536
|
+
* frame and a system that "carries on, degraded" gives up the body while the operator reads a
|
|
537
|
+
* warning they have learned to scroll past. That is why this is a throw and not a warning.
|
|
538
|
+
*/
|
|
539
|
+
// FAIL FAST, before a stream is opened for a message that cannot go out. The key this reads is
|
|
540
|
+
// NOT the one that seals the body — see the read beside `sealSessionContent` below.
|
|
541
|
+
const preflight = this.#ctx.ephemerals.contentEncryptionState(agentName, sessionId);
|
|
542
|
+
if (preflight.key === null) {
|
|
543
|
+
throw new Error(`content_not_encryptable: ${preflight.reason} — ${CONTENT_ENCRYPTION_GUIDANCE[preflight.reason]}`);
|
|
544
|
+
}
|
|
545
|
+
const stream = await this.openContentStream(agentName, sessionId, entry, correlationId);
|
|
546
|
+
sendStream = stream;
|
|
547
|
+
// AC-001/AC-003: arm the TTF tracking BEFORE the frame goes on the wire. The
|
|
548
|
+
// receiver's `persisted` ACK can come back fast (in-process / low-latency
|
|
549
|
+
// transports), so registering the awaiting entry after send would let the ACK
|
|
550
|
+
// race ahead of it and be dropped — the timer would then spuriously fire. The
|
|
551
|
+
// content is delivered to the wire but NOT yet confirmed persisted; the ACK
|
|
552
|
+
// resolves it (content.delivery.acked) and TTF expiry hands it to the park
|
|
553
|
+
// backstop. The correlationId rides in the frame so the receiver's
|
|
554
|
+
// session.content.received shares ONE flow id with the sender.
|
|
555
|
+
/**
|
|
556
|
+
* ⚠️ THE TRACKER KEEPS THE PLAINTEXT, AND THAT IS DELIBERATE — 007-CRYPTO.
|
|
557
|
+
*
|
|
558
|
+
* When live delivery times out, THIS is the copy the park backstop seals into the relay
|
|
559
|
+
* mailbox, under `sealToRecipient` to the counterparty's long-term identity key. Encrypting
|
|
560
|
+
* `content` in place would put a body locked under a session key — one about to be destroyed —
|
|
561
|
+
* inside the mailbox envelope, and the recipient would open the outer seal onto bytes nothing
|
|
562
|
+
* can read. It passes every live-delivery test and fails only for messages that park.
|
|
563
|
+
*
|
|
564
|
+
* So the session key encrypts the copy that goes ON THE WIRE, below, and nothing else.
|
|
565
|
+
*/
|
|
566
|
+
// 034-CARRYLEAF: the SIGNED claim and its domain ride the awaiting entry, so a message that
|
|
567
|
+
// ends up re-parked after a restart still reaches its recipient in a shape they can witness.
|
|
568
|
+
this.#trackAwaitingAck(agentName, sessionId, content, contentHash, correlationId, frameS1 ?? orderingS1, orderingS2, contentHashAlg, frameSig, leafKind);
|
|
569
|
+
/**
|
|
570
|
+
* THE WIRE COPY. `content_hash` above was computed over the PLAINTEXT and stays that way: the
|
|
571
|
+
* transcript, the seal and the salted hash all depend on it meaning what it means today, and
|
|
572
|
+
* the receiver decrypts before it verifies.
|
|
573
|
+
*
|
|
574
|
+
* ⚠️ **THE KEY IS READ HERE, ADJACENT TO THE SEAL, AND IT USED TO BE READ FAR ABOVE.**
|
|
575
|
+
*
|
|
576
|
+
* The read sat before `#openContentStream`, so the captured key crossed an `await` — several,
|
|
577
|
+
* once this unit added signing — before it sealed anything. A session key agreed with the
|
|
578
|
+
* counterparty inside that window left this side sealing under the key it captured while the
|
|
579
|
+
* far side had already moved on, and **every message was then refused as `decrypt_failed`**:
|
|
580
|
+
* a false tamper report on honest content, on both sides, for the life of the session.
|
|
581
|
+
*
|
|
582
|
+
* Not theoretical. It is what reddened four live-libp2p fixtures when identity keys were first
|
|
583
|
+
* wired into them — both daemons logged `session.key.agreed`, and the refusal followed.
|
|
584
|
+
*
|
|
585
|
+
* A window cannot be closed by reasoning about who wins it, only by removing it: nothing may
|
|
586
|
+
* run between the read and the seal. The preflight above stays because failing before a stream
|
|
587
|
+
* is opened is worth one extra read.
|
|
588
|
+
*
|
|
589
|
+
* ⚠️ THIS CLOSES THE LOCAL WINDOW AND NOT THE CLASS (review F8). The sender still seals at T
|
|
590
|
+
* and the receiver still decrypts at T+flight, so a re-key landing in THAT interval produces
|
|
591
|
+
* the same false tamper report. What is removed is the part this side controls; the rest is a
|
|
592
|
+
* property of there being two machines.
|
|
593
|
+
*/
|
|
594
|
+
const sealState = this.#ctx.ephemerals.contentEncryptionState(agentName, sessionId);
|
|
595
|
+
if (sealState.key === null) {
|
|
596
|
+
// Reachable only if the key vanished between the preflight and here — a re-key or a
|
|
597
|
+
// teardown mid-send. Named as its own cause rather than reusing the preflight's, so a log
|
|
598
|
+
// reader can tell "never had one" from "had one and lost it while sending".
|
|
599
|
+
throw new Error(`content_not_encryptable: ${sealState.reason} — the key was present when this send began ` +
|
|
600
|
+
`and gone by the time it sealed. ${CONTENT_ENCRYPTION_GUIDANCE[sealState.reason]}`);
|
|
601
|
+
}
|
|
602
|
+
const wireBody = sealSessionContent(sealState.key, content);
|
|
603
|
+
const frame = encodeCbor({
|
|
604
|
+
type: "content_frame",
|
|
605
|
+
session_id: sessionId,
|
|
606
|
+
content_hash: contentHash,
|
|
607
|
+
content_bytes: wireBody,
|
|
608
|
+
// WHICH scheme the body is under. Present because the receiver must not have to guess from
|
|
609
|
+
// a length, and absent is not a valid state — a frame without it is refused, not read raw.
|
|
610
|
+
content_encryption: SESSION_CONTENT_ENCRYPTION_V1,
|
|
611
|
+
correlation_id: correlationId,
|
|
612
|
+
// DOD-MSG-4 (self-ordering): the relay's signed ordering record, so the receiver verifies +
|
|
613
|
+
// orders from the frame ALONE (no dependence on the separate leaf_deliver witness timing).
|
|
614
|
+
// structure1_cbor = sender-signed bytes (verify); structure2_cbor = relay's committed seq +
|
|
615
|
+
// prev_root (order). Structure 2 is omitted if the relay was unreachable — the receiver
|
|
616
|
+
// falls back to the witness stream for POSITION. Structure 1 and its signature are never
|
|
617
|
+
// omitted: `DOD-M15-AUTHORSHIP-ABSENT-1`, and a frame without them is refused on arrival.
|
|
618
|
+
structure1_cbor: frameS1,
|
|
619
|
+
sender_signature: frameSig,
|
|
620
|
+
structure2_cbor: frameS2,
|
|
621
|
+
// DOD-M15-SEALWIRE-1 part B2b: HOW `content_hash` was produced. An older peer ignores an
|
|
622
|
+
// unknown CBOR key, so emitting it is safe for every build in existence; a newer one reads
|
|
623
|
+
// it and verifies under the named algorithm instead of assuming.
|
|
624
|
+
content_hash_alg: contentHashAlg,
|
|
625
|
+
/**
|
|
626
|
+
* 034-CARRYLEAF review F5 — WHICH LEAF DOMAIN this content belongs to.
|
|
627
|
+
*
|
|
628
|
+
* Documents and rejection envelopes ride this same frame, and the receiver had no way to
|
|
629
|
+
* recover their kind: it appended them locally as "doc" from its own inspection of the
|
|
630
|
+
* body, while anything it witnessed on the sender's behalf went to the relay as a MESSAGE
|
|
631
|
+
* leaf. The certified root is over content hashes, so no root diverges — but the relay's
|
|
632
|
+
* canonical log and the carried `leaf_kind` would describe the leaf as something it is not,
|
|
633
|
+
* and a leaf kind selects a HASH DOMAIN everywhere else in this protocol.
|
|
634
|
+
*
|
|
635
|
+
* Same argument as `content_hash_alg` beside it: an older peer ignores an unknown CBOR key,
|
|
636
|
+
* so emitting it is safe for every build in existence, and a receiver that does not see it
|
|
637
|
+
* declines to witness rather than guessing (see `#witnessReceivedLeaf`).
|
|
638
|
+
*/
|
|
639
|
+
leaf_kind: leafKind,
|
|
640
|
+
});
|
|
641
|
+
// Injected dial failure — thrown from inside the try so it lands in exactly the catch the
|
|
642
|
+
// real connection_lost lands in, and the whole downstream path (untrack → park → durable
|
|
643
|
+
// enqueue) runs unmodified.
|
|
644
|
+
if (this.#ctx.sendFaultRemaining > 0) {
|
|
645
|
+
this.#ctx.sendFaultRemaining -= 1;
|
|
646
|
+
this.#ctx.logger.warn("content.send.fault.injected", { sessionId, contentHash: Buffer.from(contentHash).toString("hex") });
|
|
647
|
+
throw new Error("connection_lost: injected direct-send fault");
|
|
648
|
+
}
|
|
649
|
+
stream.send(lp.encode.single(frame));
|
|
650
|
+
// NOT SWALLOWED. This was `try { await stream.close(); } catch { }` followed by
|
|
651
|
+
// `delivered: true` — which reports a frame as delivered when the flush failed.
|
|
652
|
+
//
|
|
653
|
+
// `close()` waits for the write buffer to drain, so a reset mid-flush throws HERE, and that
|
|
654
|
+
// is precisely the case where the bytes never left. Discarding it made `delivered: true` mean
|
|
655
|
+
// "we called send and close did not visibly complain", while every caller reads it as "the
|
|
656
|
+
// peer has it" — and the sender then never retries, because nothing told it to.
|
|
657
|
+
//
|
|
658
|
+
// Letting it reach the catch below is the honest outcome: that path parks the content against
|
|
659
|
+
// the relay backstop and reports `delivered: false` if it can, or `ok: false` if it cannot.
|
|
660
|
+
// A close that failed for a benign reason costs a redundant park, which the receiver dedups
|
|
661
|
+
// on the content hash. A false delivered costs the message.
|
|
662
|
+
await stream.close();
|
|
663
|
+
/**
|
|
664
|
+
* 🔗 THE MESSAGE HAS GONE, SO THE CHAIN ADVANCES — `DOD-M15-SELFCHAIN-1`, review F7.
|
|
665
|
+
*
|
|
666
|
+
* Below `stream.close()` deliberately: close waits for the write buffer to drain, so a reset
|
|
667
|
+
* mid-flush throws above this line and the bytes never left. Advancing before it would point
|
|
668
|
+
* the chain at a message the counterparty never saw, and every later message would then be
|
|
669
|
+
* refused by them for a reason that names tampering.
|
|
670
|
+
*/
|
|
671
|
+
if (ownClaimAwaitingSend)
|
|
672
|
+
this.#advanceOwnChain(agentName, sessionId, entry, contentHash);
|
|
673
|
+
this.#ctx.liveness.clearSessionImpairment(agentName, sessionId, "direct_send", correlationId);
|
|
674
|
+
return { ok: true, delivered: true, ...(assignedSeq === undefined ? {} : { sequenceNumber: assignedSeq }), ...(sentAuthorship === undefined ? {} : { authorship: sentAuthorship }), ...(relayRefusal === undefined ? {} : { relayRefusal }) };
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
/**
|
|
678
|
+
* Review F7: a content-key fault is NOT a transport fault, and labelling it `direct_send`
|
|
679
|
+
* points the operator at the connection when the connection is fine. `content_not_encryptable`
|
|
680
|
+
* is thrown twice above — once before the stream is opened, once at the seal — and both are
|
|
681
|
+
* about this machine's key state.
|
|
682
|
+
*/
|
|
683
|
+
const failure = extractErrorMessage(err);
|
|
684
|
+
this.#ctx.liveness.markSessionImpaired(agentName, sessionId, {
|
|
685
|
+
cause: failure.startsWith("content_not_encryptable") ? "content_key" : "direct_send",
|
|
686
|
+
error: failure, correlationId,
|
|
687
|
+
});
|
|
688
|
+
if (sendStream !== undefined) {
|
|
689
|
+
try {
|
|
690
|
+
sendStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
691
|
+
}
|
|
692
|
+
catch { /* already gone */ }
|
|
693
|
+
}
|
|
694
|
+
// The send failed after (possibly) arming the awaiting tracking — drop it so a
|
|
695
|
+
// never-delivered frame does not later fire a spurious TTF park.
|
|
696
|
+
this.#untrackAwaitingAck(agentName, sessionId, contentHash);
|
|
697
|
+
// 2b: direct delivery failed (counterparty offline). The hash is already witnessed (R1, the
|
|
698
|
+
// sequence is assigned), so deposit the content to the relay store-and-forward backstop now;
|
|
699
|
+
// the recipient pulls + recovers it on next online (DOD-MSG-3/4).
|
|
700
|
+
// DOD-LEAVEMSG-1: the deposit is now AWAITED (was fire-and-forget) so a genuine park success
|
|
701
|
+
// can be reported as "dispatched to relay" instead of a raw stream failure — the operator/
|
|
702
|
+
// agent sees the truth (the message IS in flight, just not direct), not a false negative.
|
|
703
|
+
const hashHex = Buffer.from(contentHash).toString("hex");
|
|
704
|
+
// NAME THE CAUSE. This catch used to discard `err` outright, so a park reported only its exit
|
|
705
|
+
// point — "dispatched to relay" — and never what went wrong. Measured 2026-08-17: 212 parks on
|
|
706
|
+
// one daemon, not one of them recording a reason, which is what made a one-way session look
|
|
707
|
+
// like a protocol mystery for a night.
|
|
708
|
+
//
|
|
709
|
+
// `counterpartySessionPeerId` is the load-bearing field. It is recorded ONCE at session
|
|
710
|
+
// establishment and never refreshed. (CORRECTED 2026-08-18: it does NOT rebuild "on every
|
|
711
|
+
// signaling reconnect". CORRECTED AGAIN, 056-SLOTDEAD: the rebuild triggers that correction
|
|
712
|
+
// named are gone too — NOTHING rebuilds a standing receiver now; a lost reservation is
|
|
713
|
+
// re-taken in place, and only promotion into a session changes a receiver's identity.)
|
|
714
|
+
// If the two ever cross, every send goes one-way forever and nothing says so. With this line that becomes a single grep instead of a
|
|
715
|
+
// night.
|
|
716
|
+
this.#ctx.logger.warn("session.content.direct.send.failed", {
|
|
717
|
+
agentName,
|
|
718
|
+
sessionId,
|
|
719
|
+
contentHash: hashHex,
|
|
720
|
+
counterpartySessionPeerId: entry.counterpartySessionPeerId,
|
|
721
|
+
error: extractErrorMessage(err),
|
|
722
|
+
// "Cannot write to a stream that is closed" names where the write died, never why. The
|
|
723
|
+
// why is almost always the per-protocol stream cap, and these two numbers are what turn
|
|
724
|
+
// that from a log-measurement session into a grep.
|
|
725
|
+
...this.#ctx.streamCensus(entry.node, entry.counterpartySessionPeerId),
|
|
726
|
+
correlationId,
|
|
727
|
+
});
|
|
728
|
+
/**
|
|
729
|
+
* ⚠️ **THE PARKED COPY CARRIES THE SIGNED CLAIM, NOT ONLY THE WITNESSED ONE — 034-CARRYLEAF
|
|
730
|
+
* review F1, and this is what closes the withholding attack on the MAILBOX route.**
|
|
731
|
+
*
|
|
732
|
+
* It passed `orderingS1`, which exists only when the relay witnessed this leaf — so a message
|
|
733
|
+
* the sender deliberately did not witness was parked with no ordering claim at all, and the
|
|
734
|
+
* recipient recovered it holding nothing the relay would accept as proof of authorship. They
|
|
735
|
+
* could read it and could never put it in a receipt.
|
|
736
|
+
*
|
|
737
|
+
* `frameS1`/`frameSig` are the pair the DIRECT frame carries for this same message: the
|
|
738
|
+
* relay's witnessed claim when there is one, and otherwise a claim this agent signed itself.
|
|
739
|
+
* Either way it is the author's signature over the author's own ordering claim, which is the
|
|
740
|
+
* only form the relay accepts when the recipient witnesses on their behalf.
|
|
741
|
+
*/
|
|
742
|
+
const attempt = await this.#ctx.park.parkContent(agentName, sessionId, hashHex, content, frameS1, orderingS2, contentHashAlg, frameSig, leafKind);
|
|
743
|
+
if (attempt.outcome === "parked") {
|
|
744
|
+
/**
|
|
745
|
+
* 🔗 A PARK IS A DELIVERY, so the chain advances here too — `DOD-M15-SELFCHAIN-1`.
|
|
746
|
+
*
|
|
747
|
+
* The mailbox copy is sealed to the counterparty's long-term key and they WILL open it, so
|
|
748
|
+
* the message is part of the conversation. Leaving the chain behind here would make this
|
|
749
|
+
* side's next message link to something the counterparty has already moved past, and they
|
|
750
|
+
* would refuse it as a broken chain — a false tamper report caused by our own relay being
|
|
751
|
+
* briefly unreachable.
|
|
752
|
+
*/
|
|
753
|
+
if (ownClaimAwaitingSend)
|
|
754
|
+
this.#advanceOwnChain(agentName, sessionId, entry, contentHash);
|
|
755
|
+
this.#ctx.liveness.noteImpairmentRetention(agentName, sessionId, "parked");
|
|
756
|
+
return { ok: true, delivered: false, parked: true, ...(assignedSeq === undefined ? {} : { sequenceNumber: assignedSeq }), ...(sentAuthorship === undefined ? {} : { authorship: sentAuthorship }), ...(relayRefusal === undefined ? {} : { relayRefusal }) };
|
|
757
|
+
}
|
|
758
|
+
// M12-P12: the deposit was refused, and #untrackAwaitingAck above already dropped the
|
|
759
|
+
// in-memory entry — so without this, NOTHING holds the content and the TTF timer that would
|
|
760
|
+
// have enqueued it is cancelled. The recipient has already witnessed this sequence, so it
|
|
761
|
+
// holds every later message in the session behind the gap, forever, and tells no one.
|
|
762
|
+
// Enqueue durably instead; the drain hook re-parks it the moment the standing receiver is
|
|
763
|
+
// rebuilt. Only on a REFUSAL — a successful deposit must not be re-parked, and an
|
|
764
|
+
// unconfigured session has no park target to retry against (F6).
|
|
765
|
+
let durable = false;
|
|
766
|
+
if (attempt.outcome === "refused") {
|
|
767
|
+
try {
|
|
768
|
+
// M12-P13 (review HIGH-1): `durable` is now OBSERVED from the enqueue, not asserted around
|
|
769
|
+
// it. Two ways this used to lie, both of which now commit a chain leaf and so cannot be
|
|
770
|
+
// allowed to: the queue's content-derived dedupe key collides and it silently drops the
|
|
771
|
+
// copy, and the `?.` no-ops entirely when the composition root never wired the hook. An
|
|
772
|
+
// absent hook is not a queue.
|
|
773
|
+
if (this.#ctx.onParkFailed === null) {
|
|
774
|
+
this.#ctx.logger.error("content.park.durable_enqueue.unwired", {
|
|
775
|
+
sessionId, contentHash: hashHex, agentName,
|
|
776
|
+
impact: "no durable queue is wired — the content is NOT retained and will NOT be retried",
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
// B2b-1 review F1: the DURABLE writer. Without the 7th argument the column this unit
|
|
781
|
+
// added has no producer at all — every queued row would carry NULL, and the crash
|
|
782
|
+
// backstop would re-park a salted message as sha256 and have it refused forever.
|
|
783
|
+
durable = this.#ctx.onParkFailed(agentName, sessionId, hashHex, content, orderingS1, orderingS2, contentHashAlg);
|
|
784
|
+
}
|
|
785
|
+
if (!durable) {
|
|
786
|
+
if (this.#ctx.onParkFailed !== null) {
|
|
787
|
+
this.#ctx.logger.error("content.park.durable_enqueue.dropped", {
|
|
788
|
+
sessionId, contentHash: hashHex, agentName,
|
|
789
|
+
impact: "the durable queue refused this copy (identical content already queued) — it is NOT separately retained",
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
// F5: the successful enqueue must be visible. Without this the live run that has to
|
|
795
|
+
// PROVE this fix has nothing to point at, and this log is the sender-side counterpart to
|
|
796
|
+
// `session.content.held` on the receiver — the two together make the trace readable.
|
|
797
|
+
// M12-P13: `witnessed` rides along because the caller is about to commit a hash-chain
|
|
798
|
+
// leaf on the strength of this. Without a relay ordering record the recipient recovers
|
|
799
|
+
// in arrival order instead — the accepted degradation, but it must not be invisible.
|
|
800
|
+
this.#ctx.logger.info("content.park.deferred", {
|
|
801
|
+
sessionId, contentHash: hashHex, agentName,
|
|
802
|
+
selfOrdering: Boolean(orderingS1 && orderingS2),
|
|
803
|
+
witnessed: Boolean(orderingS1 && orderingS2),
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (hookErr) {
|
|
808
|
+
// F3: enqueueAwaitingContent throws ON PURPOSE when the persist fails, because that is
|
|
809
|
+
// data loss. Swallowing it into the same response the durable case returns would tell the
|
|
810
|
+
// operator "it will retry" about a message that is simply gone. Named for its own cause,
|
|
811
|
+
// and the response says so below.
|
|
812
|
+
this.#ctx.logger.error("content.park.durable_enqueue.failed", {
|
|
813
|
+
sessionId, contentHash: hashHex, agentName,
|
|
814
|
+
impact: "content is NOT durable and will NOT be retried — the message is lost",
|
|
815
|
+
error: extractErrorMessage(hookErr),
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
// error.message extracted — never [object Object]. libp2p/cross-package errors are not
|
|
820
|
+
// always `instanceof Error` in this realm, so fall back to a message property / JSON.
|
|
821
|
+
// This was fourteen lines reimplementing `extractErrorMessage` exactly; the helper it was
|
|
822
|
+
// duplicating already existed two files away. DOD-M15-ERRFORMAT-1.
|
|
823
|
+
const errMsg = extractErrorMessage(err);
|
|
824
|
+
// F3: the two failures are NOT interchangeable to the caller. `reason` is a contract string
|
|
825
|
+
// and stays put; `guidance` carries the difference, because "we are retrying this" and "this
|
|
826
|
+
// message is gone, send it again" demand opposite actions from the operator.
|
|
827
|
+
//
|
|
828
|
+
// The same distinction is recorded on the session, because `cello_receive` will be asked
|
|
829
|
+
// about this later and would otherwise have to guess — and its guess ("it was parked, do not
|
|
830
|
+
// resend") is the exact opposite of what the lost case needs.
|
|
831
|
+
this.#ctx.liveness.noteImpairmentRetention(agentName, sessionId, durable ? "durable" : "lost");
|
|
832
|
+
/**
|
|
833
|
+
* DOD-M15-SEALPRECOND-1 — the send failed, so nothing here will place this leaf.
|
|
834
|
+
*
|
|
835
|
+
* UNCONDITIONAL, including the durable case whose caller DOES place: those callers place
|
|
836
|
+
* synchronously on the continuation of this return, with no await between, so nothing can
|
|
837
|
+
* read the marker in the interval. `document-delivery-transport.ts` places nothing on a
|
|
838
|
+
* failure at all — leaving its marker would make every later close of that session decline,
|
|
839
|
+
* and a session whose only exit is force-abandon has no receipt.
|
|
840
|
+
*/
|
|
841
|
+
this.#clearOwnLeafOrdered(agentName, sessionId, Buffer.from(contentHash).toString("hex"));
|
|
842
|
+
return {
|
|
843
|
+
ok: false,
|
|
844
|
+
reason: "session_stream_unavailable",
|
|
845
|
+
error: errMsg,
|
|
846
|
+
// Carried on the failure path too: a DURABLY QUEUED message still owns the position the
|
|
847
|
+
// relay witnessed for it before delivery was attempted, and its leaf must go there.
|
|
848
|
+
...(assignedSeq === undefined ? {} : { sequenceNumber: assignedSeq }),
|
|
849
|
+
// …and so does its PROOF, for the same reason. It was signed before the hand-off failed.
|
|
850
|
+
...(sentAuthorship === undefined ? {} : { authorship: sentAuthorship }),
|
|
851
|
+
// M12-P13: the machine-readable half of the distinction below. M12-P12 shipped it in the
|
|
852
|
+
// guidance SENTENCE only, so the callers that have to ACT on it — commit the leaf for a
|
|
853
|
+
// queued message, never for a lost one — would have had to substring-match English. None
|
|
854
|
+
// did, and the sequence the relay had already witnessed was left as a permanent hole.
|
|
855
|
+
durable,
|
|
856
|
+
// M12-P13 (review MEDIUM-5): the specific standing-receiver state, carried rather than
|
|
857
|
+
// discarded. `reason` names where this surfaced; `cause` names what actually blocked it —
|
|
858
|
+
// the exact distinction M12-P12 added `standingReceiverAbsenceReason()` for, which then
|
|
859
|
+
// died inside #parkContent. An operator keying on `reason` alone is sent to the transport
|
|
860
|
+
// when the blocker is the receiver.
|
|
861
|
+
...(attempt.cause !== undefined ? { cause: attempt.cause } : {}),
|
|
862
|
+
/**
|
|
863
|
+
* ⚠️ A FAULT THAT IS NOT THE RELAY MUST NOT SAY IT IS — B2b-2 constraint 6.
|
|
864
|
+
*
|
|
865
|
+
* The `durable` branch below is written for a relay that is down: queued, retried, nothing
|
|
866
|
+
* for you to do. That is right for the case it was written for and WRONG for a producer-side
|
|
867
|
+
* refusal, which reaches the same branch by the same route. There the relay was never asked,
|
|
868
|
+
* and the drain re-parks the same entry into the same throw — so the message is genuinely
|
|
869
|
+
* durable and will genuinely never leave. Telling that operator to wait costs them however
|
|
870
|
+
* long they are willing to wait before they stop believing the message.
|
|
871
|
+
*
|
|
872
|
+
* This is the reason `cause` had to become a code first: the distinction is unbranchable
|
|
873
|
+
* while the field holds an English paragraph.
|
|
874
|
+
*/
|
|
875
|
+
guidance: parkRefusalGuidance(attempt.cause, durable, attempt.retryAfterMs),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* DOD-M12B-REDIAL-1 — open a content stream, re-dialling once if the connection has gone.
|
|
881
|
+
*
|
|
882
|
+
* `newStream` never dials. It looks for an already-open connection filed under the recorded peer
|
|
883
|
+
* id and throws `connection_lost` when there is none — and NOTHING re-dialled: not on
|
|
884
|
+
* `session.liveness.changed → gone`, not on signaling reconnect, not on agent offline→online, not
|
|
885
|
+
* in the drain hook. So one blip and that session parked EVERY message for the rest of its life,
|
|
886
|
+
* on both sides, permanently. The relay backstop kept the messages moving, which is exactly why
|
|
887
|
+
* it hid: nothing was lost, the conversation just stopped being a conversation.
|
|
888
|
+
*
|
|
889
|
+
* DEMAND-DRIVEN, never a timer. A background re-dial loop is what produced the 2026-08-17
|
|
890
|
+
* notification storm — surviving halves of abandoned sessions dialling continuously while the
|
|
891
|
+
* operator saw connection requests from agents nobody was driving. This fires only when a send
|
|
892
|
+
* actually needs the connection, and a cooldown bounds a burst against a peer that is genuinely
|
|
893
|
+
* gone: five sends cost one dial, not five.
|
|
894
|
+
*/
|
|
895
|
+
async openContentStream(agentName, sessionId, entry, correlationId) {
|
|
896
|
+
const attempt = async () => {
|
|
897
|
+
if (this.#ctx.connectionLossRemaining > 0) {
|
|
898
|
+
this.#ctx.connectionLossRemaining -= 1;
|
|
899
|
+
throw { reason: "no_connection", message: "injected connection loss" };
|
|
900
|
+
}
|
|
901
|
+
return entry.node.newStream(entry.counterpartySessionPeerId, CELLO_CONTENT_PROTOCOL_ID);
|
|
902
|
+
};
|
|
903
|
+
try {
|
|
904
|
+
return await attempt();
|
|
905
|
+
}
|
|
906
|
+
catch (err) {
|
|
907
|
+
const reason = err?.reason;
|
|
908
|
+
// ONLY for a missing connection, and `no_connection` is the reason that means exactly that.
|
|
909
|
+
// NOT `connection_lost`, which is the transport's catch-all default and therefore also covers
|
|
910
|
+
// a stream that failed on a healthy connection — the per-protocol stream cap of
|
|
911
|
+
// DOD-M12B-ACK-1. Dialling there fixes nothing and shows the counterparty a connection
|
|
912
|
+
// request caused by a defect on this side, which is the storm this unit exists to avoid.
|
|
913
|
+
if (reason !== "no_connection")
|
|
914
|
+
throw err;
|
|
915
|
+
const key = this.#ctx.sessionKey(agentName, sessionId);
|
|
916
|
+
const addrs = this.#ctx.counterpartyAddrs.get(key);
|
|
917
|
+
if (!addrs || addrs.length === 0) {
|
|
918
|
+
// ABSENT IS NOT FINE, and it is not silent. A session we never dialled — the responder's
|
|
919
|
+
// half — has no addresses to dial back with, and that is a real limitation the operator
|
|
920
|
+
// should be able to see rather than infer from a park.
|
|
921
|
+
this.#ctx.logger.warn("session.transport.redial.unavailable", {
|
|
922
|
+
sessionId, agentName, correlationId,
|
|
923
|
+
impact: "the direct path is down and this side holds no address for the counterparty, so every send parks until they re-establish",
|
|
924
|
+
});
|
|
925
|
+
throw err;
|
|
926
|
+
}
|
|
927
|
+
const now = Date.now();
|
|
928
|
+
const notBefore = this.#ctx.redialNotBefore.get(key) ?? 0;
|
|
929
|
+
if (now < notBefore) {
|
|
930
|
+
this.#ctx.logger.debug("session.transport.redial.cooldown", {
|
|
931
|
+
sessionId, agentName, retryInMs: notBefore - now, correlationId,
|
|
932
|
+
});
|
|
933
|
+
throw err;
|
|
934
|
+
}
|
|
935
|
+
this.#ctx.redialNotBefore.set(key, now + REDIAL_COOLDOWN_MS);
|
|
936
|
+
this.#ctx.logger.info("session.transport.redial.attempted", { sessionId, agentName, addrs: addrs.length, correlationId });
|
|
937
|
+
const reconnected = await this.#ctx.connectToCounterparty(agentName, sessionId, addrs);
|
|
938
|
+
if (!reconnected.ok) {
|
|
939
|
+
this.#ctx.logger.warn("session.transport.redial.failed", {
|
|
940
|
+
sessionId, agentName, reason: reconnected.reason, error: reconnected.error, correlationId,
|
|
941
|
+
});
|
|
942
|
+
throw err;
|
|
943
|
+
}
|
|
944
|
+
this.#ctx.logger.info("session.transport.redial.succeeded", { sessionId, agentName, correlationId });
|
|
945
|
+
// Cleared so the NEXT blip is repaired immediately: the cooldown exists to bound a dead peer,
|
|
946
|
+
// not to make a live one wait.
|
|
947
|
+
this.#ctx.redialNotBefore.delete(key);
|
|
948
|
+
return attempt();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* DOD-M12B-INDEX-1 — commit THIS agent's own leaf at the position the relay assigned it.
|
|
953
|
+
*
|
|
954
|
+
* The receiver has always enforced "leaf index === canonical position": content witnessed ahead
|
|
955
|
+
* of the next expected leaf is held, not appended out of order. The sender never did. It had the
|
|
956
|
+
* position in hand — the relay answers about 4 ms before the append — and called a push-only
|
|
957
|
+
* append that puts the leaf at the tail whatever the tail happens to be. While its own tree has
|
|
958
|
+
* no gap the two agree and nothing shows; the first gap puts its leaf at someone else's index,
|
|
959
|
+
* parts its root from the counterparty's, and the next seal gets `leaf_count_mismatch`, which is
|
|
960
|
+
* terminal.
|
|
961
|
+
*
|
|
962
|
+
* DELIVERY IS NOT DEFERRED BY THIS. The caller has already put the bytes on the wire; only the
|
|
963
|
+
* leaf waits for its slot, exactly as a received message does. Holding our own send is only
|
|
964
|
+
* affordable because holds are durable (DOD-M12B-STRAND-1) — before that it would have risked
|
|
965
|
+
* losing the message outright.
|
|
966
|
+
*
|
|
967
|
+
* `assignedSeq` absent means no ordering authority answered. That is the documented degradation
|
|
968
|
+
* and it appends in arrival order as before: with no position there is no discipline to enforce,
|
|
969
|
+
* and refusing would take messaging down whenever the relay is unreachable.
|
|
970
|
+
*/
|
|
971
|
+
/**
|
|
972
|
+
* DOD-M15-SEALPRECOND-1 — the relay has ordered a leaf of ours; the tree has not placed it yet.
|
|
973
|
+
* Public so a test can drive the exact state the 2026-09-11 close was in with no live relay.
|
|
974
|
+
*/
|
|
975
|
+
noteOwnLeafOrdered(agentName, sessionId, contentHashHex, assignedSeq) {
|
|
976
|
+
this.#noteOwnLeafOrdered(agentName, sessionId, contentHashHex, assignedSeq);
|
|
977
|
+
}
|
|
978
|
+
#noteOwnLeafOrdered(agentName, sessionId, contentHashHex, assignedSeq) {
|
|
979
|
+
if (assignedSeq < 0)
|
|
980
|
+
return;
|
|
981
|
+
const key = this.#ctx.sessionKey(agentName, sessionId);
|
|
982
|
+
let map = this.#ctx.ownLeavesOrdered.get(key);
|
|
983
|
+
if (!map) {
|
|
984
|
+
map = new Map();
|
|
985
|
+
this.#ctx.ownLeavesOrdered.set(key, map);
|
|
986
|
+
}
|
|
987
|
+
map.set(contentHashHex, assignedSeq);
|
|
988
|
+
}
|
|
989
|
+
#clearOwnLeafOrdered(agentName, sessionId, contentHashHex) {
|
|
990
|
+
const map = this.#ctx.ownLeavesOrdered.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
991
|
+
map?.delete(contentHashHex);
|
|
992
|
+
if (map?.size === 0)
|
|
993
|
+
this.#ctx.ownLeavesOrdered.delete(this.#ctx.sessionKey(agentName, sessionId));
|
|
994
|
+
}
|
|
995
|
+
placeOwnLeaf(agentName, sessionId, contentHashHex, sentBytes, assignedSeq, correlationId,
|
|
996
|
+
/**
|
|
997
|
+
* ⚠️ NO DEFAULT, for the same reason `authorship` has none.
|
|
998
|
+
*
|
|
999
|
+
* `kind = "msg"` meant a caller writing a `doc` or a `ctrl` leaf got a `msg` leaf by saying
|
|
1000
|
+
* nothing, and the tree recorded a leaf kind the author never chose. TypeScript also forbids a
|
|
1001
|
+
* required parameter after a defaulted one, so leaving the default here would have forced
|
|
1002
|
+
* `authorship` back to optional — which is the defect above. Every one of the seven call sites
|
|
1003
|
+
* already passed a kind or wanted "msg"; making it explicit cost nothing and removes a second
|
|
1004
|
+
* silent answer from the same signature.
|
|
1005
|
+
*/
|
|
1006
|
+
kind,
|
|
1007
|
+
/**
|
|
1008
|
+
* DOD-M15-SEALWIRE-1 bullet 5 — the proof for THIS send, so a held row keeps it.
|
|
1009
|
+
*
|
|
1010
|
+
* ⚠️ REQUIRED, AND `undefined` IS A VALID ANSWER — the two are not the same thing.
|
|
1011
|
+
*
|
|
1012
|
+
* This was `authorship?:` for exactly one review cycle, and in that cycle THREE of the seven
|
|
1013
|
+
* call sites omitted it: `daemon.ts` 1440, 1671, 1685 — the away-reply path, which is the
|
|
1014
|
+
* highest-traffic sent-writer in the daemon and the one with no human watching it. All three
|
|
1015
|
+
* had the proof **already in a local variable one line below**, handed to
|
|
1016
|
+
* `recordTranscriptMessage` and not to this method. Nothing went red, because an optional
|
|
1017
|
+
* parameter's whole behaviour on omission is to look deliberate.
|
|
1018
|
+
*
|
|
1019
|
+
* An unwitnessed send genuinely has no proof, so absence must stay expressible. Requiring the
|
|
1020
|
+
* parameter keeps that while making the caller SAY it: omission is now a type error, and
|
|
1021
|
+
* `undefined` is a claim the author made rather than one the signature made for them.
|
|
1022
|
+
*/
|
|
1023
|
+
authorship) {
|
|
1024
|
+
/**
|
|
1025
|
+
* DOD-M15-SEALPRECOND-1 — the marker is resolved HERE, on every outcome, and that is the point.
|
|
1026
|
+
*
|
|
1027
|
+
* Appended, appended-behind-the-frontier, or held: all three have dealt with the relay's
|
|
1028
|
+
* position for this content, and a held leaf is already refused by `heldOwn` — counting it in
|
|
1029
|
+
* both places would report one message as two conditions. Cleared at the TOP so no early
|
|
1030
|
+
* return can leave a marker behind, which would make the session unsealable.
|
|
1031
|
+
*/
|
|
1032
|
+
this.#clearOwnLeafOrdered(agentName, sessionId, contentHashHex);
|
|
1033
|
+
// Hydrate before reading the frontier: a durable hold this process has not read back yet would
|
|
1034
|
+
// make the tree look further along than it is.
|
|
1035
|
+
this.#ctx.held.ensureHeldRestored(agentName, sessionId);
|
|
1036
|
+
const nextExpected = this.#ctx.getSessionTree(agentName, sessionId).size();
|
|
1037
|
+
if (assignedSeq === undefined) {
|
|
1038
|
+
const { leafIndex } = this.#ctx.appendSessionLeaf(agentName, sessionId, kind, contentHashHex, correlationId);
|
|
1039
|
+
/**
|
|
1040
|
+
* ─── DOD-M15-UNWITNESSED-1(b): SAY IT HERE, where the code says the damage happens ────────
|
|
1041
|
+
*
|
|
1042
|
+
* This branch was entirely silent — no log, no flag — while the `position_behind_frontier`
|
|
1043
|
+
* branch twenty lines below logs at ERROR and its own comment says the loss occurred
|
|
1044
|
+
* *"at the unwitnessed append, not here."* So the system announced the consequence one send
|
|
1045
|
+
* later than it announced nothing about the cause.
|
|
1046
|
+
*
|
|
1047
|
+
* **This does NOT gate the seal, deliberately.** The DoD bar is explicit: do not gate on
|
|
1048
|
+
* suspicion, because a relay that has not witnessed a leaf YET is indistinguishable here from
|
|
1049
|
+
* one that never will, and refusing on the first would make a healthy session unsealable —
|
|
1050
|
+
* which is worse than the thing it guards. What was missing is not a refusal, it is the
|
|
1051
|
+
* ERROR at the moment the code already believes something was lost.
|
|
1052
|
+
*/
|
|
1053
|
+
this.#ctx.logger.error("session.tree.own_leaf_unwitnessed", {
|
|
1054
|
+
sessionId,
|
|
1055
|
+
leafIndex,
|
|
1056
|
+
kind,
|
|
1057
|
+
correlationId,
|
|
1058
|
+
impact: "this message was appended to the local record with NO relay witness, so the ordering " +
|
|
1059
|
+
"authority has no copy of it. The counterparty's tree cannot gain this leaf from the " +
|
|
1060
|
+
"relay, so the two records may no longer agree — and a bilateral seal needs them to.",
|
|
1061
|
+
guidance:
|
|
1062
|
+
// ⚠️ DO NOT REINSTATE "the next send re-establishes ordering". It is false and it is the
|
|
1063
|
+
// opposite of the remedy: an unwitnessed append leaves this tree one leaf ahead of the
|
|
1064
|
+
// relay's, so the next WITNESSED send takes the divergence branch and makes the session
|
|
1065
|
+
// permanently unsealable. The response guidance in session-content-handlers.ts was
|
|
1066
|
+
// corrected for exactly this and calls the old sentence the most damaging one in the unit
|
|
1067
|
+
// that added it; this copy was missed and kept telling the operator to keep talking.
|
|
1068
|
+
"Delivered and in your transcript, but the relay did not witness it — neither side has " +
|
|
1069
|
+
"independent proof you sent it. Do NOT resend, and do not keep sending: your record is " +
|
|
1070
|
+
"now one leaf ahead of the relay's, so the next witnessed message reports this session " +
|
|
1071
|
+
"as diverged and a diverged session can never be sealed. If the receipt matters, close now.",
|
|
1072
|
+
});
|
|
1073
|
+
/**
|
|
1074
|
+
* ─── `016-RELAYLOSS`: CARRIED TO THE CALLER, because the log is not a consumer ────────────
|
|
1075
|
+
*
|
|
1076
|
+
* Measured with a real relay black-holed mid-conversation: the send stalls for the submit
|
|
1077
|
+
* timeout, then returns `{ok: true, sequence_number: N, delivered: true}` — BYTE-IDENTICAL to
|
|
1078
|
+
* the witnessed send that preceded it. The error above is correct, complete, and read by
|
|
1079
|
+
* nobody the operator can ask.
|
|
1080
|
+
*
|
|
1081
|
+
* Its two siblings twenty lines below already do this: `diverged` and the held case both
|
|
1082
|
+
* refuse to return an ordinary success, on the stated grounds that doing so "reports a
|
|
1083
|
+
* healthy send on a conversation that has silently lost the one thing the protocol exists to
|
|
1084
|
+
* produce." This branch is where that loss OCCURS — `position_behind_frontier`'s own comment
|
|
1085
|
+
* says the seal "was already lost at the unwitnessed append, not here" — so it is the branch
|
|
1086
|
+
* that most needed to say so, and it was the one that said nothing.
|
|
1087
|
+
*/
|
|
1088
|
+
return { placed: true, leafIndex, unwitnessed: true };
|
|
1089
|
+
}
|
|
1090
|
+
if (assignedSeq === nextExpected) {
|
|
1091
|
+
const { leafIndex } = this.#ctx.appendSessionLeaf(agentName, sessionId, kind, contentHashHex, correlationId);
|
|
1092
|
+
return { placed: true, leafIndex };
|
|
1093
|
+
}
|
|
1094
|
+
if (assignedSeq < nextExpected) {
|
|
1095
|
+
// THE TREE AND THE RELAY HAVE ALREADY DIVERGED, and refusing here does not undo that.
|
|
1096
|
+
//
|
|
1097
|
+
// This side is AHEAD of the relay's counter, which happens by design: a message whose relay
|
|
1098
|
+
// submit failed still appends unwitnessed (the documented degradation). From then on every
|
|
1099
|
+
// ack comes back behind our frontier and the two can never agree again — the seal was already
|
|
1100
|
+
// lost at the unwitnessed append, not here.
|
|
1101
|
+
//
|
|
1102
|
+
// So the choice is between a record that is short by every subsequent message and one that is
|
|
1103
|
+
// complete but skewed. Appending at the tail keeps the operator's own words in their own
|
|
1104
|
+
// transcript, which is worth more than a tidiness the roots cannot recover anyway; writing
|
|
1105
|
+
// over the assigned slot is the one thing never done, because that rewrites a leaf a root has
|
|
1106
|
+
// already been computed over. The divergence is reported at ERROR and carried to the caller
|
|
1107
|
+
// rather than dressed up as an ordinary success.
|
|
1108
|
+
this.#ctx.logger.error("session.tree.position_behind_frontier", {
|
|
1109
|
+
agentName, sessionId, assignedSeq, nextExpected, contentHash: contentHashHex, correlationId,
|
|
1110
|
+
impact: "this side's tree is ahead of the relay's counter, so the two can no longer agree on a root — the message is kept in the local record and this session can no longer be sealed bilaterally",
|
|
1111
|
+
});
|
|
1112
|
+
this.#ctx.records.markSessionDiverged(agentName, sessionId);
|
|
1113
|
+
const { leafIndex } = this.#ctx.appendSessionLeaf(agentName, sessionId, kind, contentHashHex, correlationId);
|
|
1114
|
+
return { placed: true, leafIndex, diverged: true };
|
|
1115
|
+
}
|
|
1116
|
+
// Ahead of the tail: hold it, exactly as the receiver holds theirs, and let #releaseHeld put it
|
|
1117
|
+
// in at its own index when the gap fills.
|
|
1118
|
+
const key = this.#ctx.sessionKey(agentName, sessionId);
|
|
1119
|
+
let held = this.#ctx.heldContent.get(key);
|
|
1120
|
+
if (!held) {
|
|
1121
|
+
held = new Map();
|
|
1122
|
+
this.#ctx.heldContent.set(key, held);
|
|
1123
|
+
}
|
|
1124
|
+
held.set(assignedSeq, { content: sentBytes, contentHashHex, correlationId, origin: "sent", kind, ...(authorship ? { authorship } : {}) });
|
|
1125
|
+
this.#ctx.queries.persistHeldContent(agentName, sessionId, assignedSeq, sentBytes, sentBytes, contentHashHex, false, correlationId, "sent", kind);
|
|
1126
|
+
this.#ctx.logger.info("session.content.held", {
|
|
1127
|
+
sessionId, canonicalSeq: assignedSeq, nextExpected, gap: assignedSeq - nextExpected,
|
|
1128
|
+
origin: "sent", correlationId,
|
|
1129
|
+
});
|
|
1130
|
+
return { placed: false, heldAt: assignedSeq };
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Arm awaiting-ACK tracking for a just-sent content frame (AC-001/AC-003). Records
|
|
1134
|
+
* the content + a TTF timer keyed by content hash; a `persisted` ACK on the inbound
|
|
1135
|
+
* content stream resolves it, TTF expiry hands it to the park backstop. The timer is
|
|
1136
|
+
* `unref`'d so an in-flight wait never keeps the daemon process (or a test runner)
|
|
1137
|
+
* alive on its own.
|
|
1138
|
+
*/
|
|
1139
|
+
/**
|
|
1140
|
+
* `contentHashAlg` is `string | undefined`, NOT optional — B2b-1 pass-2 F1.
|
|
1141
|
+
*
|
|
1142
|
+
* The previous fix applied this shape to `#parkContent` and left its SIBLING on the same code path
|
|
1143
|
+
* optional, so dropping the argument here compiled clean and no test could see it — the TTF park
|
|
1144
|
+
* route reads this map, and a v2 envelope omits the field entirely whenever the value is `sha256`,
|
|
1145
|
+
* which is every value in play today. That re-opened the exact finding the fix closed.
|
|
1146
|
+
*/
|
|
1147
|
+
#trackAwaitingAck(agentName, sessionId, content, contentHash, correlationId, structure1Cbor, structure2Cbor, contentHashAlg, structure1Signature, leafKind) {
|
|
1148
|
+
const hashHex = Buffer.from(contentHash).toString("hex");
|
|
1149
|
+
const ackKey = this.#ctx.sessionKey(agentName, sessionId);
|
|
1150
|
+
let bySession = this.#ctx.awaitingAck.get(ackKey);
|
|
1151
|
+
if (!bySession) {
|
|
1152
|
+
bySession = new Map();
|
|
1153
|
+
this.#ctx.awaitingAck.set(ackKey, bySession);
|
|
1154
|
+
}
|
|
1155
|
+
// Replace any prior timer for the same (session, hash) so we never leak a timer.
|
|
1156
|
+
const prior = bySession.get(hashHex);
|
|
1157
|
+
if (prior)
|
|
1158
|
+
clearTimeout(prior.timer);
|
|
1159
|
+
const timer = setTimeout(() => {
|
|
1160
|
+
this.#ctx.handleTtfExpiry(agentName, sessionId, hashHex);
|
|
1161
|
+
}, this.#ctx.contentTtfMs);
|
|
1162
|
+
if (typeof timer.unref === "function")
|
|
1163
|
+
timer.unref();
|
|
1164
|
+
// DOD-MSG-4 (2b, review #1): retain the relay's ordering record so a TTF-triggered park carries
|
|
1165
|
+
// it too (not only the direct-dial-fail park) — so a TTF-parked entry is self-ordering on recover.
|
|
1166
|
+
// B2b-1 review F2: the algorithm rides WITH the entry. The TTF-expiry park route reads this map
|
|
1167
|
+
// minutes later, in-process, and without it that copy names nothing (= sha256) while the direct
|
|
1168
|
+
// frame named something else — the same message, two claims about what it is, no restart needed.
|
|
1169
|
+
bySession.set(hashHex, { timer, content, correlationId, structure1Cbor, structure2Cbor, contentHashAlg, structure1Signature, leafKind });
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Resolve an awaiting-ACK entry on a `persisted` delivery ACK (AC-001/AC-002): cancel
|
|
1173
|
+
* the TTF timer, emit content.delivery.acked, and clear the durable backstop entry.
|
|
1174
|
+
* A `received`-level ACK is NOT handled here — the protocol acts on `persisted` only,
|
|
1175
|
+
* so a received ACK leaves the timer armed.
|
|
1176
|
+
*/
|
|
1177
|
+
resolveAwaitingAck(agentName, sessionId, contentHash) {
|
|
1178
|
+
const hashHex = Buffer.from(contentHash).toString("hex");
|
|
1179
|
+
const ackKey = this.#ctx.sessionKey(agentName, sessionId);
|
|
1180
|
+
const bySession = this.#ctx.awaitingAck.get(ackKey);
|
|
1181
|
+
const entry = bySession?.get(hashHex);
|
|
1182
|
+
if (!entry || !bySession)
|
|
1183
|
+
return; // unknown / already resolved — idempotent
|
|
1184
|
+
clearTimeout(entry.timer);
|
|
1185
|
+
bySession.delete(hashHex);
|
|
1186
|
+
if (bySession.size === 0)
|
|
1187
|
+
this.#ctx.awaitingAck.delete(ackKey);
|
|
1188
|
+
this.#ctx.logger.info("content.delivery.acked", {
|
|
1189
|
+
sessionId,
|
|
1190
|
+
contentHash: hashHex,
|
|
1191
|
+
level: "persisted",
|
|
1192
|
+
correlationId: entry.correlationId,
|
|
1193
|
+
});
|
|
1194
|
+
// Clear the durable crash-backstop entry so the startup flush does not re-park
|
|
1195
|
+
// already-delivered content.
|
|
1196
|
+
try {
|
|
1197
|
+
this.#ctx.onAwaitingPersisted?.(agentName, sessionId, hashHex);
|
|
1198
|
+
}
|
|
1199
|
+
catch (err) {
|
|
1200
|
+
this.#ctx.logger.error("content.delivery.ack.backstop.failed", {
|
|
1201
|
+
sessionId, contentHash: hashHex, error: extractErrorMessage(err),
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
/** Cancel and drop a single awaiting-ACK entry (e.g. the send failed after arming). */
|
|
1206
|
+
#untrackAwaitingAck(agentName, sessionId, contentHash) {
|
|
1207
|
+
const hashHex = Buffer.from(contentHash).toString("hex");
|
|
1208
|
+
const ackKey = this.#ctx.sessionKey(agentName, sessionId);
|
|
1209
|
+
const bySession = this.#ctx.awaitingAck.get(ackKey);
|
|
1210
|
+
const entry = bySession?.get(hashHex);
|
|
1211
|
+
if (!entry || !bySession)
|
|
1212
|
+
return;
|
|
1213
|
+
clearTimeout(entry.timer);
|
|
1214
|
+
bySession.delete(hashHex);
|
|
1215
|
+
if (bySession.size === 0)
|
|
1216
|
+
this.#ctx.awaitingAck.delete(ackKey);
|
|
1217
|
+
}
|
|
1218
|
+
/** Cancel and drop all awaiting-ACK timers for a session (teardown). */
|
|
1219
|
+
clearAwaitingForSession(agentName, sessionId) {
|
|
1220
|
+
const ackKey = this.#ctx.sessionKey(agentName, sessionId);
|
|
1221
|
+
const bySession = this.#ctx.awaitingAck.get(ackKey);
|
|
1222
|
+
if (!bySession)
|
|
1223
|
+
return;
|
|
1224
|
+
for (const entry of bySession.values())
|
|
1225
|
+
clearTimeout(entry.timer);
|
|
1226
|
+
this.#ctx.awaitingAck.delete(ackKey);
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* `DOD-M15-AUTHORSHIP-ABSENT-1` — SIGN OUR OWN CLAIM, with no relay involved.
|
|
1230
|
+
*
|
|
1231
|
+
* The relay submit has always built these bytes and signed them (`session-relay-client.ts`); this
|
|
1232
|
+
* is the same construction, for the path where no submit happens. It is not a fallback in the
|
|
1233
|
+
* silent sense — it produces exactly the artifact the witnessed path produces, minus the relay's
|
|
1234
|
+
* countersigned position, which was never part of the authorship claim.
|
|
1235
|
+
*
|
|
1236
|
+
* ⚠️ THROWS when this agent has no identity key, and the throw is the correct outcome. It lands in
|
|
1237
|
+
* the direct-send catch, which parks the message to the relay mailbox exactly as a failed dial
|
|
1238
|
+
* does — so the message is not lost, and the operator hears about a local fault instead of a
|
|
1239
|
+
* counterparty who mysteriously stopped receiving. Shipping the frame unsigned would guarantee a
|
|
1240
|
+
* refusal at the far end and blame the wrong machine for it.
|
|
1241
|
+
*/
|
|
1242
|
+
async #signOwnContentClaim(agentName, sessionId, entry, contentHash) {
|
|
1243
|
+
const signer = this.#ctx.keyProvider(agentName);
|
|
1244
|
+
if (!signer) {
|
|
1245
|
+
throw new Error("content_not_signable: this machine has no identity key for this agent, so it cannot sign " +
|
|
1246
|
+
"the message it is about to send and the counterparty would refuse it as unattributable");
|
|
1247
|
+
}
|
|
1248
|
+
// The 16-byte relay session id when this session has one, so a frame built here is
|
|
1249
|
+
// byte-comparable with one built by the submit. Falling back to the local id is not a
|
|
1250
|
+
// second meaning: for every session created without an assignment the two are the same value
|
|
1251
|
+
// (`relaySessionIdBytes` is set from `sessionId` on exactly those paths).
|
|
1252
|
+
const sessionIdBytes = entry.relaySessionIdBytes ?? Uint8Array.from(Buffer.from(sessionId, "hex"));
|
|
1253
|
+
/**
|
|
1254
|
+
* ⚠️ **THIS COMMENT USED TO READ "v1 DELIBERATELY" AND IT WAS RIGHT UNTIL NOW — 033-ACKEMIT.**
|
|
1255
|
+
*
|
|
1256
|
+
* It said `last_seen_hash` was `WITHHOLD-SEAL-1`'s emitter and "not owed here", and that a v1
|
|
1257
|
+
* claim makes no content acknowledgement at all, "which is honest, where an invented one would
|
|
1258
|
+
* not be." Accurate for `020-ACKHASH`, which shipped the reader only. It is rewritten rather
|
|
1259
|
+
* than deleted because it is the sentence that would otherwise explain away the LAST production
|
|
1260
|
+
* path still emitting v1 — and this unit's own Definition of Done is that a grep finds none.
|
|
1261
|
+
*
|
|
1262
|
+
* The reasoning it rested on has been answered: nothing is invented here. The acknowledgement
|
|
1263
|
+
* is read from the same `#lastSeen` entry the submit reads, so a frame built on this path and
|
|
1264
|
+
* one built by a submit make the same claim about the same message.
|
|
1265
|
+
*/
|
|
1266
|
+
const sessionIdHexForAck = Buffer.from(sessionIdBytes).toString("hex");
|
|
1267
|
+
/**
|
|
1268
|
+
* The pair, from ONE accessor. Falling back to the session's genesis when there is no relay
|
|
1269
|
+
* client at all is not an invention either: nothing has been witnessed on this session, so the
|
|
1270
|
+
* honest acknowledgement is position 0 and the agreed starting point of the chain.
|
|
1271
|
+
*/
|
|
1272
|
+
const ack = entry.relayClient?.lastSeenAck(sessionIdHexForAck)
|
|
1273
|
+
?? this.#ctx.lastAck.get(this.#ctx.sessionKey(agentName, sessionId))
|
|
1274
|
+
?? (() => { const g = this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId); return g ? { seq: 0, hash: g } : undefined; })();
|
|
1275
|
+
if (!ack) {
|
|
1276
|
+
/**
|
|
1277
|
+
* ⚠️ NO STARTING POINT MEANS NO SEND — `DOD-M15-SELFCHAIN-1`.
|
|
1278
|
+
*
|
|
1279
|
+
* This used to emit a shorter claim: `last_seen_seq: 0` with no hashes, on the argument that
|
|
1280
|
+
* "I have seen nothing of yours" is honest and asserts nothing false. It IS honest, and it is
|
|
1281
|
+
* no longer a shape this protocol has. Both chain links are required, and a session with no
|
|
1282
|
+
* recorded starting point has nothing for them to anchor to.
|
|
1283
|
+
*
|
|
1284
|
+
* Refusing costs a message on a session that was brokered without an assignment. Sending one
|
|
1285
|
+
* costs the ability to prove the order of the whole conversation later, and the cost is
|
|
1286
|
+
* invisible until someone disputes it — which is exactly the failure this unit exists to end.
|
|
1287
|
+
* Refuse loudly, and say what to do about it.
|
|
1288
|
+
*/
|
|
1289
|
+
throw new Error("session_unchainable: this session has no recorded starting point on this machine, so a " +
|
|
1290
|
+
"message sent on it could not link to anything and its place in the conversation could " +
|
|
1291
|
+
"never be proven. Restart the session so it is registered with its genesis.");
|
|
1292
|
+
}
|
|
1293
|
+
const structure1 = encodeStructure1({
|
|
1294
|
+
contentHash,
|
|
1295
|
+
senderPubkey: await signer.getPublicKey(),
|
|
1296
|
+
sessionId: sessionIdBytes,
|
|
1297
|
+
// The highest counterparty position this session has seen, from the same source the submit
|
|
1298
|
+
// reads — and now the content hash at it, taken from the same entry so the two cannot
|
|
1299
|
+
// describe different messages.
|
|
1300
|
+
lastSeenSeq: ack.seq,
|
|
1301
|
+
timestamp: Date.now(),
|
|
1302
|
+
lastSeenHash: ack.hash,
|
|
1303
|
+
/**
|
|
1304
|
+
* ─── THE SELF LINK ON THE UNWITNESSED PATH — `DOD-M15-SELFCHAIN-1` ────────────────────────
|
|
1305
|
+
*
|
|
1306
|
+
* This is the path that matters most for it. A conversation that ran while the relay was down
|
|
1307
|
+
* is precisely the one whose order gets disputed later, so the chain cannot depend on a relay
|
|
1308
|
+
* having been there to witness it.
|
|
1309
|
+
*
|
|
1310
|
+
* ⚠️ ONE CHAIN, READ THROUGH THE RELAY CLIENT FIRST. This used to read only the durable
|
|
1311
|
+
* store, while the witnessed path reads an in-memory map first — so a session that mixed the
|
|
1312
|
+
* two, which is every session where the relay comes and goes, walked two different chains and
|
|
1313
|
+
* the lagging one produced a link the counterparty refuses.
|
|
1314
|
+
*
|
|
1315
|
+
* ⚠️ AND THE LAST FALLBACK IS THE SESSION GENESIS, NOT `ack.hash`. `ack.hash` is what the
|
|
1316
|
+
* COUNTERPARTY last said; it is only the genesis until they have said anything. Falling back
|
|
1317
|
+
* to it meant this agent's own first message linked to the other party's message — refused by
|
|
1318
|
+
* every checker, and reported as tampering against a party that had done nothing.
|
|
1319
|
+
*/
|
|
1320
|
+
prevOwnHash: this.#ctx.ownChainOf(agentName, sessionId, entry, await signer.getPublicKey())
|
|
1321
|
+
?? this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId)
|
|
1322
|
+
?? ack.hash,
|
|
1323
|
+
});
|
|
1324
|
+
/**
|
|
1325
|
+
* ⚠️ THE CHAIN IS NOT ADVANCED HERE, AND IT USED TO BE — review F7.
|
|
1326
|
+
*
|
|
1327
|
+
* `SessionOwnChainStore.record` says in capitals that it is called AFTER the send succeeds. This
|
|
1328
|
+
* ran at signing time, before a stream was even opened. Any failure between here and delivery
|
|
1329
|
+
* left the chain pointing at a message the counterparty never saw, and every later message was
|
|
1330
|
+
* then refused by them for a reason that names tampering. A retransmission must re-use the same
|
|
1331
|
+
* predecessor, because a retransmission is the same message.
|
|
1332
|
+
*
|
|
1333
|
+
* `#advanceOwnChain` is called by the send path at each point where the message has actually
|
|
1334
|
+
* gone — the direct send, and the relay park that is the fallback for it.
|
|
1335
|
+
*/
|
|
1336
|
+
return { structure1, signature: await signer.sign(structure1) };
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Record what this agent just sent — called ONLY once the message has actually gone.
|
|
1340
|
+
*
|
|
1341
|
+
* Writes through the relay client when there is one, so the in-memory chain the witnessed path
|
|
1342
|
+
* reads and the durable row stay one chain rather than two. Falls back to the store directly for
|
|
1343
|
+
* a session with no relay client at all, which is exactly the unwitnessed case this unit exists
|
|
1344
|
+
* to cover.
|
|
1345
|
+
*/
|
|
1346
|
+
#advanceOwnChain(agentName, sessionId, entry, contentHash) {
|
|
1347
|
+
const relayHex = entry?.relaySessionIdBytes
|
|
1348
|
+
? Buffer.from(entry.relaySessionIdBytes).toString("hex")
|
|
1349
|
+
: sessionId;
|
|
1350
|
+
if (entry?.relayClient) {
|
|
1351
|
+
entry.relayClient.noteOwnLeaf(relayHex, contentHash);
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const ownPubkeyHex = this.#ctx.queries.ownPubkeyHex(agentName);
|
|
1355
|
+
if (!ownPubkeyHex)
|
|
1356
|
+
return;
|
|
1357
|
+
this.#ctx.ownChainStore?.record(ownPubkeyHex, sessionId, contentHash, Date.now());
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
//# sourceMappingURL=session-content-send.js.map
|