@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
package/dist/session-relay.js
DELETED
|
@@ -1,1636 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CELLO Daemon — THE BLIND WITNESS, AND STAYING REACHABLE THROUGH IT
|
|
3
|
-
*
|
|
4
|
-
* Split out of `session-node-manager.ts`. A relay countersigns the ORDER of a conversation and is
|
|
5
|
-
* given only hashes, never plaintext — so what lives here is everything about talking to one:
|
|
6
|
-
* connecting a session to its witness, proving key possession to it, holding a circuit reservation
|
|
7
|
-
* open so a counterparty behind a NAT can still reach us, watching that reservation decay and
|
|
8
|
-
* renewing it, quarantining a relay that misbehaves, and detaching cleanly when a session ends.
|
|
9
|
-
*
|
|
10
|
-
* **Moved verbatim, comments included.**
|
|
11
|
-
*
|
|
12
|
-
* ⚠️ **A RELAY IS NOT TRUSTED, AND THE CODE HERE IS WHERE THAT IS ENFORCED.** It authenticates to
|
|
13
|
-
* us as much as we authenticate to it; it is quarantined rather than believed when it misbehaves;
|
|
14
|
-
* and the one thing it must never learn — the operator's own address — is why the circuit-dial
|
|
15
|
-
* authorisation below is narrow rather than convenient. Anything added here that widens what a
|
|
16
|
-
* relay can see or do is a protocol change, not a refactor.
|
|
17
|
-
*/
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
19
|
-
import * as lp from "it-length-prefixed";
|
|
20
|
-
import { decode } from "cbor-x";
|
|
21
|
-
import { isValidMultiaddr } from "@cello-protocol/transport";
|
|
22
|
-
import { contentHashFor } from "./wire-content-hash.js";
|
|
23
|
-
import { extractErrorMessage } from "./error-message.js";
|
|
24
|
-
import { RelayReceiptStore } from "./relay-receipt-store.js";
|
|
25
|
-
import { SessionSealLeafStore } from "./session-seal-leaf-store.js";
|
|
26
|
-
import { AgentRelayClient } from "./session-relay-client.js";
|
|
27
|
-
import { mergeRelayEndpoints } from "./relay-endpoints.js";
|
|
28
|
-
import { RELAY_QUARANTINE_MS, SR_RESERVATION_MAX_RETRIES, heldRelayIdsOf, relayPeerIdOf, } from "./session-node-types.js";
|
|
29
|
-
export class SessionRelay {
|
|
30
|
-
#ctx;
|
|
31
|
-
constructor(ctx) {
|
|
32
|
-
this.#ctx = ctx;
|
|
33
|
-
}
|
|
34
|
-
/** A getter so the moved queries still read `this.#db` and narrow exactly as they did. */
|
|
35
|
-
get #db() {
|
|
36
|
-
return this.#ctx.db();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* DOD-M15-RELAYAUTH-1: authenticate a fresh standing receiver to its reservation relay over the
|
|
40
|
-
* CELLO relay protocol — proof of K_local key possession, not a session. Reuses the SAME
|
|
41
|
-
* `#relayClients` cache `connectSessionRelay`/`#resolveSealTransport` read from, keyed
|
|
42
|
-
* identically (`${agentName}::${relayPeerId}`), so a session created moments later on this same
|
|
43
|
-
* relay finds an already-authenticated client instead of dialing and authenticating twice.
|
|
44
|
-
*
|
|
45
|
-
* The manager holds no K_local (M12-P15's own rationale for `#detachedRelayClientBuilder`) —
|
|
46
|
-
* without a builder wired (a narrow startup race, or a test harness that never wires one), this
|
|
47
|
-
* is a no-op and the relay's own grace-window revoke is the backstop, not a defect in this path.
|
|
48
|
-
*/
|
|
49
|
-
async authenticateStandingReceiver(agentName, node, relayPeerId, heldCircuitAddr, correlationId) {
|
|
50
|
-
const clientKey = `${agentName}::${relayPeerId}`;
|
|
51
|
-
let client = this.#ctx.relayClients.get(clientKey);
|
|
52
|
-
if (!client) {
|
|
53
|
-
if (!this.#ctx.relayReceiptStore && this.#db)
|
|
54
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
55
|
-
if (!this.#ctx.sealLeafStore && this.#db)
|
|
56
|
-
this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
|
|
57
|
-
/**
|
|
58
|
-
* ⚠️ SPLIT, NOT AN ANCHORED STRIP. The held address is
|
|
59
|
-
* `/ip4/…/tcp/…/p2p/<relay>/p2p-circuit/p2p/<self>` — the `/p2p-circuit` marker is in the
|
|
60
|
-
* MIDDLE, not at the end, so a `/\/p2p-circuit$/` replace matches nothing and silently
|
|
61
|
-
* hands the relay client a circuit address as its DIAL address. Measured, not assumed:
|
|
62
|
-
* that first version failed this file's own test because the client could not dial.
|
|
63
|
-
*/
|
|
64
|
-
const baseRelayAddr = heldCircuitAddr.split("/p2p-circuit")[0] ?? heldCircuitAddr;
|
|
65
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
|
|
66
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
67
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
68
|
-
ownChainStore: this.#ctx.ownChainStore ?? undefined,
|
|
69
|
-
// DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
|
|
70
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
71
|
-
});
|
|
72
|
-
if (!client) {
|
|
73
|
-
/**
|
|
74
|
-
* Review M5: this was a `debug` line, and it is not a debug-level event.
|
|
75
|
-
*
|
|
76
|
-
* If no builder is wired we return without proving key possession, the relay revokes this
|
|
77
|
-
* receiver's reservation about fifteen seconds later, and the agent stops being reachable
|
|
78
|
-
* from behind NAT — while reporting itself perfectly healthy. Calling that "a narrow startup
|
|
79
|
-
* race" in a comment concedes it happens in production, and a system that is unreachable
|
|
80
|
-
* must not be quieter about it than a system that is merely slow.
|
|
81
|
-
*/
|
|
82
|
-
this.#ctx.logger.warn("session.standing_receiver.relay_auth.no_builder", {
|
|
83
|
-
agentName,
|
|
84
|
-
relayPeerId,
|
|
85
|
-
correlationId,
|
|
86
|
-
impact: "this receiver cannot prove key possession, so the relay will revoke its reservation and " +
|
|
87
|
-
"the agent becomes unreachable from behind NAT — nobody can start a session with it — " +
|
|
88
|
-
"even though it still reports itself online.",
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
this.#ctx.relayClients.set(clientKey, client);
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* ⚠️ `proveReservation`, NOT `connect`. Review HIGH-1: `connect()` short-circuits on the
|
|
96
|
-
* client's cached stream, which belongs to whichever node connected FIRST — so every
|
|
97
|
-
* REPLACEMENT standing receiver (the one built behind each new session) sent nothing, the relay
|
|
98
|
-
* never saw its transport identity, and its reservation was revoked ~15s later. The agent then
|
|
99
|
-
* churned reserve→revoke→rebuild for the life of the conversation, holding no usable circuit
|
|
100
|
-
* address, so while you were talking to one person nobody else could reach you.
|
|
101
|
-
*
|
|
102
|
-
* `proveReservation` always opens its own stream from THIS node, and marks it so the relay
|
|
103
|
-
* proves possession without rebinding the agent's delivery target away from the live session.
|
|
104
|
-
*/
|
|
105
|
-
const proven = await client.proveReservation(node);
|
|
106
|
-
/**
|
|
107
|
-
* DOD-M15-RELAYSLOTS-1: keep the relay's refusal where the OPERATOR can reach it.
|
|
108
|
-
*
|
|
109
|
-
* The log line below is a good log line and it is not an answer to "why is my agent
|
|
110
|
-
* unreachable?" — nobody opens the file. The relay now refuses for reasons a person can act on
|
|
111
|
-
* (no token yet, too many sessions still open, this relay is misconfigured), each with its own
|
|
112
|
-
* next step, and every one of them is useless if it stops at a log.
|
|
113
|
-
*/
|
|
114
|
-
if (!proven) {
|
|
115
|
-
const refusal = client.getLastAuthRefusal();
|
|
116
|
-
/**
|
|
117
|
-
* Review L2: the `else` is not symmetry for its own sake. `proveReservation` also fails for
|
|
118
|
-
* transport reasons, which leave `getLastAuthRefusal()` null — and without this branch the
|
|
119
|
-
* PREVIOUS refusal stayed in the map, so `cello_status` went on showing a cause and an
|
|
120
|
-
* affordance for something that was no longer what was wrong.
|
|
121
|
-
*/
|
|
122
|
-
if (refusal)
|
|
123
|
-
this.#ctx.srRelayRefusal.set(agentName, { ...this.#ctx.withDirectoryCause(agentName, refusal), relayPeerId });
|
|
124
|
-
else
|
|
125
|
-
this.#ctx.srRelayRefusal.delete(agentName);
|
|
126
|
-
/**
|
|
127
|
-
* DOD-M15-RELAYSLOTS-1 clause 9 — **ACT on the classification, do not merely record it.**
|
|
128
|
-
* A relay-side fault means a different relay will work now, so quarantine this one and
|
|
129
|
-
* rebuild the receiver against the rest of the pool. Everything else stays put: a token
|
|
130
|
-
* problem reproduces on every relay, and walking the fleet would turn one client fault into
|
|
131
|
-
* what looks like a fleet-wide outage.
|
|
132
|
-
*/
|
|
133
|
-
if (refusal?.tryAnotherRelay && !this.#ctx.shuttingDown) {
|
|
134
|
-
/**
|
|
135
|
-
* ⚠️ **THE QUARANTINE ACTS; THE REBUILD USED TO AND NO LONGER CAN — 056-SLOTDEAD.**
|
|
136
|
-
*
|
|
137
|
-
* This used to `rebuildStandingReceiver` so the new node would reserve against the rest of
|
|
138
|
-
* the pool. A rebuilt receiver reserves NOTHING now (055-ONDEMAND), so the rebuild bought
|
|
139
|
-
* nothing and cost the agent its transport identity. The quarantine is the half that still
|
|
140
|
-
* does the work: the next offer reserves against the pool minus this relay.
|
|
141
|
-
*/
|
|
142
|
-
this.#quarantineRelay(agentName, relayPeerId, refusal.reason);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
this.#ctx.srRelayRefusal.delete(agentName);
|
|
147
|
-
}
|
|
148
|
-
this.#ctx.logger.info("session.standing_receiver.relay_auth.result", {
|
|
149
|
-
agentName,
|
|
150
|
-
relayPeerId,
|
|
151
|
-
// The node that actually proved itself — without this, HIGH-1 was invisible in the logs: the
|
|
152
|
-
// line said `connected: true` for a receiver that had sent nothing.
|
|
153
|
-
nodePeerId: node.getPeerId(),
|
|
154
|
-
proven,
|
|
155
|
-
...(proven ? {} : {
|
|
156
|
-
refusalReason: client.getLastAuthRefusal()?.reason ?? "no_relay_verdict",
|
|
157
|
-
tryAnotherRelay: client.getLastAuthRefusal()?.tryAnotherRelay ?? false,
|
|
158
|
-
}),
|
|
159
|
-
correlationId,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* RELAYSIG-1: the durably-stored, signature-verified relay ordering-record receipts for an agent
|
|
164
|
-
* (optionally a single session). Empty when no receipts have been recorded yet. Read-only.
|
|
165
|
-
*/
|
|
166
|
-
getRelayReceipts(agentPubkeyHex, sessionIdHex) {
|
|
167
|
-
if (!this.#ctx.relayReceiptStore && this.#db) {
|
|
168
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
169
|
-
}
|
|
170
|
-
return this.#ctx.relayReceiptStore?.getAll(agentPubkeyHex, sessionIdHex) ?? [];
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* DOD-M15-REFUSEDEVIDENCE-1 — retain a message refused OUTSIDE `ingestReceivedContent`
|
|
174
|
-
* (`session-content-ingest.ts`).
|
|
175
|
-
*
|
|
176
|
-
* Review F6. The park drain terminally-blocks a message that arrived for an already-committed
|
|
177
|
-
* session, then confirm-deletes the relay copy — the one other route in the tree that discarded
|
|
178
|
-
* refused content, and the highest-suspicion combination in the product: hostile bytes aimed at a
|
|
179
|
-
* conversation somebody has already sealed. Shipped guidance now tells every operator that
|
|
180
|
-
* refused messages are kept, so this is made true rather than the promise narrowed.
|
|
181
|
-
*
|
|
182
|
-
* A thin delegate, not a second implementation: the bound, the dedup, the sequence allocation and
|
|
183
|
-
* the logging are the ones every other refusal uses.
|
|
184
|
-
*/
|
|
185
|
-
quarantineRefusedInbound(agentName, sessionId, reason, content, contentHashHex, senderPubkeyHex, correlationId) {
|
|
186
|
-
return this.#ctx.refusals.quarantineRefusedContent(agentName, sessionId, reason, content, contentHashHex, {
|
|
187
|
-
senderPubkeyHex, correlationId,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
/** The metadata half of a framed quarantine read — everything known ABOUT the message, none of it
|
|
191
|
-
* taken from the message. Split out so the framing module never touches the database. */
|
|
192
|
-
quarantineFrameMeta(agentName, sessionId, rec) {
|
|
193
|
-
return {
|
|
194
|
-
reason: rec.reason,
|
|
195
|
-
senderPubkeyHex: rec.senderPubkeyHex,
|
|
196
|
-
senderLabel: rec.senderPubkeyHex === null ? null : this.#ctx.records.getContactMoniker(agentName, rec.senderPubkeyHex),
|
|
197
|
-
// `attribution` is the column that exists to answer exactly this, so it is read rather than
|
|
198
|
-
// inferred from `sender_sig` being non-null — a stored signature that was never checked
|
|
199
|
-
// against the sender's key would otherwise be reported as VERIFIED.
|
|
200
|
-
signature: rec.attribution === "verified_signature" ? "VERIFIED" : "NOT SIGNED",
|
|
201
|
-
sessionId,
|
|
202
|
-
position: rec.sequence,
|
|
203
|
-
arrivedAtMs: rec.createdAt,
|
|
204
|
-
/**
|
|
205
|
-
* The hash OF THE RETAINED BYTES, recomputed here — not the hash the sender committed to.
|
|
206
|
-
*
|
|
207
|
-
* On the highest-value case in the whole unit those two differ ON PURPOSE:
|
|
208
|
-
* `content_hash_mismatch` means the sender's committed hash does not describe these bytes.
|
|
209
|
-
* Printing their claim over our bytes would label the payload with a hash it does not have,
|
|
210
|
-
* which is the one thing a reader would use this line to check.
|
|
211
|
-
*/
|
|
212
|
-
contentHashHex: Buffer.from(contentHashFor(rec.content, { alg: "sha256", salt: null })).toString("hex"),
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* The current standing receiver node's session-transport coordinates (peer id +
|
|
217
|
-
* listen multiaddrs), or null if it is not ready. These are the addresses a local
|
|
218
|
-
* SessionNegotiator advertises as this node's counterparty endpoint so the initiator
|
|
219
|
-
* can dial it, and the value an inbound session_assignment carries in its
|
|
220
|
-
* counterparty_session_* fields. Read-only — does NOT consume the standing receiver
|
|
221
|
-
* (unlike acceptSession, which hands it off).
|
|
222
|
-
*/
|
|
223
|
-
/**
|
|
224
|
-
* 032-RELAYSPREAD — would this receiver ADMIT an inbound dial from this relay?
|
|
225
|
-
*
|
|
226
|
-
* The gater's inbound carve-out is the security-sensitive half of the spread: only relays whose
|
|
227
|
-
* own reservation is confirmed held earn it, so a directory that merely NAMES a relay cannot dial
|
|
228
|
-
* in behind the gate. Nothing could observe that from outside the manager, and the review found
|
|
229
|
-
* the consequence: substituting the CANDIDATE list for the held list at the `setReservedRelayPeers`
|
|
230
|
-
* call kept every test in the unit green while shipping exactly that hole. A guard whose wiring
|
|
231
|
-
* cannot be observed is a guard nothing can test.
|
|
232
|
-
*
|
|
233
|
-
* Reads the live gater rather than a copy, so it cannot drift from what the gate actually does.
|
|
234
|
-
*/
|
|
235
|
-
isRelayCarvedOutInbound(agentName, relayPeerId) {
|
|
236
|
-
return this.#ctx.standingReceivers.get(agentName)?.gater.holdsInboundCarveOut(relayPeerId) ?? false;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* M7 DOD-SPINE-6 / MSG-001-3b: connect a session node to the relay witness and
|
|
240
|
-
* store the client on the active entry. Best-effort: a connect/auth failure logs
|
|
241
|
-
* and leaves relayClient undefined — the session is NOT destroyed and the direct
|
|
242
|
-
* content path keeps working (the relay-park/recovery path is MSG-001-3b's domain).
|
|
243
|
-
*
|
|
244
|
-
* ⚠️ THIS BLOCK SPENT TWO MILESTONES ABOVE THE WRONG METHOD. It sat stacked on top of
|
|
245
|
-
* `relayLeafHandler`'s own docblock, so the file showed two descriptions in a row and the first
|
|
246
|
-
* one described a method further down the page. The content split then carried it verbatim into
|
|
247
|
-
* `session-content-ingest.ts`, where `connectSessionRelay` does not exist at all and the
|
|
248
|
-
* misattribution could no longer be worked out from context. Returned to the method it describes.
|
|
249
|
-
*/
|
|
250
|
-
async connectSessionRelay(sessionId, node, agentName, relay, correlationId) {
|
|
251
|
-
try {
|
|
252
|
-
// The session node's gater admits only the counterparty; the relay witness is a
|
|
253
|
-
// third peer. Permit it OUTBOUND so the dial isn't denied — inbound stays
|
|
254
|
-
// counterparty-only (INV-5). The relay peer id comes from the signed assignment.
|
|
255
|
-
this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId))?.gater.setAllowedOutboundPeer(relay.relayPeerId);
|
|
256
|
-
// One relay client per (AGENT, RELAY NODE). The relay keys by agent pubkey, so the
|
|
257
|
-
// collision H1 addresses is per relay; CELLO is federated, so a different session for
|
|
258
|
-
// the same agent may be assigned a DIFFERENT relay — that needs its own client.
|
|
259
|
-
const clientKey = `${agentName}::${relay.relayPeerId}`;
|
|
260
|
-
let client = this.#ctx.relayClients.get(clientKey);
|
|
261
|
-
if (!client) {
|
|
262
|
-
// RELAYSIG-1: one shared receipt store (keyed by agent_pubkey, so a single instance serves all
|
|
263
|
-
// agents + relays). Lazy — the encrypted DB is open by the time sessions are active.
|
|
264
|
-
if (!this.#ctx.relayReceiptStore && this.#db) {
|
|
265
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
266
|
-
}
|
|
267
|
-
// FED-OPTIONB-SEAL-001: one shared seal-leaf log (keyed by agent_pubkey), same lazy lifecycle.
|
|
268
|
-
if (!this.#ctx.sealLeafStore && this.#db)
|
|
269
|
-
this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
|
|
270
|
-
if (!this.#ctx.sealLeafStore)
|
|
271
|
-
this.#ctx.logger.error("session.relay.stores_absent", { agentName, sessionId, correlationId, impact: "no leaf record: this session cannot resume after a restart and keeps no seal evidence" });
|
|
272
|
-
client = new AgentRelayClient({
|
|
273
|
-
relayPeerId: relay.relayPeerId,
|
|
274
|
-
relayAddrs: relay.relayAddrs,
|
|
275
|
-
keyProvider: relay.keyProvider,
|
|
276
|
-
senderPubkey: relay.senderPubkey,
|
|
277
|
-
logger: this.#ctx.logger,
|
|
278
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
279
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
280
|
-
ownChainStore: this.#ctx.ownChainStore ?? undefined, // absent = own leaves not durably chained
|
|
281
|
-
// DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
|
|
282
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
283
|
-
// DOD-M15-CORROBORATE-1: a relay's witness alert reaches the operator's inbox from here.
|
|
284
|
-
// The DETACHED clients get the same callback from the builder in daemon.ts.
|
|
285
|
-
onWitnessAlert: (alert) => { this.#ctx.witness.recordRelayWitnessAlert(agentName, alert); },
|
|
286
|
-
onWitnessUnreadable: (relayPeerId, why) => { this.#ctx.records.recordRelayWitnessUnreadable(agentName, relayPeerId, why); },
|
|
287
|
-
});
|
|
288
|
-
this.#ctx.relayClients.set(clientKey, client);
|
|
289
|
-
}
|
|
290
|
-
const sessionIdHexForRelay = Buffer.from(relay.sessionIdBytes).toString("hex");
|
|
291
|
-
/**
|
|
292
|
-
* ⚠️ THE GENESIS IS WRITTEN BEFORE THE REGISTRATION, NOT AFTER — review F10.
|
|
293
|
-
*
|
|
294
|
-
* `#leafRecords.sessionGenesisPrevRoot` reads the entry's assignment first and the column second,
|
|
295
|
-
* and BOTH were still unset at this line: the entry's assignment is set below and the column
|
|
296
|
-
* is written below that. So the argument was always `undefined` here on a first attach, and
|
|
297
|
-
* the seed only survived because `registerSession` falls back to deriving one from the
|
|
298
|
-
* assignment it is handed. That is a dead argument standing next to a live fallback, which
|
|
299
|
-
* reads as deliberate and is the shape a later edit removes the wrong half of.
|
|
300
|
-
*/
|
|
301
|
-
const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
302
|
-
if (entry)
|
|
303
|
-
entry.relayAssignment = relay.assignment;
|
|
304
|
-
if (relay.assignment)
|
|
305
|
-
this.#ctx.leafRecords.persistGenesisPrevRoot(agentName, sessionId, relay.assignment);
|
|
306
|
-
client.registerSession(sessionIdHexForRelay, node, this.#ctx.contentIn.relayLeafHandler(agentName, sessionId, correlationId), relay.assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
|
|
307
|
-
if (entry) {
|
|
308
|
-
entry.relayClient = client;
|
|
309
|
-
entry.relaySessionIdBytes = relay.sessionIdBytes;
|
|
310
|
-
entry.relayClientKey = clientKey;
|
|
311
|
-
// DOD-M15-AWAYSCOPE-1: the session just gained a relay, so tell it where this agent stands.
|
|
312
|
-
// The three attendance transitions fan out over sessions that already exist; this is the
|
|
313
|
-
// other direction — a session that arrives DURING an away stretch, which is exactly the
|
|
314
|
-
// case the order is about and the one the transitions cannot see.
|
|
315
|
-
client.announceAttendance(node, relay.sessionIdBytes, this.#ctx.currentAttendance(agentName));
|
|
316
|
-
// 2b: remember the relay endpoint so the content-park backstop deposits to the SAME relay.
|
|
317
|
-
entry.relayPeerId = relay.relayPeerId;
|
|
318
|
-
entry.relayAddrs = relay.relayAddrs;
|
|
319
|
-
// Review H1: the dial path needs the credential in hand, not just the endpoint.
|
|
320
|
-
// (Set above, before `registerSession`, so the genesis lookup it does has something to
|
|
321
|
-
// find — see the note there.)
|
|
322
|
-
// MSG-2 startup-flush: also PERSIST it, so a restart's crash-backstop flush (which runs
|
|
323
|
-
// before the in-memory entry exists) can deposit un-acked content to the same relay.
|
|
324
|
-
try {
|
|
325
|
-
this.#db
|
|
326
|
-
?.prepare("UPDATE sessions SET relay_peer_id = ?, relay_addrs = ?, updated_at = ? WHERE agent_id = ? AND session_id = ?")
|
|
327
|
-
.run(relay.relayPeerId, JSON.stringify(relay.relayAddrs), Date.now(), this.#ctx.requireAgentId(agentName), sessionId);
|
|
328
|
-
}
|
|
329
|
-
catch (err) {
|
|
330
|
-
this.#ctx.logger.warn("session.relay.endpoint.persist.failed", {
|
|
331
|
-
sessionId,
|
|
332
|
-
error: extractErrorMessage(err),
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
// The session was torn down while we were wiring — undo the registration.
|
|
338
|
-
client.unregisterSession(sessionIdHexForRelay);
|
|
339
|
-
if (!client.hasSessions() && this.#ctx.relayClients.get(clientKey) === client) {
|
|
340
|
-
client.close();
|
|
341
|
-
this.#ctx.relayClients.delete(clientKey);
|
|
342
|
-
}
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
// Proactively connect so the relay has this agent's stream to deliver leaves to
|
|
346
|
-
// (the RECEIVER must be connected before the counterparty submits). Best-effort.
|
|
347
|
-
await client.connect(node);
|
|
348
|
-
// DOD-M15-RELAYAUTH-1 review HIGH-2 — see below. Best-effort and non-blocking: a session must
|
|
349
|
-
// never fail to come up because a SECOND relay could not be told about it.
|
|
350
|
-
//
|
|
351
|
-
// Review M1: `.catch()` is not decoration. This is an unawaited promise, so a throw it does not
|
|
352
|
-
// handle is an unhandled rejection — and this file already carries a comment elsewhere about an
|
|
353
|
-
// absent `await` that became a remote process kill. The method's own try/catch does not cover
|
|
354
|
-
// its prologue, so the catch here is the only thing standing between a torn-down node and the
|
|
355
|
-
// daemon dying.
|
|
356
|
-
void this.#presentAssignmentToReservationRelay(agentName, node, relay, sessionIdHexForRelay, correlationId, entry)
|
|
357
|
-
.catch((err) => {
|
|
358
|
-
this.#ctx.logger.warn("session.relay.assignment.reservation_relay_failed", {
|
|
359
|
-
agentName,
|
|
360
|
-
sessionId: sessionIdHexForRelay.slice(0, 16),
|
|
361
|
-
error: extractErrorMessage(err),
|
|
362
|
-
impact: "inbound relayed dials to this node may be refused by its reservation relay; the session still works over the direct path and the park backstop",
|
|
363
|
-
correlationId,
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
catch (err) {
|
|
368
|
-
this.#ctx.logger.warn("session.relay.connect.error", {
|
|
369
|
-
sessionId,
|
|
370
|
-
error: extractErrorMessage(err),
|
|
371
|
-
correlationId,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* DOD-M15-RELAYAUTH-1 review HIGH-2 — **THE RELAY THAT GATES THE DIAL IS NOT ALWAYS THE RELAY
|
|
377
|
-
* THAT HOLDS THE ASSIGNMENT, AND THE GATE DENIES WHEN THEY DIFFER.**
|
|
378
|
-
*
|
|
379
|
-
* Two relays are in play for one session, chosen by unrelated rules:
|
|
380
|
-
* - the WITNESS relay, `assignment.relay_endpoint`, picked by the directory. Both parties
|
|
381
|
-
* present `client_record_assignment` to it and to nowhere else.
|
|
382
|
-
* - the RESERVATION relay, whichever one this node's circuit address is held on — the first
|
|
383
|
-
* candidate that granted when it was a standing receiver.
|
|
384
|
-
*
|
|
385
|
-
* The counterparty dials our CIRCUIT address, so it is the RESERVATION relay whose gater is asked
|
|
386
|
-
* `denyOutboundRelayedConnection(them, us)`. With no assignment recorded there it finds no binding
|
|
387
|
-
* and refuses a completely legitimate dial. Two relays run in production and the client's own
|
|
388
|
-
* logs show the fall-through to the second candidate is frequent, so this is the ordinary case,
|
|
389
|
-
* not a corner: the session still opens, but every message falls to the store-and-forward park
|
|
390
|
-
* path, and the only trace is a denial on a relay nobody is tailing.
|
|
391
|
-
*
|
|
392
|
-
* So the node that will be DIALLED presents the same assignment to the relay that will be asked
|
|
393
|
-
* to allow it. Safe by construction: the assignment is self-authenticating (a per-node directory
|
|
394
|
-
* signature the relay verifies against its consortium set), so presenting it more widely grants
|
|
395
|
-
* nothing that forging it would not already require. No directory change, no new frame.
|
|
396
|
-
*/
|
|
397
|
-
async #presentAssignmentToReservationRelay(agentName, node, relay, sessionIdHex, correlationId, entry) {
|
|
398
|
-
// Review M1: the prologue below lives INSIDE the try. `listenAddresses()` on a node torn down
|
|
399
|
-
// while we were wiring is exactly the case handled 30 lines above at the caller, and out here it
|
|
400
|
-
// would have escaped both this method's catch and (before the caller's `.catch`) the process.
|
|
401
|
-
let reservationRelayPeerId;
|
|
402
|
-
try {
|
|
403
|
-
if (!relay.assignment)
|
|
404
|
-
return; // direct/legacy/persisted-reconnect: nothing to present anywhere
|
|
405
|
-
const heldCircuitAddr = node.listenAddresses().find((a) => a.includes("/p2p-circuit"));
|
|
406
|
-
if (!heldCircuitAddr)
|
|
407
|
-
return; // no reservation held → nobody will gate a dial to us
|
|
408
|
-
reservationRelayPeerId = /\/p2p\/([^/]+)\/p2p-circuit/.exec(heldCircuitAddr)?.[1];
|
|
409
|
-
if (!reservationRelayPeerId || reservationRelayPeerId === relay.relayPeerId)
|
|
410
|
-
return; // same relay — already recorded
|
|
411
|
-
const clientKey = `${agentName}::${reservationRelayPeerId}`;
|
|
412
|
-
let client = this.#ctx.relayClients.get(clientKey);
|
|
413
|
-
if (!client) {
|
|
414
|
-
if (!this.#ctx.relayReceiptStore && this.#db)
|
|
415
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
416
|
-
if (!this.#ctx.sealLeafStore && this.#db)
|
|
417
|
-
this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
|
|
418
|
-
// Split on the marker, not an anchored strip: the held address is
|
|
419
|
-
// `…/p2p/<relay>/p2p-circuit/p2p/<self>`, so the marker is in the MIDDLE.
|
|
420
|
-
const baseRelayAddr = heldCircuitAddr.split("/p2p-circuit")[0] ?? heldCircuitAddr;
|
|
421
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, reservationRelayPeerId, [baseRelayAddr], {
|
|
422
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
423
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
424
|
-
// DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
|
|
425
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
426
|
-
});
|
|
427
|
-
if (!client)
|
|
428
|
-
return;
|
|
429
|
-
this.#ctx.relayClients.set(clientKey, client);
|
|
430
|
-
// Review M4: record the key so teardown releases this client too — `relayClientKey` names
|
|
431
|
-
// only the witness relay, so before this the client and its session registration leaked.
|
|
432
|
-
if (entry)
|
|
433
|
-
entry.extraRelayClientKeys = [...(entry.extraRelayClientKeys ?? []), clientKey];
|
|
434
|
-
}
|
|
435
|
-
// registerSession presents the assignment eagerly (see its own comment). No leaf handler: this
|
|
436
|
-
// relay is not witnessing the session, it only needs the binding that authorizes the dial.
|
|
437
|
-
// 033-ACKEMIT: no genesis is passed and none is needed. This client is not witnessing the
|
|
438
|
-
// session — it never submits — and `registerSession` derives a seed from the assignment
|
|
439
|
-
// anyway. Reaching into the session record for one — or for the ANCHOR (069-ORDERPROOF), which
|
|
440
|
-
// is why none is passed — reaches with the RELAY session id, not the key it is stored under.
|
|
441
|
-
client.registerSession(sessionIdHex, node, undefined, relay.assignment, undefined, undefined);
|
|
442
|
-
this.#ctx.logger.info("session.relay.assignment.presented_to_reservation_relay", {
|
|
443
|
-
agentName,
|
|
444
|
-
sessionId: sessionIdHex.slice(0, 16),
|
|
445
|
-
witnessRelayPeerId: relay.relayPeerId,
|
|
446
|
-
reservationRelayPeerId,
|
|
447
|
-
impact: "the relay that will be asked to allow inbound circuit dials to this node now holds the assignment authorizing them",
|
|
448
|
-
correlationId,
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
this.#ctx.logger.warn("session.relay.assignment.reservation_relay_failed", {
|
|
453
|
-
agentName,
|
|
454
|
-
sessionId: sessionIdHex.slice(0, 16),
|
|
455
|
-
reservationRelayPeerId,
|
|
456
|
-
error: extractErrorMessage(err),
|
|
457
|
-
impact: "inbound relayed dials to this node may be refused by its reservation relay; the session still works over the direct path and the park backstop",
|
|
458
|
-
correlationId,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* M7 DOD-SPINE-6 / MSG-001-3b: detach a session from its (agent, relay) client and
|
|
464
|
-
* close the client when it has no remaining sessions. Idempotent and identity-guarded:
|
|
465
|
-
* the map delete only fires if the map still holds THIS client (a racing teardown of a
|
|
466
|
-
* sibling session must not close a freshly-created replacement client for the same key).
|
|
467
|
-
*/
|
|
468
|
-
detachSessionRelay(entry) {
|
|
469
|
-
const client = entry.relayClient;
|
|
470
|
-
const key = entry.relayClientKey;
|
|
471
|
-
if (!entry.relaySessionIdBytes)
|
|
472
|
-
return;
|
|
473
|
-
const sidHex = Buffer.from(entry.relaySessionIdBytes).toString("hex");
|
|
474
|
-
/**
|
|
475
|
-
* Review M4: release the EXTRA relay clients first — the ones opened to relays that gate circuit
|
|
476
|
-
* dials. `relayClientKey` above names only the witness relay, so these were registered and never
|
|
477
|
-
* unregistered: an authenticated relay stream and a `#sessions` entry leaked per session, and
|
|
478
|
-
* relay-side the dial-through binding they hold is then cleared only by the idle timer, which is
|
|
479
|
-
* now 24h. Runs before the early return below so it happens even for a session that never got a
|
|
480
|
-
* witness client.
|
|
481
|
-
*/
|
|
482
|
-
for (const extraKey of entry.extraRelayClientKeys ?? []) {
|
|
483
|
-
const extra = this.#ctx.relayClients.get(extraKey);
|
|
484
|
-
if (!extra)
|
|
485
|
-
continue;
|
|
486
|
-
extra.unregisterSession(sidHex);
|
|
487
|
-
if (!extra.hasSessions()) {
|
|
488
|
-
extra.close();
|
|
489
|
-
this.#ctx.relayClients.delete(extraKey);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
entry.extraRelayClientKeys = undefined;
|
|
493
|
-
if (!client)
|
|
494
|
-
return;
|
|
495
|
-
// Idempotent: clear the entry's reference so a second teardown of the same entry no-ops.
|
|
496
|
-
entry.relayClient = undefined;
|
|
497
|
-
client.unregisterSession(sidHex);
|
|
498
|
-
if (!client.hasSessions() && key && this.#ctx.relayClients.get(key) === client) {
|
|
499
|
-
client.close();
|
|
500
|
-
this.#ctx.relayClients.delete(key);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* DOD-M15-RELAYAUTH-1 review H1 — **THE GATE WAS DENYING THE LEGITIMATE DIAL, AND USUALLY.**
|
|
505
|
-
*
|
|
506
|
-
* With the gater installed, a relay refuses a circuit dial unless it already holds a
|
|
507
|
-
* directory-signed assignment naming both transport peer ids. Both parties get that assignment
|
|
508
|
-
* from the directory independently, and until this method existed, each only presented it to
|
|
509
|
-
* relays IT had chosen — so whether a dial was allowed came down to which of two independent
|
|
510
|
-
* network races finished first:
|
|
511
|
-
*
|
|
512
|
-
* 1. WE connect to the witness relay, then dial the counterparty's circuit address. ~2 RTT.
|
|
513
|
-
* 2. THEY connect to their witness relay, then — unawaited, on a fresh dial + auth + record —
|
|
514
|
-
* tell their RESERVATION relay about the session. ~3–4 RTT.
|
|
515
|
-
*
|
|
516
|
-
* Nothing sequenced (1) against (2), and (1) is shorter, so we usually arrived first and were
|
|
517
|
-
* refused. The session still opened and reported `transportMode: "relay"`, so the failure was
|
|
518
|
-
* invisible: every message for the life of that conversation quietly took the store-and-forward
|
|
519
|
-
* park path, and the only trace was a denial logged on a third machine nobody tails.
|
|
520
|
-
*
|
|
521
|
-
* The fix is to stop racing. Whoever is about to dial presents the assignment to the relay that
|
|
522
|
-
* will gate that dial, and WAITS for the relay to confirm it recorded it. The ordering becomes
|
|
523
|
-
* local to one thread of execution, so there is nothing left to lose.
|
|
524
|
-
*
|
|
525
|
-
* Safe by construction: we are a participant the assignment names, so the relay's own participant
|
|
526
|
-
* check passes; and the assignment is self-authenticating (a directory signature the relay
|
|
527
|
-
* verifies against its consortium set), so presenting it more widely grants nothing that forging
|
|
528
|
-
* it would not already require.
|
|
529
|
-
*
|
|
530
|
-
* Best-effort by design — a relay we cannot reach must not stop us from dialling. If the record
|
|
531
|
-
* fails we dial anyway: a dial that might be refused is strictly better than no dial.
|
|
532
|
-
*/
|
|
533
|
-
async authorizeCircuitDialsToCounterparty(agentName, sessionId, entry, addrs) {
|
|
534
|
-
const assignment = entry.relayAssignment;
|
|
535
|
-
if (!assignment || !entry.relaySessionIdBytes)
|
|
536
|
-
return; // direct/legacy/persisted: no credential to present
|
|
537
|
-
const sessionIdHex = Buffer.from(entry.relaySessionIdBytes).toString("hex");
|
|
538
|
-
// One presentation per distinct relay, not per address: a counterparty commonly advertises
|
|
539
|
-
// several circuit addresses on the SAME relay.
|
|
540
|
-
const seen = new Set();
|
|
541
|
-
for (const addr of addrs) {
|
|
542
|
-
const relayPeerId = /\/p2p\/([^/]+)\/p2p-circuit/.exec(addr)?.[1];
|
|
543
|
-
if (!relayPeerId || seen.has(relayPeerId))
|
|
544
|
-
continue;
|
|
545
|
-
seen.add(relayPeerId);
|
|
546
|
-
// The witness relay already has it — registerSession presented it when the session was wired.
|
|
547
|
-
if (relayPeerId === entry.relayPeerId)
|
|
548
|
-
continue;
|
|
549
|
-
const baseRelayAddr = addr.split("/p2p-circuit")[0] ?? addr;
|
|
550
|
-
try {
|
|
551
|
-
const clientKey = `${agentName}::${relayPeerId}`;
|
|
552
|
-
let client = this.#ctx.relayClients.get(clientKey);
|
|
553
|
-
if (!client) {
|
|
554
|
-
if (!this.#ctx.relayReceiptStore && this.#db)
|
|
555
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
556
|
-
if (!this.#ctx.sealLeafStore && this.#db)
|
|
557
|
-
this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
|
|
558
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
|
|
559
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
560
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
561
|
-
// DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
|
|
562
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
563
|
-
});
|
|
564
|
-
if (!client) {
|
|
565
|
-
this.#ctx.logger.warn("session.transport.dial_authorization.no_builder", {
|
|
566
|
-
sessionId,
|
|
567
|
-
relayPeerId,
|
|
568
|
-
impact: "cannot present the assignment to the relay that gates this dial; if the counterparty has not presented it either, the dial will be refused and every message will fall to the park path",
|
|
569
|
-
correlationId: entry.correlationId,
|
|
570
|
-
});
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
this.#ctx.relayClients.set(clientKey, client);
|
|
574
|
-
// Review M4: remember it so teardown releases it — this is a SECOND client for the
|
|
575
|
-
// session, and detach only knows about the witness one.
|
|
576
|
-
entry.extraRelayClientKeys = [...(entry.extraRelayClientKeys ?? []), clientKey];
|
|
577
|
-
}
|
|
578
|
-
// No leaf handler: this relay is not witnessing the session, it only needs the binding.
|
|
579
|
-
client.registerSession(sessionIdHex, entry.node, undefined, assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
|
|
580
|
-
const recorded = await client.recordAssignmentAndWait(entry.node, sessionIdHex);
|
|
581
|
-
if (recorded) {
|
|
582
|
-
this.#ctx.logger.info("session.transport.dial_authorized", {
|
|
583
|
-
sessionId,
|
|
584
|
-
relayPeerId,
|
|
585
|
-
impact: "the relay that gates this circuit dial now holds the assignment authorizing it",
|
|
586
|
-
correlationId: entry.correlationId,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
this.#ctx.logger.warn("session.transport.dial_authorization.not_recorded", {
|
|
591
|
-
sessionId,
|
|
592
|
-
relayPeerId,
|
|
593
|
-
impact: "the relay did not confirm the assignment; the dial below may be refused and messages would fall to the park path",
|
|
594
|
-
correlationId: entry.correlationId,
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
catch (err) {
|
|
599
|
-
this.#ctx.logger.warn("session.transport.dial_authorization.failed", {
|
|
600
|
-
sessionId,
|
|
601
|
-
relayPeerId,
|
|
602
|
-
error: extractErrorMessage(err),
|
|
603
|
-
impact: "could not tell the relay that gates this dial about the session; the dial is attempted anyway",
|
|
604
|
-
correlationId: entry.correlationId,
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* TEST-ONLY (M8C-INBOX-1 reviewer F1): buffer a received message + persist its transcript row,
|
|
611
|
-
* exactly as the real inbound path (appendVerifiedContent, in `session-content-ingest.ts`) does,
|
|
612
|
-
* WITHOUT standing up a session
|
|
613
|
-
* tree — so a test can drive a live cello_receive that advances the read watermark (the N3
|
|
614
|
-
* "delivery marks read" coupling). Only reachable via the CELLO_ENV=test IPC hook.
|
|
615
|
-
*/
|
|
616
|
-
/** CELLO_ENV=test only: patch a relay client and session-id bytes onto an existing active node entry
|
|
617
|
-
* so submitSealLeaf succeeds without a real relay handshake (used by the oneshot relay-path test). */
|
|
618
|
-
patchRelayClientForTest(agentName, sessionId, relayClient, relaySessionIdBytes,
|
|
619
|
-
/**
|
|
620
|
-
* 069-ORDERPROOF: the session's assignment ANCHOR — the relay key the directory named, which
|
|
621
|
-
* every relay ordering attestation is verified against. Production puts it on the node entry
|
|
622
|
-
* when the session is created with a relay; a fixture that built its node without one supplies
|
|
623
|
-
* it here, or every submit is refused for want of an anchor and the fixture measures that
|
|
624
|
-
* refusal instead of whatever it was written for.
|
|
625
|
-
*/
|
|
626
|
-
assignment) {
|
|
627
|
-
const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
628
|
-
if (!entry)
|
|
629
|
-
throw new Error(`patchRelayClientForTest: no active node for (${agentName}, ${sessionId})`);
|
|
630
|
-
entry.relayClient = relayClient;
|
|
631
|
-
entry.relaySessionIdBytes = relaySessionIdBytes;
|
|
632
|
-
/**
|
|
633
|
-
* ⚠️ REGISTER THE SESSION TOO — the seam must leave the state production leaves.
|
|
634
|
-
*
|
|
635
|
-
* A relay client that has never been told about a session holds no starting point for it, and
|
|
636
|
-
* since `DOD-M15-SELFCHAIN-1` every submit on such a session is refused: there is nothing for
|
|
637
|
-
* the chain links to anchor to. Production always registers, because attaching a relay is what
|
|
638
|
-
* registration IS. A seam that attached the client and skipped the registration left fixtures
|
|
639
|
-
* exercising a refusal path, and the failure surfaced as "the seal never happened" in a test
|
|
640
|
-
* about away-mode replies.
|
|
641
|
-
*/
|
|
642
|
-
relayClient.registerSession(Buffer.from(relaySessionIdBytes).toString("hex"), entry.node, undefined, entry.relayAssignment ?? assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId) ?? assignment?.relayPubkeyHex);
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* M7-SESSION-001 AC-004/AC-005: Register a relay stream for an active session.
|
|
646
|
-
* Starts a background reader that watches for session_interrupted frames and
|
|
647
|
-
* stream close events. Both detection paths call markInterruptedWithDetails().
|
|
648
|
-
*
|
|
649
|
-
* The reader runs for the lifetime of the relay stream. If the stream closes
|
|
650
|
-
* without delivering a session_interrupted frame (AC-005 / 'stream_close' path),
|
|
651
|
-
* the session is still marked interrupted.
|
|
652
|
-
*
|
|
653
|
-
* @param sessionId The hex session ID
|
|
654
|
-
* @param stream The relay stream to monitor
|
|
655
|
-
* @param messageCount Number of message leaves at the time of registration
|
|
656
|
-
* (used as the count at interruption — best effort since exact count at frame
|
|
657
|
-
* receipt may differ, but this is the value available at stream setup time)
|
|
658
|
-
*/
|
|
659
|
-
registerRelayStream(agentName, sessionId, stream, messageCount = 0) {
|
|
660
|
-
void this.#watchRelayStream(agentName, sessionId, stream, messageCount);
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Background relay stream watcher.
|
|
664
|
-
* Pseudocode:
|
|
665
|
-
* 1. Create LP-framed iterator over the stream
|
|
666
|
-
* 2. For each frame:
|
|
667
|
-
* a. If type === 'session_interrupted':
|
|
668
|
-
* - Record receivedInterruptFrame = true
|
|
669
|
-
* - Call markInterruptedWithDetails(sessionId, messageCount, 'relay_frame')
|
|
670
|
-
* - Break (no more frames expected)
|
|
671
|
-
* 3. On stream close (loop ends normally or with error):
|
|
672
|
-
* a. If !receivedInterruptFrame:
|
|
673
|
-
* - Call markInterruptedWithDetails(sessionId, messageCount, 'stream_close')
|
|
674
|
-
*/
|
|
675
|
-
async #watchRelayStream(agentName, sessionId, stream, messageCount) {
|
|
676
|
-
let receivedInterruptFrame = false;
|
|
677
|
-
// CELLO-M7-TRANSPORT-001: cast the stream input to lp.decode. Adding the
|
|
678
|
-
// @libp2p/autonat service (interface@3.2.2 / uint8arraylist v2) to the
|
|
679
|
-
// transport package surfaced a benign mixed-version split between the Stream
|
|
680
|
-
// type (now v2) and it-length-prefixed's expected Uint8ArrayList (v3). The two
|
|
681
|
-
// are structurally identical at runtime — this is a build-time-only artifact.
|
|
682
|
-
const lpSource = stream;
|
|
683
|
-
const source = lp.decode(lpSource)[Symbol.asyncIterator]();
|
|
684
|
-
try {
|
|
685
|
-
while (true) {
|
|
686
|
-
let result;
|
|
687
|
-
try {
|
|
688
|
-
result = await source.next();
|
|
689
|
-
}
|
|
690
|
-
catch {
|
|
691
|
-
// Stream error (e.g. stream aborted) — treat as stream close
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
if (result.done || result.value === undefined)
|
|
695
|
-
break;
|
|
696
|
-
let frame;
|
|
697
|
-
try {
|
|
698
|
-
const bytes = result.value instanceof Uint8Array ? result.value
|
|
699
|
-
: Buffer.isBuffer(result.value) ? new Uint8Array(result.value)
|
|
700
|
-
: result.value.slice();
|
|
701
|
-
frame = decode(bytes);
|
|
702
|
-
}
|
|
703
|
-
catch {
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
if (frame["type"] === "session_interrupted") {
|
|
707
|
-
// H-3 SECURITY: this stream is registered (bound) to a specific
|
|
708
|
-
// sessionId. A malicious or buggy relay could put a DIFFERENT session_id
|
|
709
|
-
// in the frame body to target a session this stream is not authorized
|
|
710
|
-
// for (cross-session targeting). Never trust the frame's id: if the frame
|
|
711
|
-
// names a different session, reject it and keep watching the bound one.
|
|
712
|
-
const frameSessionId = typeof frame["session_id"] === "string"
|
|
713
|
-
? frame["session_id"]
|
|
714
|
-
: (frame["session_id"] instanceof Uint8Array
|
|
715
|
-
? Buffer.from(frame["session_id"]).toString("hex")
|
|
716
|
-
: null);
|
|
717
|
-
if (frameSessionId !== null && frameSessionId !== sessionId) {
|
|
718
|
-
this.#ctx.logger.warn("session.interrupt.frame.session_mismatch", {
|
|
719
|
-
boundSessionId: sessionId,
|
|
720
|
-
frameSessionId,
|
|
721
|
-
reason: "cross_session_frame_rejected",
|
|
722
|
-
});
|
|
723
|
-
continue; // ignore the hostile/mismatched frame; keep reading
|
|
724
|
-
}
|
|
725
|
-
receivedInterruptFrame = true;
|
|
726
|
-
// Always mark the BOUND sessionId — never the id carried in the frame.
|
|
727
|
-
await this.#ctx.markInterruptedWithDetails(agentName, sessionId, messageCount, "relay_frame");
|
|
728
|
-
break; // No more relay frames expected after session_interrupted
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
catch {
|
|
733
|
-
// Stream read loop ended — fall through to stream_close check
|
|
734
|
-
}
|
|
735
|
-
// AC-005: stream closed without a session_interrupted frame
|
|
736
|
-
if (!receivedInterruptFrame) {
|
|
737
|
-
// Only mark interrupted if this session is still active in SQLite
|
|
738
|
-
const record = this.#ctx.queries.getSessionRecord(agentName, sessionId);
|
|
739
|
-
if (record && record.status === "active") {
|
|
740
|
-
await this.#ctx.markInterruptedWithDetails(agentName, sessionId, messageCount, "stream_close");
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* DOD-NAT-REACHABILITY-1 (Phase 2): accept the directory's relay-pool endpoints
|
|
746
|
-
* for an agent (arrives with signaling_auth_ok, i.e. on every connect AND every
|
|
747
|
-
* reconnect). If the agent's standing receiver is up but holds NO reservation —
|
|
748
|
-
* the agent-online ensure raced ahead of auth_ok, or every relay was down at
|
|
749
|
-
* create time — rebuild it now so the agent becomes dialable without waiting
|
|
750
|
-
* for a session handoff that (being unreachable) would never come.
|
|
751
|
-
*/
|
|
752
|
-
setDirectoryRelayEndpoints(agentName, endpoints) {
|
|
753
|
-
// DOD-M12B-RESERVATION-RETRY-1: a DIFFERENT relay pool re-arms the retry budget; the same one
|
|
754
|
-
// does not. This fires on every signaling connect AND reconnect, so clearing unconditionally
|
|
755
|
-
// would reset the bound on a short grid and defeat the whole point of having one. But once the
|
|
756
|
-
// budget is spent, `getStandingReceiverReachability` reports `unreachable` for the rest of the
|
|
757
|
-
// online episode — including while this path is actively re-attempting against relays we have
|
|
758
|
-
// never tried. A new relay is new information; a repeat of the same list is not.
|
|
759
|
-
const previous = this.#ctx.directoryRelayEndpoints.get(agentName);
|
|
760
|
-
const poolChanged = previous === undefined ||
|
|
761
|
-
previous.length !== endpoints.length ||
|
|
762
|
-
endpoints.some((e, i) => e.relayPeerId !== previous[i]?.relayPeerId);
|
|
763
|
-
this.#ctx.directoryRelayEndpoints.set(agentName, endpoints);
|
|
764
|
-
if (poolChanged) {
|
|
765
|
-
this.#ctx.srReservationRetry.delete(agentName);
|
|
766
|
-
this.#ctx.srLastRejectionReason.delete(agentName);
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* ⚠️ **ENDPOINTS ARRIVING NO LONGER REBUILD THE RECEIVER — 055-ONDEMAND.**
|
|
770
|
-
*
|
|
771
|
-
* This used to rebuild so the new node could reserve with the relays that had just been
|
|
772
|
-
* announced, because reservations were fixed at node creation and acquired at login. Neither is
|
|
773
|
-
* true now: nothing is reserved at login, and a running node can take one on demand.
|
|
774
|
-
*
|
|
775
|
-
* So a rebuild here would buy nothing and cost something real. The receiver it replaces may be
|
|
776
|
-
* holding the circuit a LIVE SESSION's counterparty was told to dial — throwing that away in
|
|
777
|
-
* response to a routine directory announcement would drop the route silently, at both ends.
|
|
778
|
-
*
|
|
779
|
-
* The endpoints are still recorded above, which is all they were ever needed for: they are the
|
|
780
|
-
* candidate list an offer reserves against.
|
|
781
|
-
*/
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Is this agent currently skipping this relay? The observable half of the failover decision — a
|
|
785
|
-
* test that asserts only on the classifier's boolean proves nothing about what the daemon does.
|
|
786
|
-
*/
|
|
787
|
-
isRelayQuarantined(agentName, relayPeerId) {
|
|
788
|
-
return this.#relayQuarantineFor(agentName).has(relayPeerId);
|
|
789
|
-
}
|
|
790
|
-
/** Live quarantine entries for an agent, expired ones swept on read. */
|
|
791
|
-
#relayQuarantineFor(agentName) {
|
|
792
|
-
const byRelay = this.#ctx.relayQuarantine.get(agentName);
|
|
793
|
-
if (!byRelay)
|
|
794
|
-
return new Set();
|
|
795
|
-
const now = Date.now();
|
|
796
|
-
for (const [relayPeerId, expiresAt] of byRelay) {
|
|
797
|
-
if (now >= expiresAt)
|
|
798
|
-
byRelay.delete(relayPeerId);
|
|
799
|
-
}
|
|
800
|
-
if (byRelay.size === 0)
|
|
801
|
-
this.#ctx.relayQuarantine.delete(agentName);
|
|
802
|
-
return new Set(byRelay.keys());
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Skip this relay for this agent for a while. Called only for refusals the classifier marks
|
|
806
|
-
* `tryAnotherRelay` — a fault of the relay's, not one that would follow us to the next one.
|
|
807
|
-
*/
|
|
808
|
-
#quarantineRelay(agentName, relayPeerId, reason) {
|
|
809
|
-
let byRelay = this.#ctx.relayQuarantine.get(agentName);
|
|
810
|
-
if (!byRelay) {
|
|
811
|
-
byRelay = new Map();
|
|
812
|
-
this.#ctx.relayQuarantine.set(agentName, byRelay);
|
|
813
|
-
}
|
|
814
|
-
byRelay.set(relayPeerId, Date.now() + RELAY_QUARANTINE_MS);
|
|
815
|
-
this.#ctx.logger.warn("session.standing_receiver.relay_quarantined", {
|
|
816
|
-
agentName,
|
|
817
|
-
relayPeerId,
|
|
818
|
-
reason,
|
|
819
|
-
forMs: RELAY_QUARANTINE_MS,
|
|
820
|
-
impact: "this relay refused this agent for a fault of its own, so the agent will ask a " +
|
|
821
|
-
"different relay for its reservation until the quarantine lapses. Its inbound reachability " +
|
|
822
|
-
"is restored by moving, not by waiting for someone to fix that relay.",
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
reservationCircuitAddrs(agentName) {
|
|
826
|
-
let persisted;
|
|
827
|
-
try {
|
|
828
|
-
persisted = this.#ctx.queries.getAgentRelayEndpoints(agentName);
|
|
829
|
-
}
|
|
830
|
-
catch (err) {
|
|
831
|
-
// No DB / unknown agent — persisted source unavailable. The directory
|
|
832
|
-
// source may still serve; reachability degrades only if both are empty.
|
|
833
|
-
// Logged (not swallowed): a genuine DB failure must be distinguishable
|
|
834
|
-
// from "fresh agent, no history" in the reachability trail.
|
|
835
|
-
this.#ctx.logger.debug("session.standing_receiver.persisted_relays.unavailable", {
|
|
836
|
-
agentName,
|
|
837
|
-
error: extractErrorMessage(err),
|
|
838
|
-
});
|
|
839
|
-
persisted = [];
|
|
840
|
-
}
|
|
841
|
-
const merged = new Map(mergeRelayEndpoints(this.#ctx.directoryRelayEndpoints.get(agentName), persisted).map((ep) => [ep.relayPeerId, ep]));
|
|
842
|
-
/**
|
|
843
|
-
* DOD-M15-RELAYSLOTS-1 — **THE FAILOVER.** Skip relays that refused this agent for a fault of
|
|
844
|
-
* their own (today: a relay holding no directory public key, which can verify nobody and is
|
|
845
|
-
* refusing everyone). We run several relays precisely so one being broken is survivable.
|
|
846
|
-
*
|
|
847
|
-
* ⚠️ NEVER TO THE POINT OF HAVING NO RELAY AT ALL. If the quarantine would empty the candidate
|
|
848
|
-
* list it is ignored wholesale: a relay that refuses is strictly better than no relay, because
|
|
849
|
-
* the refusal at least has a cause the operator can read, while an agent with no candidates is
|
|
850
|
-
* simply unreachable with nothing to show for it. This is the same "refusing too eagerly is the
|
|
851
|
-
* failure mode" rule, applied to the client's own choice of where to ask.
|
|
852
|
-
*/
|
|
853
|
-
const quarantined = this.#relayQuarantineFor(agentName);
|
|
854
|
-
const eligible = [...merged.values()].filter((ep) => !quarantined.has(ep.relayPeerId));
|
|
855
|
-
const usable = eligible.length > 0 ? eligible : [...merged.values()];
|
|
856
|
-
if (eligible.length === 0 && quarantined.size > 0 && merged.size > 0) {
|
|
857
|
-
this.#ctx.logger.warn("session.standing_receiver.relay_quarantine.ignored", {
|
|
858
|
-
agentName,
|
|
859
|
-
quarantined: [...quarantined],
|
|
860
|
-
impact: "every known relay has refused this agent for a relay-side fault, so the quarantine " +
|
|
861
|
-
"is being ignored and they are all being tried again. A relay that refuses with a cause " +
|
|
862
|
-
"is better than no relay at all — but if this persists, every relay this agent knows " +
|
|
863
|
-
"about is misconfigured, and that is the thing to look at.",
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
const addrs = [];
|
|
867
|
-
const relayPeerIds = [];
|
|
868
|
-
for (const ep of usable) {
|
|
869
|
-
const base = ep.relayAddrs[0];
|
|
870
|
-
if (!base)
|
|
871
|
-
continue;
|
|
872
|
-
const candidate = base.includes("/p2p/")
|
|
873
|
-
? `${base}/p2p-circuit`
|
|
874
|
-
: `${base}/p2p/${ep.relayPeerId}/p2p-circuit`;
|
|
875
|
-
// These addresses are built from DIRECTORY-supplied endpoints — data from off
|
|
876
|
-
// this machine. A malformed one throws inside libp2p node construction, which
|
|
877
|
-
// would take the standing receiver down entirely and leave the agent deaf to
|
|
878
|
-
// ALL inbound (worse than the defect this fixes). A bad endpoint must cost one
|
|
879
|
-
// relay, never the receiver.
|
|
880
|
-
if (!isValidMultiaddr(candidate)) {
|
|
881
|
-
this.#ctx.logger.warn("session.standing_receiver.relay_endpoint.invalid", {
|
|
882
|
-
agentName,
|
|
883
|
-
relayPeerId: ep.relayPeerId,
|
|
884
|
-
addr: candidate,
|
|
885
|
-
});
|
|
886
|
-
continue;
|
|
887
|
-
}
|
|
888
|
-
addrs.push(candidate);
|
|
889
|
-
relayPeerIds.push(ep.relayPeerId);
|
|
890
|
-
}
|
|
891
|
-
return { addrs, relayPeerIds };
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* DOD-NAT-REACHABILITY-1: notice when a standing receiver has SILENTLY LOST its
|
|
895
|
-
* reservation, and get it another one.
|
|
896
|
-
*
|
|
897
|
-
* libp2p refreshes a circuit reservation before it expires. If the relay has died,
|
|
898
|
-
* that refresh fails and the /p2p-circuit address simply DISAPPEARS from the node's
|
|
899
|
-
* addresses. Nothing throws. The receiver is still up, still directly dialable, and
|
|
900
|
-
* still looks perfectly healthy — but no NAT'd peer can reach the agent any more.
|
|
901
|
-
* That is precisely the silent-loss-of-inbound failure this whole story exists to
|
|
902
|
-
* kill, so it cannot be left to chance: we watch for it and re-pick a relay.
|
|
903
|
-
*
|
|
904
|
-
* ⚠️ 056-SLOTDEAD: this paragraph used to end "Only receivers that HAD a reservation are watched.
|
|
905
|
-
* One that never got one is already degraded and already loud (reservation.none /
|
|
906
|
-
* reservation.timeout); rebuilding it on a timer would just thrash against relays we know are
|
|
907
|
-
* refusing." Every clause of that is now false — an idle receiver holds nothing BY DESIGN so
|
|
908
|
-
* "degraded" is the normal state, `reservation.none` is not emitted at build any more, and
|
|
909
|
-
* nothing rebuilds. What is watched is a receiver whose recorded set has shrunk, and what
|
|
910
|
-
* happens is a re-take in place for a LIVE session, never a rebuild.
|
|
911
|
-
*/
|
|
912
|
-
/**
|
|
913
|
-
* DOD-M12B-RESERVATION-RETRY-1 / 055-ONDEMAND — is a re-attempt due, and claim it if so.
|
|
914
|
-
*
|
|
915
|
-
* ⚠️ **TWO DOC BLOCKS DESCRIBING `#retryReservationIfDue` USED TO SIT HERE — 056-SLOTDEAD, review
|
|
916
|
-
* F7.** That function is deleted. One of them said "the rebuild is the re-attempt … which is why
|
|
917
|
-
* it stays", so a reader following the comment landed on this function and concluded a rebuild
|
|
918
|
-
* ladder was still running. There is no rebuild anywhere in this file.
|
|
919
|
-
*
|
|
920
|
-
* What this does: a live session that lost its circuit re-takes it IN PLACE, on the node that
|
|
921
|
-
* already has the session, and this owns whether it is allowed to try yet. ONE ladder — a
|
|
922
|
-
* reservation is scarce, the relay holds it for its full TTL even after the client disconnects,
|
|
923
|
-
* and this file's own warning is that churning attempts across a fleet is how a relay is
|
|
924
|
-
* exhausted.
|
|
925
|
-
*/
|
|
926
|
-
#retryDue(agentName) {
|
|
927
|
-
const now = Date.now();
|
|
928
|
-
const state = this.#ctx.srReservationRetry.get(agentName)
|
|
929
|
-
?? { attempts: 0, nextAt: now + this.#ctx.srReservationRetryMs, correlationId: randomUUID() };
|
|
930
|
-
const lastReason = this.#ctx.srLastRejectionReason.get(agentName);
|
|
931
|
-
if (lastReason !== undefined)
|
|
932
|
-
state.lastReason = lastReason;
|
|
933
|
-
if (!this.#ctx.srReservationRetry.has(agentName)) {
|
|
934
|
-
// First sighting — schedule, do not fire. Something just tried.
|
|
935
|
-
this.#ctx.srReservationRetry.set(agentName, state);
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
if (now < state.nextAt)
|
|
939
|
-
return false;
|
|
940
|
-
if (state.attempts >= SR_RESERVATION_MAX_RETRIES) {
|
|
941
|
-
if (state.attempts === SR_RESERVATION_MAX_RETRIES) {
|
|
942
|
-
state.attempts += 1; // report once
|
|
943
|
-
this.#ctx.srReservationRetry.set(agentName, state);
|
|
944
|
-
this.#ctx.logger.error("session.standing_receiver.reservation.gave_up", {
|
|
945
|
-
agentName,
|
|
946
|
-
attempts: SR_RESERVATION_MAX_RETRIES,
|
|
947
|
-
correlationId: state.correlationId,
|
|
948
|
-
// WHY, not just the consequence. Three different problems reach this one message and they
|
|
949
|
-
// need three different responses: `relay_granted_no_reservation` is relay CAPACITY (and a
|
|
950
|
-
// trustless-cello problem), `relay_unreachable` is the NETWORK, and
|
|
951
|
-
// `reservation_did_not_complete_in_time` is LATENCY.
|
|
952
|
-
...(state.lastReason !== undefined ? { lastRejectionReason: state.lastReason } : {}),
|
|
953
|
-
/**
|
|
954
|
-
* ⚠️ **THESE TWO CAME BACK — 056-SLOTDEAD, review F9.** The deleted emitter of this SAME
|
|
955
|
-
* event name carried them; the surviving one did not, so the fields quietly vanished from
|
|
956
|
-
* an operator's view while the event kept appearing.
|
|
957
|
-
*
|
|
958
|
-
* "No relay would grant" and "there was no relay to ask" are different facts that lead to
|
|
959
|
-
* different places — the first at relay capacity, the second at this agent's directory
|
|
960
|
-
* connection. Without them they are the same sentence.
|
|
961
|
-
*
|
|
962
|
-
* `hadRelayToAsk` reads the directory pool alone; `relaysOffered` is the merged,
|
|
963
|
-
* quarantine-filtered candidate list the re-take actually walks. Two populations, so two
|
|
964
|
-
* names — the mis-naming that made `reservationsRequested` unreadable is the reason this
|
|
965
|
-
* note exists.
|
|
966
|
-
*/
|
|
967
|
-
hadRelayToAsk: (this.#ctx.directoryRelayEndpoints.get(agentName)?.length ?? 0) > 0,
|
|
968
|
-
relaysOffered: this.reservationCircuitAddrs(agentName).addrs.length,
|
|
969
|
-
impact: "a live session lost the circuit its counterparty dials, and no relay would give " +
|
|
970
|
-
"it back inside the retry budget. Messages still reach this agent through the relay's " +
|
|
971
|
-
"store-and-forward; a direct dial to it will not connect until the session is rebuilt.",
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
return false;
|
|
975
|
-
}
|
|
976
|
-
state.attempts += 1;
|
|
977
|
-
// Doubling, floored at the configured interval. 056-SLOTDEAD: this said "the same shape the
|
|
978
|
-
// rebuild ladder uses" — there is no rebuild ladder any more, this IS the ladder. The reason is
|
|
979
|
-
// unchanged: a fixed short interval is what exhausts a relay.
|
|
980
|
-
state.nextAt = now + this.#ctx.srReservationRetryMs * Math.pow(2, state.attempts - 1);
|
|
981
|
-
this.#ctx.srReservationRetry.set(agentName, state);
|
|
982
|
-
this.#ctx.logger.info("session.standing_receiver.reservation.retry", {
|
|
983
|
-
agentName, attempts: state.attempts, correlationId: state.correlationId,
|
|
984
|
-
});
|
|
985
|
-
return true;
|
|
986
|
-
}
|
|
987
|
-
async #reservationWatchdogTick() {
|
|
988
|
-
if (this.#ctx.shuttingDown)
|
|
989
|
-
return;
|
|
990
|
-
for (const [agentName, sr] of this.#ctx.standingReceivers) {
|
|
991
|
-
if (!this.#ctx.agentsWantingReceiver.has(agentName))
|
|
992
|
-
continue; // agent went offline
|
|
993
|
-
// DOD-M12B-RESERVATION-RETRY-1 — NEVER HAD ONE IS NOT "NOTHING TO DO".
|
|
994
|
-
//
|
|
995
|
-
// This used to `continue` unconditionally, on the grounds that a receiver with no reservation
|
|
996
|
-
// is "already degraded and already loud". Measured over 17 days: `reservation.none` fired 481
|
|
997
|
-
// times and `relay.rejected` 2,215 — every one of the latter `relay_granted_no_reservation`,
|
|
998
|
-
// a relay out of slots completing the handshake and granting nothing. Nothing ever acted on
|
|
999
|
-
// the noise, and each of those receivers is a plain TCP node with no circuit address: behind
|
|
1000
|
-
// NAT, dialable by NOBODY, for its whole life. That is the silent loss of inbound this file
|
|
1001
|
-
// says three lines below it exists to kill.
|
|
1002
|
-
//
|
|
1003
|
-
// A relay out of slots at boot may have one minutes later, so re-attempt — on a BACKOFF and
|
|
1004
|
-
// BOUNDED, never on this 30-second grid. A reservation is scarce: the relay holds it for its
|
|
1005
|
-
// full TTL even after the client disconnects, and churning attempts across a fleet is how a
|
|
1006
|
-
// relay is exhausted (`#startReceiverNode` records that hazard).
|
|
1007
|
-
if (sr.relayPeerIds.length === 0) {
|
|
1008
|
-
/**
|
|
1009
|
-
* ⚠️ **AN IDLE AGENT HOLDING NOTHING IS THE DESIGN NOW, NOT A DEGRADATION — 055-ONDEMAND.**
|
|
1010
|
-
*
|
|
1011
|
-
* Everything above this line was written when a receiver reserved at login and holding zero
|
|
1012
|
-
* meant an agent dialable by nobody for its whole life. A reservation is taken at OFFER time
|
|
1013
|
-
* now and given back at the seal, so zero is the correct steady state for an agent nobody is
|
|
1014
|
-
* calling — and retrying here would have every idle agent in the fleet asking relays for
|
|
1015
|
-
* slots the design says it must not hold. That is the exact churn the note below warns
|
|
1016
|
-
* about, pointed at the whole fleet instead of one receiver.
|
|
1017
|
-
*
|
|
1018
|
-
* The retry still matters for an agent that HAS a live session and lost the circuit that
|
|
1019
|
-
* session depends on, which is what the condition now says.
|
|
1020
|
-
*/
|
|
1021
|
-
const liveSessions = [...this.#ctx.activeNodes.values()].filter((e) => e.agentName === agentName);
|
|
1022
|
-
if (liveSessions.length === 0) {
|
|
1023
|
-
/**
|
|
1024
|
-
* ⚠️ **AN AGENT WITH NO SESSION HAS NOTHING TO RETRY, SO IT MUST NOT LOOK LIKE IT DOES —
|
|
1025
|
-
* `DOD-M15-IDLERETRY-1`, second half.**
|
|
1026
|
-
*
|
|
1027
|
-
* 061 stopped the ladder being OPENED for a session that needs nothing. It did not clear
|
|
1028
|
-
* one that was legitimately opened and is now moot. A ladder exists to get a LIVE session
|
|
1029
|
-
* its circuit back; when that session ends there is nothing left to get, and the entry is
|
|
1030
|
-
* the only thing `getStandingReceiverReachability` consults — so a perfectly idle agent
|
|
1031
|
-
* went on reporting `retrying`, and eventually `unreachable`, until the daemon restarted.
|
|
1032
|
-
*
|
|
1033
|
-
* Observed on the shipped 0.0.217 build: an agent with no live session at all, reading
|
|
1034
|
-
* `retrying`. That can only happen through a stale entry, which is what sent this back for
|
|
1035
|
-
* a second pass.
|
|
1036
|
-
*
|
|
1037
|
-
* Cleared HERE rather than at teardown on purpose: this is the one place that already
|
|
1038
|
-
* knows, every tick, that an agent holds nothing and wants nothing. A teardown hook would
|
|
1039
|
-
* be a second thing to keep in step with session lifecycle, and the whole class of defect
|
|
1040
|
-
* in this file is two things drifting apart.
|
|
1041
|
-
*/
|
|
1042
|
-
this.#ctx.srReservationRetry.delete(agentName);
|
|
1043
|
-
this.#ctx.srLastRejectionReason.delete(agentName);
|
|
1044
|
-
continue;
|
|
1045
|
-
}
|
|
1046
|
-
/**
|
|
1047
|
-
* ⚠️ **RE-TAKE THE SESSION'S CIRCUIT — REBUILDING THE RECEIVER NO LONGER DOES IT.**
|
|
1048
|
-
*
|
|
1049
|
-
* `#retryReservationIfDue` rebuilds the receiver, and a rebuilt receiver reserves NOTHING
|
|
1050
|
-
* (055-ONDEMAND). So on its own the retry ladder became a no-op by construction: a relay
|
|
1051
|
-
* that flapped mid-session would leave that session permanently un-dialable while the
|
|
1052
|
-
* ladder churned receivers to no effect.
|
|
1053
|
-
*
|
|
1054
|
-
* The circuit a live session needs is the one its ASSIGNMENT named, which is persisted with
|
|
1055
|
-
* the session — so ask for that one back, rather than rebuilding and hoping.
|
|
1056
|
-
*/
|
|
1057
|
-
/**
|
|
1058
|
-
* ⚠️ **ON THE SAME BUDGET AND BACKOFF AS THE LADDER IT REPLACES.** A reservation is scarce:
|
|
1059
|
-
* the relay holds it for its full TTL even after the client disconnects, and this file's own
|
|
1060
|
-
* warning is that churning attempts across a fleet is how a relay is exhausted. Asking on
|
|
1061
|
-
* every 30-second tick would be exactly that churn, wearing a new name — measured here as 52
|
|
1062
|
-
* asks where the budget allows 37.
|
|
1063
|
-
*/
|
|
1064
|
-
/**
|
|
1065
|
-
* ⚠️ **WHAT ACTUALLY NEEDS A CIRCUIT, DECIDED BEFORE THE BUDGET IS TOUCHED —
|
|
1066
|
-
* `DOD-M15-IDLERETRY-1`.**
|
|
1067
|
-
*
|
|
1068
|
-
* `#retryDue` was called FIRST, and it does not merely answer a question: on an agent it has
|
|
1069
|
-
* not seen before it WRITES a retry entry and returns false. So simply having a live session
|
|
1070
|
-
* while holding no circuit was enough to open a retry ladder — and for the INITIATOR of a
|
|
1071
|
-
* call that state is permanent and wrong, because an initiator DIALS OUT and never needs to
|
|
1072
|
-
* be dialable (055-ONDEMAND narrowed the reservation to the party being dialed, deliberately).
|
|
1073
|
-
*
|
|
1074
|
-
* Two things followed, and both were measured live on 2026-09-11 rather than reasoned:
|
|
1075
|
-
* - `cello_status` reported `retrying` for a healthy agent from its first call onward,
|
|
1076
|
-
* cleared only by a daemon restart — the same "a broken agent looks like a fine one"
|
|
1077
|
-
* defect 060-READY had just fixed, in a case it did not cover;
|
|
1078
|
-
* - worse, it is not cosmetic. Held past the retry interval, both agents emitted a real
|
|
1079
|
-
* `session.standing_receiver.reservation.retry` — the daemon asking relays for slots on
|
|
1080
|
-
* behalf of sessions that need none. That is churn against the scarce resource this
|
|
1081
|
-
* whole story exists to conserve.
|
|
1082
|
-
*
|
|
1083
|
-
* So the question "is there anything to re-take?" is answered from the SESSION NODES first.
|
|
1084
|
-
* A session that already holds its circuit, or has no relay endpoint recorded, needs
|
|
1085
|
-
* nothing. If none needs anything, this tick is a no-op and must leave no trace.
|
|
1086
|
-
*/
|
|
1087
|
-
const needRetake = liveSessions.filter((entry) => {
|
|
1088
|
-
const ep = this.#ctx.queries.getPersistedRelayEndpoint(agentName, entry.sessionId);
|
|
1089
|
-
if (!ep || ep.relayAddrs.length === 0)
|
|
1090
|
-
return false;
|
|
1091
|
-
/**
|
|
1092
|
-
* ⚠️ `heldRelayIdsOf`, NOT a substring test — the same distinction 056-SLOTDEAD had to
|
|
1093
|
-
* make one layer up. A circuit address that does not NAME its relay cannot be dialled
|
|
1094
|
-
* through, so a session announcing one holds nothing usable and DOES need a re-take.
|
|
1095
|
-
* A substring test counts it as held and leaves that session silently unreachable.
|
|
1096
|
-
*/
|
|
1097
|
-
return heldRelayIdsOf(entry.node).length === 0;
|
|
1098
|
-
});
|
|
1099
|
-
if (needRetake.length === 0)
|
|
1100
|
-
continue;
|
|
1101
|
-
if (!this.#retryDue(agentName))
|
|
1102
|
-
continue;
|
|
1103
|
-
/**
|
|
1104
|
-
* ⚠️ **ON THE SESSION'S OWN NODE, NOT THE IDLE RECEIVER — and the first version got this
|
|
1105
|
-
* wrong in a way that was worse than doing nothing.**
|
|
1106
|
-
*
|
|
1107
|
-
* It called the take path, which reserves on `standingReceivers.get(agentName).node`. That
|
|
1108
|
-
* is the fresh receiver built after the promotion — a DIFFERENT peer id from the one the
|
|
1109
|
-
* counterparty was told to dial. So the circuit it obtained helped no one, and it made an
|
|
1110
|
-
* IDLE receiver hold a relay slot, which is the exact thing this unit exists to stop. It
|
|
1111
|
-
* also set `relayPeerIds` non-empty, hiding the real loss from every later tick.
|
|
1112
|
-
*/
|
|
1113
|
-
let retook = false;
|
|
1114
|
-
for (const entry of needRetake) {
|
|
1115
|
-
const ep = this.#ctx.queries.getPersistedRelayEndpoint(agentName, entry.sessionId);
|
|
1116
|
-
const base = ep.relayAddrs[0];
|
|
1117
|
-
const circuitAddr = base.includes(`/p2p/${ep.relayPeerId}`) ? `${base}/p2p-circuit` : `${base}/p2p/${ep.relayPeerId}/p2p-circuit`;
|
|
1118
|
-
if (await this.#ctx.retakeReservationOn(agentName, entry.node, circuitAddr, entry.correlationId))
|
|
1119
|
-
retook = true;
|
|
1120
|
-
}
|
|
1121
|
-
if (retook)
|
|
1122
|
-
continue;
|
|
1123
|
-
// …unless one has arrived since. Review F4, same class as the recompute below: the
|
|
1124
|
-
// slow-start path installs a receiver before every circuit has bound, so "held nothing at
|
|
1125
|
-
// install" is not the same fact as "holds nothing now". Adopting it here is what stops the
|
|
1126
|
-
// retry ladder rebuilding a receiver that is already reachable.
|
|
1127
|
-
const arrived = heldRelayIdsOf(sr.node)
|
|
1128
|
-
.filter((id) => sr.node.getConnections().some((c) => c.peerId === id && c.status === "open"));
|
|
1129
|
-
if (arrived.length === 0) {
|
|
1130
|
-
/**
|
|
1131
|
-
* ⚠️ **`#retryReservationIfDue` WAS CALLED HERE AND COULD NEVER DO ANYTHING — 056-SLOTDEAD.**
|
|
1132
|
-
*
|
|
1133
|
-
* It guards on `now < state.nextAt`, and `#retryDue` above advances that same state on
|
|
1134
|
-
* this very tick, so the second call always returned immediately. Reachable, referenced,
|
|
1135
|
-
* and inert — the shape a reference scan cannot find.
|
|
1136
|
-
*
|
|
1137
|
-
* *(The first version of this order claimed it DOUBLE-ADVANCED the budget and triggered a
|
|
1138
|
-
* useless rebuild. That was wrong, and it was wrong because it was reasoned rather than
|
|
1139
|
-
* run. It was a no-op.)*
|
|
1140
|
-
*
|
|
1141
|
-
* Nothing replaces it: `#retryDue` owns the budget and the re-take above owns the work.
|
|
1142
|
-
*/
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
sr.relayPeerIds = arrived;
|
|
1146
|
-
sr.gater.setReservedRelayPeers(arrived);
|
|
1147
|
-
this.#ctx.logger.info("session.standing_receiver.reservation.gained", {
|
|
1148
|
-
agentName,
|
|
1149
|
-
relayPeerIds: arrived,
|
|
1150
|
-
reservationsHeld: arrived.length,
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
// It has at least one — any earlier retry budget, and the reason the last attempt failed, are
|
|
1154
|
-
// stale.
|
|
1155
|
-
this.#ctx.srReservationRetry.delete(agentName);
|
|
1156
|
-
this.#ctx.srLastRejectionReason.delete(agentName);
|
|
1157
|
-
// Watch the CONNECTION to the relay, not the circuit address.
|
|
1158
|
-
//
|
|
1159
|
-
// Killing the relay does NOT make the /p2p-circuit address disappear: libp2p
|
|
1160
|
-
// keeps the listen address until the reservation's own refresh, up to two hours
|
|
1161
|
-
// away. Watching the address would therefore miss a dead relay for hours — the
|
|
1162
|
-
// agent would advertise a circuit address that routes through a relay that no
|
|
1163
|
-
// longer exists, which is exactly the silent unreachability we are hunting. The
|
|
1164
|
-
// live connection to the relay is the honest signal: no connection, no relay,
|
|
1165
|
-
// no reservation.
|
|
1166
|
-
//
|
|
1167
|
-
// AND IT MUST BE OPEN. `getConnections()` returns libp2p's registry, which keeps a connection
|
|
1168
|
-
// listed while it is closing and after its muxer has died — the state the whole M12 Tier P5
|
|
1169
|
-
// investigation turned on. Without the status check a registered corpse reads as "still
|
|
1170
|
-
// connected", the rebuild never fires, and the agent silently stops being reachable while
|
|
1171
|
-
// this loop reports it healthy. The comment above claimed liveness; only this tests it.
|
|
1172
|
-
// 032-RELAYSPREAD — PER RELAY, and the health question is now a COUNT.
|
|
1173
|
-
//
|
|
1174
|
-
// This used to evaluate one peer id and rebuild the entire standing receiver when it went
|
|
1175
|
-
// false. With a pool of one that was the only thing it could do; with a pool of three it is
|
|
1176
|
-
// the churn engine — every relay is another watchdog subject, and a full rebuild per loss
|
|
1177
|
-
// multiplies the 30-second grid by the size of the pool while throwing away reservations that
|
|
1178
|
-
// are perfectly healthy.
|
|
1179
|
-
/**
|
|
1180
|
-
* RECOMPUTED FROM THE NODE, never filtered down from the stored list. Review F4: filtering
|
|
1181
|
-
* `sr.relayPeerIds` makes it SHRINK-ONLY, and a list that can only shrink cannot see a
|
|
1182
|
-
* circuit arrive. Three things went wrong with that, and the first one happens routinely:
|
|
1183
|
-
* - the slow-start path installs the receiver before every circuit has bound, so a relay
|
|
1184
|
-
* that binds four seconds later was invisible to this watchdog and absent from the
|
|
1185
|
-
* gater's carve-out set FOREVER — its AutoNAT probe reply refused by our own gate;
|
|
1186
|
-
* - shrinking to zero then rebuilt a receiver that was announcing live circuits, which is
|
|
1187
|
-
* the exact defect this unit is against;
|
|
1188
|
-
* - anything that ever restores a circuit could not be counted.
|
|
1189
|
-
* Reading the node's own addresses costs the same and has none of that.
|
|
1190
|
-
*/
|
|
1191
|
-
const open = sr.node.getConnections().filter((c) => c.status === "open").map((c) => c.peerId);
|
|
1192
|
-
const stillHeld = heldRelayIdsOf(sr.node).filter((id) => open.includes(id));
|
|
1193
|
-
const lost = sr.relayPeerIds.filter((id) => !stillHeld.includes(id));
|
|
1194
|
-
const gained = stillHeld.filter((id) => !sr.relayPeerIds.includes(id));
|
|
1195
|
-
sr.relayPeerIds = stillHeld;
|
|
1196
|
-
if (gained.length > 0) {
|
|
1197
|
-
// A circuit this receiver did not have at install. Said out loud because it is the visible
|
|
1198
|
-
// half of the slow-start case, and because it is the moment that relay earns its inbound
|
|
1199
|
-
// carve-out — a silent widening of the gate is not something to do without a line.
|
|
1200
|
-
this.#ctx.logger.info("session.standing_receiver.reservation.gained", {
|
|
1201
|
-
agentName,
|
|
1202
|
-
relayPeerIds: gained,
|
|
1203
|
-
reservationsHeld: stillHeld.length,
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
if (lost.length === 0) {
|
|
1207
|
-
// Nothing lost. The gater still gets the current set, because `gained` may have widened it.
|
|
1208
|
-
if (gained.length > 0)
|
|
1209
|
-
sr.gater.setReservedRelayPeers(stillHeld);
|
|
1210
|
-
continue;
|
|
1211
|
-
}
|
|
1212
|
-
// REVOKE FIRST. A relay whose reservation is gone must lose its inbound carve-out in the same
|
|
1213
|
-
// breath as the loss is noticed, or the gater's bound quietly becomes "granted one once".
|
|
1214
|
-
sr.gater.setReservedRelayPeers(stillHeld);
|
|
1215
|
-
for (const relayPeerId of lost) {
|
|
1216
|
-
// DOD-RELAY-KEEPALIVE-1 (review F4): carry the CAUSE, not just the exit point.
|
|
1217
|
-
// `relay_connection_gone` says where this was noticed — a poll of getConnections() — by
|
|
1218
|
-
// which time the abort reason that actually killed the link is long discarded. The relay
|
|
1219
|
-
// client for this (agent, relay) pair kept the error that ended its reader; that is the
|
|
1220
|
-
// nearest thing to an upstream cause available here, and its absence is how 2,061 of these
|
|
1221
|
-
// went untraced.
|
|
1222
|
-
/**
|
|
1223
|
-
* ⚠️ **A DIAGNOSTIC MUST NOT BE ABLE TO KILL THE WATCHDOG TICK.** This is optional-chained on
|
|
1224
|
-
* the map lookup but the METHOD was called unguarded, so a client without it threw — an
|
|
1225
|
-
* unhandled rejection inside the tick, which takes the rest of the sweep with it. Surfaced
|
|
1226
|
-
* when 055-ONDEMAND made this branch reachable in more cases. The cause line is worth
|
|
1227
|
-
* having; it is not worth the loss detection it rides on.
|
|
1228
|
-
*/
|
|
1229
|
-
const client = this.#ctx.relayClients.get(`${agentName}::${relayPeerId}`);
|
|
1230
|
-
const upstreamReason = typeof client?.getLastReaderError === "function" ? client.getLastReaderError() : null;
|
|
1231
|
-
this.#ctx.logger.warn("session.standing_receiver.reservation.lost", {
|
|
1232
|
-
agentName,
|
|
1233
|
-
relayPeerId,
|
|
1234
|
-
reason: open.includes(relayPeerId) ? "circuit_address_vanished" : "relay_connection_gone",
|
|
1235
|
-
...(upstreamReason ? { upstreamReason } : {}),
|
|
1236
|
-
reservationsHeld: stillHeld.length,
|
|
1237
|
-
/**
|
|
1238
|
-
* The line an operator reads, and the two cases are not the same event at all.
|
|
1239
|
-
*
|
|
1240
|
-
* ⚠️ **THE ZERO CASE PROMISED A REPAIR THAT NO LONGER HAPPENS — 056-SLOTDEAD, review F3.**
|
|
1241
|
-
* It ended "The receiver is being rebuilt against the rest of the pool", which was true
|
|
1242
|
-
* when this branch rebuilt. Nothing rebuilds now. An operator reading the old line would
|
|
1243
|
-
* wait for a recovery that was never coming, and the only thing that WOULD have told them
|
|
1244
|
-
* otherwise is a `zero_held` line at debug level they are not reading. Both branches now
|
|
1245
|
-
* say what actually happens next and who owns it.
|
|
1246
|
-
*/
|
|
1247
|
-
impact: stillHeld.length > 0
|
|
1248
|
-
? "this agent still holds " + stillHeld.length + " other circuit reservation(s), so it "
|
|
1249
|
-
+ "stays dialable from behind NAT. Losing one relay costs this agent nothing it can feel."
|
|
1250
|
-
: "this agent now holds NO circuit reservation. If it has no live session that is the "
|
|
1251
|
-
+ "normal idle state and costs nothing — a slot is taken when someone calls. If it "
|
|
1252
|
-
+ "DOES have a live session, that session's counterparty can no longer dial it, and "
|
|
1253
|
-
+ "the re-take above is what gets it back, on a bounded budget. Nothing rebuilds the "
|
|
1254
|
-
+ "receiver any more, so do not wait for one.",
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
/**
|
|
1258
|
-
* ⚠️ **DRAIN ON THE LOSS ITSELF — 056-SLOTDEAD.** This used to happen one step downstream, as
|
|
1259
|
-
* a side effect of the rebuild below: lose every reservation → rebuild → the new receiver
|
|
1260
|
-
* reports ready → drain. Deleting the rebuild would have deleted the drain with it, leaving
|
|
1261
|
-
* content the counterparty parked to sit until the periodic backstop. The loss is the cause
|
|
1262
|
-
* and always was; the rebuild was only where it happened to be noticed.
|
|
1263
|
-
*/
|
|
1264
|
-
this.#ctx.park.fireParkedDrain(agentName, "reservation_lost");
|
|
1265
|
-
if (stillHeld.length === 0) {
|
|
1266
|
-
/**
|
|
1267
|
-
* ⚠️ **THIS REBUILT THE RECEIVER ON A JUSTIFICATION THAT STOPPED BEING TRUE IN UNIT 1, AND
|
|
1268
|
-
* UNDER ON-DEMAND IT DESTROYS AN IN-FLIGHT OFFER'S RESERVATION — 056-SLOTDEAD.**
|
|
1269
|
-
*
|
|
1270
|
-
* It read: *"only a new node can take a new reservation, because a circuit listener is fixed
|
|
1271
|
-
* at node creation."* `listenOnCircuit` removed that constraint (`DOD-M15-RELAYPROVE-ORDER-1`),
|
|
1272
|
-
* and a rebuilt receiver now reserves nothing at all.
|
|
1273
|
-
*
|
|
1274
|
-
* The state it fires in is the dangerous one: between an offer taking a reservation and the
|
|
1275
|
-
* session being created, the receiver holds exactly one circuit. Lose the connection in that
|
|
1276
|
-
* window and this rebuilt the node — discarding the slot the offer just took, after the
|
|
1277
|
-
* accept may already have advertised it. **Observed**, not reasoned: a fixture that reported
|
|
1278
|
-
* no relay connection drove this path and the test caught the rebuild.
|
|
1279
|
-
*
|
|
1280
|
-
* Nothing replaces it here. An idle agent holding zero is the design; a LIVE session that
|
|
1281
|
-
* lost its circuit is re-taken on its own node by the branch above, on a bounded budget.
|
|
1282
|
-
*/
|
|
1283
|
-
this.#ctx.logger.debug("session.standing_receiver.reservation.zero_held", {
|
|
1284
|
-
agentName,
|
|
1285
|
-
impact: "this agent holds no circuit. If it has no live session that is the idle steady " +
|
|
1286
|
-
"state; if it does, the re-take above owns getting it back.",
|
|
1287
|
-
});
|
|
1288
|
-
continue;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
/** Start the reservation watchdog (idempotent). Stopped by gracefulShutdown. */
|
|
1293
|
-
startReservationWatchdog() {
|
|
1294
|
-
if (this.#ctx.reservationWatchdog !== null)
|
|
1295
|
-
return;
|
|
1296
|
-
// Arm the backstop clock from the START of watching, not from the epoch — otherwise the first
|
|
1297
|
-
// tick always fires a sweep on top of the install drain that just ran.
|
|
1298
|
-
this.#ctx.park.armBackstopClock(Date.now());
|
|
1299
|
-
this.#ctx.reservationWatchdog = setInterval(() => {
|
|
1300
|
-
try {
|
|
1301
|
-
void this.#reservationWatchdogTick();
|
|
1302
|
-
this.#ctx.park.parkedDrainBackstopTick(Date.now());
|
|
1303
|
-
}
|
|
1304
|
-
catch (err) {
|
|
1305
|
-
this.#ctx.logger.warn("session.standing_receiver.watchdog.failed", { error: extractErrorMessage(err) });
|
|
1306
|
-
}
|
|
1307
|
-
}, this.#ctx.srWatchdogIntervalMs);
|
|
1308
|
-
// Never hold the process open on account of the watchdog.
|
|
1309
|
-
this.#ctx.reservationWatchdog.unref?.();
|
|
1310
|
-
}
|
|
1311
|
-
/**
|
|
1312
|
-
* Start the standing receiver's libp2p node, holding a circuit-relay reservation
|
|
1313
|
-
* if one can be had — and WITHOUT one if it cannot.
|
|
1314
|
-
*
|
|
1315
|
-
* THE INVARIANT, learned live: **standing-receiver creation must NEVER be gated on
|
|
1316
|
-
* a relay.** libp2p's circuit listener awaits a live connection to its relay before
|
|
1317
|
-
* start() resolves, and it does not time out. A relay that does not answer parks
|
|
1318
|
-
* start() forever: no created event, no failure, no retry, no alarm — the agent
|
|
1319
|
-
* simply has no receiver and is deaf to ALL inbound, including the direct path that
|
|
1320
|
-
* worked before reservations existed. Strictly worse than the NAT defect this whole
|
|
1321
|
-
* line exists to fix. So every attempt is raced against a deadline, and failure
|
|
1322
|
-
* ALWAYS falls through to a plain TCP receiver.
|
|
1323
|
-
*
|
|
1324
|
-
* ONE relay, tried on the REAL node — not a probe.
|
|
1325
|
-
*
|
|
1326
|
-
* A relay reservation is a scarce resource: the relay holds it for its full TTL
|
|
1327
|
-
* even after the client disconnects, and it has a finite number of slots. An
|
|
1328
|
-
* earlier design probed each relay on a throwaway node and then reserved AGAIN on
|
|
1329
|
-
* the receiver — burning TWO slots per agent to get one, and leaving the throwaway's
|
|
1330
|
-
* slot pinned for hours. That is how a fleet exhausts a relay. Here the receiver
|
|
1331
|
-
* itself makes the attempt: if the relay grants the reservation, we KEEP that node.
|
|
1332
|
-
* One slot per agent, which is the true cost.
|
|
1333
|
-
*
|
|
1334
|
-
* Candidates are tried in order (directory pool first). The first that actually
|
|
1335
|
-
* grants a reservation wins; the rest are never touched.
|
|
1336
|
-
*/
|
|
1337
|
-
/**
|
|
1338
|
-
* DOD-M15-RELAYSLOTS-1 — tell the relay this transport identity belongs to a registered agent, so
|
|
1339
|
-
* the reservation it just refused is granted on the next attempt.
|
|
1340
|
-
*
|
|
1341
|
-
* Returns `"proven"` on success, or the shape of the failure so the candidate loop can act on it.
|
|
1342
|
-
* It never throws: throwing would turn one unreachable relay into a failure to build a receiver
|
|
1343
|
-
* at all.
|
|
1344
|
-
*
|
|
1345
|
-
* ─── Why this returns a verdict instead of a boolean ──────────────────────────────────────────
|
|
1346
|
-
*
|
|
1347
|
-
* Review HIGH-1/HIGH-2. Putting the reservation behind a proof MOVED THE FIRST REFUSAL onto this
|
|
1348
|
-
* path. `authenticateStandingReceiver` does everything right with a refusal — records it where
|
|
1349
|
-
* `cello_status` can read it, and quarantines a relay whose fault is its own — but it runs only
|
|
1350
|
-
* on a receiver that ALREADY HAS a reservation, so under the new gate a total failure never
|
|
1351
|
-
* reaches it. This method was logging `proven: false` and returning.
|
|
1352
|
-
*
|
|
1353
|
-
* What that cost the operator: an expired token, or an agent at its slot cap, refused by every
|
|
1354
|
-
* relay in the pool. `cello_status` shows an agent that is online and reachable by nobody, with
|
|
1355
|
-
* no cause anywhere the person will look — while the relay had computed the cause, the count, and
|
|
1356
|
-
* the next step, and put them on the wire. And `slot_cap_exceeded` is classified
|
|
1357
|
-
* `tryAnotherRelay: false` precisely so the client STOPS walking the fleet; without the verdict,
|
|
1358
|
-
* the loop walked it anyway, turning one client-side fault into what reads as a fleet outage.
|
|
1359
|
-
*/
|
|
1360
|
-
/**
|
|
1361
|
-
* 055-ONDEMAND — **tell the relay this agent has finished with its slot.**
|
|
1362
|
-
*
|
|
1363
|
-
* The only thing that actually frees one: closing a circuit listener sends the relay nothing, and
|
|
1364
|
-
* the relay reclaims on its own only at the reservation TTL (two hours) or under reaper pressure.
|
|
1365
|
-
* Best-effort by design — an undelivered release costs a slot until that TTL, and must never fail
|
|
1366
|
-
* the seal that triggered it.
|
|
1367
|
-
*/
|
|
1368
|
-
async tellRelayReleased(agentName, relayPeerId, node, correlationId) {
|
|
1369
|
-
/**
|
|
1370
|
-
* ⚠️ **THE NODE IS THE SOURCE OF TRUTH FOR WHERE THIS RELAY IS — review MEDIUM-6.**
|
|
1371
|
-
*
|
|
1372
|
-
* The first version looked the relay up in `directoryRelayEndpoints`. But the reservation was
|
|
1373
|
-
* taken on the relay the OFFER named, whose addresses came off that frame; if the directory's
|
|
1374
|
-
* last announcement does not contain it — a pool relay dropped from the roster, a stale
|
|
1375
|
-
* announcement — the release warns `no_endpoint` and the slot is held to its TTL. The node
|
|
1376
|
-
* announces the circuit it actually holds, so derive the address from that and fall back to the
|
|
1377
|
-
* directory list only when there is nothing to derive from.
|
|
1378
|
-
*/
|
|
1379
|
-
const fromNode = node.listenAddresses()
|
|
1380
|
-
.filter((a) => a.split("/").includes("p2p-circuit") && a.includes(`/p2p/${relayPeerId}/`))
|
|
1381
|
-
.map((a) => a.split("/p2p-circuit")[0])
|
|
1382
|
-
.filter((a) => a.length > 0);
|
|
1383
|
-
const ep = this.#ctx.directoryRelayEndpoints.get(agentName)?.find((e) => e.relayPeerId === relayPeerId);
|
|
1384
|
-
const relayAddrs = fromNode.length > 0 ? [...new Set(fromNode)] : (ep ? [...ep.relayAddrs] : []);
|
|
1385
|
-
if (relayAddrs.length === 0) {
|
|
1386
|
-
this.#ctx.logger.warn("session.relay.reservation_release.no_endpoint", {
|
|
1387
|
-
agentName, relayPeerId, correlationId,
|
|
1388
|
-
impact: "no address is known for this relay, so it cannot be told the slot is free and " +
|
|
1389
|
-
"holds it until the reservation TTL expires.",
|
|
1390
|
-
});
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1393
|
-
let client;
|
|
1394
|
-
try {
|
|
1395
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, relayAddrs, {
|
|
1396
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
1397
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
1398
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
1399
|
-
});
|
|
1400
|
-
if (!client)
|
|
1401
|
-
return;
|
|
1402
|
-
await client.releaseReservation(node);
|
|
1403
|
-
}
|
|
1404
|
-
catch (err) {
|
|
1405
|
-
this.#ctx.logger.warn("session.relay.reservation_release.failed", {
|
|
1406
|
-
agentName, relayPeerId, correlationId,
|
|
1407
|
-
error: extractErrorMessage(err),
|
|
1408
|
-
impact: "the relay holds this slot until its TTL expires.",
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
finally {
|
|
1412
|
-
client?.close();
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
async proveToRelay(agentName, circuitAddr, node, correlationId,
|
|
1416
|
-
/**
|
|
1417
|
-
* Whether this proof is the STANDING RECEIVER's, and may therefore write the surface
|
|
1418
|
-
* `cello_status` reads as "your standing receiver was refused".
|
|
1419
|
-
*
|
|
1420
|
-
* A revival proves itself too, and its refusal is real — but it is not evidence about the
|
|
1421
|
-
* receiver. A receiver that proved thirty seconds ago and holds a slot, plus a revival refused
|
|
1422
|
-
* with `slot_cap_exceeded`, would otherwise have `cello_status` report a front door as refused
|
|
1423
|
-
* while it is open. The refusal is still logged and still steers the candidate loop; what it
|
|
1424
|
-
* does not do is claim to be about something it did not measure.
|
|
1425
|
-
*/
|
|
1426
|
-
surfaceAsReceiverRefusal) {
|
|
1427
|
-
const relayPeerId = relayPeerIdOf(circuitAddr);
|
|
1428
|
-
const baseRelayAddr = circuitAddr.split("/p2p-circuit")[0];
|
|
1429
|
-
if (!relayPeerId || !baseRelayAddr) {
|
|
1430
|
-
this.#ctx.logger.warn("session.standing_receiver.prove.address_unreadable", {
|
|
1431
|
-
agentName,
|
|
1432
|
-
circuitAddr,
|
|
1433
|
-
correlationId,
|
|
1434
|
-
impact: "this candidate's circuit address does not name a relay peer, so there is nothing " +
|
|
1435
|
-
"to prove to and its reservation will stay refused. Skipped silently before — which made " +
|
|
1436
|
-
"a malformed address look identical to a relay that simply said no.",
|
|
1437
|
-
});
|
|
1438
|
-
return "unavailable";
|
|
1439
|
-
}
|
|
1440
|
-
/**
|
|
1441
|
-
* Declared out here so the `finally` can close it. Scoped inside the `try` before, so a throw
|
|
1442
|
-
* from `proveReservation` skipped the close and left the stream and its pending settles behind.
|
|
1443
|
-
*/
|
|
1444
|
-
let client;
|
|
1445
|
-
try {
|
|
1446
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
|
|
1447
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
1448
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
1449
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
1450
|
-
});
|
|
1451
|
-
if (!client) {
|
|
1452
|
-
this.#ctx.logger.warn("session.standing_receiver.prove.no_builder", {
|
|
1453
|
-
agentName,
|
|
1454
|
-
relayPeerId,
|
|
1455
|
-
correlationId,
|
|
1456
|
-
impact: "no relay client could be built, so this receiver cannot prove itself and the " +
|
|
1457
|
-
"relay will refuse its reservation again. The agent is reachable only over a direct " +
|
|
1458
|
-
"connection until this is wired.",
|
|
1459
|
-
});
|
|
1460
|
-
return "unavailable";
|
|
1461
|
-
}
|
|
1462
|
-
const proven = await client.proveReservation(node);
|
|
1463
|
-
if (proven) {
|
|
1464
|
-
if (surfaceAsReceiverRefusal)
|
|
1465
|
-
this.#ctx.srRelayRefusal.delete(agentName);
|
|
1466
|
-
this.#ctx.logger.info("session.standing_receiver.prove.result", {
|
|
1467
|
-
agentName, relayPeerId, peerId: node.getPeerId(), proven: true, correlationId,
|
|
1468
|
-
});
|
|
1469
|
-
return "proven";
|
|
1470
|
-
}
|
|
1471
|
-
// ⚠️ A NULL REFUSAL IS A TRANSPORT FAILURE, NOT A VERDICT (review HIGH-1): set only on a
|
|
1472
|
-
// refusal ON THE MERITS, so the `else` clears a stale one `cello_status` would misreport, and
|
|
1473
|
-
// the RETURN below says `unavailable` — the caller decides whether to ask on it.
|
|
1474
|
-
const refusal = client.getLastAuthRefusal();
|
|
1475
|
-
if (surfaceAsReceiverRefusal) {
|
|
1476
|
-
if (refusal) {
|
|
1477
|
-
this.#ctx.srRelayRefusal.set(agentName, { ...this.#ctx.withDirectoryCause(agentName, refusal), relayPeerId });
|
|
1478
|
-
}
|
|
1479
|
-
else {
|
|
1480
|
-
this.#ctx.srRelayRefusal.delete(agentName);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
this.#ctx.logger.warn("session.standing_receiver.prove.result", {
|
|
1484
|
-
agentName,
|
|
1485
|
-
relayPeerId,
|
|
1486
|
-
peerId: node.getPeerId(),
|
|
1487
|
-
proven: false,
|
|
1488
|
-
refusalReason: refusal?.reason ?? "no_relay_verdict",
|
|
1489
|
-
tryAnotherRelay: refusal?.tryAnotherRelay ?? true,
|
|
1490
|
-
correlationId,
|
|
1491
|
-
impact: refusal?.advice ??
|
|
1492
|
-
"the relay said nothing about why, which is what a transport failure mid-handshake looks " +
|
|
1493
|
-
"like. No verdict was reached, so the reservation is still asked for.",
|
|
1494
|
-
});
|
|
1495
|
-
if (refusal && !refusal.tryAnotherRelay)
|
|
1496
|
-
return "refused_this_agent";
|
|
1497
|
-
if (refusal?.tryAnotherRelay && !this.#ctx.shuttingDown) {
|
|
1498
|
-
this.#quarantineRelay(agentName, relayPeerId, refusal.reason);
|
|
1499
|
-
}
|
|
1500
|
-
if (!refusal)
|
|
1501
|
-
return "unavailable"; // no verdict — see the refusal note above.
|
|
1502
|
-
return "refused_try_another_relay";
|
|
1503
|
-
}
|
|
1504
|
-
catch (err) {
|
|
1505
|
-
this.#ctx.logger.warn("session.standing_receiver.prove.failed", {
|
|
1506
|
-
agentName,
|
|
1507
|
-
correlationId,
|
|
1508
|
-
error: extractErrorMessage(err),
|
|
1509
|
-
impact: "this receiver could not prove itself, so its retry will be refused and the " +
|
|
1510
|
-
"candidate loop will try the next relay.",
|
|
1511
|
-
});
|
|
1512
|
-
return "unavailable";
|
|
1513
|
-
}
|
|
1514
|
-
finally {
|
|
1515
|
-
client?.close();
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* DOD-M12B-REVIVE-RELAY-1 — reconnect the session's relay WITNESS, which revival never did.
|
|
1520
|
-
*
|
|
1521
|
-
* THE FIRST-PRINCIPLES DEFECT, and the one that explains every symptom chased separately before
|
|
1522
|
-
* it. Establishment does five things: build the node, register the content handler, wire liveness,
|
|
1523
|
-
* **connect the relay**, and dial the counterparty. Revival did the first three. A revived session
|
|
1524
|
-
* was therefore not a session — it looked live, reported `active`, and had no live inbound path at
|
|
1525
|
-
* all.
|
|
1526
|
-
*
|
|
1527
|
-
* MEASURED 2026-08-18 with two real agents: a message on a reconnected session took **three
|
|
1528
|
-
* minutes**, against seconds on a fresh one, because only the five-minute mailbox backstop ever
|
|
1529
|
-
* found it. Doorbells stopped firing for the same reason — the relay stream is what rings them.
|
|
1530
|
-
* And `#parkContent` refuses without `entry.relayClient`, so sends could not park either.
|
|
1531
|
-
*
|
|
1532
|
-
* NO ASSIGNMENT IS PRESENTED, and that is by design rather than omission: `RelayConnectParams`
|
|
1533
|
-
* documents the reconnect mode itself — *"absent … on the restart/persisted reconnect path (the
|
|
1534
|
-
* relay already recorded the session at first establishment) — the client then just reconnects
|
|
1535
|
-
* without re-recording."* A revival is exactly that path.
|
|
1536
|
-
*
|
|
1537
|
-
* Best-effort and non-fatal: a session that comes back without its witness is still better than
|
|
1538
|
-
* one that does not come back, and the failure is named rather than silent.
|
|
1539
|
-
*/
|
|
1540
|
-
async reconnectRevivedSessionRelay(agentName, sessionId, node, gater, correlationId, ep) {
|
|
1541
|
-
if (!ep) {
|
|
1542
|
-
this.#ctx.logger.warn("session.revive.relay.absent", {
|
|
1543
|
-
agentName,
|
|
1544
|
-
sessionId,
|
|
1545
|
-
impact: "no relay is recorded for this session, so it comes back with no live inbound path — "
|
|
1546
|
-
+ "messages arrive only on the periodic mailbox poll, and a failed send cannot park and is "
|
|
1547
|
-
+ "reported lost",
|
|
1548
|
-
});
|
|
1549
|
-
return;
|
|
1550
|
-
}
|
|
1551
|
-
try {
|
|
1552
|
-
// The gater admits only the counterparty inbound; the relay is a third peer and must be
|
|
1553
|
-
// permitted OUTBOUND or our own gate refuses the dial (INV-5 keeps inbound counterparty-only).
|
|
1554
|
-
gater.setAllowedOutboundPeer(ep.relayPeerId);
|
|
1555
|
-
const clientKey = `${agentName}::${ep.relayPeerId}`;
|
|
1556
|
-
let client = this.#ctx.relayClients.get(clientKey);
|
|
1557
|
-
if (!client) {
|
|
1558
|
-
if (!this.#ctx.relayReceiptStore && this.#db)
|
|
1559
|
-
this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
|
|
1560
|
-
if (!this.#ctx.sealLeafStore && this.#db)
|
|
1561
|
-
this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
|
|
1562
|
-
client = this.#ctx.detachedRelayClientBuilder?.(agentName, ep.relayPeerId, [...ep.relayAddrs], {
|
|
1563
|
-
receiptStore: this.#ctx.relayReceiptStore ?? undefined,
|
|
1564
|
-
sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
|
|
1565
|
-
// DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
|
|
1566
|
-
onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
|
|
1567
|
-
});
|
|
1568
|
-
if (!client) {
|
|
1569
|
-
this.#ctx.logger.warn("session.revive.relay.builder_absent", {
|
|
1570
|
-
agentName,
|
|
1571
|
-
sessionId,
|
|
1572
|
-
impact: "no relay client could be built, so this revived session has no live inbound path",
|
|
1573
|
-
});
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
this.#ctx.relayClients.set(clientKey, client);
|
|
1577
|
-
}
|
|
1578
|
-
// 033-ACKEMIT: a revived session re-registers with no assignment in hand, so the genesis comes
|
|
1579
|
-
// from the entry that was just restored above.
|
|
1580
|
-
// 069-ORDERPROOF: a revived session re-registers with no assignment, so the anchor comes from
|
|
1581
|
-
// the session row — the reason it is stored there at all.
|
|
1582
|
-
client.registerSession(sessionId, node, this.#ctx.contentIn.relayLeafHandler(agentName, sessionId, correlationId), undefined, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
|
|
1583
|
-
const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
1584
|
-
if (entry) {
|
|
1585
|
-
entry.relayClient = client;
|
|
1586
|
-
entry.relaySessionIdBytes = Uint8Array.from(Buffer.from(sessionId, "hex"));
|
|
1587
|
-
entry.relayClientKey = clientKey;
|
|
1588
|
-
}
|
|
1589
|
-
/**
|
|
1590
|
-
* THE STEP THIS METHOD IS NAMED AFTER, and the first version did not take it (review HIGH-2).
|
|
1591
|
-
*
|
|
1592
|
-
* `registerSession` files a handler in a Map. It opens nothing — no dial, no auth, no reader
|
|
1593
|
-
* loop. `connectSessionRelay` ends with exactly this line and the reconnect ended without it,
|
|
1594
|
-
* so a revived session registered a handler on a client whose stream was `null` and then
|
|
1595
|
-
* logged that its live inbound path was back. It was not: the counterparty's leaves queued at
|
|
1596
|
-
* the relay, no doorbell fired, and delivery fell back to the five-minute mailbox poll — the
|
|
1597
|
-
* three-minutes-versus-seconds symptom the whole unit exists to remove.
|
|
1598
|
-
*
|
|
1599
|
-
* Worse, the client is usually BRAND NEW here: `markInterruptedWithDetails` closes and drops
|
|
1600
|
-
* the client for the last session on a relay, so a single-session agent always lands in the
|
|
1601
|
-
* build branch above with a fresh, unconnected client.
|
|
1602
|
-
*
|
|
1603
|
-
* `#ensureConnected` is idempotent, so this also repairs the cached-but-dead-stream case.
|
|
1604
|
-
*/
|
|
1605
|
-
await client.connect(node);
|
|
1606
|
-
this.#ctx.logger.info("session.revive.relay.connected", {
|
|
1607
|
-
agentName,
|
|
1608
|
-
sessionId,
|
|
1609
|
-
relayPeerId: ep.relayPeerId,
|
|
1610
|
-
impact: "the revived session has its live inbound path back — messages arrive promptly "
|
|
1611
|
-
+ "instead of waiting for the periodic mailbox poll",
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
catch (err) {
|
|
1615
|
-
this.#ctx.logger.warn("session.revive.relay.failed", {
|
|
1616
|
-
agentName,
|
|
1617
|
-
sessionId,
|
|
1618
|
-
error: extractErrorMessage(err),
|
|
1619
|
-
impact: "the session is back but without its witness — delivery falls back to the periodic poll",
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
/** DOD-M12B-REVIVE-PARK-1 test seam: the relay the live entry will park to. Not otherwise
|
|
1624
|
-
* observable — `#activeNodes` is private and the park's own refusal is silent about which of its
|
|
1625
|
-
* four preconditions was missing. */
|
|
1626
|
-
getSessionRelayForTest(agentName, sessionId) {
|
|
1627
|
-
const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
|
|
1628
|
-
if (!entry)
|
|
1629
|
-
return null;
|
|
1630
|
-
return {
|
|
1631
|
-
...(entry.relayPeerId !== undefined ? { relayPeerId: entry.relayPeerId } : {}),
|
|
1632
|
-
...(entry.relayAddrs !== undefined ? { relayAddrs: entry.relayAddrs } : {}),
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
//# sourceMappingURL=session-relay.js.map
|