@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/content-park.js
DELETED
|
@@ -1,1309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The content park: relay store-and-forward, and recovery from it.
|
|
3
|
-
*
|
|
4
|
-
* When a counterparty is offline, content is PARKED on the relay — encrypted, so the relay never
|
|
5
|
-
* sees plaintext — and pulled when they come back. autoRecoverForAgent is what runs on reconnect.
|
|
6
|
-
*
|
|
7
|
-
* TWO-PHASE ON PURPOSE, and this is the pattern that unblocks the rest of the daemon:
|
|
8
|
-
*
|
|
9
|
-
* createContentPark(deps) — constructed EARLY, because autoRecoverForAgent is called from
|
|
10
|
-
* BOOT-TIME paths (an agent's onConnected, and the seal upgrade's
|
|
11
|
-
* content gate) that run long before the IPC handler map exists.
|
|
12
|
-
* .registerHandlers(handlers) — called LATER, once that map does exist.
|
|
13
|
-
*
|
|
14
|
-
* Fusing the two would force construction after the handler map, which puts autoRecoverForAgent in
|
|
15
|
-
* the TEMPORAL DEAD ZONE for every boot-time caller — the daemon then dies at startup with "cannot
|
|
16
|
-
* access before initialization". Separating construction from registration is what makes a module's
|
|
17
|
-
* POSITION IN THE FILE stop mattering, which is the whole reason startDaemon could be taken apart.
|
|
18
|
-
*/
|
|
19
|
-
// `createHash` is gone: the annex verifier's hardcoded `sha256(0x00 ‖ content)` became
|
|
20
|
-
// `contentHashFor`, so this file no longer computes a content hash without asking which algorithm
|
|
21
|
-
// the sender used (`DOD-M15-SEALWIRE-1` part B2a).
|
|
22
|
-
import { randomUUID } from "node:crypto";
|
|
23
|
-
import { ContentParkClient, ContentParkRefusedError, ContentParkUnreachableError } from "./content-park-client.js";
|
|
24
|
-
import { extractErrorMessage } from "./error-message.js";
|
|
25
|
-
// DOD-M15-DELIVERYACK-1 unit 1: the mailbox also carries acknowledgements of OUR messages, parked
|
|
26
|
-
// because this daemon was down when the counterparty read one. Same five rules, other road.
|
|
27
|
-
import { acceptParkedDeliveryAck } from "./session-delivery-acks.js";
|
|
28
|
-
import { decodeParkEnvelope, sealParkEnvelope } from "./park-envelope.js";
|
|
29
|
-
import { contentHashFor, resolveContentHashAlg, isKnownContentHashAlg, CONTENT_HASH_ALGS } from "./wire-content-hash.js";
|
|
30
|
-
import { PARK_REFUSAL_REASONS, PARK_REFUSAL_NOTICE, TERMINAL_SESSION_STATUSES, } from "./park-refusals.js";
|
|
31
|
-
export function createContentPark(deps) {
|
|
32
|
-
const { logger, sessionNodeManager, agents, getKeyProvider, securityGateway } = deps;
|
|
33
|
-
const newParkClient = deps.makeContentParkClient ?? ((o) => new ContentParkClient(o));
|
|
34
|
-
/**
|
|
35
|
-
* `041-PARKSTUCK` Unit 2 — **THE PARK DRAIN'S REFUSALS REACH A PERSON.**
|
|
36
|
-
*
|
|
37
|
-
* Every branch below already wrote a `reason`, an `impact` and a `guidance`, at ERROR, addressed
|
|
38
|
-
* to nobody: the drain runs unattended, its return value goes to an IPC caller that in production
|
|
39
|
-
* is `autoRecoverForAgent`, and that only tallies counts into a log line. So the operator's whole
|
|
40
|
-
* account of a message that never leaves the mailbox was the INGEST reason recorded one layer up
|
|
41
|
-
* — `session_committed`, which is where the message was turned away on arrival, not why it is
|
|
42
|
-
* stuck. Naming the exit point instead of the cause is the substitution this project's debugging
|
|
43
|
-
* discipline is written against, and pointed at an operator it is a dead end: there is no next
|
|
44
|
-
* hop, and `daemon.log` is not an affordance.
|
|
45
|
-
*
|
|
46
|
-
* ⚠️ **BOTH REASONS REACH THE INBOX, and neither replaces the other.** They answer different
|
|
47
|
-
* questions — one says the conversation is closed, this one says why the message cannot leave —
|
|
48
|
-
* and the notice store keys on (session, reason), so they sit side by side rather than
|
|
49
|
-
* overwriting.
|
|
50
|
-
*/
|
|
51
|
-
/**
|
|
52
|
-
* ⚠️ **IT RETURNS THE REFUSAL RECORD, AND THAT IS THE ENFORCEMENT — review H3, second half.**
|
|
53
|
-
*
|
|
54
|
-
* The first version only wrote the notice, and the caller pushed its own `{ reason }` object
|
|
55
|
-
* separately. So a future branch could push a reason and forget to note it, and the enforcer
|
|
56
|
-
* could not see the difference: its scan asked only whether the reason NAME appeared somewhere in
|
|
57
|
-
* the file, which a bare `refusals.push({ reason })` satisfies on its own.
|
|
58
|
-
*
|
|
59
|
-
* Constructing the record HERE makes the pairing structural rather than remembered — the only way
|
|
60
|
-
* to get a park reason into the drain's refusal list is to have already told the operator about
|
|
61
|
-
* it. `dod-m15-inboxcause-1.test.ts` then has something it can actually check.
|
|
62
|
-
*/
|
|
63
|
-
function noteParkRefusal(agentName, sessionId, contentHash, reason, ctx) {
|
|
64
|
-
// `noteContentRefusal` does not throw — a persistence failure logs and falls back to memory —
|
|
65
|
-
// so this needs no guard of its own, and adding one would hide the ERROR it already emits.
|
|
66
|
-
sessionNodeManager.noteContentRefusal(agentName, sessionId, reason, PARK_REFUSAL_NOTICE[reason](ctx));
|
|
67
|
-
return { contentHash, sessionId, reason };
|
|
68
|
-
}
|
|
69
|
-
// MSG-001-3b: content-park deposit/pull IPC handlers. These drive the daemon's
|
|
70
|
-
// ContentParkClient directly so the daemon↔relay store-and-forward transport can be
|
|
71
|
-
// proven (J-CONTENT increment 1) before the send/receive-path integration. The relay
|
|
72
|
-
// multiaddr (with /p2p/<peerId>) comes from the session assignment's relay endpoint;
|
|
73
|
-
// dials run from the standing receiver (open-gater) node.
|
|
74
|
-
/**
|
|
75
|
-
* ⚠️ THE `/p2p/` SEGMENT IS SHAPE-CHECKED — review HIGH-1.
|
|
76
|
-
*
|
|
77
|
-
* This used to accept whatever followed `/p2p/`, so a truncated or mistyped multiaddr became a
|
|
78
|
-
* "peer id" that only `newStream` rejected, four layers down, as `invalid_peer_id` — and the
|
|
79
|
-
* deposit then reported it as a relay that could not be REACHED. A payload fault wearing a
|
|
80
|
-
* network fault's name is the substitution this milestone is named for.
|
|
81
|
-
*
|
|
82
|
-
* This proves the segment is a plausible base58btc peer id, NOT that the peer exists — the
|
|
83
|
-
* multibase/multihash decode still happens in the transport, and `invalid_peer_id` is still
|
|
84
|
-
* mapped honestly below for anything this misses. It is defence in depth on the cheapest fault,
|
|
85
|
-
* not a replacement for the mapping.
|
|
86
|
-
*/
|
|
87
|
-
const PEER_ID_SHAPE = /^(1[1-9A-HJ-NP-Za-km-z]{47,52}|Qm[1-9A-HJ-NP-Za-km-z]{44}|12D3Koo[1-9A-HJ-NP-Za-km-z]{45,50})$/;
|
|
88
|
-
const parseRelayPeer = (multiaddr) => {
|
|
89
|
-
if (!multiaddr)
|
|
90
|
-
return null;
|
|
91
|
-
// Up to the next `/`, so a CIRCUIT address (`…/p2p/<relay>/p2p-circuit/p2p/<target>`) yields the
|
|
92
|
-
// relay rather than `<relay>/p2p-circuit` — which the old form produced and only `newStream`
|
|
93
|
-
// ever rejected, four layers down, as an invalid peer id.
|
|
94
|
-
const peerId = multiaddr.split("/p2p/")[1]?.split("/")[0];
|
|
95
|
-
return peerId && PEER_ID_SHAPE.test(peerId) ? { peerId, addr: multiaddr } : null;
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* DOD-M15-PARKCONN-1 review HIGH-1 — **ONE STREAM FAILURE IS SIX FAULTS, AND ONLY TWO ARE ABOUT
|
|
99
|
-
* REACHING THE RELAY.**
|
|
100
|
-
*
|
|
101
|
-
* `#open` wraps every `newStream` rejection, because the dial evidence it collects is worth having
|
|
102
|
-
* on all of them. Labelling them all `relay_unreachable` is a different question, and getting it
|
|
103
|
-
* wrong reproduces the exact defect this unit exists to remove: `invalid_peer_id` is a MALFORMED
|
|
104
|
-
* ARGUMENT, `node_stopped` is THIS daemon's own transport, and `protocol_not_supported` means the
|
|
105
|
-
* connection worked and the relay does not speak content-park. Telling any of those three
|
|
106
|
-
* "the relay could not be reached — retry once it is up" sends the operator to the network for a
|
|
107
|
-
* fault that is not there, and the guidance is the half an operator actually reads.
|
|
108
|
-
*
|
|
109
|
-
* Reasons come from `core/transport/src/node.ts` (`newStream` + `mapStreamError`). An unknown one
|
|
110
|
-
* gets a neutral label rather than a reachability claim, because the default is where this class
|
|
111
|
-
* of defect hides.
|
|
112
|
-
*/
|
|
113
|
-
const parkTransportFault = (reason, relayPeerId) => {
|
|
114
|
-
switch (reason) {
|
|
115
|
-
case "no_connection":
|
|
116
|
-
case "connection_lost":
|
|
117
|
-
return {
|
|
118
|
-
reason: `relay_unreachable:${reason}`,
|
|
119
|
-
guidance: `Relay ${relayPeerId} could not be reached from this daemon (${reason}), so nothing was deposited and no content was lost. ` +
|
|
120
|
-
`Retry once the relay is up. A conversation cannot be moved to a different relay from here — the park relay comes from the ` +
|
|
121
|
-
`session assignment — so if this relay stays down, broker a new session with cello_initiate_session.`,
|
|
122
|
-
};
|
|
123
|
-
case "invalid_peer_id":
|
|
124
|
-
return {
|
|
125
|
-
reason: "relay_address_invalid",
|
|
126
|
-
guidance: `The /p2p/ segment of the relayMultiaddr is not a usable peer id, so no relay was ever contacted. The relay is not at fault ` +
|
|
127
|
-
`and retrying will not help. Pass the relay endpoint from the session assignment verbatim — a truncated paste is the usual cause.`,
|
|
128
|
-
};
|
|
129
|
-
case "node_stopped":
|
|
130
|
-
return {
|
|
131
|
-
reason: "daemon_transport_stopped",
|
|
132
|
-
guidance: `This daemon's own transport is stopped, so the relay was never asked. Nothing was deposited. This is local: restart the ` +
|
|
133
|
-
`daemon, or start the agent with cello_start_agent if it was stopped deliberately.`,
|
|
134
|
-
};
|
|
135
|
-
case "protocol_not_supported":
|
|
136
|
-
return {
|
|
137
|
-
reason: "relay_protocol_unsupported",
|
|
138
|
-
guidance: `Relay ${relayPeerId} is CONNECTED and does not speak the content-park protocol — a version skew, not an outage. Retrying ` +
|
|
139
|
-
`will not change it. Use a relay running a build that carries content-park, or update this one.`,
|
|
140
|
-
};
|
|
141
|
-
case "limited_connection_refused":
|
|
142
|
-
return {
|
|
143
|
-
reason: "relay_limited_connection",
|
|
144
|
-
guidance: `Relay ${relayPeerId} allowed only a limited (relayed) connection and refused a content-park stream on it. CELLO's own paths ` +
|
|
145
|
-
`always permit limited connections, so this points at a defect on this side rather than at the relay; report it with the daemon log.`,
|
|
146
|
-
};
|
|
147
|
-
default:
|
|
148
|
-
return {
|
|
149
|
-
reason: `park_stream_failed:${reason}`,
|
|
150
|
-
guidance: `The content-park stream to relay ${relayPeerId} could not be opened (${reason}), and nothing was deposited. This daemon has no ` +
|
|
151
|
-
`specific remedy for that reason — the dial detail below is the evidence to report.`,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
/** The IPC refusal for a content-park stream that could not be opened, with the dial evidence. */
|
|
156
|
-
const parkTransportRefusal = (err) => {
|
|
157
|
-
const fault = parkTransportFault(err.reason, err.relayPeerId);
|
|
158
|
-
const dials = err.dialFailures.length > 0
|
|
159
|
-
? ` Dials tried: ${err.dialFailures.map((f) => `${f.addr} → ${f.error}`).join("; ")}.`
|
|
160
|
-
: ` No dial was refused, so the connection existed a moment ago or was never attempted.`;
|
|
161
|
-
return {
|
|
162
|
-
ok: false,
|
|
163
|
-
reason: fault.reason,
|
|
164
|
-
guidance: fault.guidance + dials,
|
|
165
|
-
// DoD 1: recoverable from the RESPONSE as well as the log — this caller has no daemon log.
|
|
166
|
-
dialFailures: err.dialFailures,
|
|
167
|
-
};
|
|
168
|
-
};
|
|
169
|
-
// MSG-001-3b (increment 3): RECOVER parked content. Pulls the recipient's parked entries,
|
|
170
|
-
// decrypts each IN-DAEMON (openContentSeal — the relay never sees plaintext), and routes the
|
|
171
|
-
// plaintext through ingestReceivedContent — the SAME inbound chokepoint as a direct receive
|
|
172
|
-
// (M9 single-funnel AC). The content completes the recipient's transcript view of an already-
|
|
173
|
-
// witnessed message so it can be read (cello_receive) and the session bilaterally sealed
|
|
174
|
-
// (DOD-INT-2). This is content-completion, NOT a resumption — the session stays interrupted.
|
|
175
|
-
// DOD-MSG-4: pull a recipient agent's parked mailbox from ONE relay and recover each entry through
|
|
176
|
-
// the inbound funnel (decode envelope → verify+order the signed Structure2 → ingest). Shared by the
|
|
177
|
-
// explicit IPC handler and the auto-recover-on-reconnect trigger below.
|
|
178
|
-
async function recoverParkedFromRelay(recipientAgent, relayPeerId, relayAddrs) {
|
|
179
|
-
const kp = getKeyProvider(recipientAgent.name);
|
|
180
|
-
if (!kp)
|
|
181
|
-
return { ok: false, reason: "signing_key_unavailable" };
|
|
182
|
-
if (!kp.openContentSeal)
|
|
183
|
-
return { ok: false, reason: "cannot_unseal" };
|
|
184
|
-
const node = sessionNodeManager.getStandingReceiverNode();
|
|
185
|
-
// Name the CAUSE, not the exit point (review F6): one label stood for "still building",
|
|
186
|
-
// "agent never came online", "shutting down" and "none exists", and it is the label that
|
|
187
|
-
// misdescribed this incident 102 times.
|
|
188
|
-
if (!node)
|
|
189
|
-
return { ok: false, reason: sessionNodeManager.standingReceiverAbsenceReason(recipientAgent.name) };
|
|
190
|
-
const recipientPubkey = recipientAgent.pubkey ?? "";
|
|
191
|
-
const client = newParkClient({ relayPeerId, relayAddrs, logger });
|
|
192
|
-
let entries;
|
|
193
|
-
try {
|
|
194
|
-
entries = await client.pull(node, Buffer.from(recipientPubkey, "hex"), kp);
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
/**
|
|
198
|
-
* DOD-M15-RELAYAUTH-1 review HIGH-3 — a REFUSAL is not an empty mailbox, and the distinction
|
|
199
|
-
* is the whole point of this branch. Falling through with `[]` would report
|
|
200
|
-
* `{ok:true, pulled:0, recovered:0}` — "nothing was waiting for you" — for a relay that
|
|
201
|
-
* explicitly said it would not release this agent's content. Same defect shape as the
|
|
202
|
-
* refused-entry comment directly below, one layer further out.
|
|
203
|
-
*/
|
|
204
|
-
if (err instanceof ContentParkRefusedError) {
|
|
205
|
-
logger.warn("content.recover.refused", { agentName: recipientAgent.name, relayPeerId, reason: err.reason });
|
|
206
|
-
return { ok: false, reason: `relay_refused_pull:${err.reason}` };
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Review MEDIUM-2 — the transport wrap reaches here too, and rethrowing it made
|
|
210
|
-
* `content_park_recover` answer `internal_error` / "An unexpected error occurred" for a relay
|
|
211
|
-
* that is simply down. `content_park_recover` is one of the three failures this order was
|
|
212
|
-
* written from, so this is the live path, not a hypothetical.
|
|
213
|
-
*/
|
|
214
|
-
if (err instanceof ContentParkUnreachableError) {
|
|
215
|
-
const refusal = parkTransportRefusal(err);
|
|
216
|
-
return { ok: false, reason: refusal.reason, guidance: refusal.guidance };
|
|
217
|
-
}
|
|
218
|
-
throw err;
|
|
219
|
-
}
|
|
220
|
-
let recovered = 0;
|
|
221
|
-
// SEC-1 / review M2: a REFUSED entry must not vanish behind `ok:true`. Report it — an operator
|
|
222
|
-
// seeing {ok:true, pulled:3, recovered:0} would otherwise have three messages evaporate with the
|
|
223
|
-
// only explanation buried in the daemon log. This is both the injection signal and, given the
|
|
224
|
-
// deliberate no-tolerant-window rollout, the lagging-peer signal.
|
|
225
|
-
const refusals = [];
|
|
226
|
-
// AC7: one correlationId per recover flow, threaded through every event it emits.
|
|
227
|
-
const correlationId = randomUUID();
|
|
228
|
-
for (const e of entries) {
|
|
229
|
-
const unsealed = await kp.openContentSeal(e.ciphertext);
|
|
230
|
-
if (!unsealed) {
|
|
231
|
-
logger.warn("content.recover.unseal_failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex });
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
// SEC-1: authenticate THEN ingest — fused in recoverParkedEntry, which is now the ONLY way
|
|
235
|
-
// parked content can enter the transcript. It verifies the sender's signature over
|
|
236
|
-
// (session_id, recipient_pubkey, content_hash) and that the signer is this session's
|
|
237
|
-
// counterparty, and it still verifies + records the DOD-MSG-4 ordering record when present.
|
|
238
|
-
// Relay deposit is unauthenticated and the seal is anonymous, so WITHOUT this gate anyone
|
|
239
|
-
// holding the recipient's public key — the relay above all — could inject content that gets
|
|
240
|
-
// attributed to the honest counterparty and then notarized by the bilateral seal.
|
|
241
|
-
const contentHashBytes = Buffer.from(e.contentHashHex, "hex");
|
|
242
|
-
const ingest = await sessionNodeManager.recoverParkedEntry(recipientAgent.name, e.sessionIdHex, Buffer.from(recipientPubkey, "hex"), unsealed, contentHashBytes, correlationId);
|
|
243
|
-
/**
|
|
244
|
-
* DOD-M15-DELIVERYACK-1 unit 1 — THE ENTRY WAS AN ACKNOWLEDGEMENT OF ONE OF OUR MESSAGES.
|
|
245
|
-
*
|
|
246
|
-
* The counterparty read something we sent while this daemon was DOWN, could not reach us
|
|
247
|
-
* directly, and left the signed acknowledgement here. It is not content: it appends no leaf,
|
|
248
|
-
* enters no transcript, and is not counted as a recovered message — counting it would report
|
|
249
|
-
* mail this operator never received.
|
|
250
|
-
*
|
|
251
|
-
* The envelope gate inside `recoverParkedEntry` has already proved the depositor is this
|
|
252
|
-
* session's counterparty. `acceptParkedDeliveryAck` runs the SECOND, independent check — the
|
|
253
|
-
* acknowledgement's own signature, against the key the SESSION recorded — plus the same bind
|
|
254
|
-
* and the same idempotence the live route uses. Two different keys, two different questions,
|
|
255
|
-
* and both must pass.
|
|
256
|
-
*
|
|
257
|
-
* CONFIRM-DELETE ONLY ON ACCEPTANCE, the same rule content follows: a refused entry is left in
|
|
258
|
-
* the mailbox so a forgery cannot evict itself, and so a transient local fault (no database,
|
|
259
|
-
* a failed write) does not destroy evidence we could have kept on the next drain.
|
|
260
|
-
*/
|
|
261
|
-
if (ingest.ok && "deliveryAck" in ingest) {
|
|
262
|
-
/**
|
|
263
|
-
* WRAPPED, because a LOCAL fault here must not cost the rest of the drain — review F3.
|
|
264
|
-
*
|
|
265
|
-
* `getDb()` and `resolveAgentId()` both throw, and an escape from this loop takes every
|
|
266
|
-
* REMAINING entry with it, messages included, unread, reported to the caller as a generic
|
|
267
|
-
* internal error. The content path never had this shape because `recoverParkedEntry` owns
|
|
268
|
-
* its own failures. The entry is left in the mailbox, so the next drain retries it.
|
|
269
|
-
*/
|
|
270
|
-
let accepted;
|
|
271
|
-
try {
|
|
272
|
-
accepted = acceptParkedDeliveryAck({
|
|
273
|
-
db: sessionNodeManager.getDb(),
|
|
274
|
-
logger,
|
|
275
|
-
agentId: sessionNodeManager.resolveAgentId(recipientAgent.name),
|
|
276
|
-
agentName: recipientAgent.name,
|
|
277
|
-
sessionId: e.sessionIdHex,
|
|
278
|
-
mailboxSlotHex: e.contentHashHex,
|
|
279
|
-
counterpartyPubkeyHex: sessionNodeManager.getSessionRecord(recipientAgent.name, e.sessionIdHex)?.counterparty_pubkey,
|
|
280
|
-
ack: ingest.deliveryAck,
|
|
281
|
-
correlationId,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
catch (err) {
|
|
285
|
-
logger.error("content.delivery.ack.parked.accept_failed", {
|
|
286
|
-
agentName: recipientAgent.name, sessionId: e.sessionIdHex, correlationId,
|
|
287
|
-
error: extractErrorMessage(err),
|
|
288
|
-
impact: "a local fault stopped this side storing an acknowledgement it had already recovered. " +
|
|
289
|
-
"The entry stays in the mailbox and the next drain retries it; the rest of the " +
|
|
290
|
-
"mailbox, messages included, is still drained",
|
|
291
|
-
});
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (accepted.ok) {
|
|
295
|
-
try {
|
|
296
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
297
|
-
}
|
|
298
|
-
catch (err) {
|
|
299
|
-
// The proof is stored; failing to clear the mailbox slot costs one redundant drain,
|
|
300
|
-
// which the stored row then absorbs. Never a reason to report the recovery as failed.
|
|
301
|
-
logger.warn("content.park.confirm.failed", {
|
|
302
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex,
|
|
303
|
-
entryKind: "delivery_ack", error: extractErrorMessage(err),
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* ⚠️ A REFUSED ACKNOWLEDGEMENT IS NOT ADDED TO `refusals`, AND THE STRUCTURAL GUARD IN
|
|
309
|
-
* `dod-m15-inboxcause-1` IS WHAT CAUGHT ME PUTTING IT THERE.
|
|
310
|
-
*
|
|
311
|
-
* That list is mail the operator did not receive, and every entry in it raises a notice
|
|
312
|
-
* telling them so. An acknowledgement is not mail: nothing of theirs is missing, nobody is
|
|
313
|
-
* waiting on it, and the notice would read as an accusation about the counterparty — which
|
|
314
|
-
* is the single inference this unit forbids. It is logged (inside `acceptParkedDeliveryAck`)
|
|
315
|
-
* and left in the mailbox unconfirmed, exactly as a refused message is, so a forgery cannot
|
|
316
|
-
* evict itself and a local fault can be retried on the next drain.
|
|
317
|
-
*/
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (ingest.ok && ingest.held) {
|
|
321
|
-
// DOD-MSG-4 (review finding #4): a held entry is NOT yet an appended leaf — its sequence is
|
|
322
|
-
// the FUTURE canonical index, not a completed recovery. Do not count it as recovered; log it
|
|
323
|
-
// distinctly so the tally reflects leaves actually written, not content still queued in memory.
|
|
324
|
-
logger.info("content.recover.held", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, canonicalSeq: ingest.sequenceNumber });
|
|
325
|
-
}
|
|
326
|
-
else if (ingest.ok && ingest.screenedOut) {
|
|
327
|
-
// M9 (code-review LOW-3): a terminal-screened recovered entry IS durably leafed (so it must be
|
|
328
|
-
// confirm-deleted, below) but was NEVER delivered to the agent — do not count it as a delivered
|
|
329
|
-
// recovery, and log it distinctly so observability separates "delivered" from "leafed-but-screened".
|
|
330
|
-
logger.info("content.recover.screened_out", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, sequenceNumber: ingest.sequenceNumber });
|
|
331
|
-
try {
|
|
332
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
333
|
-
}
|
|
334
|
-
catch (err) {
|
|
335
|
-
logger.warn("content.recover.confirm.failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, error: extractErrorMessage(err) });
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
else if (ingest.ok) {
|
|
339
|
-
// DOD-MSG-4 (review #3): count leaves ACTUALLY written — the directly-ingested leaf PLUS any
|
|
340
|
-
// held out-of-order entries this ingest unblocked (appendedCount), not just 1.
|
|
341
|
-
recovered += ingest.appendedCount ?? 1;
|
|
342
|
-
logger.info("content.recovered", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, sequenceNumber: ingest.sequenceNumber });
|
|
343
|
-
// Delete-on-confirm (review #1): the entry is now durably ingested (a fresh leaf, or a dedup
|
|
344
|
-
// of one already present), so confirm-delete it from the relay mailbox. The relay is
|
|
345
|
-
// delete-on-CONFIRM, not delete-on-pull — without this the queue never drains and every
|
|
346
|
-
// reconnect re-pulls the whole history. Held entries are deliberately NOT confirmed (not yet
|
|
347
|
-
// durable). Best-effort: a failed confirm leaves the entry to be re-pulled + deduped next time.
|
|
348
|
-
try {
|
|
349
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
350
|
-
}
|
|
351
|
-
catch (err) {
|
|
352
|
-
logger.warn("content.recover.confirm.failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, error: extractErrorMessage(err) });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
else if (ingest.reason === "session_committed") {
|
|
356
|
-
// M12-P17: the session ENDED before this arrived. It can never join that chain — appending
|
|
357
|
-
// would change `sealed_root` and invalidate the notarization — but it is VERIFIED content
|
|
358
|
-
// (SEC-1 authenticated it above; that is what separates this branch from the one below), and
|
|
359
|
-
// discarding it silently loses a real message the operator was actually sent.
|
|
360
|
-
//
|
|
361
|
-
// Without this branch the entry is refused and never confirm-deleted, so every drain pulls,
|
|
362
|
-
// verifies and refuses it again — measured at ~120 repeats per message, forever, while the
|
|
363
|
-
// operator never sees it once. Noisy invisible loss.
|
|
364
|
-
//
|
|
365
|
-
// ORDER IS LOAD-BEARING: annex FIRST, confirm-delete SECOND, and only if the annex committed.
|
|
366
|
-
// A crash between them must lose nothing. Reversed, or delete-on-best-effort, converts the
|
|
367
|
-
// loop into PERMANENT SILENT loss — strictly worse than the bug.
|
|
368
|
-
// Review F1: annex `env.content`, NOT `unsealed`. `unsealed` is the whole CBOR park envelope
|
|
369
|
-
// — every other consumer decodes it first (recoverParkedEntry does exactly this before
|
|
370
|
-
// ingesting). Storing the envelope and then deleting the relay copy on the strength of it
|
|
371
|
-
// would leave the operator an unreadable blob as the ONLY surviving copy: the loop stops,
|
|
372
|
-
// the message is still never read, and now nothing else holds it. That is the permanent
|
|
373
|
-
// silent loss the ordering above exists to prevent, arriving one line earlier.
|
|
374
|
-
//
|
|
375
|
-
// Review F5: the hash cross-check has to happen HERE too. `parkSig` covers
|
|
376
|
-
// (session_id, recipient_pubkey, content_hash) — it does NOT cover the envelope's `content`
|
|
377
|
-
// field, and this branch returns before `ingestReceivedContent`'s own check. Without it a
|
|
378
|
-
// relay holding the ciphertext could substitute content under a genuine hash, have it
|
|
379
|
-
// annexed, and have the real copy confirm-deleted. It also makes INSERT OR IGNORE sound:
|
|
380
|
-
// identical hash now genuinely means identical bytes.
|
|
381
|
-
// All three verdict branches below are covered by m12-p17-annex-screening.test.ts, driven
|
|
382
|
-
// through this real function with an injected park client and REAL sealed envelopes. The one
|
|
383
|
-
// that matters is TRANSIENT: collapsing it into the terminal branch would delete a good
|
|
384
|
-
// message because the screener was momentarily down — permanent loss caused by an outage.
|
|
385
|
-
// That exact confusion was injected and the test goes red on it.
|
|
386
|
-
let annexed = false;
|
|
387
|
-
let screenedOut = false;
|
|
388
|
-
/**
|
|
389
|
-
* Did the terminal-screen branch actually KEEP the bytes? — verification NEW-2, which is
|
|
390
|
-
* H1's defect one branch over and in the code H1 was written for.
|
|
391
|
-
*
|
|
392
|
-
* `quarantineRefusedInbound` answers `null` on the same four reachable paths, and this
|
|
393
|
-
* branch confirm-deleted the relay copy regardless while its notice said "It is KEPT as
|
|
394
|
-
* evidence" unconditionally. Declared out here rather than inside the `try` so the delete
|
|
395
|
-
* and the notice can both read it.
|
|
396
|
-
*/
|
|
397
|
-
let screenRetained = false;
|
|
398
|
-
let screenDeferred = false;
|
|
399
|
-
/**
|
|
400
|
-
* WHY the entry is stuck, for the caller — review B2a F5.
|
|
401
|
-
*
|
|
402
|
-
* Every non-annexing branch fell through to one `annex_write_failed`, at the very line whose
|
|
403
|
-
* comment forbids exactly this: *"name why the entry is STUCK, not why ingest refused it."*
|
|
404
|
-
* The annex write was never attempted in any of these cases. Four distinct causes shared a
|
|
405
|
-
* label that named none of them, and the pre-existing hash-mismatch branch — a genuine
|
|
406
|
-
* tamper — was one of the four.
|
|
407
|
-
*/
|
|
408
|
-
let annexRefusal = null;
|
|
409
|
-
/** The thrown message, carried into the operator's notice rather than left in the log. */
|
|
410
|
-
let annexErrorDetail = null;
|
|
411
|
-
/**
|
|
412
|
-
* `041-PARKSTUCK` — CAN THIS EVER BE CHECKED AGAIN, or is the answer fixed for the rest of
|
|
413
|
-
* time? Two different states, and only one of them is worth retrying.
|
|
414
|
-
*
|
|
415
|
-
* The status is read HERE rather than inferred from `ingest.reason`, because
|
|
416
|
-
* `session_committed` covers THREE statuses and one of them — `seal_interrupted_pending` —
|
|
417
|
-
* is documented as explicitly non-terminal. Collapsing them would release a message on a
|
|
418
|
-
* session still moving toward a notarization.
|
|
419
|
-
*
|
|
420
|
-
* `null` when the record cannot be read, and that is NOT treated as terminal: an
|
|
421
|
-
* unreadable record is "we do not know", and releasing on it would delete the only
|
|
422
|
-
* surviving copy of a message on an assumption. Fail-closed costs another drain.
|
|
423
|
-
*/
|
|
424
|
-
const sessionRecord = sessionNodeManager.getSessionRecord(recipientAgent.name, e.sessionIdHex);
|
|
425
|
-
const sessionStatus = sessionRecord?.status ?? null;
|
|
426
|
-
const sessionTerminal = sessionStatus !== null && TERMINAL_SESSION_STATUSES.has(sessionStatus);
|
|
427
|
-
let saltReason = null;
|
|
428
|
-
let declaredAlgSeen = "(absent)";
|
|
429
|
-
/**
|
|
430
|
-
* ⚠️ **WHICH STEP THREW — review M4, and without it one label spoke for four.**
|
|
431
|
-
*
|
|
432
|
-
* The `try` below opens at the decode and does not close until after the screen, the
|
|
433
|
-
* quarantine and the annex write, so a throw from ANY of them was reported as
|
|
434
|
-
* `annex_decode_failed`. This unit then promoted that label to an operator notice asserting
|
|
435
|
-
* *"a wire or version difference, not a claim about the sender"* and sending them to a log
|
|
436
|
-
* event that had not fired. It also contradicted the sibling comment below, which promises
|
|
437
|
-
* `annex_write_failed` is *"only what it says — the annex write ran and failed"*.
|
|
438
|
-
*
|
|
439
|
-
* And the mislabel is the common case, not the rare one: `decodeParkEnvelope` already
|
|
440
|
-
* succeeded on these exact bytes in `recoverParkedEntry` before ingest returned
|
|
441
|
-
* `session_committed`, so a genuine decode failure here is close to unreachable.
|
|
442
|
-
*/
|
|
443
|
-
let decoded = false;
|
|
444
|
-
try {
|
|
445
|
-
const env = decodeParkEnvelope(unsealed);
|
|
446
|
-
decoded = true;
|
|
447
|
-
/**
|
|
448
|
-
* ⚠️ THE SECOND CONTENT-HASH VERIFIER — `DOD-M15-SEALWIRE-1` part B1 review F3, closed by
|
|
449
|
-
* part B2a.
|
|
450
|
-
*
|
|
451
|
-
* This check exists on its own (review F5: `parkSig` does not cover `env.content`, and
|
|
452
|
-
* this branch returns before `ingestReceivedContent`'s own cross-check ever runs), so it
|
|
453
|
-
* is NOT covered by B1's discriminator. Correct today and provably so — **no sender salts
|
|
454
|
-
* in this build**, so every parked entry in existence was hashed `sha256(0x00 ‖ content)`.
|
|
455
|
-
*
|
|
456
|
-
* ✅ FIXED IN PART B2a. The envelope carries the algorithm from v3 onward and this check
|
|
457
|
-
* now runs under it, so the re-pull loop it would otherwise have caused — refuse, keep the
|
|
458
|
-
* relay copy, pull again, refuse again — cannot happen. The other site is
|
|
459
|
-
* `session-node-manager.ts`'s `recoverParkedEntry`; both were changed together, because
|
|
460
|
-
* fixing one and leaving the other is how this defect was created.
|
|
461
|
-
*/
|
|
462
|
-
/**
|
|
463
|
-
* TWO CAUSES, TWO REFUSALS — review B2a F2, and the first version collapsed them.
|
|
464
|
-
*
|
|
465
|
-
* "We cannot read the algorithm name" and "we hold no salt" are structurally different and
|
|
466
|
-
* they send the operator to different places. One guidance string served both, and for the
|
|
467
|
-
* salt case both halves of it were wrong: the counterparty's version is irrelevant, and
|
|
468
|
-
* *"delivered once this daemon can verify it"* is a promise this daemon cannot keep — the
|
|
469
|
-
* salt row is not coming back, so the entry loops on every drain forever.
|
|
470
|
-
*
|
|
471
|
-
* The DIRECT path already splits these (`content_hash_alg_unknown` vs
|
|
472
|
-
* `content_hash_salt_unavailable`, B1 review F6). Collapsing them here undid, on the park
|
|
473
|
-
* route, a distinction already paid for on the other one.
|
|
474
|
-
*
|
|
475
|
-
* It is not an edge case: `DOD-M15-SEALWIRE-1`'s own pass-1 F9 records that a park-only
|
|
476
|
-
* session never agrees a salt, because the announcement hangs off `onPeerConnect`. So a
|
|
477
|
-
* null salt is the DEFAULT for exactly the sessions whose content arrives this way.
|
|
478
|
-
*/
|
|
479
|
-
const alg = resolveContentHashAlg(env.contentHashAlg);
|
|
480
|
-
declaredAlgSeen = env.contentHashAlg ?? "(absent)";
|
|
481
|
-
/**
|
|
482
|
-
* `getSessionContentSaltState`, NOT `getSessionContentSalt` — `041-PARKSTUCK` Unit 2.
|
|
483
|
-
*
|
|
484
|
-
* A bare `null` answers "no salt was ever agreed" and "a salt row is here and this
|
|
485
|
-
* machine cannot read it" identically, and those send the operator to opposite places:
|
|
486
|
-
* one to the conversation, the other to their own disk. The hashing path is unchanged —
|
|
487
|
-
* this reads the same salt through the same method, and only the REASON is new.
|
|
488
|
-
*/
|
|
489
|
-
const saltState = alg.ok
|
|
490
|
-
? sessionNodeManager.getSessionContentSaltState(recipientAgent.name, e.sessionIdHex)
|
|
491
|
-
: null;
|
|
492
|
-
const sessionSalt = saltState?.salt ?? null;
|
|
493
|
-
if (saltState !== null && saltState.salt === null)
|
|
494
|
-
saltReason = saltState.reason;
|
|
495
|
-
let computed = null;
|
|
496
|
-
// Discriminated BEFORE the call rather than recovered from a thrown string, so the two
|
|
497
|
-
// causes are branches rather than message-matching.
|
|
498
|
-
let algFailure = null;
|
|
499
|
-
if (!alg.ok) {
|
|
500
|
-
// A name this build cannot read. NOT a hash mismatch: there is no value to compare
|
|
501
|
-
// against, and reporting it as one would be a tamper claim for a version difference —
|
|
502
|
-
// the same substitution `DOD-M15-SEALWIRE-1` part B1 removed on the direct path.
|
|
503
|
-
algFailure = { reason: PARK_REFUSAL_REASONS.ANNEX_ALG_UNKNOWN, detail: `the sender named "${alg.value}"` };
|
|
504
|
-
}
|
|
505
|
-
else if (alg.alg !== CONTENT_HASH_ALGS.SHA256 && !sessionSalt) {
|
|
506
|
-
algFailure = {
|
|
507
|
-
reason: PARK_REFUSAL_REASONS.ANNEX_SALT_UNAVAILABLE,
|
|
508
|
-
detail: `the sender used ${alg.alg} and this side holds no salt for the session (${saltReason ?? "unknown"})`,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
try {
|
|
513
|
-
computed = contentHashFor(env.content, {
|
|
514
|
-
alg: alg.alg,
|
|
515
|
-
// OUR salt for the session. The envelope never carries one and could not be trusted
|
|
516
|
-
// if it did — the salt is the shared secret that makes the hash unguessable.
|
|
517
|
-
salt: sessionSalt,
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
catch (err) {
|
|
521
|
-
// Unreachable given the two checks above; kept so a future algorithm that throws for a
|
|
522
|
-
// third reason cannot fall through into the mismatch branch and be called a tamper.
|
|
523
|
-
algFailure = { reason: PARK_REFUSAL_REASONS.ANNEX_ALG_UNKNOWN, detail: extractErrorMessage(err) };
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (algFailure !== null) {
|
|
527
|
-
annexRefusal = algFailure.reason;
|
|
528
|
-
logger.error(algFailure.reason === PARK_REFUSAL_REASONS.ANNEX_SALT_UNAVAILABLE
|
|
529
|
-
? "content.recover.annex.salt_unavailable"
|
|
530
|
-
: "content.recover.annex.alg_unknown", {
|
|
531
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
532
|
-
declaredAlg: env.contentHashAlg ?? "(absent)",
|
|
533
|
-
detail: algFailure.detail,
|
|
534
|
-
sessionStatus: sessionStatus ?? "(unreadable)",
|
|
535
|
-
impact: "this parked message could not be CHECKED — not that it failed a check. It was not annexed. Nothing here says the sender did anything wrong.",
|
|
536
|
-
guidance: algFailure.reason === PARK_REFUSAL_REASONS.ANNEX_SALT_UNAVAILABLE
|
|
537
|
-
// Lifted from the direct path's equivalent, because the operator needs the same
|
|
538
|
-
// answer wherever the message arrived. Deliberately does NOT promise delivery:
|
|
539
|
-
// without the salt this entry cannot be verified on any future drain either.
|
|
540
|
-
// DOD-M15-SALTSPLIT-1 review MEDIUM-3: "never completed" is now one of TWO ways to
|
|
541
|
-
// hold no salt. The discard undoes an agreement that DID complete, so an operator
|
|
542
|
-
// reading the old sentence would look for a failure that never happened.
|
|
543
|
-
//
|
|
544
|
-
// ⚠️ **THE LAST SENTENCE USED TO BE "close it and start a new one" — `041-PARKSTUCK`.**
|
|
545
|
-
// It ran 731 times over 64 hours against a session that was ALREADY closed, and
|
|
546
|
-
// closing it is what made the refusal permanent: the ingest side then refuses
|
|
547
|
-
// `session_committed` on every drain. Naming an action the reader took three days
|
|
548
|
-
// ago is worse than saying nothing. What replaces it branches on the status,
|
|
549
|
-
// which this line has always had and never read.
|
|
550
|
-
? `This side holds no salt for the session, for one of two reasons. If session.salt.discarded is in the log, the agreement completed and this side then dropped its salt on purpose because the counterparty said it could never hold one. Otherwise the agreement never completed — look for session.salt.read.failed or session.salt.persist.failed. Either way their build is NOT the problem; do not ask them to upgrade. ${sessionTerminal
|
|
551
|
-
? `This session is "${sessionStatus}" and cannot agree a salt ever again, so this message can never be checked — the relay copy is being released so it stops being re-pulled.`
|
|
552
|
-
: "This message stays on the relay and is re-checked on every drain; a salt is agreed while both sides are connected, so staying connected is what clears it."}`
|
|
553
|
-
: "Their CELLO build is newer than this one: ask which version they run, and upgrade. The message stays on the relay and is delivered once this daemon can read that algorithm.",
|
|
554
|
-
correlationId,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
else if (Buffer.from(computed).toString("hex") !== e.contentHashHex) {
|
|
558
|
-
annexRefusal = PARK_REFUSAL_REASONS.ANNEX_HASH_MISMATCH;
|
|
559
|
-
logger.error("content.recover.annex.hash_mismatch", {
|
|
560
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
561
|
-
// The algorithm the comparison RAN UNDER. Without it a mismatch is unfalsifiable from
|
|
562
|
-
// the log: "the bytes were altered" and "we checked it the wrong way" look identical.
|
|
563
|
-
declaredAlg: env.contentHashAlg ?? "(absent → sha256)",
|
|
564
|
-
impact: "content does not match its attested hash — NOT annexed, relay copy kept",
|
|
565
|
-
correlationId,
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
// M12-P17 (review F4): screen before storing. Same terminal-vs-transient split the live
|
|
570
|
-
// inbound funnel uses, because the consequences are the same shape.
|
|
571
|
-
const verdict = await securityGateway.screenInbound(env.content, {
|
|
572
|
-
direction: "inbound", agentName: recipientAgent.name, sessionId: e.sessionIdHex, correlationId,
|
|
573
|
-
});
|
|
574
|
-
const terminalBlock = verdict.disposition === "block" && verdict.terminal === true;
|
|
575
|
-
if (verdict.disposition !== "allow" && verdict.disposition !== "redact" && !terminalBlock) {
|
|
576
|
-
// TRANSIENT (gateway down / timeout): keep the relay copy and re-screen next drain.
|
|
577
|
-
// Fail-closed — never store unscreened content because the screen was unreachable.
|
|
578
|
-
logger.warn("content.recover.annex.screen_unavailable", {
|
|
579
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex,
|
|
580
|
-
disposition: verdict.disposition, correlationId,
|
|
581
|
-
});
|
|
582
|
-
screenDeferred = true;
|
|
583
|
-
}
|
|
584
|
-
else if (terminalBlock) {
|
|
585
|
-
/**
|
|
586
|
-
* TERMINAL: the detector rejected the CONTENT itself, so identical bytes would be
|
|
587
|
-
* rejected identically forever — leaving it on the relay would restore the re-pull
|
|
588
|
-
* loop this drain exists to end. It is still dropped from the relay and still never
|
|
589
|
-
* annexed (the annex is a READABLE record of the conversation, and this was never part
|
|
590
|
-
* of it).
|
|
591
|
-
*
|
|
592
|
-
* ⚠️ **BUT IT IS NO LONGER DISCARDED — review F6.** This comment used to end *"and do
|
|
593
|
-
* NOT store it"*, and that was the last route in the tree that threw a refused message
|
|
594
|
-
* away. `DOD-M15-REFUSEDEVIDENCE-1` retains every other one, and shipped guidance now
|
|
595
|
-
* tells operators that refused messages are kept — so this branch falsified a promise
|
|
596
|
-
* on the case with the most evidence value in the product: hostile bytes aimed at a
|
|
597
|
-
* conversation that has already been sealed.
|
|
598
|
-
*
|
|
599
|
-
* Quarantined, not annexed. The two are different answers to different questions: the
|
|
600
|
-
* annex is what arrived late and is readable, quarantine is what was refused and is
|
|
601
|
-
* withheld. Bounded by the same tier cap and deduped by content, so the loop stays
|
|
602
|
-
* closed.
|
|
603
|
-
*/
|
|
604
|
-
const kept = sessionNodeManager.quarantineRefusedInbound(recipientAgent.name, e.sessionIdHex, verdict.reason ?? "inbound_screen_blocked", env.content, e.contentHashHex, env.senderPubkey ? Buffer.from(env.senderPubkey).toString("hex") : null, correlationId);
|
|
605
|
-
screenRetained = kept !== null;
|
|
606
|
-
logger.warn("content.recover.annex.screened_out", {
|
|
607
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
608
|
-
retained: kept !== null,
|
|
609
|
-
impact: kept !== null
|
|
610
|
-
? "content was terminally blocked by the inbound screen — NOT annexed, and RETAINED as quarantined evidence"
|
|
611
|
-
: "content was terminally blocked by the inbound screen and could NOT be retained — see session.content.quarantine.skipped or .failed",
|
|
612
|
-
correlationId,
|
|
613
|
-
});
|
|
614
|
-
screenedOut = true;
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
// A redact verdict stores the ALTERED bytes, exactly as the live path delivers them.
|
|
618
|
-
// Those no longer match `content_hash`, which is correct here: the annex is a readable
|
|
619
|
-
// record, not chain content, and the hash column stays the entry's relay identity.
|
|
620
|
-
const body = verdict.disposition === "redact" && verdict.content !== undefined
|
|
621
|
-
? new Uint8Array(verdict.content)
|
|
622
|
-
: env.content;
|
|
623
|
-
annexed = sessionNodeManager.recordSealedAnnex(recipientAgent.name, e.sessionIdHex, e.contentHashHex, body, env.senderPubkey ? Buffer.from(env.senderPubkey).toString("hex") : null);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
catch (err) {
|
|
628
|
-
annexRefusal = decoded
|
|
629
|
-
? PARK_REFUSAL_REASONS.ANNEX_WRITE_FAILED
|
|
630
|
-
: PARK_REFUSAL_REASONS.ANNEX_DECODE_FAILED;
|
|
631
|
-
annexErrorDetail = extractErrorMessage(err);
|
|
632
|
-
logger.error(decoded ? "content.recover.annex.write_failed" : "content.recover.annex.decode_failed", {
|
|
633
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex,
|
|
634
|
-
impact: decoded
|
|
635
|
-
? "the envelope decoded and a later step threw — screening, retention or the annex write. NOT annexed, relay copy kept."
|
|
636
|
-
: "envelope could not be decoded — NOT annexed, relay copy kept",
|
|
637
|
-
error: annexErrorDetail, correlationId,
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
if (annexed) {
|
|
641
|
-
// Review F6: named under `content.recover.*` like its five siblings in this loop, so a
|
|
642
|
-
// grep for the recovery family does not miss the one branch you most want to count.
|
|
643
|
-
logger.info("content.recover.annexed", {
|
|
644
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
645
|
-
reason: "session_committed", correlationId,
|
|
646
|
-
});
|
|
647
|
-
try {
|
|
648
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
649
|
-
}
|
|
650
|
-
catch (err) {
|
|
651
|
-
// The annex holds it, so the message is safe; the relay copy simply gets re-pulled and
|
|
652
|
-
// deduped by the INSERT OR IGNORE next time. Not data loss.
|
|
653
|
-
logger.warn("content.recover.confirm.failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, error: extractErrorMessage(err) });
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
else if (screenedOut) {
|
|
657
|
-
/**
|
|
658
|
-
* Terminal block: identical bytes are rejected identically forever, so the relay copy goes
|
|
659
|
-
* or the re-pull loop returns.
|
|
660
|
-
*
|
|
661
|
-
* ⚠️ **AND ONLY IF THE BYTES WERE ACTUALLY KEPT — verification NEW-2.** This deleted
|
|
662
|
-
* unconditionally while asserting "retained as quarantined evidence above", which is H1's
|
|
663
|
-
* defect reproduced in the branch H1 was written for. Screener-blocked content aimed at a
|
|
664
|
-
* closed conversation is the highest-value evidence in the product; losing it because the
|
|
665
|
-
* quarantine was full is the one outcome worse than it arriving again.
|
|
666
|
-
*/
|
|
667
|
-
let screenReleased = false;
|
|
668
|
-
if (screenRetained) {
|
|
669
|
-
try {
|
|
670
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
671
|
-
screenReleased = true;
|
|
672
|
-
}
|
|
673
|
-
catch (err) {
|
|
674
|
-
logger.warn("content.recover.confirm.failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, error: extractErrorMessage(err) });
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
else {
|
|
678
|
-
logger.warn("content.recover.release.withheld", {
|
|
679
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
680
|
-
reason: PARK_REFUSAL_REASONS.ANNEX_SCREENED_OUT,
|
|
681
|
-
impact: "screener-blocked content could NOT be retained locally — see session.content.quarantine.skipped or .failed above — so the relay copy is KEPT rather than deleted. It will arrive and be blocked again, which is the lesser harm: deleting it would destroy the only record that this was ever sent.",
|
|
682
|
-
guidance: "Free space for this conversation and the copy is retained on a later drain. cello_quarantined shows what it is already holding.",
|
|
683
|
-
correlationId,
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* ⚠️ **THIS BRANCH TOLD THE OPERATOR NOTHING — review M9.**
|
|
688
|
-
*
|
|
689
|
-
* It is the only park branch that DELETES the relay's copy on purpose, and it is the one
|
|
690
|
-
* with the strongest claim on an operator's attention: content rejected outright by the
|
|
691
|
-
* screener, aimed at a conversation that has already closed. Its `impact` field was
|
|
692
|
-
* written for a person and read by nobody, which is the pattern this whole unit exists to
|
|
693
|
-
* end — and leaving it here while fixing the neighbouring branches would have been the
|
|
694
|
-
* class fixed one instance at a time.
|
|
695
|
-
*
|
|
696
|
-
* Deliberately NOT pushed into `refusals`: that list is what the drain reports as STUCK,
|
|
697
|
-
* and this entry is resolved — retained, and gone from the relay. The operator surface is
|
|
698
|
-
* the part that was missing, not the stuck-list entry.
|
|
699
|
-
*/
|
|
700
|
-
noteParkRefusal(recipientAgent.name, e.sessionIdHex, e.contentHashHex, PARK_REFUSAL_REASONS.ANNEX_SCREENED_OUT, { sessionStatus, released: screenReleased, retained: screenRetained, declaredAlg: declaredAlgSeen, saltReason, errorDetail: null });
|
|
701
|
-
}
|
|
702
|
-
else if (screenDeferred) {
|
|
703
|
-
refusals.push(noteParkRefusal(recipientAgent.name, e.sessionIdHex, e.contentHashHex, PARK_REFUSAL_REASONS.ANNEX_SCREEN_UNAVAILABLE, { sessionStatus, released: false, declaredAlg: declaredAlgSeen, saltReason, errorDetail: null }));
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
// Keep the relay copy — it is now the only one. Review F7: name why the entry is STUCK,
|
|
707
|
-
// not why ingest refused it. `annexRefusal` carries the branch that actually stopped it;
|
|
708
|
-
// the bare `annex_write_failed` fallback is now only what it says — the annex write ran
|
|
709
|
-
// and failed.
|
|
710
|
-
const stuckReason = annexRefusal ?? PARK_REFUSAL_REASONS.ANNEX_WRITE_FAILED;
|
|
711
|
-
/**
|
|
712
|
-
* ─── `041-PARKSTUCK` Unit 1: THE EXIT THE SAFETY RULE DID NOT HAVE ────────────────────
|
|
713
|
-
*
|
|
714
|
-
* *"Never drop a message we could not verify"* is right, and it had no case for
|
|
715
|
-
* verification being IMPOSSIBLE FOR THE REST OF TIME rather than merely failing now.
|
|
716
|
-
* Every input to this decision is then immutable and the code re-derives the same answer
|
|
717
|
-
* forever: measured at 731 refusals in 64 hours on one message, roughly one every five
|
|
718
|
-
* minutes, with the operator never shown it once.
|
|
719
|
-
*
|
|
720
|
-
* All three conditions must hold, and the middle one is why this cannot widen:
|
|
721
|
-
* 1. the session is TERMINAL (`sealed` / `abandoned`) — no salt can ever be agreed for
|
|
722
|
-
* it again, because agreement runs while both sides are connected;
|
|
723
|
-
* 2. the sender named a SALTED algorithm, so a salt is required to check the bytes;
|
|
724
|
-
* 3. this side holds no usable salt.
|
|
725
|
-
*
|
|
726
|
-
* ⚠️ EVERY OTHER REFUSAL IN THIS BRANCH STILL KEEPS THE RELAY COPY, deliberately. A
|
|
727
|
-
* screener that is down comes back; a build that cannot compute an algorithm gets
|
|
728
|
-
* upgraded; a decode failure is a version difference. Those answers CHANGE, so releasing
|
|
729
|
-
* one drain early would lose a message that would have gone through — which is strictly
|
|
730
|
-
* worse than the loop.
|
|
731
|
-
*
|
|
732
|
-
* ⚠️ **AND THE LOCAL COPY IS PROVEN, NEVER ASSUMED — review H1.**
|
|
733
|
-
*
|
|
734
|
-
* This comment used to assert that `ingestReceivedContent`'s `session_committed` exit
|
|
735
|
-
* *had* quarantined the bytes, and gate on nothing. That claim is true on the ordinary
|
|
736
|
-
* path and false on four reachable ones: no database, the conversation's byte budget
|
|
737
|
-
* already spent, the row not stored, and a throwing write — each logging, in its own
|
|
738
|
-
* words, that *nothing holds a copy of it*. The budget case is not exotic; it is the
|
|
739
|
-
* ordinary shape of a long conversation that then ends.
|
|
740
|
-
*
|
|
741
|
-
* Deleting the relay's copy on the strength of an unchecked retention is delete-on-
|
|
742
|
-
* best-effort, which the comment forty lines above forbids by name: *"annex FIRST,
|
|
743
|
-
* confirm-delete SECOND, and only if the annex committed… delete-on-best-effort converts
|
|
744
|
-
* the loop into PERMANENT SILENT loss — strictly worse than the bug."* So `retained` is
|
|
745
|
-
* now a fact carried out of ingest, and a failed retention keeps the relay copy. The loop
|
|
746
|
-
* is the lesser harm and it is loud while it lasts.
|
|
747
|
-
*/
|
|
748
|
-
/**
|
|
749
|
-
* ⚠️ **`saltReason === "none"` IS THE THIRD CONDITION, and leaving it out was a way to
|
|
750
|
-
* destroy a message — review H2.**
|
|
751
|
-
*
|
|
752
|
-
* `getSessionContentSaltState` answers `unreadable` for THREE situations and only one of
|
|
753
|
-
* them is permanent: the database is not open (a shutdown or startup race), the read
|
|
754
|
-
* THREW, or the stored blob is the wrong width (real corruption). `getSessionSalt` also
|
|
755
|
-
* returns null on a throwing read. So a SQLCipher lock or an IO blip on one drain makes a
|
|
756
|
-
* perfectly good salt look absent — and without this clause the next line deletes the
|
|
757
|
-
* relay's last copy of a message the FOLLOWING drain would have annexed.
|
|
758
|
-
*
|
|
759
|
-
* That is the order's explicit prohibition: a release one drain too early loses a message
|
|
760
|
-
* that would have gone through. `none` is the only answer that cannot change back: no
|
|
761
|
-
* salt row exists, and on a terminal session none can ever be written.
|
|
762
|
-
*
|
|
763
|
-
* The live `dcec3c3f…` case is `none` — there is not one `session.salt.*` event for that
|
|
764
|
-
* session — so the loop this unit exists to stop still closes.
|
|
765
|
-
*/
|
|
766
|
-
const releasable = stuckReason === PARK_REFUSAL_REASONS.ANNEX_SALT_UNAVAILABLE &&
|
|
767
|
-
sessionTerminal &&
|
|
768
|
-
saltReason === "none" &&
|
|
769
|
-
ingest.retained === true;
|
|
770
|
-
if (!releasable && stuckReason === PARK_REFUSAL_REASONS.ANNEX_SALT_UNAVAILABLE && sessionTerminal && saltReason === "none") {
|
|
771
|
-
/**
|
|
772
|
-
* The loop continues, and the reason it continues is a LOCAL storage failure rather than
|
|
773
|
-
* anything about this message. Said out loud because otherwise this is indistinguishable
|
|
774
|
-
* from the defect this unit fixed — the same refusal, at the same cadence, forever.
|
|
775
|
-
*/
|
|
776
|
-
logger.warn("content.recover.release.withheld", {
|
|
777
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
778
|
-
impact: "this message can never be checked and the relay copy would normally be dropped, but this daemon could NOT retain a local copy of it — see session.content.quarantine.skipped or .failed above. The relay copy is kept instead, so it will keep being pulled and refused, because deleting it would leave the message nowhere at all.",
|
|
779
|
-
guidance: "Free space for this conversation and the copy is retained on a later drain, after which the loop stops on its own. A conversation that has spent its storage budget cannot retain more; cello_quarantined shows what it is already holding.",
|
|
780
|
-
correlationId,
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* ⚠️ **REPORTED ON THE SUCCESS PATH — the notice is written AFTER the delete, never
|
|
785
|
-
* before it.** `released` is what the operator's sentence turns on ("it is now gone" vs
|
|
786
|
-
* "the relay still holds it"), and a confirm can fail. Deciding the wording from the
|
|
787
|
-
* INTENT to release rather than from the release having happened is how a notice comes to
|
|
788
|
-
* state a conclusion nothing has reached yet.
|
|
789
|
-
*/
|
|
790
|
-
let released = false;
|
|
791
|
-
if (releasable) {
|
|
792
|
-
try {
|
|
793
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
794
|
-
released = true;
|
|
795
|
-
logger.warn("content.recover.released", {
|
|
796
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
797
|
-
reason: stuckReason, sessionStatus, saltReason: saltReason ?? "(unknown)",
|
|
798
|
-
impact: "this message can NEVER be checked — the conversation is closed, so no salt can ever be agreed for it — and the relay's copy has been deleted so it stops being pulled and refused on every drain. The content itself is retained locally as quarantined evidence and is readable with cello_quarantined.",
|
|
799
|
-
guidance: "Nothing to retry and nothing to repair. If the message mattered, ask the sender OUT OF BAND to say it again in a NEW conversation.",
|
|
800
|
-
correlationId,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
catch (err) {
|
|
804
|
-
// The relay copy survives, so the loop continues for now and nothing is lost. Named
|
|
805
|
-
// separately from `content.recover.confirm.failed` because on THIS branch the failed
|
|
806
|
-
// delete is the whole reason the entry is still there.
|
|
807
|
-
logger.warn("content.recover.release.failed", {
|
|
808
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
809
|
-
error: extractErrorMessage(err),
|
|
810
|
-
impact: "the relay copy could not be deleted, so this message will be pulled and refused again on the next drain. Nothing is lost; the loop simply has not stopped yet.",
|
|
811
|
-
correlationId,
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
refusals.push(noteParkRefusal(recipientAgent.name, e.sessionIdHex, e.contentHashHex, stuckReason, { sessionStatus, released, declaredAlg: declaredAlgSeen, saltReason, errorDetail: annexErrorDetail }));
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
// M12-P18: content for a session WE REFUSED can be swept — deleting it acts on our own
|
|
820
|
-
// refusal decision, not on the content, so it does not violate the rule that a forgery must
|
|
821
|
-
// not evict itself. This is the fix for the counterparty_unknown re-pull loop (78 of 121
|
|
822
|
-
// stranded entries on one box): the abuse cap refused the session, no session row was ever
|
|
823
|
-
// created, so every drain re-pulled and re-refused the parked content forever.
|
|
824
|
-
if (sessionNodeManager.wasSessionRefused(recipientAgent.name, e.sessionIdHex)) {
|
|
825
|
-
logger.info("content.recover.refused_session.swept", {
|
|
826
|
-
sessionId: e.sessionIdHex, contentHash: e.contentHashHex, agentName: recipientAgent.name,
|
|
827
|
-
reason: ingest.reason,
|
|
828
|
-
impact: "content was parked for a session this agent refused — deleted from the relay, not re-pullable",
|
|
829
|
-
correlationId,
|
|
830
|
-
});
|
|
831
|
-
try {
|
|
832
|
-
await client.confirm(node, Buffer.from(recipientPubkey, "hex"), contentHashBytes, kp);
|
|
833
|
-
}
|
|
834
|
-
catch (err) {
|
|
835
|
-
logger.warn("content.recover.confirm.failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, error: extractErrorMessage(err) });
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
else {
|
|
839
|
-
// SEC-1 (M2): carry the refusal out to the caller, not just to the log. Deliberately still
|
|
840
|
-
// NOT confirm-deleted for a session we did NOT ourselves refuse — a forgery must not be
|
|
841
|
-
// able to evict itself, and a genuine message we simply have no row for yet must survive.
|
|
842
|
-
// M12-P17: this is also why `counterparty_unknown` is NOT annexed above — the content is
|
|
843
|
-
// UNVERIFIED. The residual (a stranger's stranded content we never refused) needs
|
|
844
|
-
// relay-side TTL, not client-side deletion.
|
|
845
|
-
refusals.push({ contentHash: e.contentHashHex, sessionId: e.sessionIdHex, reason: ingest.reason });
|
|
846
|
-
logger.warn("content.recover.ingest_failed", { sessionId: e.sessionIdHex, contentHash: e.contentHashHex, reason: ingest.reason, correlationId });
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
return { ok: true, recovered, pulled: entries.length, refused: refusals.length, refusals };
|
|
851
|
-
}
|
|
852
|
-
// DOD-MSG-4 (auto-recover-on-reconnect): when an agent comes online, drain its parked mailbox from
|
|
853
|
-
// every relay it has sessions on — symmetric to the SENDER's flushAwaitingContent. Without this,
|
|
854
|
-
// nothing in production pulls a recipient's store-and-forward mailbox and parked content is never
|
|
855
|
-
// delivered. Best-effort; a relay miss is retried on the next agent-online.
|
|
856
|
-
/**
|
|
857
|
-
* DOD-PARK-DRAIN-1 (review F1): one drain per agent at a time, and at most one re-run queued.
|
|
858
|
-
*
|
|
859
|
-
* The drain used to have two triggers; it now has five (agent start, receiver install, a
|
|
860
|
-
* reservation LOST, a reservation REGAINED, signaling reconnect, the periodic backstop — the
|
|
861
|
-
* middle pair replaced "every watchdog rebuild" in 056-SLOTDEAD, which is where the count came
|
|
862
|
-
* from). Under the relay churn this unit exists to survive, those arrive faster than a drain
|
|
863
|
-
* completes. Concurrent drains are
|
|
864
|
-
* NOT safe by dedup alone: ingestReceivedContent decides "already present" synchronously from
|
|
865
|
-
* the in-memory tree and then AWAITS the security gateway before appending, so two drains that
|
|
866
|
-
* pull the same parked entry can both pass that check and both append — a duplicate leaf, the
|
|
867
|
-
* frontier divergence the recovery path exists to avoid.
|
|
868
|
-
*
|
|
869
|
-
* Coalesced, not queued: a trigger that arrives mid-drain asks for exactly ONE more pass when
|
|
870
|
-
* the current one finishes. Ten rebuilds during one slow pull cost one re-run, not ten.
|
|
871
|
-
*/
|
|
872
|
-
const draining = new Set();
|
|
873
|
-
/** agentName → the trigger of the coalesced re-run owed to it (absent = none owed). */
|
|
874
|
-
const drainRerunRequested = new Map();
|
|
875
|
-
async function autoRecoverForAgent(agentName, trigger = "unspecified") {
|
|
876
|
-
if (draining.has(agentName)) {
|
|
877
|
-
drainRerunRequested.set(agentName, trigger);
|
|
878
|
-
logger.debug("content.recover.auto.coalesced", { agentName, trigger });
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
draining.add(agentName);
|
|
882
|
-
try {
|
|
883
|
-
let pass = trigger;
|
|
884
|
-
do {
|
|
885
|
-
drainRerunRequested.delete(agentName);
|
|
886
|
-
await drainOnce(agentName, pass);
|
|
887
|
-
pass = drainRerunRequested.get(agentName) ?? pass;
|
|
888
|
-
} while (drainRerunRequested.has(agentName));
|
|
889
|
-
}
|
|
890
|
-
finally {
|
|
891
|
-
draining.delete(agentName);
|
|
892
|
-
drainRerunRequested.delete(agentName);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
async function drainOnce(agentName, trigger) {
|
|
896
|
-
const agent = agents.find((a) => a.name === agentName);
|
|
897
|
-
if (!agent?.pubkey)
|
|
898
|
-
return;
|
|
899
|
-
const relays = sessionNodeManager.getAgentRelayEndpoints(agentName);
|
|
900
|
-
if (relays.length === 0)
|
|
901
|
-
return;
|
|
902
|
-
let total = 0;
|
|
903
|
-
let refusedTotal = 0;
|
|
904
|
-
const refusedReasons = {};
|
|
905
|
-
let failed = 0;
|
|
906
|
-
for (const r of relays) {
|
|
907
|
-
try {
|
|
908
|
-
const res = await recoverParkedFromRelay(agent, r.relayPeerId, r.relayAddrs);
|
|
909
|
-
if (res.ok) {
|
|
910
|
-
total += res.recovered;
|
|
911
|
-
// F3: tally by reason so the unattended drain reports refusals it would otherwise swallow.
|
|
912
|
-
refusedTotal += res.refused;
|
|
913
|
-
for (const r2 of res.refusals)
|
|
914
|
-
refusedReasons[r2.reason] = (refusedReasons[r2.reason] ?? 0) + 1;
|
|
915
|
-
}
|
|
916
|
-
else {
|
|
917
|
-
// Review #2: a non-ok result (signing_key_unavailable / cannot_unseal / the precise
|
|
918
|
-
// no-receiver cause) was previously silent — log the reason so a run where every relay
|
|
919
|
-
// failed is distinguishable from "nothing was parked".
|
|
920
|
-
failed++;
|
|
921
|
-
logger.warn("content.recover.auto.relay_failed", { agentName, trigger, relayPeerId: r.relayPeerId, reason: res.reason });
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
catch (err) {
|
|
925
|
-
failed++;
|
|
926
|
-
// extractErrorMessage, NOT String(err): libp2p and the transport reject with structured
|
|
927
|
-
// plain objects, and String() renders every one of them "[object Object]". 102 of these
|
|
928
|
-
// fired during the 2026-08-04 incident and not one was diagnosable from the log.
|
|
929
|
-
logger.warn("content.recover.auto.failed", { agentName, trigger, stage: "relay", relayPeerId: r.relayPeerId, error: extractErrorMessage(err) });
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
// Emit the completion event UNCONDITIONALLY — not only when total > 0 — so a clean "nothing
|
|
933
|
-
// parked" run is observable, and distinct from an all-failed run.
|
|
934
|
-
// `refusedReasons` for the same reason as above (F3): the drain is the path that runs
|
|
935
|
-
// unattended, so if a refusal is invisible here it is invisible altogether. Counts by reason
|
|
936
|
-
// rather than per-entry, so a mailbox full of one fault does not become a wall of log.
|
|
937
|
-
logger.info("content.recover.auto.completed", {
|
|
938
|
-
agentName, trigger, recovered: total, relayCount: relays.length, failedRelays: failed,
|
|
939
|
-
refused: refusedTotal,
|
|
940
|
-
...(Object.keys(refusedReasons).length > 0 ? { refusedReasons } : {}),
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
/** Phase 2: register the IPC handlers, once the handler map exists. */
|
|
944
|
-
function registerHandlers(handlers) {
|
|
945
|
-
/**
|
|
946
|
-
* ⚠️ THIS HANDLER HAS NO PRODUCTION CALLER, AND THAT IS WHY IT DRIFTED.
|
|
947
|
-
*
|
|
948
|
-
* Production parks through `#parkContent` (`daemon.ts`), which seals a SIGNED envelope via
|
|
949
|
-
* `sealParkEnvelope`. This IPC handler is reached only from the spine tests, and it deposited
|
|
950
|
-
* whatever bytes it was handed — so the tests were parking a shape production stopped producing
|
|
951
|
-
* when SEC-1 landed. `authenticateParkedEntry` then refused every one of them with
|
|
952
|
-
* `unsigned_envelope`, which `park-envelope.ts` calls "the ATTACKER shape". Three j-content tests
|
|
953
|
-
* were asserting downstream security properties — tamper detection, dedup, the post-seal
|
|
954
|
-
* straggler guard — that the code never reached, because the entry was thrown out one step
|
|
955
|
-
* earlier for a reason none of them mentioned.
|
|
956
|
-
*
|
|
957
|
-
* So `content` (plaintext hex) now parks the PRODUCTION way: same `sealParkEnvelope`, the sole
|
|
958
|
-
* signer, no second copy of the encoder anywhere near a test.
|
|
959
|
-
*
|
|
960
|
-
* **`contentHash` stays a separate parameter on purpose — it is NOT derived from `content`.**
|
|
961
|
-
* The signature covers `(sessionId, recipient, contentHash)` and deliberately does not bind the
|
|
962
|
-
* content, so a sender CAN sign an entry whose claimed hash does not match what is inside. That
|
|
963
|
-
* is a real malicious-sender case, it is what the recover path's hash cross-check exists to
|
|
964
|
-
* catch, and a caller that wants to exercise it must be able to state the two independently.
|
|
965
|
-
* Deriving the hash here would make that case unreachable and quietly hollow the tamper test.
|
|
966
|
-
*
|
|
967
|
-
* `ciphertext` (the raw path) is kept for the one caller that wants byte fidelity through the
|
|
968
|
-
* relay rather than envelope semantics.
|
|
969
|
-
*/
|
|
970
|
-
const runDeposit = async (params, _connectionId) => {
|
|
971
|
-
/**
|
|
972
|
-
* `Buffer.from(x, "hex")` TRUNCATES at the first invalid pair instead of throwing — the trap
|
|
973
|
-
* `park-envelope.ts` already documents. Unvalidated, a typo'd `content` is signed and sealed at
|
|
974
|
-
* its truncated length and surfaces at the far end as `content_hash_mismatch`: a TAMPER verdict
|
|
975
|
-
* on a local argument mistake. Review MEDIUM-4.
|
|
976
|
-
*/
|
|
977
|
-
const hexOrNull = (v) => typeof v === "string" && v.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(v) ? v : null;
|
|
978
|
-
const relay = parseRelayPeer(params?.relayMultiaddr);
|
|
979
|
-
const recipientPubkey = hexOrNull(params?.recipientPubkey);
|
|
980
|
-
const contentHash = hexOrNull(params?.contentHash);
|
|
981
|
-
const sessionId = hexOrNull(params?.sessionId);
|
|
982
|
-
const senderAgentName = params?.senderAgentName;
|
|
983
|
-
const hasCiphertext = params?.ciphertext !== undefined;
|
|
984
|
-
const hasContent = params?.content !== undefined;
|
|
985
|
-
if (!relay || !recipientPubkey || !contentHash || !sessionId) {
|
|
986
|
-
const bad = [
|
|
987
|
-
!relay ? "relayMultiaddr (needs /p2p/<peerId>)" : null,
|
|
988
|
-
recipientPubkey === null ? "recipientPubkey" : null,
|
|
989
|
-
contentHash === null ? "contentHash" : null,
|
|
990
|
-
sessionId === null ? "sessionId" : null,
|
|
991
|
-
].filter(Boolean);
|
|
992
|
-
return { ok: false, reason: "missing_params", guidance: `Missing or not even-length hex: ${bad.join(", ")}. Hex is validated here because Buffer.from truncates silently, and a truncated value is signed and refused at the far end as a content hash mismatch.` };
|
|
993
|
-
}
|
|
994
|
-
if (!hasCiphertext && !hasContent) {
|
|
995
|
-
return { ok: false, reason: "missing_params", guidance: "Provide either `content` (plaintext hex — sealed and SIGNED here, the production shape) or `ciphertext` (hex — deposited verbatim, no envelope)." };
|
|
996
|
-
}
|
|
997
|
-
if (hasCiphertext && hasContent) {
|
|
998
|
-
return { ok: false, reason: "conflicting_params", guidance: "Provide `content` OR `ciphertext`, not both — they are two different deposit shapes and honouring one would silently discard the other." };
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* ⚠️ PARSED BEFORE THE BRANCH, and refused on the raw branch rather than dropped — review
|
|
1002
|
-
* HIGH-2, and it was the same defect this parameter exists to prevent, reintroduced one branch
|
|
1003
|
-
* over. It used to be read INSIDE the `content` branch, so `ciphertext` + `contentHashAlg`
|
|
1004
|
-
* discarded the algorithm in silence: the far end then resolves the absent field to `sha256`,
|
|
1005
|
-
* recomputes unsalted, and reports `content_hash_mismatch` — a tamper verdict on an honest
|
|
1006
|
-
* message, with nothing on the sender side logging anything at all.
|
|
1007
|
-
*
|
|
1008
|
-
* A raw deposit has nowhere to PUT the name (there is no envelope), so naming one is a
|
|
1009
|
-
* contradiction, not a preference. Refuse it.
|
|
1010
|
-
*/
|
|
1011
|
-
let contentHashAlg;
|
|
1012
|
-
const rawAlg = params?.contentHashAlg;
|
|
1013
|
-
if (rawAlg !== undefined && rawAlg !== null) {
|
|
1014
|
-
if (typeof rawAlg !== "string" || !isKnownContentHashAlg(rawAlg)) {
|
|
1015
|
-
return { ok: false, reason: "unknown_content_hash_alg", guidance: `This daemon cannot compute "${typeof rawAlg === "string" ? rawAlg : `(${typeof rawAlg})`}". Known algorithms: ${Object.values(CONTENT_HASH_ALGS).join(", ")}. Omit the field to mean ${CONTENT_HASH_ALGS.SHA256}.` };
|
|
1016
|
-
}
|
|
1017
|
-
if (hasCiphertext) {
|
|
1018
|
-
return { ok: false, reason: "conflicting_params", guidance: "`contentHashAlg` cannot ride with `ciphertext`: a raw deposit carries no envelope, so there is nowhere to record the algorithm and the far end would recompute under sha256 and report a content hash mismatch on honest content. Use `content` to deposit under a named algorithm." };
|
|
1019
|
-
}
|
|
1020
|
-
contentHashAlg = rawAlg;
|
|
1021
|
-
}
|
|
1022
|
-
const node = sessionNodeManager.getStandingReceiverNode();
|
|
1023
|
-
if (!node) {
|
|
1024
|
-
/**
|
|
1025
|
-
* DOD-M15-PARKCONN-1 — **NAME THE CAUSE, NOT THE EXIT POINT** (Invariant 3).
|
|
1026
|
-
*
|
|
1027
|
-
* `standing_receiver_unavailable` is the label that already misnamed this incident 102
|
|
1028
|
-
* times, which is why `standingReceiverAbsenceReason()` exists — and `recoverParkedFromRelay`
|
|
1029
|
-
* has used it since M12-P12 while this handler kept returning the bare label. The four causes
|
|
1030
|
-
* demand opposite responses: `standing_receiver_creating` clears in seconds and a retry
|
|
1031
|
-
* works; `agent_offline` never clears until the agent is started; `daemon_shutting_down`
|
|
1032
|
-
* means stop.
|
|
1033
|
-
*
|
|
1034
|
-
* The wire reason is unchanged — a caller matching on it keeps working — and the cause rides
|
|
1035
|
-
* alongside, which is the same shape the send path settled on.
|
|
1036
|
-
*/
|
|
1037
|
-
/**
|
|
1038
|
-
* ⚠️ THE GUESS USES THE SIGNABLE LIST, review MEDIUM-4 — the same filter the signing branch
|
|
1039
|
-
* below applies, and for the same reason it documents: a `load_failed` agent holds no key
|
|
1040
|
-
* provider, so counting it makes a daemon with ONE healthy agent say it cannot tell which.
|
|
1041
|
-
*
|
|
1042
|
-
* And a name that matches no agent is not an offline agent, review LOW-6:
|
|
1043
|
-
* `standingReceiverAbsenceReason("typo")` returns `agent_offline` because the name is simply
|
|
1044
|
-
* absent from the wanting-a-receiver set, so the operator was told to start an agent that
|
|
1045
|
-
* does not exist — a real verb pointed at nothing.
|
|
1046
|
-
*/
|
|
1047
|
-
const signableAgents = agents.filter((a) => a.state !== "load_failed");
|
|
1048
|
-
const namedAgent = senderAgentName
|
|
1049
|
-
? (signableAgents.some((a) => a.name === senderAgentName) ? senderAgentName : undefined)
|
|
1050
|
-
: (signableAgents.length === 1 ? signableAgents[0].name : undefined);
|
|
1051
|
-
const cause = namedAgent
|
|
1052
|
-
? sessionNodeManager.standingReceiverAbsenceReason(namedAgent)
|
|
1053
|
-
: senderAgentName
|
|
1054
|
-
? "sender_agent_not_found"
|
|
1055
|
-
// Review LOW-5: zero is not "more than one". An agentless daemon was being told it had
|
|
1056
|
-
// several and could not say which.
|
|
1057
|
-
: signableAgents.length === 0
|
|
1058
|
-
? "no_loadable_agent"
|
|
1059
|
-
: "unknown_agent";
|
|
1060
|
-
const guidanceByCause = {
|
|
1061
|
-
standing_receiver_creating: "The standing receiver is still being built; retry this deposit in a few seconds.",
|
|
1062
|
-
agent_offline: `Agent '${namedAgent}' is not online, so this daemon has no node to deposit from. Start it with cello_start_agent.`,
|
|
1063
|
-
no_standing_receiver: "No agent on this daemon has a standing receiver; start an agent with cello_start_agent first.",
|
|
1064
|
-
daemon_shutting_down: "The daemon is shutting down — nothing was deposited, and retrying will not help until it is back up.",
|
|
1065
|
-
unknown_agent: `This daemon has no standing receiver and ${agents.filter((a) => a.state !== "load_failed").length} signable agents, so it cannot say which one is missing it; pass senderAgentName, or start an agent with cello_start_agent.`,
|
|
1066
|
-
sender_agent_not_found: `This daemon has no loadable agent named '${senderAgentName}', so there is nothing to check a standing receiver for. Check the name — cello_agents lists what this daemon holds.`,
|
|
1067
|
-
no_loadable_agent: "This daemon has no loadable agent at all, so it can never have a standing receiver. Create and register one, then start it with cello_start_agent.",
|
|
1068
|
-
};
|
|
1069
|
-
return { ok: false, reason: "standing_receiver_unavailable", cause, guidance: guidanceByCause[cause] ?? "The daemon's standing receiver is not ready yet; retry after startup." };
|
|
1070
|
-
}
|
|
1071
|
-
let payload;
|
|
1072
|
-
if (hasContent) {
|
|
1073
|
-
const content = hexOrNull(params?.content);
|
|
1074
|
-
if (content === null) {
|
|
1075
|
-
return { ok: false, reason: "missing_params", guidance: "`content` is not even-length hex. It is validated because Buffer.from truncates silently, and a truncated body is sealed at the wrong length and refused at the far end as a content hash mismatch." };
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Which agent signs. Named explicitly when given; otherwise the only local agent that COULD
|
|
1079
|
-
* sign, and if there is more than one we REFUSE rather than pick — silently signing as the
|
|
1080
|
-
* wrong sender produces an entry the recipient authenticates against a pubkey nobody
|
|
1081
|
-
* expected, surfacing at recover time as a signature mismatch with no clue the wrong key was
|
|
1082
|
-
* chosen here.
|
|
1083
|
-
*
|
|
1084
|
-
* `load_failed` agents are excluded (review LOW-5): they hold no key provider, so listing
|
|
1085
|
-
* one as a candidate makes a daemon with one healthy agent report `ambiguous_sender` and
|
|
1086
|
-
* name a candidate that cannot sign.
|
|
1087
|
-
*/
|
|
1088
|
-
const signable = agents.filter((a) => a.state !== "load_failed");
|
|
1089
|
-
const candidates = senderAgentName ? signable.filter((a) => a.name === senderAgentName) : signable;
|
|
1090
|
-
if (candidates.length === 0) {
|
|
1091
|
-
return { ok: false, reason: "agent_not_found", guidance: senderAgentName ? `No local agent named '${senderAgentName}' that can sign this deposit.` : "This daemon has no loadable local agent to sign the deposit." };
|
|
1092
|
-
}
|
|
1093
|
-
if (candidates.length > 1) {
|
|
1094
|
-
return { ok: false, reason: "ambiguous_sender", guidance: `This daemon has ${candidates.length} signable agents (${candidates.map((a) => a.name).join(", ")}); pass senderAgentName to say which one signs.` };
|
|
1095
|
-
}
|
|
1096
|
-
const signerAgent = candidates[0];
|
|
1097
|
-
/**
|
|
1098
|
-
* ⚠️ BIND THE SIGNATURE TO A SESSION THIS DAEMON ACTUALLY HOLDS — review HIGH-1.
|
|
1099
|
-
*
|
|
1100
|
-
* Both production producers derive `(sessionId, recipientPubkey)` from a session record;
|
|
1101
|
-
* this handler took them from the caller and signed whatever pair it was handed. A caller
|
|
1102
|
-
* passing the wrong session id or the wrong recipient therefore got a perfectly-formed
|
|
1103
|
-
* SIGNED entry, which the far end refuses as `bad_signature` / `signer_not_counterparty` —
|
|
1104
|
-
* **an attack verdict for a local argument mistake** — and a refused entry is deliberately
|
|
1105
|
-
* never confirm-deleted, so it re-pulls forever.
|
|
1106
|
-
*
|
|
1107
|
-
* `getSessionRecord` returns the row regardless of status, so a COMMITTED session still
|
|
1108
|
-
* resolves and the post-seal straggler case keeps working.
|
|
1109
|
-
*/
|
|
1110
|
-
const record = sessionNodeManager.getSessionRecord(signerAgent.name, sessionId);
|
|
1111
|
-
if (!record) {
|
|
1112
|
-
return { ok: false, reason: "session_not_found", guidance: `Agent '${signerAgent.name}' holds no session ${sessionId.slice(0, 16)}…. Signing a park entry for a session this daemon does not hold produces bytes the recipient can only read as a forgery.` };
|
|
1113
|
-
}
|
|
1114
|
-
if ((record.counterparty_pubkey ?? "").toLowerCase() !== recipientPubkey.toLowerCase()) {
|
|
1115
|
-
return { ok: false, reason: "session_recipient_mismatch", guidance: `Session ${sessionId.slice(0, 16)}… has counterparty ${(record.counterparty_pubkey ?? "(none)").slice(0, 16)}…, not ${recipientPubkey.slice(0, 16)}…. Refused rather than signed: the entry would authenticate against a key the recipient never expects and be reported as a bad signature — an attack verdict for a wrong argument.` };
|
|
1116
|
-
}
|
|
1117
|
-
const signer = getKeyProvider(signerAgent.name);
|
|
1118
|
-
if (!signer)
|
|
1119
|
-
return { ok: false, reason: "signing_key_unavailable", guidance: `Signing key for '${signerAgent.name}' is not loaded.` };
|
|
1120
|
-
payload = await sealParkEnvelope({
|
|
1121
|
-
signer,
|
|
1122
|
-
sessionIdHex: sessionId,
|
|
1123
|
-
recipientPubkey: Buffer.from(recipientPubkey, "hex"),
|
|
1124
|
-
contentHash: Buffer.from(contentHash, "hex"),
|
|
1125
|
-
content: Buffer.from(content, "hex"),
|
|
1126
|
-
...(contentHashAlg !== undefined ? { contentHashAlg } : {}),
|
|
1127
|
-
});
|
|
1128
|
-
}
|
|
1129
|
-
else {
|
|
1130
|
-
const ciphertext = hexOrNull(params?.ciphertext);
|
|
1131
|
-
if (ciphertext === null) {
|
|
1132
|
-
return { ok: false, reason: "missing_params", guidance: "`ciphertext` is not even-length hex." };
|
|
1133
|
-
}
|
|
1134
|
-
payload = Buffer.from(ciphertext, "hex");
|
|
1135
|
-
}
|
|
1136
|
-
/**
|
|
1137
|
-
* DOD-M15-PARKCONN-1 — **THE ODD HANDLER OUT NOW REFUSES LIKE ITS SIBLINGS, AND SAYS SO ONCE.**
|
|
1138
|
-
*
|
|
1139
|
-
* Every other exit above returns `{ ok: false, reason, guidance }`; this one ended in a bare
|
|
1140
|
-
* `return await client.deposit(...)` with no catch and no log. An unreachable relay — the
|
|
1141
|
-
* ordinary condition park exists for — therefore left the daemon as a THROW, which IPC shapes
|
|
1142
|
-
* into `internal_error` + "An unexpected error occurred. Check daemon logs for details." The
|
|
1143
|
-
* daemon logs held nothing either: this handler logged NOTHING AT ALL, which is why
|
|
1144
|
-
* `019-PARKERROR` went looking for a deposit line and found none.
|
|
1145
|
-
*
|
|
1146
|
-
* Only `ContentParkUnreachableError` is converted. Anything else still throws, because a
|
|
1147
|
-
* blanket catch here would turn a genuine defect into a tidy refusal — the failure this
|
|
1148
|
-
* milestone is named for.
|
|
1149
|
-
*/
|
|
1150
|
-
const client = newParkClient({ relayPeerId: relay.peerId, relayAddrs: [relay.addr], logger });
|
|
1151
|
-
try {
|
|
1152
|
-
return await client.deposit(node, {
|
|
1153
|
-
recipientPubkey: Buffer.from(recipientPubkey, "hex"),
|
|
1154
|
-
contentHash: Buffer.from(contentHash, "hex"),
|
|
1155
|
-
sessionId: Buffer.from(sessionId, "hex"),
|
|
1156
|
-
ciphertext: payload,
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
catch (err) {
|
|
1160
|
-
// Only the transport wrap is converted. A blanket catch here would turn a genuine defect
|
|
1161
|
-
// into a tidy refusal — the failure this milestone is named for.
|
|
1162
|
-
if (!(err instanceof ContentParkUnreachableError))
|
|
1163
|
-
throw err;
|
|
1164
|
-
return parkTransportRefusal(err);
|
|
1165
|
-
}
|
|
1166
|
-
};
|
|
1167
|
-
/**
|
|
1168
|
-
* ⚠️ **THE RESULT LOG WRAPS THE WHOLE HANDLER, review MEDIUM-3.**
|
|
1169
|
-
*
|
|
1170
|
-
* It used to sit beside the two exits that reach the relay, so eleven refusals — every bad
|
|
1171
|
-
* argument, `agent_not_found`, `session_not_found`, `session_recipient_mismatch` — still
|
|
1172
|
-
* returned in total silence. That is the same hole `019-PARKERROR` fell into in this very
|
|
1173
|
-
* handler, for a different reason: an operator greps for a deposit line and finds nothing.
|
|
1174
|
-
*
|
|
1175
|
-
* Wrapping rather than sprinkling is deliberate. A call per exit drifts the moment someone adds
|
|
1176
|
-
* a fourteenth; a wrapper cannot miss one.
|
|
1177
|
-
*/
|
|
1178
|
-
handlers.set("content_park_deposit", async (params, connectionId) => {
|
|
1179
|
-
const res = (await runDeposit(params, connectionId));
|
|
1180
|
-
const line = {
|
|
1181
|
-
// Read off the CALLER's params: a refusal can fire before either is parsed, and a log line
|
|
1182
|
-
// that omits which deposit it is about cannot be correlated with anything.
|
|
1183
|
-
relayMultiaddr: typeof params?.["relayMultiaddr"] === "string" ? params["relayMultiaddr"] : undefined,
|
|
1184
|
-
sessionId: typeof params?.["sessionId"] === "string" ? params["sessionId"] : undefined,
|
|
1185
|
-
contentHash: typeof params?.["contentHash"] === "string" ? params["contentHash"] : undefined,
|
|
1186
|
-
ok: res.ok === true,
|
|
1187
|
-
...(res.reason !== undefined ? { reason: res.reason } : {}),
|
|
1188
|
-
...(res.cause !== undefined ? { cause: res.cause } : {}),
|
|
1189
|
-
// Review LOW-8: the relay's own "when" already rides on the response; dropping it from the
|
|
1190
|
-
// log leaves an aggregated log unable to say when a throttled deposit clears.
|
|
1191
|
-
...(res.retryAfterMs !== undefined ? { retryAfterMs: res.retryAfterMs } : {}),
|
|
1192
|
-
};
|
|
1193
|
-
// NOT `content.park.deposit.result` — the ContentParkClient already emits that name one layer
|
|
1194
|
-
// down, and two events from different layers under one name make an aggregated log unreadable
|
|
1195
|
-
// (the same review finding as `content.park.pull.refused_by_relay`).
|
|
1196
|
-
if (line.ok)
|
|
1197
|
-
logger.info("content.park.deposit.ipc.result", line);
|
|
1198
|
-
else
|
|
1199
|
-
logger.warn("content.park.deposit.ipc.result", line);
|
|
1200
|
-
return res;
|
|
1201
|
-
});
|
|
1202
|
-
handlers.set("content_park_pull", async (params, _connectionId) => {
|
|
1203
|
-
const relay = parseRelayPeer(params?.relayMultiaddr);
|
|
1204
|
-
const recipientPubkey = params?.recipientPubkey;
|
|
1205
|
-
if (!relay || !recipientPubkey) {
|
|
1206
|
-
return { ok: false, reason: "missing_params", guidance: "Provide relayMultiaddr (with /p2p/<peerId>) and recipientPubkey (hex)." };
|
|
1207
|
-
}
|
|
1208
|
-
// The recipient must be a local agent — its K_local signs the relay's auth challenge.
|
|
1209
|
-
const recipientAgent = agents.find((a) => a.pubkey === recipientPubkey);
|
|
1210
|
-
if (!recipientAgent)
|
|
1211
|
-
return { ok: false, reason: "agent_not_found", guidance: "No local agent matches recipientPubkey; only the recipient can pull its own parked content." };
|
|
1212
|
-
const kp = getKeyProvider(recipientAgent.name);
|
|
1213
|
-
if (!kp)
|
|
1214
|
-
return { ok: false, reason: "signing_key_unavailable", guidance: `Signing key for '${recipientAgent.name}' is not loaded.` };
|
|
1215
|
-
const node = sessionNodeManager.getStandingReceiverNode();
|
|
1216
|
-
if (!node)
|
|
1217
|
-
return { ok: false, reason: "standing_receiver_unavailable", guidance: "The daemon's standing receiver is not ready yet; retry after startup." };
|
|
1218
|
-
// Review LOW-9: the seam, like every other client in this file. This handler was still
|
|
1219
|
-
// constructing directly, which is why its `#open` failure path had no unit coverage.
|
|
1220
|
-
const client = newParkClient({ relayPeerId: relay.peerId, relayAddrs: [relay.addr], logger });
|
|
1221
|
-
let entries;
|
|
1222
|
-
try {
|
|
1223
|
-
entries = await client.pull(node, Buffer.from(recipientPubkey, "hex"), kp);
|
|
1224
|
-
}
|
|
1225
|
-
catch (err) {
|
|
1226
|
-
/**
|
|
1227
|
-
* Review MEDIUM-2 — `#open` is shared, so this handler now receives the same transport wrap
|
|
1228
|
-
* the deposit handler does. Rethrowing it turned an unreachable relay into
|
|
1229
|
-
* `internal_error` + "An unexpected error occurred" — the exact response this unit exists to
|
|
1230
|
-
* abolish, one handler over, on a path the order's own evidence names.
|
|
1231
|
-
*/
|
|
1232
|
-
if (err instanceof ContentParkUnreachableError)
|
|
1233
|
-
return parkTransportRefusal(err);
|
|
1234
|
-
// DOD-M15-RELAYAUTH-1 review HIGH-3: a refusal must not be reported as an empty list.
|
|
1235
|
-
if (err instanceof ContentParkRefusedError) {
|
|
1236
|
-
return {
|
|
1237
|
-
ok: false,
|
|
1238
|
-
reason: `relay_refused_pull:${err.reason}`,
|
|
1239
|
-
guidance: "This relay refused to release parked content for this agent — it is NOT an empty mailbox, " +
|
|
1240
|
-
"and content may still be waiting. A relay only recognises agents named by a session " +
|
|
1241
|
-
"assignment it has recorded, and it forgets them when it restarts. Establish a session " +
|
|
1242
|
-
"with this relay (cello_initiate_session) and retry, or pull from the relay that " +
|
|
1243
|
-
"brokered the session the content belongs to.",
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
throw err;
|
|
1247
|
-
}
|
|
1248
|
-
return {
|
|
1249
|
-
ok: true,
|
|
1250
|
-
entries: entries.map((e) => ({ contentHash: e.contentHashHex, sessionId: e.sessionIdHex, ciphertext: Buffer.from(e.ciphertext).toString("hex") })),
|
|
1251
|
-
};
|
|
1252
|
-
});
|
|
1253
|
-
handlers.set("content_park_recover", async (params, _connectionId) => {
|
|
1254
|
-
const relay = parseRelayPeer(params?.relayMultiaddr);
|
|
1255
|
-
const recipientPubkey = params?.recipientPubkey;
|
|
1256
|
-
if (!relay || !recipientPubkey) {
|
|
1257
|
-
return { ok: false, reason: "missing_params", guidance: "Provide relayMultiaddr (with /p2p/<peerId>) and recipientPubkey (hex)." };
|
|
1258
|
-
}
|
|
1259
|
-
const recipientAgent = agents.find((a) => a.pubkey === recipientPubkey);
|
|
1260
|
-
if (!recipientAgent)
|
|
1261
|
-
return { ok: false, reason: "agent_not_found", guidance: "No local agent matches recipientPubkey." };
|
|
1262
|
-
const res = await recoverParkedFromRelay(recipientAgent, relay.peerId, [relay.addr]);
|
|
1263
|
-
if (!res.ok) {
|
|
1264
|
-
const guidanceByReason = {
|
|
1265
|
-
signing_key_unavailable: `Signing key for '${recipientAgent.name}' is not loaded.`,
|
|
1266
|
-
cannot_unseal: `Agent '${recipientAgent.name}' key provider cannot open content seals.`,
|
|
1267
|
-
standing_receiver_creating: "The daemon's standing receiver is still being built; retry in a few seconds.",
|
|
1268
|
-
agent_offline: `Agent '${recipientAgent.name}' is not online, so this daemon has no node to pull from. Start it with cello_start_agent.`,
|
|
1269
|
-
no_standing_receiver: "No agent on this daemon has a standing receiver; start an agent first.",
|
|
1270
|
-
daemon_shutting_down: "The daemon is shutting down.",
|
|
1271
|
-
};
|
|
1272
|
-
// The WIRE reason stays the documented contract string for every no-receiver cause
|
|
1273
|
-
// (types.ts STANDING_RECEIVER_UNAVAILABLE); the precise cause rides in the guidance,
|
|
1274
|
-
// where it helps the operator without breaking a consumer that branches on the reason.
|
|
1275
|
-
const noReceiver = new Set(["standing_receiver_creating", "agent_offline", "no_standing_receiver", "daemon_shutting_down"]);
|
|
1276
|
-
const wireReason = noReceiver.has(res.reason) ? "standing_receiver_unavailable" : res.reason;
|
|
1277
|
-
/**
|
|
1278
|
-
* Review MEDIUM-2 — a guidance the drain already computed WINS, and the bare "Recover
|
|
1279
|
-
* failed." is the last resort rather than the answer for everything the table misses. That
|
|
1280
|
-
* default is not an affordance: it names no next step, which is what Invariant 4 forbids on
|
|
1281
|
-
* exactly this kind of response.
|
|
1282
|
-
*/
|
|
1283
|
-
return {
|
|
1284
|
-
ok: false,
|
|
1285
|
-
reason: wireReason,
|
|
1286
|
-
guidance: res.guidance ?? guidanceByReason[res.reason] ?? `Recover failed (${res.reason}) and this daemon has no specific remedy for that reason. The daemon log holds the detail — report it with the reason above.`,
|
|
1287
|
-
};
|
|
1288
|
-
}
|
|
1289
|
-
/**
|
|
1290
|
-
* `refusals` REACHES THE CALLER — review B2a pass-2 F3, "no consumer, no ship".
|
|
1291
|
-
*
|
|
1292
|
-
* `recoverParkedFromRelay` has always returned it and both callers dropped it: this handler
|
|
1293
|
-
* returned only `{recovered, pulled}`, and `drainOnce` reads only `res.recovered`. So every
|
|
1294
|
-
* refusal reason computed in that loop reached nothing but a vitest assertion — including the
|
|
1295
|
-
* four this milestone added to tell a version skew from a tamper from a storage fault.
|
|
1296
|
-
*
|
|
1297
|
-
* Which made the pass-1 F5 fix a label with no reader, and three of its tests assertions on a
|
|
1298
|
-
* channel nobody could observe. An operator seeing `{recovered: 0, pulled: 3}` had three
|
|
1299
|
-
* messages evaporate with the explanation only in the daemon log.
|
|
1300
|
-
*/
|
|
1301
|
-
return { ok: true, recovered: res.recovered, pulled: res.pulled, refused: res.refused, refusals: res.refusals };
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
// M12-P17: exported for the screening/annex tests. `autoRecoverForAgent` walks every relay the
|
|
1305
|
-
// agent has a session on and swallows per-relay errors by design, so it cannot assert what one
|
|
1306
|
-
// entry did; this is the single-relay drain the assertions need.
|
|
1307
|
-
return { autoRecoverForAgent, registerHandlers, recoverParkedFromRelay };
|
|
1308
|
-
}
|
|
1309
|
-
//# sourceMappingURL=content-park.js.map
|