1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Bounded WhatsApp history sync (#1525).
//!
//! WhatsApp answers a PDO history request by streaming encrypted sync frames
//! back over the normal socket; the wa crate decrypts them and re-emits the
//! messages through the same event stream, distinguishable only by the
//! per-message markers (`is_offline`, `unavailable_request_id`). Per the
//! decision doc (~/.opencrabs/research/whatsapp-history-sync-decisions-1525.md):
//!
//! - **Store**: the existing `channel_messages` partition the live capture
//! already uses, keyed by the SAME normalized `chat_id` the live path
//! formats (#1544), tagged `message_type = "imported"` and deduped by
//! platform message id.
//! - **Opt-in**: per-chat, OFF by default (`[channels.whatsapp]
//! history_import_chats`) — a config edit IS the consent gesture, so there
//! is no global enable and no agent-side write surface.
//! - **Bounds**: hard caps, whichever hits first — 10,000 messages per chat
//! or 90 days back (the request plan below), plus a window filter at
//! capture for frames the phone answers outside the range.
//! - **Surface**: read-only search through the `whatsapp_history` tool.
//!
//! The request trigger lives in `agent.rs` (fires on connect); the capture
//! gate lives in `handle_message` (frames must never wake the agent). This
//! module owns only the decisions, as pure functions.
use ;
/// Cap on messages pulled per chat per request (decision 3).
pub const HISTORY_IMPORT_MAX_MESSAGES: i32 = 10_000;
/// How far back an import may go (decision 3).
pub const HISTORY_IMPORT_MAX_AGE_DAYS: i64 = 90;
/// Message type tag distinguishing imported rows from live captures in the
/// shared `channel_messages` store (decision 1).
pub const IMPORTED_TYPE: &str = "imported";
/// One bounded PDO history request, anchored on a message already stored.
pub
/// Decide whether an import request is worth sending for one chat.
///
/// `oldest` is the chat's earliest stored row (platform id, whether the
/// sender is us, its timestamp). Semantics: the phone answers with up to
/// `count` messages OLDER than the anchor.
///
/// - No rows yet: anchor an empty id at `now` — the phone starts from the
/// newest messages (the only supported head).
/// - Anchor already at or before the 90-day cutoff: nothing in-window can
/// remain below it — `None`, do not ask.
/// - Otherwise: full page, capped at [`HISTORY_IMPORT_MAX_MESSAGES`].
pub
/// Window filter for frames arriving at the capture gate: the phone may
/// answer with messages older than the 90-day bound; those are dropped.
pub
/// Per-chat opt-in (decision 2): exact match on the normalized chat id the
/// live path formats (`Display` of the source JID), never a phone prefix
/// scan — #1544's lesson is that loose key matching merges partitions.
pub
/// Whether a stored row's sender identifies us, for the anchor's `from_me`
/// flag. No owner known yet is conservatively `false` (the phone tolerates
/// it; the anchor is still exact on id + timestamp).
pub
/// Clamp the read-only search tool's knobs to the feature's bounds
/// (decision 3's window applies to retrieval too, and an unbounded
/// `LIMIT` would defeat the point of a soft store).
pub