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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Recall scoping: turn "which rooms may this read see?" into one filter.
//!
//! Why (ADR-0027 D2): a Wing enables "recall everything the `engineer` agent
//! type has learned" as a single query, instead of requiring the caller to
//! already know that agent's complete topic set — which is the discovery
//! problem the ADR exists to fix. The drawer table stores only `room_id`, so a
//! wing has to be projected onto a set of room ids before it can filter
//! anything; doing that in one place means `retrieve_l2` and `list_drawers`
//! cannot drift on what a scope means.
//! What: [`RecallScope`] (`All` / `Room` / `Wing`), its projection onto a set
//! of room ids, and [`list_drawers_in_wing`].
//!
//! **Fail-closed, unlike the room filter.** `resolve_room_filter_id` falls back
//! to the legacy fold when a room has no registry row, because filtering "as it
//! did before ADR-0027" is the safe answer for a topic. A *wing* is a
//! scope/ownership boundary — #3064's "two agent types cannot accidentally
//! read/write the same room unless configured to do so" — so a wing that cannot
//! be resolved yields the EMPTY set, never the unfiltered one. A topic filter
//! that fails open shows extra results; a scope that fails open is a leak.
//!
//! Test: `scope_all_matches_everything`, `wing_scope_is_fail_closed`,
//! `wing_scope_returns_only_that_wings_drawers`,
//! `same_named_rooms_in_two_wings_stay_distinct`.
use crate;
use crateKnowledgeGraph;
use crateresolve_room_filter_id;
use craterooms_in_wing;
use HashSet;
use Uuid;
use PalaceHandle;
/// Which rooms a read is allowed to see.
///
/// Why: `retrieve_l2` used to take `Option<RoomType>`, which cannot express
/// "this wing". Generalising the filter — rather than adding a second,
/// parallel wing parameter — keeps one implementation of the matching rule.
/// What: `All` (no filter, the pre-T9 default), `Room` (one topic), `Wing`
/// (every room that wing owns).
/// Test: `scope_all_matches_everything`, `wing_scope_is_fail_closed`.
/// Whether `room_id` is admitted by a resolved scope.
///
/// Why: one predicate shared by `retrieve_l2` and `list_drawers_in_wing`, so a
/// future scope variant cannot be honoured by one and ignored by the other.
/// List drawers belonging to `wing_id`, sorted by importance descending.
///
/// Why: the wing-axis counterpart of `PalaceHandle::list_drawers`. It lives
/// here rather than as a method because `retrieval/handle.rs` sits at 487 of
/// its 500-SLOC cap (ADR-0027 C5) and can absorb a call site, not an
/// implementation.
/// What: resolves the wing to its room set BEFORE taking the drawer read guard
/// — that resolution is a redb read transaction, and holding the lock across
/// I/O would stall every writer on the palace for its duration — then applies
/// the same tag filter, `drawer_listing_order` ranking, and truncation
/// `list_drawers` uses.
/// Test: `wing_scope_returns_only_that_wings_drawers`,
/// `list_drawers_in_wing_keeps_the_newest_drawer_within_an_importance_tie`.