@cello-protocol/daemon 0.0.225 → 0.0.227
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/package.json +5 -5
- package/dist/agent-admin-handlers.d.ts +0 -56
- package/dist/agent-admin-handlers.d.ts.map +0 -1
- package/dist/agent-admin-handlers.js +0 -73
- package/dist/agent-admin-handlers.js.map +0 -1
- package/dist/agent-handlers.d.ts +0 -72
- package/dist/agent-handlers.d.ts.map +0 -1
- package/dist/agent-handlers.js +0 -604
- package/dist/agent-handlers.js.map +0 -1
- package/dist/agent-id-migration.d.ts +0 -70
- package/dist/agent-id-migration.d.ts.map +0 -1
- package/dist/agent-id-migration.js +0 -468
- package/dist/agent-id-migration.js.map +0 -1
- package/dist/agent-loader.d.ts +0 -37
- package/dist/agent-loader.d.ts.map +0 -1
- package/dist/agent-loader.js +0 -37
- package/dist/agent-loader.js.map +0 -1
- package/dist/agent-selection-root.d.ts +0 -29
- package/dist/agent-selection-root.d.ts.map +0 -1
- package/dist/agent-selection-root.js +0 -126
- package/dist/agent-selection-root.js.map +0 -1
- package/dist/agent-selection.d.ts +0 -73
- package/dist/agent-selection.d.ts.map +0 -1
- package/dist/agent-selection.js +0 -80
- package/dist/agent-selection.js.map +0 -1
- package/dist/agent-settings-keys.d.ts +0 -52
- package/dist/agent-settings-keys.d.ts.map +0 -1
- package/dist/agent-settings-keys.js +0 -114
- package/dist/agent-settings-keys.js.map +0 -1
- package/dist/agent-state.d.ts +0 -67
- package/dist/agent-state.d.ts.map +0 -1
- package/dist/agent-state.js +0 -66
- package/dist/agent-state.js.map +0 -1
- package/dist/assignment-verify.d.ts +0 -90
- package/dist/assignment-verify.d.ts.map +0 -1
- package/dist/assignment-verify.js +0 -301
- package/dist/assignment-verify.js.map +0 -1
- package/dist/attendance-wiring.d.ts +0 -23
- package/dist/attendance-wiring.d.ts.map +0 -1
- package/dist/attendance-wiring.js +0 -248
- package/dist/attendance-wiring.js.map +0 -1
- package/dist/attestation-body.d.ts +0 -47
- package/dist/attestation-body.d.ts.map +0 -1
- package/dist/attestation-body.js +0 -71
- package/dist/attestation-body.js.map +0 -1
- package/dist/authorship-verification.d.ts +0 -91
- package/dist/authorship-verification.d.ts.map +0 -1
- package/dist/authorship-verification.js +0 -532
- package/dist/authorship-verification.js.map +0 -1
- package/dist/away-detection.d.ts +0 -139
- package/dist/away-detection.d.ts.map +0 -1
- package/dist/away-detection.js +0 -186
- package/dist/away-detection.js.map +0 -1
- package/dist/away-inbox-oneshot.d.ts +0 -40
- package/dist/away-inbox-oneshot.d.ts.map +0 -1
- package/dist/away-inbox-oneshot.js +0 -311
- package/dist/away-inbox-oneshot.js.map +0 -1
- package/dist/backup-restore-handlers.d.ts +0 -10
- package/dist/backup-restore-handlers.d.ts.map +0 -1
- package/dist/backup-restore-handlers.js +0 -82
- package/dist/backup-restore-handlers.js.map +0 -1
- package/dist/backup-restore.d.ts +0 -102
- package/dist/backup-restore.d.ts.map +0 -1
- package/dist/backup-restore.js +0 -341
- package/dist/backup-restore.js.map +0 -1
- package/dist/bin/cello-daemon.d.ts +0 -13
- package/dist/bin/cello-daemon.d.ts.map +0 -1
- package/dist/bin/cello-daemon.js.map +0 -1
- package/dist/boot-agents.d.ts +0 -64
- package/dist/boot-agents.d.ts.map +0 -1
- package/dist/boot-agents.js +0 -241
- package/dist/boot-agents.js.map +0 -1
- package/dist/boot-connection-state.d.ts +0 -38
- package/dist/boot-connection-state.d.ts.map +0 -1
- package/dist/boot-connection-state.js +0 -95
- package/dist/boot-connection-state.js.map +0 -1
- package/dist/boot-core.d.ts +0 -36
- package/dist/boot-core.d.ts.map +0 -1
- package/dist/boot-core.js +0 -293
- package/dist/boot-core.js.map +0 -1
- package/dist/boot-parked-content.d.ts +0 -34
- package/dist/boot-parked-content.d.ts.map +0 -1
- package/dist/boot-parked-content.js +0 -528
- package/dist/boot-parked-content.js.map +0 -1
- package/dist/boot-sweeps.d.ts +0 -22
- package/dist/boot-sweeps.d.ts.map +0 -1
- package/dist/boot-sweeps.js +0 -51
- package/dist/boot-sweeps.js.map +0 -1
- package/dist/bundled-consortium-manifest.d.ts +0 -47
- package/dist/bundled-consortium-manifest.d.ts.map +0 -1
- package/dist/bundled-consortium-manifest.js +0 -116
- package/dist/bundled-consortium-manifest.js.map +0 -1
- package/dist/cbor-blob-migration.d.ts +0 -8
- package/dist/cbor-blob-migration.d.ts.map +0 -1
- package/dist/cbor-blob-migration.js +0 -116
- package/dist/cbor-blob-migration.js.map +0 -1
- package/dist/challenge-verifier.d.ts +0 -12
- package/dist/challenge-verifier.d.ts.map +0 -1
- package/dist/challenge-verifier.js +0 -11
- package/dist/challenge-verifier.js.map +0 -1
- package/dist/close-commitment.d.ts +0 -67
- package/dist/close-commitment.d.ts.map +0 -1
- package/dist/close-commitment.js +0 -78
- package/dist/close-commitment.js.map +0 -1
- package/dist/close-session-handler.d.ts +0 -94
- package/dist/close-session-handler.d.ts.map +0 -1
- package/dist/close-session-handler.js +0 -1418
- package/dist/close-session-handler.js.map +0 -1
- package/dist/co-attendance.d.ts +0 -29
- package/dist/co-attendance.d.ts.map +0 -1
- package/dist/co-attendance.js +0 -32
- package/dist/co-attendance.js.map +0 -1
- package/dist/column-birth.d.ts +0 -40
- package/dist/column-birth.d.ts.map +0 -1
- package/dist/column-birth.js +0 -61
- package/dist/column-birth.js.map +0 -1
- package/dist/connect-or-start.d.ts +0 -25
- package/dist/connect-or-start.d.ts.map +0 -1
- package/dist/connect-or-start.js +0 -175
- package/dist/connect-or-start.js.map +0 -1
- package/dist/connection-agents.d.ts +0 -16
- package/dist/connection-agents.d.ts.map +0 -1
- package/dist/connection-agents.js +0 -68
- package/dist/connection-agents.js.map +0 -1
- package/dist/consent-migration.d.ts +0 -49
- package/dist/consent-migration.d.ts.map +0 -1
- package/dist/consent-migration.js +0 -128
- package/dist/consent-migration.js.map +0 -1
- package/dist/consortium-bootstrap.d.ts +0 -138
- package/dist/consortium-bootstrap.d.ts.map +0 -1
- package/dist/consortium-bootstrap.js +0 -339
- package/dist/consortium-bootstrap.js.map +0 -1
- package/dist/consortium-fingerprint.d.ts +0 -115
- package/dist/consortium-fingerprint.d.ts.map +0 -1
- package/dist/consortium-fingerprint.js +0 -175
- package/dist/consortium-fingerprint.js.map +0 -1
- package/dist/contact-handlers.d.ts +0 -59
- package/dist/contact-handlers.d.ts.map +0 -1
- package/dist/contact-handlers.js +0 -343
- package/dist/contact-handlers.js.map +0 -1
- package/dist/contact-pubkey-case.d.ts +0 -65
- package/dist/contact-pubkey-case.d.ts.map +0 -1
- package/dist/contact-pubkey-case.js +0 -136
- package/dist/contact-pubkey-case.js.map +0 -1
- package/dist/contacts-tier-migration.d.ts +0 -90
- package/dist/contacts-tier-migration.d.ts.map +0 -1
- package/dist/contacts-tier-migration.js +0 -150
- package/dist/contacts-tier-migration.js.map +0 -1
- package/dist/content-encryption-status.d.ts +0 -111
- package/dist/content-encryption-status.d.ts.map +0 -1
- package/dist/content-encryption-status.js +0 -158
- package/dist/content-encryption-status.js.map +0 -1
- package/dist/content-park-client.d.ts +0 -90
- package/dist/content-park-client.d.ts.map +0 -1
- package/dist/content-park-client.js +0 -362
- package/dist/content-park-client.js.map +0 -1
- package/dist/content-park.d.ts +0 -52
- package/dist/content-park.d.ts.map +0 -1
- package/dist/content-park.js +0 -1309
- package/dist/content-park.js.map +0 -1
- package/dist/cross-node-negotiation.d.ts +0 -44
- package/dist/cross-node-negotiation.d.ts.map +0 -1
- package/dist/cross-node-negotiation.js +0 -34
- package/dist/cross-node-negotiation.js.map +0 -1
- package/dist/daemon-handle.d.ts +0 -57
- package/dist/daemon-handle.d.ts.map +0 -1
- package/dist/daemon-handle.js +0 -2
- package/dist/daemon-handle.js.map +0 -1
- package/dist/daemon-status-report.d.ts +0 -50
- package/dist/daemon-status-report.d.ts.map +0 -1
- package/dist/daemon-status-report.js +0 -81
- package/dist/daemon-status-report.js.map +0 -1
- package/dist/daemon.d.ts +0 -45
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js +0 -1177
- package/dist/daemon.js.map +0 -1
- package/dist/db-identity-store.d.ts +0 -159
- package/dist/db-identity-store.d.ts.map +0 -1
- package/dist/db-identity-store.js +0 -502
- package/dist/db-identity-store.js.map +0 -1
- package/dist/delivery-open-registry.d.ts +0 -92
- package/dist/delivery-open-registry.d.ts.map +0 -1
- package/dist/delivery-open-registry.js +0 -121
- package/dist/delivery-open-registry.js.map +0 -1
- package/dist/delivery-session-suspects.d.ts +0 -56
- package/dist/delivery-session-suspects.d.ts.map +0 -1
- package/dist/delivery-session-suspects.js +0 -94
- package/dist/delivery-session-suspects.js.map +0 -1
- package/dist/directory-auth-posture.d.ts +0 -87
- package/dist/directory-auth-posture.d.ts.map +0 -1
- package/dist/directory-auth-posture.js +0 -134
- package/dist/directory-auth-posture.js.map +0 -1
- package/dist/directory-bootstrap.d.ts +0 -310
- package/dist/directory-bootstrap.d.ts.map +0 -1
- package/dist/directory-bootstrap.js +0 -557
- package/dist/directory-bootstrap.js.map +0 -1
- package/dist/directory-connect.d.ts +0 -27
- package/dist/directory-connect.d.ts.map +0 -1
- package/dist/directory-connect.js +0 -106
- package/dist/directory-connect.js.map +0 -1
- package/dist/disconnect-cleanup.d.ts +0 -51
- package/dist/disconnect-cleanup.d.ts.map +0 -1
- package/dist/disconnect-cleanup.js +0 -72
- package/dist/disconnect-cleanup.js.map +0 -1
- package/dist/document-amendment-store.d.ts +0 -120
- package/dist/document-amendment-store.d.ts.map +0 -1
- package/dist/document-amendment-store.js +0 -266
- package/dist/document-amendment-store.js.map +0 -1
- package/dist/document-delivery-transport.d.ts +0 -168
- package/dist/document-delivery-transport.d.ts.map +0 -1
- package/dist/document-delivery-transport.js +0 -206
- package/dist/document-delivery-transport.js.map +0 -1
- package/dist/document-engine.d.ts +0 -134
- package/dist/document-engine.d.ts.map +0 -1
- package/dist/document-engine.js +0 -282
- package/dist/document-engine.js.map +0 -1
- package/dist/document-flag.d.ts +0 -58
- package/dist/document-flag.d.ts.map +0 -1
- package/dist/document-flag.js +0 -70
- package/dist/document-flag.js.map +0 -1
- package/dist/document-frame-router.d.ts +0 -245
- package/dist/document-frame-router.d.ts.map +0 -1
- package/dist/document-frame-router.js +0 -397
- package/dist/document-frame-router.js.map +0 -1
- package/dist/document-gate-wiring.d.ts +0 -61
- package/dist/document-gate-wiring.d.ts.map +0 -1
- package/dist/document-gate-wiring.js +0 -125
- package/dist/document-gate-wiring.js.map +0 -1
- package/dist/document-gate.d.ts +0 -149
- package/dist/document-gate.d.ts.map +0 -1
- package/dist/document-gate.js +0 -509
- package/dist/document-gate.js.map +0 -1
- package/dist/document-handlers.d.ts +0 -47
- package/dist/document-handlers.d.ts.map +0 -1
- package/dist/document-handlers.js +0 -2203
- package/dist/document-handlers.js.map +0 -1
- package/dist/document-handshake.d.ts +0 -176
- package/dist/document-handshake.d.ts.map +0 -1
- package/dist/document-handshake.js +0 -452
- package/dist/document-handshake.js.map +0 -1
- package/dist/document-inbound.d.ts +0 -162
- package/dist/document-inbound.d.ts.map +0 -1
- package/dist/document-inbound.js +0 -530
- package/dist/document-inbound.js.map +0 -1
- package/dist/document-json.d.ts +0 -120
- package/dist/document-json.d.ts.map +0 -1
- package/dist/document-json.js +0 -191
- package/dist/document-json.js.map +0 -1
- package/dist/document-layer.d.ts +0 -215
- package/dist/document-layer.d.ts.map +0 -1
- package/dist/document-layer.js +0 -1025
- package/dist/document-layer.js.map +0 -1
- package/dist/document-lifecycle.d.ts +0 -52
- package/dist/document-lifecycle.d.ts.map +0 -1
- package/dist/document-lifecycle.js +0 -134
- package/dist/document-lifecycle.js.map +0 -1
- package/dist/document-live-docs.d.ts +0 -58
- package/dist/document-live-docs.d.ts.map +0 -1
- package/dist/document-live-docs.js +0 -126
- package/dist/document-live-docs.js.map +0 -1
- package/dist/document-notify.d.ts +0 -228
- package/dist/document-notify.d.ts.map +0 -1
- package/dist/document-notify.js +0 -580
- package/dist/document-notify.js.map +0 -1
- package/dist/document-profile.d.ts +0 -61
- package/dist/document-profile.d.ts.map +0 -1
- package/dist/document-profile.js +0 -112
- package/dist/document-profile.js.map +0 -1
- package/dist/document-publish.d.ts +0 -87
- package/dist/document-publish.d.ts.map +0 -1
- package/dist/document-publish.js +0 -173
- package/dist/document-publish.js.map +0 -1
- package/dist/document-reachability.d.ts +0 -42
- package/dist/document-reachability.d.ts.map +0 -1
- package/dist/document-reachability.js +0 -80
- package/dist/document-reachability.js.map +0 -1
- package/dist/document-reconcile-engine.d.ts +0 -66
- package/dist/document-reconcile-engine.d.ts.map +0 -1
- package/dist/document-reconcile-engine.js +0 -225
- package/dist/document-reconcile-engine.js.map +0 -1
- package/dist/document-reconcile-scheduler.d.ts +0 -163
- package/dist/document-reconcile-scheduler.d.ts.map +0 -1
- package/dist/document-reconcile-scheduler.js +0 -303
- package/dist/document-reconcile-scheduler.js.map +0 -1
- package/dist/document-rejection.d.ts +0 -251
- package/dist/document-rejection.d.ts.map +0 -1
- package/dist/document-rejection.js +0 -435
- package/dist/document-rejection.js.map +0 -1
- package/dist/document-screen.d.ts +0 -114
- package/dist/document-screen.d.ts.map +0 -1
- package/dist/document-screen.js +0 -223
- package/dist/document-screen.js.map +0 -1
- package/dist/document-store.d.ts +0 -372
- package/dist/document-store.d.ts.map +0 -1
- package/dist/document-store.js +0 -931
- package/dist/document-store.js.map +0 -1
- package/dist/document-surface.d.ts +0 -32
- package/dist/document-surface.d.ts.map +0 -1
- package/dist/document-surface.js +0 -151
- package/dist/document-surface.js.map +0 -1
- package/dist/document-types.d.ts +0 -94
- package/dist/document-types.d.ts.map +0 -1
- package/dist/document-types.js +0 -90
- package/dist/document-types.js.map +0 -1
- package/dist/document-watch.d.ts +0 -69
- package/dist/document-watch.d.ts.map +0 -1
- package/dist/document-watch.js +0 -108
- package/dist/document-watch.js.map +0 -1
- package/dist/document-wiring.d.ts +0 -53
- package/dist/document-wiring.d.ts.map +0 -1
- package/dist/document-wiring.js +0 -323
- package/dist/document-wiring.js.map +0 -1
- package/dist/document-write-guard.d.ts +0 -66
- package/dist/document-write-guard.d.ts.map +0 -1
- package/dist/document-write-guard.js +0 -98
- package/dist/document-write-guard.js.map +0 -1
- package/dist/document-write-path.d.ts +0 -94
- package/dist/document-write-path.d.ts.map +0 -1
- package/dist/document-write-path.js +0 -530
- package/dist/document-write-path.js.map +0 -1
- package/dist/error-message.d.ts +0 -7
- package/dist/error-message.d.ts.map +0 -1
- package/dist/error-message.js +0 -19
- package/dist/error-message.js.map +0 -1
- package/dist/file-manifest-provider.d.ts +0 -37
- package/dist/file-manifest-provider.d.ts.map +0 -1
- package/dist/file-manifest-provider.js +0 -105
- package/dist/file-manifest-provider.js.map +0 -1
- package/dist/frame-values.d.ts +0 -3
- package/dist/frame-values.d.ts.map +0 -1
- package/dist/frame-values.js +0 -84
- package/dist/frame-values.js.map +0 -1
- package/dist/frontier-mismatch.d.ts +0 -73
- package/dist/frontier-mismatch.d.ts.map +0 -1
- package/dist/frontier-mismatch.js +0 -89
- package/dist/frontier-mismatch.js.map +0 -1
- package/dist/gateway-config-handlers.d.ts +0 -26
- package/dist/gateway-config-handlers.d.ts.map +0 -1
- package/dist/gateway-config-handlers.js +0 -429
- package/dist/gateway-config-handlers.js.map +0 -1
- package/dist/held-content.d.ts +0 -145
- package/dist/held-content.d.ts.map +0 -1
- package/dist/held-content.js +0 -389
- package/dist/held-content.js.map +0 -1
- package/dist/http-manifest-poll.d.ts +0 -67
- package/dist/http-manifest-poll.d.ts.map +0 -1
- package/dist/http-manifest-poll.js +0 -150
- package/dist/http-manifest-poll.js.map +0 -1
- package/dist/identity-migration.d.ts +0 -40
- package/dist/identity-migration.d.ts.map +0 -1
- package/dist/identity-migration.js +0 -461
- package/dist/identity-migration.js.map +0 -1
- package/dist/inbound-refusals.d.ts +0 -283
- package/dist/inbound-refusals.d.ts.map +0 -1
- package/dist/inbound-refusals.js +0 -919
- package/dist/inbound-refusals.js.map +0 -1
- package/dist/inbound-seal-request.d.ts +0 -32
- package/dist/inbound-seal-request.d.ts.map +0 -1
- package/dist/inbound-seal-request.js +0 -228
- package/dist/inbound-seal-request.js.map +0 -1
- package/dist/inbound-sessions.d.ts +0 -291
- package/dist/inbound-sessions.d.ts.map +0 -1
- package/dist/inbound-sessions.js +0 -1609
- package/dist/inbound-sessions.js.map +0 -1
- package/dist/inclusion-proof-handlers.d.ts +0 -43
- package/dist/inclusion-proof-handlers.d.ts.map +0 -1
- package/dist/inclusion-proof-handlers.js +0 -565
- package/dist/inclusion-proof-handlers.js.map +0 -1
- package/dist/inclusion-proof.d.ts +0 -151
- package/dist/inclusion-proof.d.ts.map +0 -1
- package/dist/inclusion-proof.js +0 -228
- package/dist/inclusion-proof.js.map +0 -1
- package/dist/index.d.ts +0 -32
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/initiate-session-handler.d.ts +0 -55
- package/dist/initiate-session-handler.d.ts.map +0 -1
- package/dist/initiate-session-handler.js +0 -310
- package/dist/initiate-session-handler.js.map +0 -1
- package/dist/ipc-client.d.ts +0 -31
- package/dist/ipc-client.d.ts.map +0 -1
- package/dist/ipc-client.js +0 -113
- package/dist/ipc-client.js.map +0 -1
- package/dist/ipc-server.d.ts +0 -63
- package/dist/ipc-server.d.ts.map +0 -1
- package/dist/ipc-server.js +0 -429
- package/dist/ipc-server.js.map +0 -1
- package/dist/ipc-surface.d.ts +0 -44
- package/dist/ipc-surface.d.ts.map +0 -1
- package/dist/ipc-surface.js +0 -107
- package/dist/ipc-surface.js.map +0 -1
- package/dist/line-lcs.d.ts +0 -51
- package/dist/line-lcs.d.ts.map +0 -1
- package/dist/line-lcs.js +0 -71
- package/dist/line-lcs.js.map +0 -1
- package/dist/lock-file.d.ts +0 -39
- package/dist/lock-file.d.ts.map +0 -1
- package/dist/lock-file.js +0 -120
- package/dist/lock-file.js.map +0 -1
- package/dist/log-collapse.d.ts +0 -66
- package/dist/log-collapse.d.ts.map +0 -1
- package/dist/log-collapse.js +0 -244
- package/dist/log-collapse.js.map +0 -1
- package/dist/log-rotate.d.ts +0 -67
- package/dist/log-rotate.d.ts.map +0 -1
- package/dist/log-rotate.js +0 -134
- package/dist/log-rotate.js.map +0 -1
- package/dist/manifest-deps.d.ts +0 -25
- package/dist/manifest-deps.d.ts.map +0 -1
- package/dist/manifest-deps.js +0 -151
- package/dist/manifest-deps.js.map +0 -1
- package/dist/manifest-poll-scheduler.d.ts +0 -31
- package/dist/manifest-poll-scheduler.d.ts.map +0 -1
- package/dist/manifest-poll-scheduler.js +0 -59
- package/dist/manifest-poll-scheduler.js.map +0 -1
- package/dist/manifest-validity.d.ts +0 -153
- package/dist/manifest-validity.d.ts.map +0 -1
- package/dist/manifest-validity.js +0 -268
- package/dist/manifest-validity.js.map +0 -1
- package/dist/manifest-version-store-db.d.ts +0 -24
- package/dist/manifest-version-store-db.d.ts.map +0 -1
- package/dist/manifest-version-store-db.js +0 -58
- package/dist/manifest-version-store-db.js.map +0 -1
- package/dist/manifest-version-store.d.ts +0 -16
- package/dist/manifest-version-store.d.ts.map +0 -1
- package/dist/manifest-version-store.js +0 -15
- package/dist/manifest-version-store.js.map +0 -1
- package/dist/network-directory-node.d.ts +0 -136
- package/dist/network-directory-node.d.ts.map +0 -1
- package/dist/network-directory-node.js +0 -810
- package/dist/network-directory-node.js.map +0 -1
- package/dist/nonce-dedup.d.ts +0 -68
- package/dist/nonce-dedup.d.ts.map +0 -1
- package/dist/nonce-dedup.js +0 -205
- package/dist/nonce-dedup.js.map +0 -1
- package/dist/notification-dispatcher.d.ts +0 -92
- package/dist/notification-dispatcher.d.ts.map +0 -1
- package/dist/notification-dispatcher.js +0 -210
- package/dist/notification-dispatcher.js.map +0 -1
- package/dist/notification-handlers.d.ts +0 -53
- package/dist/notification-handlers.d.ts.map +0 -1
- package/dist/notification-handlers.js +0 -461
- package/dist/notification-handlers.js.map +0 -1
- package/dist/onboarding-guidance.d.ts +0 -79
- package/dist/onboarding-guidance.d.ts.map +0 -1
- package/dist/onboarding-guidance.js +0 -95
- package/dist/onboarding-guidance.js.map +0 -1
- package/dist/operator-guidance.d.ts +0 -25
- package/dist/operator-guidance.d.ts.map +0 -1
- package/dist/operator-guidance.js +0 -50
- package/dist/operator-guidance.js.map +0 -1
- package/dist/orphan-triage.d.ts +0 -130
- package/dist/orphan-triage.d.ts.map +0 -1
- package/dist/orphan-triage.js +0 -207
- package/dist/orphan-triage.js.map +0 -1
- package/dist/outbound-sessions.d.ts +0 -126
- package/dist/outbound-sessions.d.ts.map +0 -1
- package/dist/outbound-sessions.js +0 -1052
- package/dist/outbound-sessions.js.map +0 -1
- package/dist/park-envelope.d.ts +0 -329
- package/dist/park-envelope.d.ts.map +0 -1
- package/dist/park-envelope.js +0 -509
- package/dist/park-envelope.js.map +0 -1
- package/dist/park-recovery.d.ts +0 -273
- package/dist/park-recovery.d.ts.map +0 -1
- package/dist/park-recovery.js +0 -717
- package/dist/park-recovery.js.map +0 -1
- package/dist/park-refusals.d.ts +0 -147
- package/dist/park-refusals.d.ts.map +0 -1
- package/dist/park-refusals.js +0 -331
- package/dist/park-refusals.js.map +0 -1
- package/dist/quarantine-framing.d.ts +0 -92
- package/dist/quarantine-framing.d.ts.map +0 -1
- package/dist/quarantine-framing.js +0 -111
- package/dist/quarantine-framing.js.map +0 -1
- package/dist/reconnect-drain.d.ts +0 -22
- package/dist/reconnect-drain.d.ts.map +0 -1
- package/dist/reconnect-drain.js +0 -65
- package/dist/reconnect-drain.js.map +0 -1
- package/dist/recovered-position.d.ts +0 -14
- package/dist/recovered-position.d.ts.map +0 -1
- package/dist/recovered-position.js +0 -59
- package/dist/recovered-position.js.map +0 -1
- package/dist/refusal-notices.d.ts +0 -196
- package/dist/refusal-notices.d.ts.map +0 -1
- package/dist/refusal-notices.js +0 -516
- package/dist/refusal-notices.js.map +0 -1
- package/dist/refusal-reasons.d.ts +0 -245
- package/dist/refusal-reasons.d.ts.map +0 -1
- package/dist/refusal-reasons.js +0 -363
- package/dist/refusal-reasons.js.map +0 -1
- package/dist/register-handler.d.ts +0 -36
- package/dist/register-handler.d.ts.map +0 -1
- package/dist/register-handler.js +0 -285
- package/dist/register-handler.js.map +0 -1
- package/dist/registration-context.d.ts +0 -72
- package/dist/registration-context.d.ts.map +0 -1
- package/dist/registration-context.js +0 -126
- package/dist/registration-context.js.map +0 -1
- package/dist/registration-manager.d.ts +0 -94
- package/dist/registration-manager.d.ts.map +0 -1
- package/dist/registration-manager.js +0 -585
- package/dist/registration-manager.js.map +0 -1
- package/dist/registration-persistence.d.ts +0 -183
- package/dist/registration-persistence.d.ts.map +0 -1
- package/dist/registration-persistence.js +0 -263
- package/dist/registration-persistence.js.map +0 -1
- package/dist/registry-poll.d.ts +0 -52
- package/dist/registry-poll.d.ts.map +0 -1
- package/dist/registry-poll.js +0 -140
- package/dist/registry-poll.js.map +0 -1
- package/dist/registry-version-store-db.d.ts +0 -22
- package/dist/registry-version-store-db.d.ts.map +0 -1
- package/dist/registry-version-store-db.js +0 -50
- package/dist/registry-version-store-db.js.map +0 -1
- package/dist/relay-endpoints.d.ts +0 -18
- package/dist/relay-endpoints.d.ts.map +0 -1
- package/dist/relay-endpoints.js +0 -9
- package/dist/relay-endpoints.js.map +0 -1
- package/dist/relay-only.d.ts +0 -140
- package/dist/relay-only.d.ts.map +0 -1
- package/dist/relay-only.js +0 -193
- package/dist/relay-only.js.map +0 -1
- package/dist/relay-receipt-store.d.ts +0 -155
- package/dist/relay-receipt-store.d.ts.map +0 -1
- package/dist/relay-receipt-store.js +0 -284
- package/dist/relay-receipt-store.js.map +0 -1
- package/dist/relay-reconnect.d.ts +0 -32
- package/dist/relay-reconnect.d.ts.map +0 -1
- package/dist/relay-reconnect.js +0 -29
- package/dist/relay-reconnect.js.map +0 -1
- package/dist/reply-lag.d.ts +0 -11
- package/dist/reply-lag.d.ts.map +0 -1
- package/dist/reply-lag.js +0 -46
- package/dist/reply-lag.js.map +0 -1
- package/dist/resolve-named-agent.d.ts +0 -49
- package/dist/resolve-named-agent.d.ts.map +0 -1
- package/dist/resolve-named-agent.js +0 -77
- package/dist/resolve-named-agent.js.map +0 -1
- package/dist/restart-seal-resolver.d.ts +0 -110
- package/dist/restart-seal-resolver.d.ts.map +0 -1
- package/dist/restart-seal-resolver.js +0 -353
- package/dist/restart-seal-resolver.js.map +0 -1
- package/dist/resume-last-seen.d.ts +0 -15
- package/dist/resume-last-seen.d.ts.map +0 -1
- package/dist/resume-last-seen.js +0 -44
- package/dist/resume-last-seen.js.map +0 -1
- package/dist/retry-queue.d.ts +0 -203
- package/dist/retry-queue.d.ts.map +0 -1
- package/dist/retry-queue.js +0 -701
- package/dist/retry-queue.js.map +0 -1
- package/dist/roster-freshness.d.ts +0 -160
- package/dist/roster-freshness.d.ts.map +0 -1
- package/dist/roster-freshness.js +0 -250
- package/dist/roster-freshness.js.map +0 -1
- package/dist/screening-status.d.ts +0 -15
- package/dist/screening-status.d.ts.map +0 -1
- package/dist/screening-status.js +0 -65
- package/dist/screening-status.js.map +0 -1
- package/dist/seal-carried-close.d.ts +0 -37
- package/dist/seal-carried-close.d.ts.map +0 -1
- package/dist/seal-carried-close.js +0 -179
- package/dist/seal-carried-close.js.map +0 -1
- package/dist/seal-certificate-pull.d.ts +0 -79
- package/dist/seal-certificate-pull.d.ts.map +0 -1
- package/dist/seal-certificate-pull.js +0 -186
- package/dist/seal-certificate-pull.js.map +0 -1
- package/dist/seal-certified-root-check.d.ts +0 -37
- package/dist/seal-certified-root-check.d.ts.map +0 -1
- package/dist/seal-certified-root-check.js +0 -127
- package/dist/seal-certified-root-check.js.map +0 -1
- package/dist/seal-coordinator.d.ts +0 -118
- package/dist/seal-coordinator.d.ts.map +0 -1
- package/dist/seal-coordinator.js +0 -927
- package/dist/seal-coordinator.js.map +0 -1
- package/dist/seal-escalation.d.ts +0 -80
- package/dist/seal-escalation.d.ts.map +0 -1
- package/dist/seal-escalation.js +0 -283
- package/dist/seal-escalation.js.map +0 -1
- package/dist/seal-evidence-root-check.d.ts +0 -70
- package/dist/seal-evidence-root-check.d.ts.map +0 -1
- package/dist/seal-evidence-root-check.js +0 -199
- package/dist/seal-evidence-root-check.js.map +0 -1
- package/dist/seal-failure-store.d.ts +0 -129
- package/dist/seal-failure-store.d.ts.map +0 -1
- package/dist/seal-failure-store.js +0 -173
- package/dist/seal-failure-store.js.map +0 -1
- package/dist/seal-flows.d.ts +0 -107
- package/dist/seal-flows.d.ts.map +0 -1
- package/dist/seal-flows.js +0 -638
- package/dist/seal-flows.js.map +0 -1
- package/dist/seal-frontier-verify.d.ts +0 -103
- package/dist/seal-frontier-verify.d.ts.map +0 -1
- package/dist/seal-frontier-verify.js +0 -143
- package/dist/seal-frontier-verify.js.map +0 -1
- package/dist/seal-leaf.d.ts +0 -58
- package/dist/seal-leaf.d.ts.map +0 -1
- package/dist/seal-leaf.js +0 -112
- package/dist/seal-leaf.js.map +0 -1
- package/dist/seal-legibility-tbs.d.ts +0 -25
- package/dist/seal-legibility-tbs.d.ts.map +0 -1
- package/dist/seal-legibility-tbs.js +0 -77
- package/dist/seal-legibility-tbs.js.map +0 -1
- package/dist/seal-local-terminus.d.ts +0 -41
- package/dist/seal-local-terminus.d.ts.map +0 -1
- package/dist/seal-local-terminus.js +0 -170
- package/dist/seal-local-terminus.js.map +0 -1
- package/dist/seal-receipt-upgrade.d.ts +0 -30
- package/dist/seal-receipt-upgrade.d.ts.map +0 -1
- package/dist/seal-receipt-upgrade.js +0 -49
- package/dist/seal-receipt-upgrade.js.map +0 -1
- package/dist/seal-relay-silence.d.ts +0 -51
- package/dist/seal-relay-silence.d.ts.map +0 -1
- package/dist/seal-relay-silence.js +0 -76
- package/dist/seal-relay-silence.js.map +0 -1
- package/dist/seal-settle.d.ts +0 -38
- package/dist/seal-settle.d.ts.map +0 -1
- package/dist/seal-settle.js +0 -33
- package/dist/seal-settle.js.map +0 -1
- package/dist/seal-upgrade.d.ts +0 -107
- package/dist/seal-upgrade.d.ts.map +0 -1
- package/dist/seal-upgrade.js +0 -200
- package/dist/seal-upgrade.js.map +0 -1
- package/dist/sealed-conversation.d.ts +0 -36
- package/dist/sealed-conversation.d.ts.map +0 -1
- package/dist/sealed-conversation.js +0 -171
- package/dist/sealed-conversation.js.map +0 -1
- package/dist/sealed-leaf-set.d.ts +0 -92
- package/dist/sealed-leaf-set.d.ts.map +0 -1
- package/dist/sealed-leaf-set.js +0 -122
- package/dist/sealed-leaf-set.js.map +0 -1
- package/dist/send-claims.d.ts +0 -56
- package/dist/send-claims.d.ts.map +0 -1
- package/dist/send-claims.js +0 -50
- package/dist/send-claims.js.map +0 -1
- package/dist/session-assignment-parser.d.ts +0 -98
- package/dist/session-assignment-parser.d.ts.map +0 -1
- package/dist/session-assignment-parser.js +0 -325
- package/dist/session-assignment-parser.js.map +0 -1
- package/dist/session-category.d.ts +0 -19
- package/dist/session-category.d.ts.map +0 -1
- package/dist/session-category.js +0 -14
- package/dist/session-category.js.map +0 -1
- package/dist/session-ceremony.d.ts +0 -297
- package/dist/session-ceremony.d.ts.map +0 -1
- package/dist/session-ceremony.js +0 -964
- package/dist/session-ceremony.js.map +0 -1
- package/dist/session-closed.d.ts +0 -79
- package/dist/session-closed.d.ts.map +0 -1
- package/dist/session-closed.js +0 -189
- package/dist/session-closed.js.map +0 -1
- package/dist/session-connection-gater.d.ts +0 -148
- package/dist/session-connection-gater.d.ts.map +0 -1
- package/dist/session-connection-gater.js +0 -332
- package/dist/session-connection-gater.js.map +0 -1
- package/dist/session-content-context.d.ts +0 -162
- package/dist/session-content-context.d.ts.map +0 -1
- package/dist/session-content-context.js +0 -2
- package/dist/session-content-context.js.map +0 -1
- package/dist/session-content-handlers.d.ts +0 -82
- package/dist/session-content-handlers.d.ts.map +0 -1
- package/dist/session-content-handlers.js +0 -1382
- package/dist/session-content-handlers.js.map +0 -1
- package/dist/session-content-ingest.d.ts +0 -206
- package/dist/session-content-ingest.d.ts.map +0 -1
- package/dist/session-content-ingest.js +0 -2167
- package/dist/session-content-ingest.js.map +0 -1
- package/dist/session-content-send.d.ts +0 -191
- package/dist/session-content-send.d.ts.map +0 -1
- package/dist/session-content-send.js +0 -1360
- package/dist/session-content-send.js.map +0 -1
- package/dist/session-delivery-acks.d.ts +0 -169
- package/dist/session-delivery-acks.d.ts.map +0 -1
- package/dist/session-delivery-acks.js +0 -569
- package/dist/session-delivery-acks.js.map +0 -1
- package/dist/session-ephemerals.d.ts +0 -279
- package/dist/session-ephemerals.d.ts.map +0 -1
- package/dist/session-ephemerals.js +0 -591
- package/dist/session-ephemerals.js.map +0 -1
- package/dist/session-leaf-records.d.ts +0 -159
- package/dist/session-leaf-records.d.ts.map +0 -1
- package/dist/session-leaf-records.js +0 -408
- package/dist/session-leaf-records.js.map +0 -1
- package/dist/session-lifecycle.d.ts +0 -303
- package/dist/session-lifecycle.d.ts.map +0 -1
- package/dist/session-lifecycle.js +0 -1679
- package/dist/session-lifecycle.js.map +0 -1
- package/dist/session-liveness.d.ts +0 -135
- package/dist/session-liveness.d.ts.map +0 -1
- package/dist/session-liveness.js +0 -347
- package/dist/session-liveness.js.map +0 -1
- package/dist/session-name.d.ts +0 -35
- package/dist/session-name.d.ts.map +0 -1
- package/dist/session-name.js +0 -60
- package/dist/session-name.js.map +0 -1
- package/dist/session-node-factory.d.ts +0 -18
- package/dist/session-node-factory.d.ts.map +0 -1
- package/dist/session-node-factory.js +0 -182
- package/dist/session-node-factory.js.map +0 -1
- package/dist/session-node-manager.d.ts +0 -875
- package/dist/session-node-manager.d.ts.map +0 -1
- package/dist/session-node-manager.js +0 -2995
- package/dist/session-node-manager.js.map +0 -1
- package/dist/session-node-types.d.ts +0 -1057
- package/dist/session-node-types.d.ts.map +0 -1
- package/dist/session-node-types.js +0 -657
- package/dist/session-node-types.js.map +0 -1
- package/dist/session-notify.d.ts +0 -46
- package/dist/session-notify.d.ts.map +0 -1
- package/dist/session-notify.js +0 -116
- package/dist/session-notify.js.map +0 -1
- package/dist/session-own-chain-store.d.ts +0 -65
- package/dist/session-own-chain-store.d.ts.map +0 -1
- package/dist/session-own-chain-store.js +0 -75
- package/dist/session-own-chain-store.js.map +0 -1
- package/dist/session-queries.d.ts +0 -476
- package/dist/session-queries.d.ts.map +0 -1
- package/dist/session-queries.js +0 -1007
- package/dist/session-queries.js.map +0 -1
- package/dist/session-read-handlers.d.ts +0 -87
- package/dist/session-read-handlers.d.ts.map +0 -1
- package/dist/session-read-handlers.js +0 -682
- package/dist/session-read-handlers.js.map +0 -1
- package/dist/session-records.d.ts +0 -341
- package/dist/session-records.d.ts.map +0 -1
- package/dist/session-records.js +0 -858
- package/dist/session-records.js.map +0 -1
- package/dist/session-relay-client.d.ts +0 -659
- package/dist/session-relay-client.d.ts.map +0 -1
- package/dist/session-relay-client.js +0 -2876
- package/dist/session-relay-client.js.map +0 -1
- package/dist/session-relay.d.ts +0 -397
- package/dist/session-relay.d.ts.map +0 -1
- package/dist/session-relay.js +0 -1636
- package/dist/session-relay.js.map +0 -1
- package/dist/session-salt-agreement.d.ts +0 -331
- package/dist/session-salt-agreement.d.ts.map +0 -1
- package/dist/session-salt-agreement.js +0 -472
- package/dist/session-salt-agreement.js.map +0 -1
- package/dist/session-salts.d.ts +0 -432
- package/dist/session-salts.d.ts.map +0 -1
- package/dist/session-salts.js +0 -1540
- package/dist/session-salts.js.map +0 -1
- package/dist/session-schema.d.ts +0 -30
- package/dist/session-schema.d.ts.map +0 -1
- package/dist/session-schema.js +0 -877
- package/dist/session-schema.js.map +0 -1
- package/dist/session-seal-leaf-store.d.ts +0 -70
- package/dist/session-seal-leaf-store.d.ts.map +0 -1
- package/dist/session-seal-leaf-store.js +0 -105
- package/dist/session-seal-leaf-store.js.map +0 -1
- package/dist/session-seal.d.ts +0 -334
- package/dist/session-seal.d.ts.map +0 -1
- package/dist/session-seal.js +0 -1017
- package/dist/session-seal.js.map +0 -1
- package/dist/session-terminal-refusal.d.ts +0 -65
- package/dist/session-terminal-refusal.d.ts.map +0 -1
- package/dist/session-terminal-refusal.js +0 -87
- package/dist/session-terminal-refusal.js.map +0 -1
- package/dist/session-tree.d.ts +0 -110
- package/dist/session-tree.d.ts.map +0 -1
- package/dist/session-tree.js +0 -144
- package/dist/session-tree.js.map +0 -1
- package/dist/session-views.d.ts +0 -47
- package/dist/session-views.d.ts.map +0 -1
- package/dist/session-views.js +0 -278
- package/dist/session-views.js.map +0 -1
- package/dist/signal-handlers.d.ts +0 -63
- package/dist/signal-handlers.d.ts.map +0 -1
- package/dist/signal-handlers.js +0 -980
- package/dist/signal-handlers.js.map +0 -1
- package/dist/signal-requirement-policy.d.ts +0 -51
- package/dist/signal-requirement-policy.d.ts.map +0 -1
- package/dist/signal-requirement-policy.js +0 -89
- package/dist/signal-requirement-policy.js.map +0 -1
- package/dist/signal-revocability.d.ts +0 -49
- package/dist/signal-revocability.d.ts.map +0 -1
- package/dist/signal-revocability.js +0 -93
- package/dist/signal-revocability.js.map +0 -1
- package/dist/signal-submission.d.ts +0 -181
- package/dist/signal-submission.d.ts.map +0 -1
- package/dist/signal-submission.js +0 -368
- package/dist/signal-submission.js.map +0 -1
- package/dist/signaling-connect.d.ts +0 -118
- package/dist/signaling-connect.d.ts.map +0 -1
- package/dist/signaling-connect.js +0 -538
- package/dist/signaling-connect.js.map +0 -1
- package/dist/signaling-wiring.d.ts +0 -105
- package/dist/signaling-wiring.d.ts.map +0 -1
- package/dist/signaling-wiring.js +0 -393
- package/dist/signaling-wiring.js.map +0 -1
- package/dist/singleton-lock.d.ts +0 -85
- package/dist/singleton-lock.d.ts.map +0 -1
- package/dist/singleton-lock.js +0 -219
- package/dist/singleton-lock.js.map +0 -1
- package/dist/sqlcipher-db.d.ts +0 -139
- package/dist/sqlcipher-db.d.ts.map +0 -1
- package/dist/sqlcipher-db.js +0 -357
- package/dist/sqlcipher-db.js.map +0 -1
- package/dist/standing-receivers.d.ts +0 -319
- package/dist/standing-receivers.d.ts.map +0 -1
- package/dist/standing-receivers.js +0 -1295
- package/dist/standing-receivers.js.map +0 -1
- package/dist/start-agent.d.ts +0 -64
- package/dist/start-agent.d.ts.map +0 -1
- package/dist/start-agent.js +0 -136
- package/dist/start-agent.js.map +0 -1
- package/dist/status-handler.d.ts +0 -54
- package/dist/status-handler.d.ts.map +0 -1
- package/dist/status-handler.js +0 -61
- package/dist/status-handler.js.map +0 -1
- package/dist/submission-retry.d.ts +0 -208
- package/dist/submission-retry.d.ts.map +0 -1
- package/dist/submission-retry.js +0 -506
- package/dist/submission-retry.js.map +0 -1
- package/dist/telegram-bot-client.d.ts +0 -34
- package/dist/telegram-bot-client.d.ts.map +0 -1
- package/dist/telegram-bot-client.js +0 -36
- package/dist/telegram-bot-client.js.map +0 -1
- package/dist/telegram-doorbell.d.ts +0 -38
- package/dist/telegram-doorbell.d.ts.map +0 -1
- package/dist/telegram-doorbell.js +0 -140
- package/dist/telegram-doorbell.js.map +0 -1
- package/dist/test-handlers.d.ts +0 -62
- package/dist/test-handlers.d.ts.map +0 -1
- package/dist/test-handlers.js +0 -240
- package/dist/test-handlers.js.map +0 -1
- package/dist/testing.d.ts +0 -10
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -10
- package/dist/testing.js.map +0 -1
- package/dist/transport-composition.d.ts +0 -31
- package/dist/transport-composition.d.ts.map +0 -1
- package/dist/transport-composition.js +0 -55
- package/dist/transport-composition.js.map +0 -1
- package/dist/transport-selector.d.ts +0 -202
- package/dist/transport-selector.d.ts.map +0 -1
- package/dist/transport-selector.js +0 -196
- package/dist/transport-selector.js.map +0 -1
- package/dist/trust-signal-pickup-listener.d.ts +0 -43
- package/dist/trust-signal-pickup-listener.d.ts.map +0 -1
- package/dist/trust-signal-pickup-listener.js +0 -48
- package/dist/trust-signal-pickup-listener.js.map +0 -1
- package/dist/trust-signal-store.d.ts +0 -406
- package/dist/trust-signal-store.d.ts.map +0 -1
- package/dist/trust-signal-store.js +0 -939
- package/dist/trust-signal-store.js.map +0 -1
- package/dist/trust-signal-sweep-tick.d.ts +0 -60
- package/dist/trust-signal-sweep-tick.d.ts.map +0 -1
- package/dist/trust-signal-sweep-tick.js +0 -101
- package/dist/trust-signal-sweep-tick.js.map +0 -1
- package/dist/trust-signal-sweep.d.ts +0 -94
- package/dist/trust-signal-sweep.d.ts.map +0 -1
- package/dist/trust-signal-sweep.js +0 -147
- package/dist/trust-signal-sweep.js.map +0 -1
- package/dist/type-registry.d.ts +0 -42
- package/dist/type-registry.d.ts.map +0 -1
- package/dist/type-registry.js +0 -37
- package/dist/type-registry.js.map +0 -1
- package/dist/types.d.ts +0 -746
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -20
- package/dist/types.js.map +0 -1
- package/dist/unresolved-nodes-report.d.ts +0 -14
- package/dist/unresolved-nodes-report.d.ts.map +0 -1
- package/dist/unresolved-nodes-report.js +0 -96
- package/dist/unresolved-nodes-report.js.map +0 -1
- package/dist/vocabulary.d.ts +0 -150
- package/dist/vocabulary.d.ts.map +0 -1
- package/dist/vocabulary.js +0 -386
- package/dist/vocabulary.js.map +0 -1
- package/dist/who-label.d.ts +0 -28
- package/dist/who-label.d.ts.map +0 -1
- package/dist/who-label.js +0 -31
- package/dist/who-label.js.map +0 -1
- package/dist/who-resolver.d.ts +0 -15
- package/dist/who-resolver.d.ts.map +0 -1
- package/dist/who-resolver.js +0 -47
- package/dist/who-resolver.js.map +0 -1
- package/dist/wire-content-hash.d.ts +0 -93
- package/dist/wire-content-hash.d.ts.map +0 -1
- package/dist/wire-content-hash.js +0 -116
- package/dist/wire-content-hash.js.map +0 -1
- package/dist/withheld-content.d.ts +0 -13
- package/dist/withheld-content.d.ts.map +0 -1
- package/dist/withheld-content.js +0 -46
- package/dist/withheld-content.js.map +0 -1
- package/dist/witness-alerts.d.ts +0 -40
- package/dist/witness-alerts.d.ts.map +0 -1
- package/dist/witness-alerts.js +0 -102
- package/dist/witness-alerts.js.map +0 -1
|
@@ -1,1382 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cello_send and cello_receive — the content path.
|
|
3
|
-
*
|
|
4
|
-
* The two biggest handlers in the daemon, and the two I had put on a DO-NOT-CUT list. That list was
|
|
5
|
-
* written before anything had been measured, and it was wrong: these close over eleven things, not
|
|
6
|
-
* the whole daemon. They are listed below, which is the point of the move.
|
|
7
|
-
*
|
|
8
|
-
* They stay TOGETHER because they are two halves of one state machine — the read cursor. cello_send
|
|
9
|
-
* appends to the daemon-owned tree; cello_receive advances the per-connection cursor and the durable
|
|
10
|
-
* watermark. Splitting them would put the cursor's writer and its reader in different modules.
|
|
11
|
-
*/
|
|
12
|
-
// `wireContentHash` is no longer imported here: every outbound hash in this file now comes from
|
|
13
|
-
// `SessionNodeManager.contentHashForSession`, which returns the hash and its ALGORITHM together
|
|
14
|
-
// (`DOD-M15-SEALWIRE-1` part B2b). A direct call would be a hash computed without deciding — or
|
|
15
|
-
// recording — how it was made, which is the state that made a version skew look like a tamper.
|
|
16
|
-
import { randomUUID } from "node:crypto";
|
|
17
|
-
import { LEAF_KIND_MSG } from "./session-relay-client.js";
|
|
18
|
-
import { MAX_CONTENT_BYTES } from "@cello-protocol/protocol-types";
|
|
19
|
-
import { TIER } from "./contacts-tier-migration.js";
|
|
20
|
-
import { GATEWAY_UNAVAILABLE, GOVERNANCE_TIMEOUT } from "@cello-protocol/gateway";
|
|
21
|
-
import { isAutoReplyMarked } from "./away-detection.js";
|
|
22
|
-
import { REFUSAL_COUNT_GUIDANCE, REFUSAL_KIND_GUIDANCE } from "./refusal-reasons.js";
|
|
23
|
-
import { extractErrorMessage } from "./error-message.js";
|
|
24
|
-
import { SESSION_CLOSED_GUIDANCE, SESSION_CLOSED_REASON, SESSION_SEALING_IMPACT, closedSessionImpact, isClosedStatus } from "./session-closed.js";
|
|
25
|
-
import { replyLag } from "./reply-lag.js";
|
|
26
|
-
/** Whether the other side's latest replies were written before they saw this side's newest message. */
|
|
27
|
-
function replyLagFor(snm, agentName, sessionId) {
|
|
28
|
-
// Runs AFTER the read position moved, so a throw here would lose the messages being handed over.
|
|
29
|
-
// The warning is optional; the messages are not.
|
|
30
|
-
try {
|
|
31
|
-
const db = snm.getDb();
|
|
32
|
-
const row = db.prepare("SELECT k_local_pubkey FROM agents WHERE agent_id = ?").get(snm.resolveAgentId(agentName));
|
|
33
|
-
return row ? replyLag(db, row.k_local_pubkey, sessionId) : undefined;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* DOD-M12B-AWAY-MARK-1 — what a reader needs to know the moment it sees a marked message.
|
|
41
|
-
*
|
|
42
|
-
* Written for the failure it prevents, not the mechanism: an agent reads an away reply, concludes a
|
|
43
|
-
* person answered, and tells its operator a conversation is under way. Two agents did exactly that
|
|
44
|
-
* to each other for a morning on 2026-08-17. So it leads with "nobody read your message", and it
|
|
45
|
-
* says what the reader should do instead of replying into an empty room.
|
|
46
|
-
*/
|
|
47
|
-
/**
|
|
48
|
-
* DOD-M15-REFUSED-INBOUND-SILENT-1 — spread the operator's unshown refusals into a reply, or
|
|
49
|
-
* nothing at all.
|
|
50
|
-
*
|
|
51
|
-
* A spread rather than a field so the key is ABSENT when there is nothing to say. An always-present
|
|
52
|
-
* `refusals: []` is a field readers learn to skip, and this is precisely the field that explains
|
|
53
|
-
* why a conversation went quiet — it has to be unusual to be read.
|
|
54
|
-
*
|
|
55
|
-
* Draining is the point: the operator is told once per reason per session. The count travels with
|
|
56
|
-
* it, so "this happened 90 times" is visible without announcing it 90 times.
|
|
57
|
-
*/
|
|
58
|
-
/**
|
|
59
|
-
* ⚠️ RETURNS THE ADVICE WITH THE NOTICE, and that pairing is the fix — not a convenience.
|
|
60
|
-
*
|
|
61
|
-
* The drain is DESTRUCTIVE: whoever reads first consumes the notice. When only the quiet exit
|
|
62
|
-
* carried the "waiting will not help" sentence, a catch-up read (`--since-seq -1`, the stateless
|
|
63
|
-
* door the refusals elsewhere point people at) drained the refusal into a payload with no advice at
|
|
64
|
-
* all — and the operator's NEXT blocking read then said "call again and keep waiting", because the
|
|
65
|
-
* notice was already gone. The catch-up door destroyed the one sentence that made it actionable and
|
|
66
|
-
* replaced it with the advice this whole line exists to stop.
|
|
67
|
-
*
|
|
68
|
-
* Returning them together means no call site can take one without the other. A future exit that
|
|
69
|
-
* spreads this gets the advice for free; that is the property, and it is why the guidance does not
|
|
70
|
-
* live at the call sites any more.
|
|
71
|
-
*/
|
|
72
|
-
function refusalsField(mgr, agentName, sessionId,
|
|
73
|
-
/**
|
|
74
|
-
* WHO is being shown this. Without it the first window to read consumed the notice for every
|
|
75
|
-
* other window attending the same agent — the defect the delivery loop below was rewritten to
|
|
76
|
-
* remove, re-created on a different surface.
|
|
77
|
-
*/
|
|
78
|
-
consumerId) {
|
|
79
|
-
const refusals = mgr.takeContentRefusals(agentName, sessionId, consumerId);
|
|
80
|
-
if (refusals.length === 0)
|
|
81
|
-
return {};
|
|
82
|
-
/**
|
|
83
|
-
* COMPOSED FROM THE KINDS PRESENT — review F4, and the same rule the inbox door follows.
|
|
84
|
-
*
|
|
85
|
-
* One fixed sentence said "received and REFUSED — not verified, neither ingested nor shown". True
|
|
86
|
-
* of the three hash-verification reasons it was written for; false of a screener block, which was
|
|
87
|
-
* verified, IS in the chain, and WAS acknowledged; false of a transcript write failure; and
|
|
88
|
-
* pointing at the wrong party entirely for `delivery_impaired`, which is this side's own send.
|
|
89
|
-
*/
|
|
90
|
-
const kindsPresent = new Set(refusals.map((r) => r.kind));
|
|
91
|
-
const refusal_guidance = Object.keys(REFUSAL_KIND_GUIDANCE)
|
|
92
|
-
.filter((k) => kindsPresent.has(k))
|
|
93
|
-
// Prefixed with the kind (review N7) so two paragraphs that read as contradicting each other —
|
|
94
|
-
// "received and REFUSED" beside "NOTHING WAS REFUSED BY THIS AGENT" — can each be joined to the
|
|
95
|
-
// rows they are about.
|
|
96
|
-
.map((k) => `[kind: ${k}] ${REFUSAL_KIND_GUIDANCE[k]}`)
|
|
97
|
-
.join("\n\n");
|
|
98
|
-
return {
|
|
99
|
-
refusals: refusals.map((r) => ({
|
|
100
|
-
reason: r.reason, kind: r.kind, impact: r.impact, guidance: r.guidance,
|
|
101
|
-
times_since_dismissed: r.timesSinceDismissed,
|
|
102
|
-
...(r.timesTotal === undefined ? {} : { times_total: r.timesTotal }),
|
|
103
|
-
...(r.timesTotalAtLeast === undefined ? {} : { times_total_at_least: r.timesTotalAtLeast }),
|
|
104
|
-
// `041-PARKSTUCK`: the cadence beside the count, so a loop is not read as a crowd.
|
|
105
|
-
...(r.recurrence === undefined ? {} : { recurrence: r.recurrence }),
|
|
106
|
-
...(r.repeat === true ? { repeat: true } : {}),
|
|
107
|
-
})),
|
|
108
|
-
// DOD-M15-REFUSALTERMINAL-1 review F4: the counts need their sentence at BOTH doors. Without
|
|
109
|
-
// it this one hands an agent the small number with nothing saying it is not the lifetime one —
|
|
110
|
-
// which is the whole defect, moved rather than fixed.
|
|
111
|
-
refusal_guidance: `${refusal_guidance}\n\n${REFUSAL_COUNT_GUIDANCE}`,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const AUTO_REPLY_GUIDANCE = "At least one message here is an AUTOMATIC reply generated by the counterparty's daemon because " +
|
|
115
|
-
"nobody was attending that agent — no person and no agent read what you sent. Do not treat it as " +
|
|
116
|
-
"an answer, do not report a conversation is happening, and do not infer anything from its " +
|
|
117
|
-
"content. Your message is queued and will be read when their operator returns.\n\n" +
|
|
118
|
-
"THE MARK IS A ONE-WAY SIGNAL. `auto_reply: true` means machine-generated. Its ABSENCE means " +
|
|
119
|
-
"nothing — the mark is produced by the SENDER's daemon, so an older build, a third-party client, " +
|
|
120
|
-
"or anyone who simply omits it sends automatic replies with no mark at all. An unmarked message " +
|
|
121
|
-
"is NOT evidence a person wrote it. Never tell the operator a human replied because a message " +
|
|
122
|
-
"was unmarked.";
|
|
123
|
-
/**
|
|
124
|
-
* DOD-M15-SEALWIRE-1 bullet 5, SENT half — the proof for a message THIS agent sent.
|
|
125
|
-
*
|
|
126
|
-
* Present only when the relay WITNESSED the leaf: an unwitnessed send never put a Structure-1 on the
|
|
127
|
-
* wire, so there is nothing signed to store, and the row must record `self_authored` with no proof
|
|
128
|
-
* rather than imply one. `sendContent` returns it per-send rather than stashing it by session,
|
|
129
|
-
* because a side map hands a caller the wrong send's signature the moment two are in flight.
|
|
130
|
-
*/
|
|
131
|
-
/**
|
|
132
|
-
* Did the RELAY witness this leaf — the single fact every `witnessed` in this file is read from.
|
|
133
|
-
*
|
|
134
|
-
* `016-RELAYLOSS` review HIGH-2. The first version of the fix put `witnessed` on two of `cello_send`'s
|
|
135
|
-
* FIVE return paths, which defeats the reason it was added at all: a field that is present on some
|
|
136
|
-
* outcomes and absent on others cannot be branched on, and its absence reads as an older daemon
|
|
137
|
-
* rather than as an answer. Worse, the two it missed are the two that matter most — the diverged
|
|
138
|
-
* path is reachable ONLY after an unwitnessed leaf, and the durably-queued path is the one
|
|
139
|
-
* production takes when the relay is also the transport (behind NAT, content and witness die
|
|
140
|
-
* together), where it was telling operators "No action needed."
|
|
141
|
-
*
|
|
142
|
-
* Read off the PLACEMENT, not re-derived from the sequence number, so this and the
|
|
143
|
-
* `session.tree.own_leaf_unwitnessed` ERROR beside it can never disagree about the same send.
|
|
144
|
-
*
|
|
145
|
-
* ⚠️ A HELD leaf (`placed: false`) is WITNESSED, and that is not a special case being papered over.
|
|
146
|
-
* A leaf is held precisely BECAUSE the relay assigned it a position beyond this side's tail — being
|
|
147
|
-
* held is itself proof of an assignment. Reporting `witnessed: false` there would tell an operator
|
|
148
|
-
* their record had lost a message when the opposite is true.
|
|
149
|
-
*/
|
|
150
|
-
export function wasWitnessed(p) {
|
|
151
|
-
return p.placed ? p.unwitnessed !== true : true;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* The durably-queued path's guidance: the relay's OWN words, plus what a missing witness costs.
|
|
155
|
-
*
|
|
156
|
-
* **Exported so the test can call THIS rather than re-implement it.** The first version of the
|
|
157
|
-
* composition test rebuilt the string inline and asserted on its own copy — which stays green if
|
|
158
|
-
* production stops composing entirely, and is therefore the hollow shape this milestone keeps
|
|
159
|
-
* finding. A rule worth pinning has to be pinned on the code that runs.
|
|
160
|
-
*
|
|
161
|
-
* The rule, and why it is a composition rather than a choice: `upstream` is where the relay's own
|
|
162
|
-
* retry window reaches the operator ("try again in about 45 seconds"), and a rate-limited park is
|
|
163
|
-
* unwitnessed BY CONSTRUCTION — so choosing between the two messages silently drops the more
|
|
164
|
-
* specific one exactly when it is most useful. Both are true at once and the operator needs both.
|
|
165
|
-
*
|
|
166
|
-
* The default splits on `witnessed` for one reason only: "No action needed" is false for a leaf the
|
|
167
|
-
* relay never recorded.
|
|
168
|
-
*/
|
|
169
|
-
export function queuedSendGuidance(upstream, witnessed) {
|
|
170
|
-
const base = upstream
|
|
171
|
-
?? (witnessed
|
|
172
|
-
? "The message is queued and will be re-sent automatically when the relay link is back. No action needed."
|
|
173
|
-
: "The message is queued and will be re-sent automatically when the relay link is back.");
|
|
174
|
-
if (witnessed)
|
|
175
|
-
return base;
|
|
176
|
-
return base
|
|
177
|
-
+ " The relay did NOT witness this leaf, so your record is now one leaf ahead of it. "
|
|
178
|
-
+ "Ordering does not repair itself: the next message the relay does witness will report this "
|
|
179
|
-
+ "session as diverged, and a diverged session can never be sealed with the counterparty. The "
|
|
180
|
-
+ "re-send is automatic — do not send it again yourself. If the receipt matters, close rather "
|
|
181
|
-
+ "than continuing.";
|
|
182
|
-
}
|
|
183
|
-
export function sentAuthorship(r) {
|
|
184
|
-
/**
|
|
185
|
-
* ⚠️ NOT `r.ok ? …` — that made two of the five call sites DEAD BY CONSTRUCTION.
|
|
186
|
-
*
|
|
187
|
-
* Both live inside `if (!sendResult.ok)`, so an `ok`-gated read is unconditionally `undefined`
|
|
188
|
-
* there: the durably-queued send — witnessed, signed, only the direct hand-off failed — wrote a
|
|
189
|
-
* row with no proof while the proof sat in the result. The union now carries `authorship` on the
|
|
190
|
-
* failure member too, so read it off whichever shape arrived.
|
|
191
|
-
*/
|
|
192
|
-
return r.authorship;
|
|
193
|
-
}
|
|
194
|
-
export function registerSessionContentHandlers(deps) {
|
|
195
|
-
const { handlers, logger, sessionNodeManager, securityGateway, retryQueue, getConnState, resolveCurrentAgent, NO_CURRENT_AGENT_RESPONSE, getConnectionCursor, advanceConnectionCursor, clearTelegramRung, attendanceCount, sendClaims, } = deps;
|
|
196
|
-
/**
|
|
197
|
-
* DOD-COATTEND-SENDWINDOW-1 (review F1) — sessions with a send ON THE WIRE right now.
|
|
198
|
-
*
|
|
199
|
-
* The frontier comparison alone was not enough, and the reasoning that produced it had a hole:
|
|
200
|
-
* it is TRUE that a send cannot be refused after `sendContent` (the counterparty already holds
|
|
201
|
-
* the message, and refusing would strand the session with disagreeing frontiers — the
|
|
202
|
-
* DOD-FRONTIER-STRAND-1 shape). It does NOT follow that checking only before it is sufficient.
|
|
203
|
-
* `sendContent` IS the last await, and the wider one: it awaits the relay hash submit, the
|
|
204
|
-
* stream open and the stream close — a network round trip in production, against the gateway
|
|
205
|
-
* round trip the frontier check covers. Reproduced by review on a real daemon with two real IPC
|
|
206
|
-
* connections: two `ok: true`, two frames on the wire, two leaves.
|
|
207
|
-
*
|
|
208
|
-
* So the window is closed by a CLAIM rather than a later check. A sibling that finds the claim
|
|
209
|
-
* held is refused BEFORE its own wire call — nothing is on the wire, nothing is stranded, and the
|
|
210
|
-
* strand argument that ruled out a post-wire refusal never applies.
|
|
211
|
-
*
|
|
212
|
-
* Keyed (agent, session): the race is two connections replying into ONE conversation. Held in the
|
|
213
|
-
* factory closure, which runs once per daemon, so it is exactly as long-lived as the handler set.
|
|
214
|
-
*/
|
|
215
|
-
/**
|
|
216
|
-
* DOD-COATTEND-VISIBLE-1 AC6 (review F2) — attendance as reported ON A RESPONSE.
|
|
217
|
-
*
|
|
218
|
-
* `attendanceCount` walks the connections that explicitly called `cello_use_agent`. A connection
|
|
219
|
-
* can reach these handlers WITHOUT having done so, through `resolveCurrentAgent`'s sole-online
|
|
220
|
-
* fallback — which is not exotic: it is every `cello` CLI invocation with no persisted selection,
|
|
221
|
-
* and any MCP client that skipped the call. Such a reader was not counted, INCLUDING ITSELF, and
|
|
222
|
-
* was handed `attendance: 0` — told that zero sessions attend the agent it is reading.
|
|
223
|
-
*
|
|
224
|
-
* That is worse than the missing field this AC set out to fix: not absent, but a definite
|
|
225
|
-
* negative that is false. The caller is holding the response, so at least one session is on this
|
|
226
|
-
* agent by construction. A response can never honestly say 0.
|
|
227
|
-
*/
|
|
228
|
-
const attendingNow = (agentName) => Math.max(1, attendanceCount(agentName));
|
|
229
|
-
// ─── CELLO-M7-DAEMON-004: cello_send (live send + daemon-owned tree append) ──
|
|
230
|
-
handlers.set("cello_send", async (params, connectionId) => {
|
|
231
|
-
const connState = getConnState(connectionId);
|
|
232
|
-
// CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > current > sole online agent.
|
|
233
|
-
const agentName = resolveCurrentAgent(connState, params?.agent);
|
|
234
|
-
if (!agentName)
|
|
235
|
-
return NO_CURRENT_AGENT_RESPONSE;
|
|
236
|
-
// round-2 BLOCKING: read the snake_case public field cello-mcp.ts actually sends.
|
|
237
|
-
const sessionId = params?.session_id;
|
|
238
|
-
const contentStr = typeof params?.content === "string" ? params.content : undefined;
|
|
239
|
-
if (!sessionId || contentStr === undefined) {
|
|
240
|
-
return { ok: false, reason: "missing_params", guidance: "Provide 'session_id' (hex) and 'content' (string) parameters." };
|
|
241
|
-
}
|
|
242
|
-
// DOD-LOOP-1: the (agent, session_id) lookup is itself the ownership scope.
|
|
243
|
-
const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
244
|
-
if (!record) {
|
|
245
|
-
return { ok: false, reason: "session_not_found", guidance: "No session found with this ID. Check cello_sessions for active sessions." };
|
|
246
|
-
}
|
|
247
|
-
if (record.agent_name !== agentName) {
|
|
248
|
-
return { ok: false, reason: "session_not_owned", guidance: "This session belongs to a different agent. Call cello_use_agent to switch to the agent that owns it, then retry." };
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* ─── A CLOSED CONVERSATION CANNOT BE ADDED TO — `DOD-M15-CLOSEDSESSION-1` ───────────────────
|
|
252
|
-
*
|
|
253
|
-
* BEFORE THE WIRE, BEFORE THE REVIVAL, BEFORE THE SCREENING. Nothing is placed, nothing reaches
|
|
254
|
-
* the relay, and nothing reports `delivered`.
|
|
255
|
-
*
|
|
256
|
-
* **Why it sits ahead of the revival rather than inside it.** `reviveIfNeededForSend` already
|
|
257
|
-
* refuses a sealed row — as `session_terminal`, a name about resurrection — but it is reached
|
|
258
|
-
* only through `record.status !== "active"`, and the case measured live had a row that still
|
|
259
|
-
* read `active`. Session `9d253bce…`: this side's own seal leaf was submitted and
|
|
260
|
-
* auto-acknowledged, the ceremony's status write had not happened yet, and the send sailed past
|
|
261
|
-
* every status check in the daemon. The relay had retired the session at the seal, so the hash
|
|
262
|
-
* submit failed `relay_session_gone`, the leaf was appended unwitnessed, and the operator was
|
|
263
|
-
* handed a paragraph about relay witnessing and ordering divergence — all true, none of it the
|
|
264
|
-
* point, and the counterparty refused the bytes as `ack_hash_unknown_content`.
|
|
265
|
-
*
|
|
266
|
-
* **Two facts, one refusal, and only local state is consulted.** The status row and this side's
|
|
267
|
-
* own seal commitment are both unambiguous and both ours. `relay_session_gone` is deliberately
|
|
268
|
-
* NOT one of them: `delivery-session-suspects.ts` refuses to make it terminal on evidence — the
|
|
269
|
-
* relay defaults to an in-memory store, so a restart tells every client the same string for
|
|
270
|
-
* sessions that are perfectly alive.
|
|
271
|
-
*/
|
|
272
|
-
if (isClosedStatus(record.status)) {
|
|
273
|
-
logger.info("session.send.refused_closed", {
|
|
274
|
-
sessionId, agentName, status: record.status, trigger: "status",
|
|
275
|
-
impact: closedSessionImpact(record.status),
|
|
276
|
-
});
|
|
277
|
-
return { ok: false, reason: SESSION_CLOSED_REASON, impact: closedSessionImpact(record.status), guidance: SESSION_CLOSED_GUIDANCE };
|
|
278
|
-
}
|
|
279
|
-
if (sessionNodeManager.hasCommittedSealLeaf(agentName, sessionId)) {
|
|
280
|
-
logger.info("session.send.refused_closed", {
|
|
281
|
-
sessionId, agentName, status: record.status, trigger: "seal_committed",
|
|
282
|
-
impact: SESSION_SEALING_IMPACT,
|
|
283
|
-
});
|
|
284
|
-
return { ok: false, reason: SESSION_CLOSED_REASON, impact: SESSION_SEALING_IMPACT, guidance: SESSION_CLOSED_GUIDANCE };
|
|
285
|
-
}
|
|
286
|
-
if (record.status !== "active") {
|
|
287
|
-
/**
|
|
288
|
-
* DOD-M12B-SESSION-SEED-1 — THE DEMAND EDGE. Before this, an interrupted session refused here
|
|
289
|
-
* forever: a transport event could take a session out of `active` and no code path anywhere
|
|
290
|
-
* put one back, so a laptop close ended the conversation permanently even though both
|
|
291
|
-
* processes were alive and both peer ids still valid.
|
|
292
|
-
*
|
|
293
|
-
* The operator sending IS the demand, and it is the only trigger — nothing revives on a timer
|
|
294
|
-
* (the REDIAL-1 discipline, and Andre's tenet: a background rebuilder would hold a dialable
|
|
295
|
-
* endpoint open for a session nobody is using).
|
|
296
|
-
*
|
|
297
|
-
* A session that cannot be revived still refuses, and says WHY — `session_terminal` for one
|
|
298
|
-
* that has ended, `session_identity_lost` for one whose keypair died with a daemon restart.
|
|
299
|
-
* The second is the one an operator most needs named: it means waiting for a reconnect is
|
|
300
|
-
* pointless and the session should be closed for its receipt.
|
|
301
|
-
*/
|
|
302
|
-
const revived = await sessionNodeManager.reviveIfNeededForSend(agentName, sessionId);
|
|
303
|
-
if (!revived.ok) {
|
|
304
|
-
return {
|
|
305
|
-
ok: false,
|
|
306
|
-
reason: revived.reason,
|
|
307
|
-
guidance: revived.guidance
|
|
308
|
-
?? `Session is '${record.status}', not active. Content can only be sent on an active session.`,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// M8C-CURSOR-1: read-before-write gate. current_seq is the tree's highest leaf index
|
|
313
|
-
// (message_count is kept in sync with leafCount on every append, both directions — DAEMON-004
|
|
314
|
-
// finding #2), so it reflects EVERY message in the session regardless of which connection sent
|
|
315
|
-
// or received it. If this connection hasn't read up to current_seq (e.g. a second attended
|
|
316
|
-
// session on the same agent that hasn't polled since the other connection's last send), refuse
|
|
317
|
-
// rather than let it send blind — the WhatsApp-group-chat model. Runs BEFORE the
|
|
318
|
-
// governance-decisions parsing below: an access-control gate must short-circuit before any
|
|
319
|
-
// unrelated prep work for a send that may not be allowed to proceed at all.
|
|
320
|
-
//
|
|
321
|
-
// The gate consults TWO authorities and passes if EITHER says the caller is caught up:
|
|
322
|
-
//
|
|
323
|
-
// 1. the connection cursor (in-memory, per-connection) — satisfied by a long-lived client
|
|
324
|
-
// like the MCP shim, which holds one socket for the whole session.
|
|
325
|
-
//
|
|
326
|
-
// 2. the persisted per-(agent, session) read watermark. A STATELESS client CANNOT satisfy
|
|
327
|
-
// authority 1: the `cello` CLI runs a fresh process, and therefore a fresh connection, per
|
|
328
|
-
// command, so it always presents cursor -1. Without the watermark, every CLI send is
|
|
329
|
-
// refused forever once the counterparty has spoken — as is every send from a RECONNECTING
|
|
330
|
-
// MCP client, whose new connectionId also resets the cursor to -1. Do not remove it.
|
|
331
|
-
//
|
|
332
|
-
// What the gate protects, precisely: an agent must never reply to COUNTERPARTY content nobody
|
|
333
|
-
// on its side has seen. That guarantee is durable — it survives the socket.
|
|
334
|
-
//
|
|
335
|
-
// What it deliberately does NOT protect: a message this agent SENT from a different local
|
|
336
|
-
// connection does not block. Two attended windows on one agent do not gate each other, because
|
|
337
|
-
// locally-authored content is not "unread counterparty content" — the AGENT wrote it. The
|
|
338
|
-
// principal is the agent, not the socket, and the daemon cannot referee which of an operator's
|
|
339
|
-
// own windows a human is looking at. This is deliberate; do not "fix" it.
|
|
340
|
-
const currentSeq = record.message_count - 1;
|
|
341
|
-
// DOD-COATTEND-SENDWINDOW-1: the frontier AS THIS SEND SAW IT. Re-read immediately before the
|
|
342
|
-
// wire, below — anything that moved it in between is a leaf this send was never checked
|
|
343
|
-
// against.
|
|
344
|
-
const frontierAtGate = sessionNodeManager.getSessionTree(agentName, sessionId).size();
|
|
345
|
-
const connectionCursor = getConnectionCursor(connectionId, sessionId);
|
|
346
|
-
const unreadReceived = sessionNodeManager.getUnreadReceivedCount(agentName, sessionId);
|
|
347
|
-
const caughtUp = connectionCursor >= currentSeq || unreadReceived === 0;
|
|
348
|
-
if (!caughtUp) {
|
|
349
|
-
// M8C-CURSOR-1 (reviewer MEDIUM fix): every sibling rejection in this handler logs; this
|
|
350
|
-
// gate must too — a security-relevant control-flow path with no observability is a gap.
|
|
351
|
-
// Both authorities are logged, so the next reader can tell WHICH one refused.
|
|
352
|
-
logger.warn("session.send.blocked", {
|
|
353
|
-
sessionId,
|
|
354
|
-
currentSeq,
|
|
355
|
-
lastReadSeq: connectionCursor,
|
|
356
|
-
unreadReceived,
|
|
357
|
-
connectionId,
|
|
358
|
-
agentName,
|
|
359
|
-
// AC2 asks the event to NAME the authority that refused (review F7). Reaching this branch
|
|
360
|
-
// means BOTH authorities said no — the gate passes if EITHER is satisfied — so there is no
|
|
361
|
-
// "binding one" to report and the ternary I first wrote here was dead: `connectionCursor <
|
|
362
|
-
// currentSeq` is always true at this point, so it could only ever print `unread_watermark`,
|
|
363
|
-
// and an operator filtering for `connection_cursor` would get zero hits forever and
|
|
364
|
-
// conclude cursor refusals do not exist. That is the defect F7 was raised to fix, rebuilt
|
|
365
|
-
// (review H2). The true statement is that both are false; `lastReadSeq` and
|
|
366
|
-
// `unreadReceived` above already carry which is how far off.
|
|
367
|
-
authority: "cursor_and_watermark",
|
|
368
|
-
});
|
|
369
|
-
return {
|
|
370
|
-
ok: false,
|
|
371
|
-
reason: "session_not_current",
|
|
372
|
-
current_seq: currentSeq,
|
|
373
|
-
last_read_seq: connectionCursor,
|
|
374
|
-
unread_received: unreadReceived,
|
|
375
|
-
// DOD-ONBOARD-HELP-1 §5: say what is wrong and what to do, in that order, in plain words.
|
|
376
|
-
// The old text opened with the internal noun ("this agent hasn't read…"); a refused send is
|
|
377
|
-
// the most common wall a new operator hits, so it leads with the COUNT and the FIX. The
|
|
378
|
-
// session id is interpolated so the remedy is copy-pasteable, not a template to fill in.
|
|
379
|
-
// Rendered per surface at the IPC boundary — a CLI caller sees `cello receive <id>`.
|
|
380
|
-
guidance: `${unreadReceived} unread message(s) — run cello_receive ${sessionId} first to read them, then send again. (Or cello_transcript ${sessionId} for the whole conversation.) You are blocked from replying to something you haven't read.`,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
// M9-FEED-001 §6: the agent's governance re-send decisions, keyed by the flagId a prior `warn`
|
|
384
|
-
// returned. Optional; validated shape only (the gateway re-scans + applies them, INV-4). A
|
|
385
|
-
// malformed map is ignored rather than failing the send (the gateway will just re-warn).
|
|
386
|
-
const rawDecisions = params?.governance_decisions;
|
|
387
|
-
let governanceDecisions;
|
|
388
|
-
if (rawDecisions && typeof rawDecisions === "object" && !Array.isArray(rawDecisions)) {
|
|
389
|
-
const valid = {};
|
|
390
|
-
for (const [k, v] of Object.entries(rawDecisions)) {
|
|
391
|
-
if (v === "redact" || v === "allow_once" || v === "allow_always")
|
|
392
|
-
valid[k] = v;
|
|
393
|
-
}
|
|
394
|
-
if (Object.keys(valid).length > 0)
|
|
395
|
-
governanceDecisions = valid;
|
|
396
|
-
}
|
|
397
|
-
const correlationId = randomUUID();
|
|
398
|
-
const contentBytes = new TextEncoder().encode(contentStr);
|
|
399
|
-
// CELLO-M7-MSG-001 (AC-013/AC-018/AC-021): enforce the 1 MB application content cap
|
|
400
|
-
// BEFORE any transmission or hash/leaf production. This replaces the silent oversize
|
|
401
|
-
// decode-failure → desync: the send is rejected with a distinct, diagnosable reason
|
|
402
|
-
// and actionable guidance; no content frame is transmitted, no leaf is appended, and
|
|
403
|
-
// the session stays usable.
|
|
404
|
-
if (contentBytes.length > MAX_CONTENT_BYTES) {
|
|
405
|
-
logger.warn("content.rejected.too_large", {
|
|
406
|
-
sessionId,
|
|
407
|
-
contentSize: contentBytes.length,
|
|
408
|
-
cap: MAX_CONTENT_BYTES,
|
|
409
|
-
correlationId,
|
|
410
|
-
});
|
|
411
|
-
return {
|
|
412
|
-
ok: false,
|
|
413
|
-
reason: "content_too_large",
|
|
414
|
-
guidance: `This message is ${contentBytes.length} bytes, over the ${MAX_CONTENT_BYTES}-byte (1 MB) per-message content cap. Split it into multiple messages each under the cap, or use the large-object/file transfer path for large payloads (not cello_send). Nothing was sent and the session is still active — retry with smaller content.`,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
const recipientPubkey = record.counterparty_pubkey;
|
|
418
|
-
// M9 outbound screening seam (INV-5/SI-001). Screen BEFORE anything reaches the wire. The
|
|
419
|
-
// gateway verdict drives the four cello_send outcomes (M9-FEED-001): block / warn → NOT sent;
|
|
420
|
-
// allow → sent as-is; redact → sent in ALTERED form. A configured-but-unreachable gateway fails
|
|
421
|
-
// closed (block, gateway_unavailable), so a screening outage can never let content out ungated.
|
|
422
|
-
const outboundVerdict = await securityGateway.screenOutbound(contentBytes, {
|
|
423
|
-
direction: "outbound",
|
|
424
|
-
agentName: record.agent_name,
|
|
425
|
-
sessionId,
|
|
426
|
-
correlationId,
|
|
427
|
-
...(governanceDecisions !== undefined ? { governanceDecisions } : {}),
|
|
428
|
-
});
|
|
429
|
-
if (outboundVerdict.disposition === "block") {
|
|
430
|
-
if (outboundVerdict.reason === GOVERNANCE_TIMEOUT) {
|
|
431
|
-
logger.error("security.gateway.timeout", { sessionId, reason: outboundVerdict.reason, correlationId });
|
|
432
|
-
}
|
|
433
|
-
else if (outboundVerdict.reason === GATEWAY_UNAVAILABLE) {
|
|
434
|
-
logger.error("security.gateway.unavailable", { direction: "outbound", reason: outboundVerdict.reason, correlationId });
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
logger.info("security.verdict.returned", { disposition: "block", sessionId, reason: outboundVerdict.reason, correlationId });
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
ok: false,
|
|
441
|
-
reason: outboundVerdict.reason ?? "blocked_by_governance",
|
|
442
|
-
guidance: outboundVerdict.guidance ??
|
|
443
|
-
"This message was blocked by the security gateway and was NOT sent. The session is still active.",
|
|
444
|
-
blocks: (outboundVerdict.events ?? []).filter((e) => e.disposition === "block"),
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
if (outboundVerdict.disposition === "warn") {
|
|
448
|
-
logger.info("security.verdict.returned", { disposition: "warn", sessionId, correlationId });
|
|
449
|
-
return {
|
|
450
|
-
ok: false,
|
|
451
|
-
reason: "governance_warn",
|
|
452
|
-
guidance: outboundVerdict.guidance ??
|
|
453
|
-
"This message was held for a governance decision and was NOT sent. Re-send the same content with a " +
|
|
454
|
-
"governance_decisions map ({flagId: redact | allow_once | allow_always}) to resolve each flagged item.",
|
|
455
|
-
flags: (outboundVerdict.events ?? []).filter((e) => e.disposition === "warn"),
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
// FAIL-CLOSED (code-review MED): a `redact` verdict MUST carry the redacted content. If it ever
|
|
459
|
-
// arrives without it, sending the original `contentBytes` would leak the pre-redaction draft — the
|
|
460
|
-
// one place M9 could fail OPEN. Treat it as a block, never an allow-original. (Unreachable today:
|
|
461
|
-
// the gateway always includes content on redact; this is the defensive floor.)
|
|
462
|
-
if (outboundVerdict.disposition === "redact" && outboundVerdict.content === undefined) {
|
|
463
|
-
logger.error("security.verdict.redact_without_content", { sessionId, correlationId });
|
|
464
|
-
return {
|
|
465
|
-
ok: false,
|
|
466
|
-
reason: "redact_without_content",
|
|
467
|
-
guidance: "The security gateway returned a redact verdict without the redacted content. To avoid " +
|
|
468
|
-
"leaking the original, nothing was sent. This is a gateway fault — check the gateway logs and retry.",
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
// allow or redact → send. On redact the ALTERED bytes are what go on the wire AND what the leaf
|
|
472
|
-
// hash binds — the transcript records what was actually sent, not the pre-redaction draft.
|
|
473
|
-
const modified = outboundVerdict.disposition === "redact" && outboundVerdict.content !== undefined;
|
|
474
|
-
const sendBytes = modified ? new Uint8Array(outboundVerdict.content) : contentBytes;
|
|
475
|
-
/**
|
|
476
|
-
* `DOD-M15-SEALWIRE-1` part B2b — the hash and its ALGORITHM come from one decision point.
|
|
477
|
-
*
|
|
478
|
-
* Four outbound sites compute a content hash. Each of them independently deciding whether to
|
|
479
|
-
* salt is the defect `wire-content-hash.ts` was created to end (its header: the expression was
|
|
480
|
-
* written out five times, the last two got it wrong, and it took two live daemons to find),
|
|
481
|
-
* with a worse failure mode — a message hashed one way and LABELLED another is refused by every
|
|
482
|
-
* peer, including a correct one.
|
|
483
|
-
*/
|
|
484
|
-
const { hash: contentHash, alg: contentHashAlg } = await sessionNodeManager.contentHashForSession(record.agent_name, sessionId, sendBytes);
|
|
485
|
-
const contentHashHex = Buffer.from(contentHash).toString("hex");
|
|
486
|
-
// ─── DOD-COATTEND-SENDWINDOW-1 (§4): re-check the gate, in the same synchronous window as the
|
|
487
|
-
// act that makes this message irreversible ───────────────────────────────────────────────
|
|
488
|
-
//
|
|
489
|
-
// NO AWAIT MAY BE INTRODUCED BETWEEN THIS CHECK AND `sendContent` BELOW. The check is only
|
|
490
|
-
// worth anything because nothing can interleave between it and the wire; adding an await here
|
|
491
|
-
// reopens exactly the window it closes, silently, while leaving code that still looks correct.
|
|
492
|
-
//
|
|
493
|
-
// WHY HERE AND NOT AT `appendSessionLeaf`, WHICH IS WHAT AC1 ASKS FOR. AC1 copies the inbound
|
|
494
|
-
// pattern (`session-node-manager.ts:3682-3695`), where the append IS the commit point.
|
|
495
|
-
// Outbound it is not: `sendContent` puts the message ON THE WIRE and runs BEFORE the append, so
|
|
496
|
-
// by the append the counterparty already holds it. Refusing there would leave them holding
|
|
497
|
-
// content this side never leafed — the two frontiers disagree, neither will co-sign, and the
|
|
498
|
-
// session is unsealable except by forfeiting the receipt. That is DOD-FRONTIER-STRAND-1, the
|
|
499
|
-
// defect that left `dbb93dfc…` stranded for a week, manufactured on purpose to satisfy the
|
|
500
|
-
// letter of an AC. The purpose of AC1 is "re-check where it still matters"; outbound, that is
|
|
501
|
-
// the wire.
|
|
502
|
-
//
|
|
503
|
-
// WHY A FRONTIER COMPARISON RATHER THAN RE-RUNNING `caughtUp`. AC3: the gate's second authority
|
|
504
|
-
// is the PERSISTED read watermark, which is AGENT-scoped — so the moment ANY connection reads,
|
|
505
|
-
// `unreadReceived === 0` for every one of them, and re-running the gate would pass a racing
|
|
506
|
-
// sibling exactly as it passed the first time. That agent-scoping is DOD-CURSOR-DURABLE-1
|
|
507
|
-
// behaving as its own §6 predicted: right for stateless clients, wrong for this. So the tighten
|
|
508
|
-
// is stated AGAINST it rather than around it — this asks a question the watermark cannot
|
|
509
|
-
// answer: "did the session move under me while I was gone?"
|
|
510
|
-
const frontierNow = sessionNodeManager.getSessionTree(agentName, sessionId).size();
|
|
511
|
-
const claimedAt = sendClaims.claimedAt(agentName, sessionId);
|
|
512
|
-
if (claimedAt !== undefined) {
|
|
513
|
-
// A sibling is between its own frontier check and its append RIGHT NOW. Its leaf does not
|
|
514
|
-
// exist yet, so the comparison below cannot see it — this is the half of the window a
|
|
515
|
-
// frontier check structurally cannot cover, whatever it is compared against.
|
|
516
|
-
logger.warn("session.send.blocked", {
|
|
517
|
-
sessionId, agentName, connectionId, correlationId,
|
|
518
|
-
authority: "sibling_send_in_flight",
|
|
519
|
-
claimHeldMs: Date.now() - claimedAt,
|
|
520
|
-
});
|
|
521
|
-
// B2b-2 review F3: this send produced NOTHING — no leaf, no wire, no peer copy — so the
|
|
522
|
-
// unsalted hash computed above must not keep this session unsalted for its life.
|
|
523
|
-
sessionNodeManager.abandonUnsaltedHash(agentName, sessionId);
|
|
524
|
-
return {
|
|
525
|
-
ok: false,
|
|
526
|
-
reason: "session_moved_under_send",
|
|
527
|
-
in_flight: true,
|
|
528
|
-
// Says "may have sent" (review L4): the in-flight send can still fail and be queued for
|
|
529
|
-
// retry, in which case nothing was said and there is nothing to read. Promising the operator
|
|
530
|
-
// a reply that does not exist sends them to an unchanged transcript to look for it.
|
|
531
|
-
guidance: `Another session on this agent is sending into this conversation right now, so nothing was sent. Wait a moment, then check cello_transcript ${sessionId} — they may have already answered — before deciding whether your reply still applies. Do not simply resend.`,
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
if (frontierNow !== frontierAtGate) {
|
|
535
|
-
logger.warn("session.send.blocked", {
|
|
536
|
-
sessionId, agentName, connectionId, correlationId,
|
|
537
|
-
authority: "frontier_moved_during_send",
|
|
538
|
-
frontierAtGate, frontierNow,
|
|
539
|
-
});
|
|
540
|
-
// B2b-2 review F3: nothing was sent, so release the unsalted hash's permanent closure. This
|
|
541
|
-
// path got MORE likely with B2b-2 — the salt wait widens the interval this check watches.
|
|
542
|
-
sessionNodeManager.abandonUnsaltedHash(agentName, sessionId);
|
|
543
|
-
return {
|
|
544
|
-
ok: false,
|
|
545
|
-
reason: "session_moved_under_send",
|
|
546
|
-
frontier_at_gate: frontierAtGate,
|
|
547
|
-
frontier_now: frontierNow,
|
|
548
|
-
// Says LEAF, not "message" (review F6). The frontier counts leaves, and a leaf can exist
|
|
549
|
-
// with no transcript row — a security-gateway terminal block commits one and never records
|
|
550
|
-
// the text. Promising N messages in the transcript and delivering fewer sends the operator
|
|
551
|
-
// hunting for content that was screened out and is not there.
|
|
552
|
-
guidance: `The conversation moved while this message was being prepared — the session's record gained ${frontierNow - frontierAtGate} leaf/leaves from another session on this agent or from the counterparty, so nothing was sent. Read what arrived with cello_transcript ${sessionId} (a leaf that was screened out has no readable text and will not appear there), then decide whether your reply still applies. Do not simply resend: another session may have already answered.`,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
// THE CLAIM — taken in the same synchronous window as the check above (no await between them),
|
|
556
|
-
// and released the instant the wire call settles.
|
|
557
|
-
//
|
|
558
|
-
// Releasing there is safe and is not an oversight: after `await sendContent` resolves, the
|
|
559
|
-
// continuation runs the `finally`, the ok-check and `appendSessionLeaf` WITHOUT YIELDING, so no
|
|
560
|
-
// other connection can observe the gap. By the time one runs again the frontier has either
|
|
561
|
-
// moved (append happened → its own frontier comparison catches it) or the send failed and
|
|
562
|
-
// nothing was committed (→ it is free to try). `finally` rather than a plain call so a throw
|
|
563
|
-
// cannot wedge the session into permanent refusal.
|
|
564
|
-
sendClaims.claim(agentName, sessionId);
|
|
565
|
-
let sendResult;
|
|
566
|
-
try {
|
|
567
|
-
sendResult = await sessionNodeManager.sendContent(record.agent_name, sessionId, sendBytes, new Uint8Array(contentHash), correlationId, LEAF_KIND_MSG, contentHashAlg);
|
|
568
|
-
}
|
|
569
|
-
finally {
|
|
570
|
-
sendClaims.release(agentName, sessionId);
|
|
571
|
-
}
|
|
572
|
-
if (!sendResult.ok) {
|
|
573
|
-
// DB-001 / dead-channel contract: never silently drop, never desync. Preserve
|
|
574
|
-
// the content in the durable retry_queue so it is retried on reconnect, and
|
|
575
|
-
// surface a named, diagnosable failure.
|
|
576
|
-
//
|
|
577
|
-
// M12-P13 (review MEDIUM-4): NOT on the durable path. `drainSession` — this nonce queue's
|
|
578
|
-
// only consumer — has no production caller, so every row it writes is permanent: unbounded
|
|
579
|
-
// growth plus a second copy of the message plaintext at rest. On a durable failure the
|
|
580
|
-
// awaiting-ACK queue already holds the content and actually drains, so writing here too
|
|
581
|
-
// stored the same plaintext twice and drained neither copy from this table.
|
|
582
|
-
if (!sendResult.durable) {
|
|
583
|
-
// B2b-2 review F3: a non-durable failure means the message is GONE — it reached no leaf, no
|
|
584
|
-
// peer, and the queue below has no production consumer. Nothing exists to split a
|
|
585
|
-
// transcript, so this session must not stay unsalted for its life over it.
|
|
586
|
-
sessionNodeManager.abandonUnsaltedHash(agentName, sessionId);
|
|
587
|
-
const nonce = randomUUID();
|
|
588
|
-
try {
|
|
589
|
-
retryQueue.enqueue(sessionId, new TextEncoder().encode(nonce), sendBytes);
|
|
590
|
-
}
|
|
591
|
-
catch (err) {
|
|
592
|
-
logger.error("session.content.queue.failed", {
|
|
593
|
-
sessionId,
|
|
594
|
-
error: extractErrorMessage(err),
|
|
595
|
-
correlationId,
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
logger.warn("session.content.send.failed", {
|
|
600
|
-
sessionId,
|
|
601
|
-
recipientPubkey,
|
|
602
|
-
reason: sendResult.reason,
|
|
603
|
-
errorMessage: sendResult.error,
|
|
604
|
-
durable: sendResult.durable,
|
|
605
|
-
cause: sendResult.cause,
|
|
606
|
-
correlationId,
|
|
607
|
-
});
|
|
608
|
-
// M12-P13: a DURABLY QUEUED message still owns the sequence the relay witnessed for it before
|
|
609
|
-
// direct delivery was ever attempted — the same reasoning that makes a parked message occupy
|
|
610
|
-
// its leaf position below. Leaving the hole here is what stalls the far side: `nextExpected`
|
|
611
|
-
// is this tree's size, so the counterparty's next message arrives at a sequence this tree can
|
|
612
|
-
// never reach and is held behind the gap forever (found live 2026-08-05, M12 Entry 89).
|
|
613
|
-
// A LOST message gets no leaf — that would commit a sequence no content will ever fill, and
|
|
614
|
-
// the two roots could then never seal.
|
|
615
|
-
if (sendResult.durable) {
|
|
616
|
-
// DOD-M12B-INDEX-1: at the position the relay assigned, not at whatever the tail is. When
|
|
617
|
-
// this side has a gap the two differ, and committing at the tail puts our leaf at someone
|
|
618
|
-
// else's index — which parts the two roots and makes the next seal terminal.
|
|
619
|
-
const placed = sessionNodeManager.placeOwnLeaf(record.agent_name, sessionId, contentHashHex, sendBytes, sendResult.sequenceNumber, correlationId, "msg", sentAuthorship(sendResult));
|
|
620
|
-
// `sequence_number` is a LEAF INDEX or nothing. Reporting the relay's number when no leaf
|
|
621
|
-
// exists hands the caller a value that matches no leaf on this side, and anything that
|
|
622
|
-
// feeds it to a cursor is then wrong.
|
|
623
|
-
const queuedLeaf = placed.placed ? placed.leafIndex : -1;
|
|
624
|
-
if (placed.placed) {
|
|
625
|
-
sessionNodeManager.recordTranscriptMessage(record.agent_name, sessionId, queuedLeaf, "sent", sendBytes, correlationId, sentAuthorship(sendResult));
|
|
626
|
-
advanceConnectionCursor(connectionId, sessionId, queuedLeaf);
|
|
627
|
-
}
|
|
628
|
-
logger.info(placed.placed ? "session.content.queued.committed" : "session.content.queued.held", {
|
|
629
|
-
sessionId, sequenceNumber: queuedLeaf, heldAt: placed.placed ? undefined : placed.heldAt,
|
|
630
|
-
contentHash: contentHashHex, correlationId,
|
|
631
|
-
});
|
|
632
|
-
return {
|
|
633
|
-
ok: false,
|
|
634
|
-
reason: sendResult.reason,
|
|
635
|
-
/**
|
|
636
|
-
* DOD-M15-RELAYABUSE-1 review MEDIUM-7 — **THE CODE MUST CROSS THE BOUNDARY, NOT JUST THE
|
|
637
|
-
* PARAGRAPH.**
|
|
638
|
-
*
|
|
639
|
-
* `reason` here is `session_stream_unavailable` — an EXIT-POINT label. The actual cause
|
|
640
|
-
* (throttled / this counterparty's mailbox is full / the relay's store is full / this
|
|
641
|
-
* build cannot seal the algorithm) was computed, logged, and then left inside the English
|
|
642
|
-
* `guidance`. So an MCP caller wanting to tell "wait 45 seconds" from "another relay will
|
|
643
|
-
* not help" was back to substring-matching prose — which is exactly what `cause` was added
|
|
644
|
-
* to stop. Three newly-distinguishable causes had just landed and none of them reached the
|
|
645
|
-
* agent that would act on them.
|
|
646
|
-
*/
|
|
647
|
-
...(sendResult.cause !== undefined ? { cause: sendResult.cause } : {}),
|
|
648
|
-
queued: true,
|
|
649
|
-
// Carried on BOTH paths: without it a caller cannot tell "queued and in the record" from
|
|
650
|
-
// "queued and not in the record yet".
|
|
651
|
-
...(placed.placed ? {} : { held: true }),
|
|
652
|
-
sequence_number: queuedLeaf,
|
|
653
|
-
/**
|
|
654
|
-
* ⚠️ THE BRANCH PRODUCTION ACTUALLY TAKES — review HIGH-2, and the reason the loopback
|
|
655
|
-
* journey could not find it. Behind NAT the direct path runs over the relay's circuit, so
|
|
656
|
-
* a relay outage kills the transport and the witness TOGETHER and the send lands here
|
|
657
|
-
* rather than on the delivered path. On the harness the two are independent, the direct
|
|
658
|
-
* send succeeds, and this branch is never reached.
|
|
659
|
-
*
|
|
660
|
-
* It was answering "No action needed" for a leaf the relay never witnessed.
|
|
661
|
-
*/
|
|
662
|
-
witnessed: wasWitnessed(placed),
|
|
663
|
-
/**
|
|
664
|
-
* ⚠️ COMPOSED, NEVER CHOSEN — the first version of this fix CHOSE, and that discarded a
|
|
665
|
-
* shipped guarantee. Caught deterministically by the `013-ABSENCE` lane in
|
|
666
|
-
* `dod-m15-park-envelope-coded-error.test.ts`.
|
|
667
|
-
*
|
|
668
|
-
* A RATE-LIMITED park is unwitnessed BY CONSTRUCTION, so making the whole string
|
|
669
|
-
* conditional on witnessing threw away `sendResult.guidance` — which is where the relay's
|
|
670
|
-
* own retry window lives ("about 45 seconds"). That is Invariant 3 aimed back at this
|
|
671
|
-
* unit: a downstream handler replacing an upstream descriptive value with a generic one.
|
|
672
|
-
* The upstream sentence is more specific than anything this layer can produce, and a
|
|
673
|
-
* missing witness does not make it less true.
|
|
674
|
-
*
|
|
675
|
-
* Both facts hold at once and the operator needs both — what the relay said to do, and
|
|
676
|
-
* what the missing witness will cost. So upstream is kept VERBATIM and the unwitnessed
|
|
677
|
-
* note is APPENDED to it, by `queuedSendGuidance`, which the test calls directly rather
|
|
678
|
-
* than re-implementing.
|
|
679
|
-
*/
|
|
680
|
-
guidance: queuedSendGuidance(sendResult.guidance, wasWitnessed(placed)),
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
return {
|
|
684
|
-
ok: false,
|
|
685
|
-
reason: sendResult.reason,
|
|
686
|
-
// DOD-M15-RELAYABUSE-1 review MEDIUM-7: the machine-readable cause crosses here too — see
|
|
687
|
-
// the note on the queued return above. `reason` is the exit point; `cause` is what happened.
|
|
688
|
-
...(sendResult.cause !== undefined ? { cause: sendResult.cause } : {}),
|
|
689
|
-
// M12-P12 (review pass 2): sendContent knows whether the content is actually recoverable;
|
|
690
|
-
// this boundary used to overwrite that with one sentence promising a retry for EVERY
|
|
691
|
-
// failure — including a persist that threw, where the message is simply gone, and a session
|
|
692
|
-
// with no relay, where the direct nonce queue it names has no production drain. Telling an
|
|
693
|
-
// operator "it will be retried" about a lost message is the same lie that let the original
|
|
694
|
-
// defect sit unnoticed; the caller's own guidance wins whenever it has one.
|
|
695
|
-
guidance: sendResult.guidance ?? "The content could not be delivered over the session stream right now, and no relay is configured for this session — it is NOT queued for automatic retry. Send it again once the counterparty is reachable; check cello_status for their status.",
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
// Delivered directly OR dispatched to relay (DOD-LEAVEMSG-1) — either way the content is now
|
|
699
|
-
// part of the daemon-owned tree: the relay witness (R1) already assigned it a sequence before
|
|
700
|
-
// direct delivery was even attempted, so a parked message occupies the SAME leaf position it
|
|
701
|
-
// would have taken if delivered live. Append once, for both outcomes.
|
|
702
|
-
// DOD-M12B-INDEX-1: the relay assigned this message a position before delivery was attempted,
|
|
703
|
-
// and the leaf goes THERE. Appending at the tail is only right while this side has no gap; the
|
|
704
|
-
// first gap puts our own leaf at a position that belongs to a message of theirs we have not
|
|
705
|
-
// seen, which parts the two roots permanently. When the position is ahead of the tail the leaf
|
|
706
|
-
// is held — the bytes are already on the wire, so only the record waits, exactly as it does for
|
|
707
|
-
// a message we receive out of order.
|
|
708
|
-
const placement = sessionNodeManager.placeOwnLeaf(record.agent_name, sessionId, contentHashHex, sendBytes, sendResult.sequenceNumber, correlationId, "msg", sentAuthorship(sendResult));
|
|
709
|
-
if (!placement.placed) {
|
|
710
|
-
// NOT IN THE RECORD YET, and the caller is told so rather than handed a plain success.
|
|
711
|
-
// `sequence_number` stays -1 because no leaf exists: `held_at` carries the relay's position
|
|
712
|
-
// separately, so nothing mistakes a relay number for a leaf index.
|
|
713
|
-
return {
|
|
714
|
-
ok: true,
|
|
715
|
-
sequence_number: -1,
|
|
716
|
-
held: true,
|
|
717
|
-
held_at: placement.heldAt,
|
|
718
|
-
/**
|
|
719
|
-
* TRUE, and it is not a courtesy default — review HIGH-2. A leaf is HELD precisely because
|
|
720
|
-
* the relay assigned it a position beyond this side's tail, so being held is itself proof
|
|
721
|
-
* that the relay witnessed it. Omitting the field here would leave an agent branching on
|
|
722
|
-
* `witnessed !== true` reading a perfectly healthy message as a lost one.
|
|
723
|
-
*/
|
|
724
|
-
witnessed: wasWitnessed(placement),
|
|
725
|
-
// The screening verdict must survive this return, or an agent whose message was ALTERED is
|
|
726
|
-
// never told — the one thing a redact verdict exists to surface.
|
|
727
|
-
...(modified ? { modified: true, transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
728
|
-
guidance: "The message was delivered — the counterparty has it. Its place in the record is waiting on an earlier message from them that has not arrived yet, so it is not in your transcript until that gap fills. Nothing to do, and do not resend: a resend takes a second position in the record.",
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
if (placement.diverged) {
|
|
732
|
-
// THE OPERATOR HEARS ABOUT IT. Their message is in their own transcript, but this side's tree
|
|
733
|
-
// is permanently ahead of the relay's counter, so the two can no longer agree on a root and
|
|
734
|
-
// this session will never produce a notarized receipt. Returning the ordinary success here —
|
|
735
|
-
// which is what the first build did — reports a healthy send on a conversation that has
|
|
736
|
-
// silently lost the one thing the protocol exists to produce.
|
|
737
|
-
logger.error("session.content.sent.diverged", {
|
|
738
|
-
sessionId, agentName: record.agent_name, sequenceNumber: placement.leafIndex, correlationId,
|
|
739
|
-
impact: "delivered and recorded locally, but this session can no longer be sealed bilaterally",
|
|
740
|
-
});
|
|
741
|
-
return {
|
|
742
|
-
ok: true,
|
|
743
|
-
sequence_number: placement.leafIndex,
|
|
744
|
-
diverged: true,
|
|
745
|
-
/**
|
|
746
|
-
* ⚠️ THIS IS `true`, AND THE FIRST VERSION OF THIS COMMENT SAID OTHERWISE — review HIGH-B.
|
|
747
|
-
*
|
|
748
|
-
* The divergence branch is reached only when the relay DID assign a sequence, one behind
|
|
749
|
-
* this side's frontier (`assignedSeq < nextExpected`). So the relay witnessed this leaf; it
|
|
750
|
-
* witnessed it at a position our tree has already passed. `witnessed: true` is the honest
|
|
751
|
-
* answer and `diverged: true` is the field that carries the bad news here.
|
|
752
|
-
*
|
|
753
|
-
* I had written that this return was "reachable only after an unwitnessed leaf" and would
|
|
754
|
-
* otherwise report `witnessed: undefined`, so the operator could never see the `false` the
|
|
755
|
-
* guidance told them to watch for. The first half is true — divergence is caused by an
|
|
756
|
-
* earlier unwitnessed append — and the conclusion drawn from it was not: THIS send was
|
|
757
|
-
* witnessed. Recorded rather than silently corrected, because a comment asserting the wrong
|
|
758
|
-
* value of the field beside it is how the next reader learns the wrong model.
|
|
759
|
-
*/
|
|
760
|
-
witnessed: wasWitnessed(placement),
|
|
761
|
-
...(modified ? { modified: true, transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
762
|
-
guidance: `The message was delivered and is in your transcript. But this session's record has drifted out of step with the relay's ordering, so it can no longer be sealed with the counterparty — there will be no notarized receipt for it. Keep talking if you want to; when you are finished, cello_close_session ${sessionId} { force: true } is the only way it can end. cello_transcript ${sessionId} is the record you keep.`,
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
const leafIndex = placement.leafIndex;
|
|
766
|
-
// DOD-LOG-1: persist the readable SENT plaintext to the durable transcript, keyed by the
|
|
767
|
-
// canonical leaf sequence so it joins the committed hash chain (survives restart).
|
|
768
|
-
// M9 merge fix: use sendBytes (the ALTERED bytes on a redact verdict), never the pre-redaction
|
|
769
|
-
// contentBytes — the leaf hash above already binds sendBytes; the transcript must match what
|
|
770
|
-
// actually went on the wire, not the pre-redaction draft (M9's own stated seam invariant).
|
|
771
|
-
/**
|
|
772
|
-
* ⚠️ THE RESULT IS READ, and a `false` is a HOLE in the record of what this side said.
|
|
773
|
-
*
|
|
774
|
-
* The leaf exists; the row does not. The operator's own message is then invisible to every
|
|
775
|
-
* reader of the transcript — including `away-inbox-oneshot.ts`, which asks exactly one question
|
|
776
|
-
* of it: "has a human spoken in this session?" A silent `false` answers no about a message that
|
|
777
|
-
* went out, and that answer ends the session. The inbound path has handled this case since
|
|
778
|
-
* `content_undeliverable`; the outbound path ignored it.
|
|
779
|
-
*/
|
|
780
|
-
const rowWritten = sessionNodeManager.recordTranscriptMessage(record.agent_name, sessionId, leafIndex, "sent", sendBytes, correlationId, sentAuthorship(sendResult));
|
|
781
|
-
if (!rowWritten) {
|
|
782
|
-
sendClaims.noteRowMissing(record.agent_name, sessionId);
|
|
783
|
-
logger.error("session.send.transcript_row_failed", {
|
|
784
|
-
sessionId, agentName: record.agent_name, sequenceNumber: leafIndex, correlationId,
|
|
785
|
-
impact: "the message was sent and its leaf committed, but this side holds no readable copy of it. " +
|
|
786
|
-
"The transcript under-reports what this agent said, and nothing that reads the transcript " +
|
|
787
|
-
"to decide whether a human spoke here may treat this session as unspoken-in.",
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
// M8C-CURSOR-1: the sender authored this leaf — advance ITS OWN cursor so it doesn't get
|
|
791
|
-
// blocked by session_not_current on its own just-sent message.
|
|
792
|
-
advanceConnectionCursor(connectionId, sessionId, leafIndex);
|
|
793
|
-
// CC-1 (2026-07-07): operator engagement promotes the counterparty to a known contact. A
|
|
794
|
-
// committed reply — past the read-before-write gate, content now on the wire AND in the tree —
|
|
795
|
-
// IS the operator choosing to trust this sender; the inbound-accept path deliberately no longer
|
|
796
|
-
// auto-adds (that defeated screening + anti-spam). For an OUTBOUND session the counterparty is
|
|
797
|
-
// already a contact (cello_initiate_session added it), so this is an idempotent no-op there; it
|
|
798
|
-
// matters for inbound-originated sessions, where the reply is the trust signal. addContact is
|
|
799
|
-
// INSERT OR IGNORE — it never refreshes added_at. DOD-TIER-4 AC3: engaging (a committed reply into
|
|
800
|
-
// an inbound session I accepted) makes the counterparty KNOWN, provenance 'accepted'. For an
|
|
801
|
-
// OUTBOUND session the row already exists ('initiated', KNOWN) and INSERT OR IGNORE leaves it
|
|
802
|
-
// untouched — 'initiated' correctly wins there.
|
|
803
|
-
sessionNodeManager.addContact(record.agent_name, recipientPubkey, undefined, "accepted", TIER.KNOWN);
|
|
804
|
-
if (modified) {
|
|
805
|
-
logger.info("security.verdict.returned", { disposition: "redact", sessionId, sequenceNumber: leafIndex, correlationId });
|
|
806
|
-
}
|
|
807
|
-
if (!sendResult.delivered) {
|
|
808
|
-
// DOD-LEAVEMSG-1 (sender half): direct delivery failed but the sealed, hashed content was
|
|
809
|
-
// successfully deposited at the relay (pickup_queue) — this is a SUCCESS outcome, not a
|
|
810
|
-
// failure. The recipient's daemon pulls it via RELAYWAKE on next reconnect. Reporting this
|
|
811
|
-
// as ok:false (the pre-LEAVEMSG-1 behavior) misrepresented an in-flight message as lost.
|
|
812
|
-
logger.info("session.content.dispatched_to_relay", {
|
|
813
|
-
sessionId,
|
|
814
|
-
recipientPubkey,
|
|
815
|
-
contentHashHex,
|
|
816
|
-
sequenceNumber: leafIndex,
|
|
817
|
-
correlationId,
|
|
818
|
-
});
|
|
819
|
-
return {
|
|
820
|
-
ok: true,
|
|
821
|
-
sequence_number: leafIndex,
|
|
822
|
-
delivered: false,
|
|
823
|
-
reason: "dispatched_to_relay",
|
|
824
|
-
modified,
|
|
825
|
-
// Same fact, same shape, both branches — see the note on the delivered path below.
|
|
826
|
-
witnessed: wasWitnessed(placement),
|
|
827
|
-
/**
|
|
828
|
-
* WHAT IS ACTUALLY TRUE, and the previous wording was neither.
|
|
829
|
-
*
|
|
830
|
-
* OBSERVED LIVE 2026-08-18. This exact response came back on a send that was delivered in
|
|
831
|
-
* **8 seconds**, and the counterparty's agent read it and correctly reported "no change" —
|
|
832
|
-
* because it is byte-identical to the response returned when the same send took THREE
|
|
833
|
-
* MINUTES. The sender had no way to tell a good outcome from a bad one, and the rational
|
|
834
|
-
* next move for anyone reading it is to send again, which produces a duplicate of a message
|
|
835
|
-
* that was never lost.
|
|
836
|
-
*
|
|
837
|
-
* The old text also asserted a cause it does not know: *"delivered the next time the
|
|
838
|
-
* counterparty's daemon reconnects."* The counterparty's daemon is frequently up the whole
|
|
839
|
-
* time — this path is taken because the two SESSION NODES hold no direct connection to each
|
|
840
|
-
* other, which is a different thing and points at a different party.
|
|
841
|
-
*
|
|
842
|
-
* So: say what happened, say what to expect, and claim nothing about their daemon.
|
|
843
|
-
*/
|
|
844
|
-
/**
|
|
845
|
-
* `016-RELAYLOSS`: **"witnessed" IS NOT SOMETHING THIS STRING GETS TO ASSERT.**
|
|
846
|
-
*
|
|
847
|
-
* It said so unconditionally, and it is false on exactly the path this order measured: a
|
|
848
|
-
* relay that stops answering fails the hash submit and the leaf is appended unwitnessed,
|
|
849
|
-
* while the content can still reach the counterparty. The reassurance was then strictly
|
|
850
|
-
* worse than silence — it named the property that had just been lost as the reason not to
|
|
851
|
-
* worry. Claimed only when true; the rest of the sentence is unchanged and still right.
|
|
852
|
-
*/
|
|
853
|
-
/**
|
|
854
|
-
* ⚠️ "NO ACTION IS NEEDED" IS CONDITIONAL TOO — review HIGH-C, and the same mistake the
|
|
855
|
-
* queued path had already fixed one branch away.
|
|
856
|
-
*
|
|
857
|
-
* The first version left that reassurance unconditional and then contradicted it three
|
|
858
|
-
* words later: no action is needed… BUT this session is on its way to being unsealable.
|
|
859
|
-
* Telling the operator to relax and then telling them the conversation is dying is worse
|
|
860
|
-
* than either sentence alone, because the one they act on is the first.
|
|
861
|
-
*
|
|
862
|
-
* This branch also named no remedy at all, while its two siblings both say to close. Same
|
|
863
|
-
* situation, same advice.
|
|
864
|
-
*/
|
|
865
|
-
guidance: "Sent. This message went via the relay rather than a direct connection, so it is sealed"
|
|
866
|
-
+ (wasWitnessed(placement) ? ", witnessed" : "") + " and on its way — the counterparty "
|
|
867
|
-
+ "normally has it within seconds. "
|
|
868
|
-
+ "`delivered: false` means it did not go over a direct link, NOT that it failed. "
|
|
869
|
-
+ (wasWitnessed(placement)
|
|
870
|
-
? "No action is needed, and re-sending would duplicate it."
|
|
871
|
-
: "Do not re-send — that would duplicate it. BUT the relay did NOT witness this leaf, so "
|
|
872
|
-
+ "the ordering authority has no copy of it and your record is a leaf ahead of the "
|
|
873
|
-
+ "relay's. Ordering does not repair itself: the next message the relay does witness "
|
|
874
|
-
+ "will report this session as diverged, and a diverged session can never be sealed "
|
|
875
|
-
+ "with the counterparty. If the receipt matters, close rather than continuing."),
|
|
876
|
-
...(modified ? { transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
logger.info("session.content.sent", {
|
|
880
|
-
sessionId,
|
|
881
|
-
recipientPubkey,
|
|
882
|
-
contentHashHex,
|
|
883
|
-
sequenceNumber: leafIndex,
|
|
884
|
-
correlationId,
|
|
885
|
-
});
|
|
886
|
-
return {
|
|
887
|
-
ok: true,
|
|
888
|
-
sequence_number: leafIndex,
|
|
889
|
-
delivered: true,
|
|
890
|
-
modified,
|
|
891
|
-
/**
|
|
892
|
-
* ─── `016-RELAYLOSS`: THE ONE FACT THIS RESPONSE WAS MISSING ─────────────────────────────
|
|
893
|
-
*
|
|
894
|
-
* Measured, not supposed: with the relay black-holed mid-conversation, this send stalled ten
|
|
895
|
-
* seconds and returned exactly the object above — the same `ok`, the same `delivered`, and a
|
|
896
|
-
* `sequence_number` that is a LOCAL leaf index and therefore looks just as healthy. The
|
|
897
|
-
* daemon logged `session.tree.own_leaf_unwitnessed` at ERROR, correctly and in detail, into a
|
|
898
|
-
* file the agent cannot read. The operator's message arrived, they were told it succeeded,
|
|
899
|
-
* and the ordering authority has no copy of it.
|
|
900
|
-
*
|
|
901
|
-
* `witnessed` is stated on BOTH outcomes rather than only the bad one. A field that appears
|
|
902
|
-
* only when something is wrong cannot be relied on by a reader who has never seen it — its
|
|
903
|
-
* absence is indistinguishable from an older daemon, and "absence is not a pass" is the rule
|
|
904
|
-
* this milestone keeps re-learning. Present always, it is a fact the caller can branch on.
|
|
905
|
-
*/
|
|
906
|
-
witnessed: wasWitnessed(placement),
|
|
907
|
-
/**
|
|
908
|
-
* ⚠️ THIS ONCE SAID *"send the next message normally — ordering re-establishes itself."* It
|
|
909
|
-
* was false, and it was the most damaging sentence in the unit that added it — review HIGH-1.
|
|
910
|
-
*
|
|
911
|
-
* `placeOwnLeaf` states the opposite sixty lines away: an unwitnessed append leaves this
|
|
912
|
-
* side's tree one leaf AHEAD of the relay's counter, so "from then on every ack comes back
|
|
913
|
-
* behind our frontier and the two can never agree again — the seal was already lost at the
|
|
914
|
-
* unwitnessed append, not here." The next WITNESSED send therefore takes the divergence
|
|
915
|
-
* branch and marks the session permanently unsealable. Telling the operator to keep talking
|
|
916
|
-
* was telling them to perform the act that makes the loss permanent.
|
|
917
|
-
*
|
|
918
|
-
* Which is this unit's own subject — a reassuring sentence asserting a property already lost
|
|
919
|
-
* — reproduced one clause later in its replacement. Kept as a comment, not quietly deleted,
|
|
920
|
-
* because the reasoning is the part that stops it coming back.
|
|
921
|
-
*
|
|
922
|
-
* `cello status` came out of the advice for the same reason: measured in this unit's own
|
|
923
|
-
* runs, its relay fields read identically during the outage and before it.
|
|
924
|
-
*/
|
|
925
|
-
...(wasWitnessed(placement)
|
|
926
|
-
? {}
|
|
927
|
-
: {
|
|
928
|
-
guidance:
|
|
929
|
-
/**
|
|
930
|
-
* ⚠️ NOT "the counterparty's record cannot gain it" — review MED-D. On THIS branch the
|
|
931
|
-
* direct send succeeded, and the receiving side appends unwitnessed content in arrival
|
|
932
|
-
* order rather than refusing it (refusing would make the relay a precondition for
|
|
933
|
-
* reading your mail). So they do get the message. What nobody gets is an independent
|
|
934
|
-
* record that you sent it — which is the thing actually lost, and the thing the remedy
|
|
935
|
-
* below is for. The old sentence would have had an operator conclude the conversation
|
|
936
|
-
* was already broken bilaterally when the two trees may still agree.
|
|
937
|
-
*/
|
|
938
|
-
"Delivered and in your transcript — but the relay did not witness it, so the ordering "
|
|
939
|
-
+ "authority has no copy of it: the counterparty has your message, and neither of you "
|
|
940
|
-
+ "has independent proof you sent it. Do NOT resend: "
|
|
941
|
-
+ "that adds a second copy to your record and does not witness the first. "
|
|
942
|
-
+ "ORDERING DOES NOT REPAIR ITSELF — your record is now one leaf ahead of the relay's, "
|
|
943
|
-
+ "so the next message the relay DOES witness will report this session as diverged, "
|
|
944
|
-
+ "and a diverged session can never be sealed with the counterparty. If the receipt "
|
|
945
|
-
+ "matters, stop sending and close now; if you keep talking, do it knowing this "
|
|
946
|
-
+ "conversation is unlikely to produce one.",
|
|
947
|
-
}),
|
|
948
|
-
// On a redact, tell the agent exactly what was transformed (the §6 sender-side surface).
|
|
949
|
-
...(modified ? { transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
950
|
-
};
|
|
951
|
-
});
|
|
952
|
-
// ─── CELLO-M7-DAEMON-004 / F1-a: cello_receive (BLOCKING, session-scoped) ────────
|
|
953
|
-
// F1-a fix: the daemon port had dropped the blocking receive (the handler was a
|
|
954
|
-
// non-blocking buf.shift). It now BLOCKS up to timeout_ms, polling the received-content
|
|
955
|
-
// buffer — resolved by the next arrival, a terminal seal answer (F1-b), or timeout. This is
|
|
956
|
-
// the "blocking receive variant" the guidance names.
|
|
957
|
-
//
|
|
958
|
-
// ONE name (DOD-ONBOARD-HELP-1, Andre 2026-07-11). This was briefly registered under
|
|
959
|
-
// `cello_receive_session` as well — one implementation behind two names. That alias is DELETED:
|
|
960
|
-
// it accepted or joined nothing (inbound sessions are auto-accepted by the standing receiver),
|
|
961
|
-
// so it was a second name for a step that does not exist, and its help said otherwise.
|
|
962
|
-
const RECEIVE_DEFAULT_TIMEOUT_MS = 30000; // matches the cello-mcp shim's documented default
|
|
963
|
-
const handleReceive = async (params, connectionId) => {
|
|
964
|
-
const connState = getConnState(connectionId);
|
|
965
|
-
// CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > current > sole online agent.
|
|
966
|
-
const agentName = resolveCurrentAgent(connState, params?.agent);
|
|
967
|
-
if (!agentName)
|
|
968
|
-
return NO_CURRENT_AGENT_RESPONSE;
|
|
969
|
-
// Read the snake_case public field cello-mcp.ts actually sends.
|
|
970
|
-
const sessionId = params?.session_id;
|
|
971
|
-
if (!sessionId) {
|
|
972
|
-
return { ok: false, reason: "missing_params", guidance: "Provide 'session_id' (hex) to receive content for a specific session." };
|
|
973
|
-
}
|
|
974
|
-
const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
975
|
-
// DOD-UNREAD-1 D4b: a TRANSCRIPT-ONLY session — received rows exist but no sessions row (the
|
|
976
|
-
// pre-D3/D4a phantom residue: the counterparty's reply landed while this side refused the
|
|
977
|
-
// session). Those rows are counted unread by getUnreadSummary, so cello_receive MUST be able
|
|
978
|
-
// to read them or the badge can never clear. Reading is a transcript operation: the read below
|
|
979
|
-
// works from the durable transcript alone. Only a session with NEITHER a row
|
|
980
|
-
// NOR transcript rows is truly not found.
|
|
981
|
-
let transcriptOnly = false;
|
|
982
|
-
if (!record) {
|
|
983
|
-
/**
|
|
984
|
-
* ⚠️ **QUARANTINED ROWS DO NOT MAKE A SESSION — review F4, and this is the clause
|
|
985
|
-
* `DOD-M15-REFUSEDEVIDENCE-1` names by surface.**
|
|
986
|
-
*
|
|
987
|
-
* Every other reader of this table pins `direction = 'received'`, which is what makes a
|
|
988
|
-
* refused message excluded by construction. This one counted rows. So after a
|
|
989
|
-
* `session_orphaned` retention — no `sessions` row, one quarantined row — `cello_receive`
|
|
990
|
-
* stopped answering `session_not_found` and began answering `session_not_live`, with guidance
|
|
991
|
-
* saying *"This session exists only as a durable transcript"*. Nothing deliverable is there
|
|
992
|
-
* and nobody opened it: the row is a probe someone aimed at an id. That is a milder shape of
|
|
993
|
-
* the phantom-session residue this whole design exists to avoid — a session id materialising
|
|
994
|
-
* in an operator surface because a stranger sent bytes at it.
|
|
995
|
-
*/
|
|
996
|
-
const deliverable = sessionNodeManager
|
|
997
|
-
.readTranscript(agentName, sessionId)
|
|
998
|
-
.messages.filter((m) => m.direction !== "quarantined");
|
|
999
|
-
if (deliverable.length === 0) {
|
|
1000
|
-
return { ok: false, reason: "session_not_found", guidance: "No session found with this ID. Check cello_sessions." };
|
|
1001
|
-
}
|
|
1002
|
-
transcriptOnly = true;
|
|
1003
|
-
}
|
|
1004
|
-
// (D4 review F3: the old `record.agent_name !== agentName` → session_not_owned branch was dead
|
|
1005
|
-
// code — getSessionRecord is keyed by (agent_name, session_id), so a record can never belong to
|
|
1006
|
-
// a different agent. A cross-agent session id simply has no record under this agent.)
|
|
1007
|
-
/**
|
|
1008
|
-
* DOD-M12B-SESSION-SEED-1 (case B) — return an interrupted session to `active` on a local read.
|
|
1009
|
-
*
|
|
1010
|
-
* WHAT THIS ACTUALLY BUYS, stated correctly after review MEDIUM-5. The first version of this
|
|
1011
|
-
* comment claimed an operator who only reads "was stuck forever" and that parked content never
|
|
1012
|
-
* drained. **Neither is true.** The park drain runs off the AGENT's standing receiver, not the
|
|
1013
|
-
* session node; `ingestReceivedContent` deliberately accepts `interrupted`; and the backstop
|
|
1014
|
-
* tick fires for every agent every 5 minutes. So case-B content already reached the transcript.
|
|
1015
|
-
*
|
|
1016
|
-
* What the read-triggered revival adds is real but narrower: the status returns to `active`, so
|
|
1017
|
-
* the operator's own next send is not refused, and the drain happens NOW rather than up to five
|
|
1018
|
-
* minutes from now.
|
|
1019
|
-
*
|
|
1020
|
-
* WHY A READ MAY TRIGGER THIS AND AN INBOUND DIAL MAY NOT. Andre's tenet is about what a REMOTE
|
|
1021
|
-
* party can cause — *"an open connection that a malicious agent can farm for."* Reviving because
|
|
1022
|
-
* a peer dialled us would hand that lever to the peer. A read is the OPERATOR asking, on their
|
|
1023
|
-
* own machine, for their own session.
|
|
1024
|
-
*
|
|
1025
|
-
* Narrowed to `interrupted` (review LOW-7). `reviveSessionNode` refuses the other non-active
|
|
1026
|
-
* statuses by DENYLIST, so an allowlist here is what stops a sixth status added later from being
|
|
1027
|
-
* force-flipped to `active` by a read.
|
|
1028
|
-
*
|
|
1029
|
-
* AWAITED — a loopback bind, sub-millisecond, and the receive deadline is computed after this so
|
|
1030
|
-
* revival latency does not eat the read window — but its RESULT NEVER BLOCKS THE READ. Reading a
|
|
1031
|
-
* stored transcript is always allowed, including for a sealed session, where this refuses.
|
|
1032
|
-
*/
|
|
1033
|
-
/**
|
|
1034
|
-
* DOD-M12B-SESSION-SEED-1 (case B) — return an interrupted session to `active` on a local read.
|
|
1035
|
-
*
|
|
1036
|
-
* **NOT AWAITED, and that is a correction made under live fire on 2026-08-18.** The first
|
|
1037
|
-
* version awaited it. At the time the rebuild only opened a loopback port, so a stall was not
|
|
1038
|
-
* credible — review asked that exact question and got that exact answer. The rebuild was then
|
|
1039
|
-
* changed to take a circuit-relay reservation, so that a counterparty could dial us again, and
|
|
1040
|
-
* nobody re-asked it. A relay that does not answer has no deadline. `cello_receive` hung for
|
|
1041
|
-
* over two minutes on a session whose message was already verified and sitting in the
|
|
1042
|
-
* transcript, and no `since_seq` catch-up could get past it either, because this ran first.
|
|
1043
|
-
*
|
|
1044
|
-
* **Reading your own stored messages must never depend on the network.** The revival is an
|
|
1045
|
-
* optimisation on top of a local read — it makes the session sendable again and pulls anything
|
|
1046
|
-
* parked — and both are worth nothing if the read cannot complete. So it runs alongside.
|
|
1047
|
-
*
|
|
1048
|
-
* WHY A READ MAY TRIGGER THIS AND AN INBOUND DIAL MAY NOT. Andre's tenet is about what a REMOTE
|
|
1049
|
-
* party can cause — *"an open connection that a malicious agent can farm for."* Reviving because
|
|
1050
|
-
* a peer dialled us would hand that lever to the peer. A read is the OPERATOR asking, on their
|
|
1051
|
-
* own machine, for their own session.
|
|
1052
|
-
*
|
|
1053
|
-
* A refusal is no longer reported inline on this call; `session.revive.declined` names it in the
|
|
1054
|
-
* log instead. That is the lesser evil against a read that never returns.
|
|
1055
|
-
*/
|
|
1056
|
-
if (record && record.status === "interrupted") {
|
|
1057
|
-
void sessionNodeManager.reviveIfNeededForRead(agentName, sessionId).catch(() => { });
|
|
1058
|
-
}
|
|
1059
|
-
// Catch-up mode is GONE (Andre, live test 2026-09-13): the ordinary read already returns
|
|
1060
|
-
// everything unread. Refused by name so an old caller learns that, rather than being ignored.
|
|
1061
|
-
if (params?.since_seq !== undefined) {
|
|
1062
|
-
return {
|
|
1063
|
-
ok: false,
|
|
1064
|
-
reason: "since_seq_removed",
|
|
1065
|
-
guidance: "since_seq no longer exists. Call cello_receive without it — it returns every unread message at once. For the whole conversation, use cello_transcript.",
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* ─── ONE READ, EVERY UNREAD MESSAGE, ONE BOOKMARK ───────────────────────────────────────────
|
|
1070
|
-
*
|
|
1071
|
-
* The bookmark is the agent's persisted watermark for this session. Everything received after it
|
|
1072
|
-
* is handed over together and the bookmark moves to the last of them.
|
|
1073
|
-
*
|
|
1074
|
-
* It replaced three bookmarks, and the live test on 2026-09-13 shows why: catch-up moved two of
|
|
1075
|
-
* them, the plain read consulted a third that belonged to the CONNECTION and started empty on
|
|
1076
|
-
* every new one — so after catching up the next read re-served the old message, and every `/mcp`
|
|
1077
|
-
* reconnect or `cello` CLI command started over at the oldest message in the conversation.
|
|
1078
|
-
*
|
|
1079
|
-
* The bookmark moves to the LAST message handed over, not through a gap-stopping walk. A hole —
|
|
1080
|
-
* a blocked message, a document frame, a row that could not be decrypted — would otherwise strand
|
|
1081
|
-
* every later message, re-served on every call. A row that could not be written is reported on
|
|
1082
|
-
* the empty answer (`content_undeliverable`) rather than by holding the bookmark behind it.
|
|
1083
|
-
*/
|
|
1084
|
-
const from = record ? record.counterparty_pubkey : null;
|
|
1085
|
-
const takeUnread = () => {
|
|
1086
|
-
const watermark = sessionNodeManager.getLastDeliveredSeq(agentName, sessionId);
|
|
1087
|
-
// Asked ~47x/second while blocked, so the cheap SQL probe runs first and the transcript is
|
|
1088
|
-
// decoded only when something is actually there.
|
|
1089
|
-
if (!sessionNodeManager.findNextReceivedAfter(agentName, sessionId, watermark))
|
|
1090
|
-
return null;
|
|
1091
|
-
const unread = sessionNodeManager.readTranscript(agentName, sessionId).messages
|
|
1092
|
-
.filter((m) => m.direction === "received" && m.sequence > watermark);
|
|
1093
|
-
if (unread.length === 0)
|
|
1094
|
-
return null;
|
|
1095
|
-
sessionNodeManager.advanceLastDeliveredSeq(agentName, sessionId, unread[unread.length - 1].sequence);
|
|
1096
|
-
clearTelegramRung(agentName, sessionId); // M8C-TGDOOR-1: read clears the ring
|
|
1097
|
-
return Object.assign(unread, { previousWatermark: watermark });
|
|
1098
|
-
};
|
|
1099
|
-
const deliver = (unread, correlationId) => {
|
|
1100
|
-
logger.info("session.receive.delivered", {
|
|
1101
|
-
sessionId, agentName, connectionId, count: unread.length,
|
|
1102
|
-
firstSequence: unread[0].sequence, lastSequence: unread[unread.length - 1].sequence,
|
|
1103
|
-
attendance: attendanceCount(agentName), correlationId,
|
|
1104
|
-
});
|
|
1105
|
-
const lastText = unread[unread.length - 1].text.trimEnd();
|
|
1106
|
-
// The turn signal belongs to the LAST message: it is what says whose move it is now.
|
|
1107
|
-
const signalGuidance = lastText.endsWith("[[WRAP]]")
|
|
1108
|
-
? "Counterparty wrapped. Call cello_close_session now — do not reply."
|
|
1109
|
-
: lastText.endsWith("[[OVER]]")
|
|
1110
|
-
? "Counterparty's turn is done. Counterparty has indicated they are expecting a reply — use cello_send to reply."
|
|
1111
|
-
: /\[\[STANDBY EST:\d+m\]\]$/.test(lastText)
|
|
1112
|
-
? "Counterparty is working and will follow up when done — no response expected. To block: call cello_receive with a longer timeout_ms. To check back later: schedule a cron and call cello_receive then."
|
|
1113
|
-
: undefined;
|
|
1114
|
-
// DOD-M12B-AWAY-MARK-1: an away auto-reply is an ordinary message leaf, so without the mark a
|
|
1115
|
-
// read is positive evidence a person answered. The content is passed through WHOLE.
|
|
1116
|
-
const autoReplyCount = unread.filter((m) => isAutoReplyMarked(m.text)).length;
|
|
1117
|
-
// A message that arrived but could not be written to the transcript has no row, so the
|
|
1118
|
-
// bookmark just moved past it. Say so HERE, on the answer that crossed it — otherwise the
|
|
1119
|
-
// operator sees only a gap in the sequence numbers (review of 86ec7524).
|
|
1120
|
-
const lastSeq = unread[unread.length - 1].sequence;
|
|
1121
|
-
// Only the lost messages THIS read moved past — the in-memory set is never pruned, so without
|
|
1122
|
-
// the lower bound every later read would repeat the warning until a restart.
|
|
1123
|
-
const readFrom = unread.previousWatermark ?? -1;
|
|
1124
|
-
const crossed = sessionNodeManager.getUndeliverableSeqs(agentName, sessionId).filter((s) => s > readFrom && s <= lastSeq);
|
|
1125
|
-
const lag = replyLagFor(sessionNodeManager, agentName, sessionId);
|
|
1126
|
-
return {
|
|
1127
|
-
...(lag ? {
|
|
1128
|
-
reply_lag: lag,
|
|
1129
|
-
reply_lag_guidance: `Their last ${lag.replies} replies were written before they saw your message at position ${lag.your_unseen_seq}, so they may be answering something older. Check whether it reached them before assuming they disagree.`,
|
|
1130
|
-
} : {}),
|
|
1131
|
-
ok: true,
|
|
1132
|
-
session_id: sessionId,
|
|
1133
|
-
count: unread.length,
|
|
1134
|
-
...(crossed.length > 0 ? {
|
|
1135
|
-
undeliverable_sequences: [...crossed],
|
|
1136
|
-
undeliverable_guidance: `${crossed.length} message(s) arrived but could not be written to the local transcript, so they cannot be shown. This is a fault on THIS machine (check disk space and ~/.cello permissions), not the counterparty. Ask them to resend once it is fixed.`,
|
|
1137
|
-
} : {}),
|
|
1138
|
-
messages: unread.map((m) => ({
|
|
1139
|
-
sequence: m.sequence,
|
|
1140
|
-
content: m.text,
|
|
1141
|
-
from,
|
|
1142
|
-
...(isAutoReplyMarked(m.text) ? { auto_reply: true } : {}),
|
|
1143
|
-
})),
|
|
1144
|
-
...(autoReplyCount > 0 ? { auto_reply_guidance: AUTO_REPLY_GUIDANCE } : {}),
|
|
1145
|
-
...refusalsField(sessionNodeManager, agentName, sessionId, connectionId),
|
|
1146
|
-
attendance: attendingNow(agentName),
|
|
1147
|
-
...(signalGuidance !== undefined ? { guidance: signalGuidance } : {}),
|
|
1148
|
-
};
|
|
1149
|
-
};
|
|
1150
|
-
// D4b: a transcript-only session has no live node, so nothing new will ever arrive. Hand over
|
|
1151
|
-
// what is unread without waiting; if nothing is, say where the history is.
|
|
1152
|
-
if (transcriptOnly) {
|
|
1153
|
-
const unread = takeUnread();
|
|
1154
|
-
if (unread)
|
|
1155
|
-
return deliver(unread, randomUUID());
|
|
1156
|
-
return {
|
|
1157
|
-
ok: false,
|
|
1158
|
-
reason: "session_not_live",
|
|
1159
|
-
attendance: attendingNow(agentName),
|
|
1160
|
-
guidance: "This session exists only as a stored transcript and nothing in it is unread. Read the whole conversation with cello_transcript.",
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
const rawTimeout = params?.timeout_ms;
|
|
1164
|
-
const timeoutMs = typeof rawTimeout === "number" && Number.isFinite(rawTimeout) && rawTimeout >= 0
|
|
1165
|
-
? rawTimeout
|
|
1166
|
-
: RECEIVE_DEFAULT_TIMEOUT_MS;
|
|
1167
|
-
const deadline = Date.now() + timeoutMs;
|
|
1168
|
-
// DOD-COATTEND-VISIBLE-1 AC3: before this line the plain blocking receive wrote NOTHING to the
|
|
1169
|
-
// log on ANY outcome — the only event in the path was `session.receive.since_seq`, which is the
|
|
1170
|
-
// OTHER branch — so a session having its message taken by a sibling left no trace anywhere, for
|
|
1171
|
-
// the operator in the moment or for anyone reading the log afterwards. One correlationId threads
|
|
1172
|
-
// every exit below.
|
|
1173
|
-
const receiveCorrelationId = randomUUID();
|
|
1174
|
-
// Recomputed at EVERY exit, never snapshotted here (review MEDIUM): the loop below blocks for
|
|
1175
|
-
// up to timeout_ms — 30 s by default — and sessions attach and detach inside that window. A
|
|
1176
|
-
// count captured before the wait is stale exactly when it matters, and it is the number the
|
|
1177
|
-
// operator reads and the number interpolated into the guidance. It is an O(connections) walk
|
|
1178
|
-
// over a handful of entries; there is nothing to save by caching it.
|
|
1179
|
-
for (;;) {
|
|
1180
|
-
// TERMINAL FIRST (DOD-COATTEND-1). Reading the DURABLE RECORD means a sealed session's rows
|
|
1181
|
-
// are still present, so a content read would keep delivering on a session that has already
|
|
1182
|
-
// terminated — and F1-b's `session_sealed` answer would never be reached. Under the old
|
|
1183
|
-
// destructive queue this ordering did not matter, because the seal EVICTED the buffer and the
|
|
1184
|
-
// read found nothing. It matters now, so the terminal answer is checked before the record.
|
|
1185
|
-
//
|
|
1186
|
-
// F1-b: the session sealed while we were (or before we started) waiting — return the
|
|
1187
|
-
// terminal answer instead of hanging to timeout. unread_count reports messages that
|
|
1188
|
-
// were evicted unread (still durable — recoverable via cello_get_transcript).
|
|
1189
|
-
const terminal = sessionNodeManager.peekTerminalMarker(agentName, sessionId);
|
|
1190
|
-
if (terminal) {
|
|
1191
|
-
const sealedRoot = sessionNodeManager.getSealedRootHex(agentName, sessionId);
|
|
1192
|
-
// ⚠️ Unread messages are deliberately NOT handed over here, although that is the one place
|
|
1193
|
-
// "every unread message" does not hold. A sealed conversation's leftover message is not live
|
|
1194
|
-
// work: on 2026-08-05 an agent obeyed a [[STANDBY]] from a session that had ended. The
|
|
1195
|
-
// operator is pointed at cello_transcript instead (DOD-TERMINAL-WAKE-1).
|
|
1196
|
-
// AC3 covers "both outcomes" — got something / got nothing. This is the third exit from the
|
|
1197
|
-
// same silent handler, and leaving it silent would reproduce the defect one branch over.
|
|
1198
|
-
logger.info("session.receive.sealed", {
|
|
1199
|
-
sessionId, agentName, connectionId, attendance: attendanceCount(agentName),
|
|
1200
|
-
unreadCount: terminal.unreadCount, correlationId: receiveCorrelationId,
|
|
1201
|
-
});
|
|
1202
|
-
return {
|
|
1203
|
-
ok: true,
|
|
1204
|
-
type: "session_sealed",
|
|
1205
|
-
session_id: sessionId,
|
|
1206
|
-
...(sealedRoot ? { sealed_root: sealedRoot } : {}),
|
|
1207
|
-
unread_count: terminal.unreadCount,
|
|
1208
|
-
attendance: attendingNow(agentName),
|
|
1209
|
-
// DOD-M15-REFUSED-INBOUND-SILENT-1: the LAST door, and without this it is a closed one.
|
|
1210
|
-
// This check runs FIRST in the loop, so once a session is sealed the quiet exit is
|
|
1211
|
-
// unreachable for it forever — and the guidance here sends the operator to
|
|
1212
|
-
// `cello_transcript`, which cannot show a message that was never ingested. The likely
|
|
1213
|
-
// sequence is exactly the bad one: skew, silence, the operator gives up and seals, and the
|
|
1214
|
-
// explanation becomes unreachable through every door they are pointed at.
|
|
1215
|
-
...refusalsField(sessionNodeManager, agentName, sessionId, connectionId),
|
|
1216
|
-
guidance: terminal.unreadCount > 0
|
|
1217
|
-
? `The session has been sealed by both parties. ${terminal.unreadCount} message(s) arrived that were not read live — call cello_transcript to retrieve the full sealed history. No further actions are required on this session.`
|
|
1218
|
-
: "The session has been sealed by both parties. The full history is available via cello_transcript. No further actions are required on this session.",
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
const unread = takeUnread();
|
|
1222
|
-
if (unread)
|
|
1223
|
-
return deliver(unread, receiveCorrelationId);
|
|
1224
|
-
// 3) Out of time — non-blocking-equivalent empty answer.
|
|
1225
|
-
const remaining = deadline - Date.now();
|
|
1226
|
-
if (remaining <= 0) {
|
|
1227
|
-
// ─── DOD-COATTEND-1 (review F2): a LOCAL failure must not wear the counterparty's label ───
|
|
1228
|
-
//
|
|
1229
|
-
// Content arrived, was verified and leafed, and its plaintext failed to reach the
|
|
1230
|
-
// transcript. Since Tier 1 the transcript IS the delivery path, so that message can never
|
|
1231
|
-
// be handed to any session. Every word of the empty answer below is wrong for this case:
|
|
1232
|
-
// waiting cannot help, the transcript does not hold it, and "do not resend your last
|
|
1233
|
-
// message" is aimed at the one party who could still fix it. Answer with the actual fault,
|
|
1234
|
-
// above the branch that would otherwise substitute for it.
|
|
1235
|
-
const undeliverable = sessionNodeManager.getUndeliverableSeqs(agentName, sessionId);
|
|
1236
|
-
if (undeliverable.length > 0) {
|
|
1237
|
-
logger.error("session.receive.undeliverable", {
|
|
1238
|
-
sessionId, agentName, connectionId, sequences: undeliverable,
|
|
1239
|
-
attendance: attendanceCount(agentName), correlationId: receiveCorrelationId,
|
|
1240
|
-
});
|
|
1241
|
-
return {
|
|
1242
|
-
ok: true,
|
|
1243
|
-
count: 0,
|
|
1244
|
-
messages: [],
|
|
1245
|
-
reason: "content_undeliverable",
|
|
1246
|
-
attendance: attendingNow(agentName),
|
|
1247
|
-
// A local write failure and a refused message look identical from the operator's chair —
|
|
1248
|
-
// nothing arrived. Carrying the refusal here keeps the two distinguishable.
|
|
1249
|
-
...refusalsField(sessionNodeManager, agentName, sessionId, connectionId),
|
|
1250
|
-
undeliverable_sequences: [...undeliverable],
|
|
1251
|
-
guidance: `${undeliverable.length} message(s) arrived but could not be written to the local transcript, so they cannot be delivered — this is a fault on THIS machine (check disk space and ~/.cello permissions; see transcript.message.record.failed in the daemon log), not a quiet counterparty. Do not wait: waiting cannot recover them. Ask the counterparty to resend once the local fault is fixed.`,
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
const liveness = sessionNodeManager.getSessionLiveness(agentName, sessionId);
|
|
1255
|
-
const attendance = attendanceCount(agentName);
|
|
1256
|
-
// M8B F16: a dead session must not return the SAME null timeout as a
|
|
1257
|
-
// quiet-but-healthy one. The liveness signal (session.liveness.changed → gone,
|
|
1258
|
-
// tracked per session by the node manager) finally reaches the MCP surface here.
|
|
1259
|
-
if (liveness === "gone") {
|
|
1260
|
-
logger.info("session.receive.empty", {
|
|
1261
|
-
sessionId, agentName, connectionId, timeoutMs, attendance,
|
|
1262
|
-
liveness, correlationId: receiveCorrelationId,
|
|
1263
|
-
});
|
|
1264
|
-
return {
|
|
1265
|
-
ok: true,
|
|
1266
|
-
count: 0,
|
|
1267
|
-
messages: [],
|
|
1268
|
-
attendance: attendingNow(agentName),
|
|
1269
|
-
// DOD-M15-NO-SILENT-REFUSAL-1 — the exit that does not merely stay silent, it MISLEADS.
|
|
1270
|
-
// A version-skewed peer never gets an ACK, so it eventually drops the direct path and
|
|
1271
|
-
// lands HERE, pre-empting the quiet exit. A wrong explanation plus an action is worse
|
|
1272
|
-
// than saying nothing at all.
|
|
1273
|
-
...refusalsField(sessionNodeManager, agentName, sessionId, connectionId),
|
|
1274
|
-
reason: "counterparty_gone",
|
|
1275
|
-
liveness: "gone",
|
|
1276
|
-
/**
|
|
1277
|
-
* ⚠️ **NAMES WHAT WAS OBSERVED, AND `gone` IS A SMALLER FACT THAN IT SOUNDS.**
|
|
1278
|
-
*
|
|
1279
|
-
* The only thing that produced this state is `onPeerDisconnect` firing for the peer id
|
|
1280
|
-
* recorded as the counterparty's session peer — one libp2p connection went away. It
|
|
1281
|
-
* does not establish that they crashed, went offline, or left. The previous wording
|
|
1282
|
-
* said "it may have crashed or gone offline" and then told the operator to seal, which
|
|
1283
|
-
* is a network story for something that is very often a verification fault: a peer
|
|
1284
|
-
* whose messages this side keeps refusing is never acknowledged, drops the direct path,
|
|
1285
|
-
* and arrives here looking exactly like a crash.
|
|
1286
|
-
*
|
|
1287
|
-
* **And it must not lead with the seal.** A seal is irreversible and, taken on this
|
|
1288
|
-
* evidence, is precisely the truncated close DOD-M15-WITHHOLD-SEAL-1 exists to stop.
|
|
1289
|
-
* The refusals spread above are the thing to read first — when they are present they
|
|
1290
|
-
* ARE the reason, and none of them is fixed by sealing.
|
|
1291
|
-
*/
|
|
1292
|
-
guidance: "The direct connection to the counterparty's session peer has dropped (liveness: gone). That is what was observed — it does not establish that they crashed or went offline. No more content will arrive on the direct path and a reply cannot reach them there. CHECK `refusals` — HERE IF PRESENT, AND IN cello_inbox EITHER WAY — BEFORE CONCLUDING ANYTHING: if this side has been refusing their messages, a refused sender is never acknowledged and eventually drops the connection, and that is a fault to fix rather than a counterparty who left. A refusal is shown once per window, so another window may already have taken it and this answer can be empty while the cause is real. Otherwise the ordinary causes are a restart, a network change, or them closing their agent — confirm out of band before concluding anything. Sealing with cello_close_session ENDS the conversation permanently and cannot be undone; do it when you have decided the conversation is over, not to clear this state. If you DO decide it is over and the counterparty never co-closes, a unilateral seal becomes available after the directory's delivery-grace window — that is the exit, not the first move.",
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
// DOD-M12B-ACK-1: the same rule one step short of 'gone'. A session whose writes are
|
|
1296
|
-
// failing looks EXACTLY like a quiet-but-healthy one from here — nothing arrives, and the
|
|
1297
|
-
// old answer said "nothing arrived", so an operator waited. Measured 2026-08-17: one
|
|
1298
|
-
// session reported healthy for 70 minutes while every message it sent was parking.
|
|
1299
|
-
// Distinct from 'gone' on purpose: the connection is up, content may still arrive FROM
|
|
1300
|
-
// them, and the session must not be steered toward a seal on this evidence.
|
|
1301
|
-
if (liveness === "impaired") {
|
|
1302
|
-
const impairment = sessionNodeManager.getSessionImpairment(agentName, sessionId);
|
|
1303
|
-
logger.info("session.receive.empty", {
|
|
1304
|
-
sessionId, agentName, connectionId, timeoutMs, attendance,
|
|
1305
|
-
liveness, impairmentCause: impairment?.cause, impairmentRetained: impairment?.retained,
|
|
1306
|
-
correlationId: receiveCorrelationId,
|
|
1307
|
-
});
|
|
1308
|
-
// EVERY CLAUSE BELOW IS SOMETHING WE ACTUALLY KNOW. The first version of this branch
|
|
1309
|
-
// asserted that the message "was parked for the relay to hand over" and that the caller
|
|
1310
|
-
// must not resend — and both are false in real cases: a refused park whose durable
|
|
1311
|
-
// enqueue was dropped is GONE, and `cello_send` already told the caller to send it again.
|
|
1312
|
-
// A receive that contradicts that, later, while the agent is waiting, is worse than
|
|
1313
|
-
// silence. The failing write can also be an ACK we owed THEM, in which case the caller
|
|
1314
|
-
// sent nothing at all and has no last message to reason about.
|
|
1315
|
-
const what = impairment?.cause === "delivery_ack"
|
|
1316
|
-
? "Our acknowledgement to the counterparty could not be sent (liveness: impaired) — this is about a receipt we owe them, not about anything you sent."
|
|
1317
|
-
// `029c` review F7: a content-key fault is LOCAL and the connection is fine. Saying "did
|
|
1318
|
-
// not reach the counterparty on the direct path" sends the operator to inspect a network
|
|
1319
|
-
// that is working, and to blame a peer who is not involved.
|
|
1320
|
-
: impairment?.cause === "content_key"
|
|
1321
|
-
? "Your own last send could not be encrypted for this conversation, so it was never put on the wire (liveness: impaired). THIS IS A FAULT ON THIS MACHINE — the connection is fine and your counterparty is not involved."
|
|
1322
|
-
: "Your own last send did not reach the counterparty on the direct path (liveness: impaired).";
|
|
1323
|
-
const whatNext = impairment?.retained === "parked" ? " That message went to the relay to hand over instead — do NOT resend it: it is not lost, and a resend takes a second position in the record."
|
|
1324
|
-
: impairment?.retained === "durable" ? " That message is queued locally and will be re-sent automatically — do NOT resend it: an identical resend is not separately queued."
|
|
1325
|
-
: impairment?.retained === "lost" ? " That message could NOT be queued and is lost — send it again. (cello_send said the same when it failed.)"
|
|
1326
|
-
: "";
|
|
1327
|
-
return {
|
|
1328
|
-
ok: true,
|
|
1329
|
-
count: 0,
|
|
1330
|
-
messages: [],
|
|
1331
|
-
attendance: attendingNow(agentName),
|
|
1332
|
-
reason: "delivery_impaired",
|
|
1333
|
-
liveness: "impaired",
|
|
1334
|
-
// DOD-M15-REFUSED-INBOUND-SILENT-1, the item I filed POST-LAUNCH as "deferred, not
|
|
1335
|
-
// lost". Taken now instead: it was only safe to spread refusals into an exit once the
|
|
1336
|
-
// notice and its advice travel together (see `refusalsField`), and they do — so this
|
|
1337
|
-
// exit cannot carry the reason without also carrying what to do about it.
|
|
1338
|
-
...refusalsField(sessionNodeManager, agentName, sessionId, connectionId),
|
|
1339
|
-
...(impairment ? { impairment } : {}),
|
|
1340
|
-
guidance: `Nothing arrived. ${what}${whatNext} Their connection is still up, so content may still arrive and this can clear on its own — call cello_status for this session rather than assuming the counterparty is ignoring you.`,
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
logger.info("session.receive.empty", {
|
|
1344
|
-
sessionId, agentName, connectionId, timeoutMs, attendance,
|
|
1345
|
-
liveness, correlationId: receiveCorrelationId,
|
|
1346
|
-
});
|
|
1347
|
-
// AC6: the QUIET answer carries it too. This is the exit a session with no doorbell to
|
|
1348
|
-
// learn from is most likely to reach — it attached, found nothing waiting, and would
|
|
1349
|
-
// otherwise have no way to know another window holds the same agent.
|
|
1350
|
-
/**
|
|
1351
|
-
* DOD-M15-REFUSED-INBOUND-SILENT-1 — THE exit this line exists for.
|
|
1352
|
-
*
|
|
1353
|
-
* "I waited and nothing came" is the moment an operator concludes the other person stopped
|
|
1354
|
-
* replying. If messages ARE arriving and being refused, the reason is known, it is already
|
|
1355
|
-
* written to the log with impact and guidance, and this is the one place the person who
|
|
1356
|
-
* needs it is actually looking. Guidance below changes to match: telling someone to "call
|
|
1357
|
-
* again and keep waiting" while their peer's every message is being refused for a version
|
|
1358
|
-
* skew is advice that cannot work.
|
|
1359
|
-
*/
|
|
1360
|
-
const quietRefusals = refusalsField(sessionNodeManager, agentName, sessionId, connectionId);
|
|
1361
|
-
return {
|
|
1362
|
-
ok: true,
|
|
1363
|
-
// The same shape as a read that found messages, so a caller reads `messages` either way.
|
|
1364
|
-
count: 0,
|
|
1365
|
-
messages: [],
|
|
1366
|
-
attendance: attendingNow(agentName),
|
|
1367
|
-
...quietRefusals,
|
|
1368
|
-
// ONE source for the refusal advice (see `refusalsField`). It used to be written out
|
|
1369
|
-
// again here, which is how the batch exit came to carry the notice without it.
|
|
1370
|
-
// ONE source for the refusal advice (see `refusalsField`), and it is now the header for
|
|
1371
|
-
// the kinds actually present rather than one sentence that is false for most of them.
|
|
1372
|
-
guidance: quietRefusals.refusal_guidance !== undefined
|
|
1373
|
-
? `No content arrived within timeout_ms. ${quietRefusals.refusal_guidance}`
|
|
1374
|
-
: "No content arrived within timeout_ms. Call cello_receive again to keep waiting — do not resend your last message. Or read cello_transcript for the full session history.",
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
await new Promise((r) => setTimeout(r, Math.min(20, remaining)));
|
|
1378
|
-
}
|
|
1379
|
-
};
|
|
1380
|
-
handlers.set("cello_receive", handleReceive);
|
|
1381
|
-
}
|
|
1382
|
-
//# sourceMappingURL=session-content-handlers.js.map
|