forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathv3_memory_read_service.py
More file actions
228 lines (192 loc) · 8.06 KB
/
Copy pathv3_memory_read_service.py
File metadata and controls
228 lines (192 loc) · 8.06 KB
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""Canonical module for ``utils.memory.v3_memory_read_service`` (WS-G8b).
Neutral ``v3_memory_read_service`` is the source of truth. Legacy ``v3_memory_read_service`` remains an importable alias.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from utils.memory.v3_compatibility import (
V3CompatibilityContext,
V3CompatibilityReadPath,
decide_v3_compatibility,
)
from utils.memory.v3_cursor import (
V3CursorContext,
V3CursorError,
V3Keyset,
create_v3_cursor,
parse_v3_cursor,
validate_v3_cursor_request,
)
from utils.memory.v3_projection_readiness import (
V3ProjectionReadinessContext,
V3ProjectionReadinessState,
decide_v3_projection_readiness,
)
V3_READ_SOURCE = 'memory_compatibility_projection'
V3_READ_MODE = 'default_memory'
_DEFAULT_CURSOR_TTL_SECONDS = 300
@dataclass(frozen=True)
class V3MemoryReadRequest:
limit: int
offset: int | None = None
cursor: str | None = None
v3_cursor_mode: bool = True
@dataclass(frozen=True)
class V3MemoryReadServiceInput:
uid: str
enrolled: bool
control_state: str
default_memory_grant: bool | None
request: V3MemoryReadRequest
projection_readiness_context: V3ProjectionReadinessContext | dict[str, Any] | None = None
page_body: list[Any] = field(default_factory=list)
cursor_context: V3CursorContext | None = None
cursor_secret: bytes | None = None
next_keyset: V3Keyset | None = None
requested_archive: bool = False
cursor_ttl_seconds: int = _DEFAULT_CURSOR_TTL_SECONDS
@dataclass(frozen=True)
class V3MemoryReadServiceResult:
http_status: int
read_plan: str
read_path: V3CompatibilityReadPath
read_decision: str
headers: dict[str, str]
body: list[Any] | None = None
should_fetch_legacy: bool = False
should_fetch_memory_projection: bool = False
legacy_fallback_allowed: bool = False
archive_default_available: bool = False
stale_short_term_default_visible: bool = False
def _projection_context(
value: V3ProjectionReadinessContext | dict[str, Any] | None,
) -> V3ProjectionReadinessContext | None:
if value is None:
return None
if isinstance(value, V3ProjectionReadinessContext):
return value
return V3ProjectionReadinessContext(**value)
def _headers(*, source: str, decision: str) -> dict[str, str]:
return {'X-Omi-Memory-Read-Source': source, 'X-Omi-Memory-Read-Decision': decision}
def _fail_closed_cursor(reason: str) -> V3MemoryReadServiceResult:
return V3MemoryReadServiceResult(
http_status=400,
read_plan='fail_closed',
read_path=V3CompatibilityReadPath.FAIL_CLOSED,
read_decision=reason,
headers=_headers(source='none', decision=reason),
)
def _classify_write_convergence_ready(reason: str) -> bool:
return reason not in {
'external_create_convergence_not_ready',
'external_update_convergence_not_ready',
'external_delete_convergence_not_ready',
}
def _validate_memory_cursor_request(service_input: V3MemoryReadServiceInput) -> V3MemoryReadServiceResult | None:
request = service_input.request
if not request.v3_cursor_mode:
return None
try:
validate_v3_cursor_request(limit=request.limit, cursor=request.cursor, offset=request.offset)
if request.cursor is not None:
if service_input.cursor_context is None or service_input.cursor_secret is None:
raise V3CursorError('cursor_validation_context_missing')
parse_v3_cursor(request.cursor, service_input.cursor_context, service_input.cursor_secret)
except V3CursorError as exc:
return _fail_closed_cursor(exc.reason)
return None
def _add_next_cursor_headers(
headers: dict[str, str], service_input: V3MemoryReadServiceInput
) -> dict[str, str] | V3MemoryReadServiceResult:
if service_input.next_keyset is None:
return headers
if service_input.cursor_context is None or service_input.cursor_secret is None:
return _fail_closed_cursor('next_cursor_context_missing')
cursor = create_v3_cursor(
service_input.next_keyset,
service_input.cursor_context,
service_input.cursor_secret,
ttl_seconds=service_input.cursor_ttl_seconds,
)
result = dict(headers)
result['X-Omi-Memory-Next-Cursor'] = cursor
result['Link'] = f'<{cursor}>; rel="next"'
return result
def plan_v3_memory_read(service_input: V3MemoryReadServiceInput) -> V3MemoryReadServiceResult:
"""Return a local `/v3` compatibility read envelope/plan.
Non-enrolled callers receive only a legacy-primary marker; this function never
fetches legacy rows itself. Enrolled memory cursor-mode callers fail closed on
invalid cursor/offset semantics and never downgrade to offset or legacy.
"""
if not service_input.enrolled:
decision = decide_v3_compatibility(
V3CompatibilityContext(
uid=service_input.uid,
enrolled=False,
control_state=service_input.control_state,
)
)
return V3MemoryReadServiceResult(
http_status=decision.http_status,
read_plan='legacy_primary_plan_only',
read_path=decision.read_path,
read_decision=decision.reason,
headers=decision.headers,
legacy_fallback_allowed=decision.legacy_fallback_allowed,
)
cursor_failure = _validate_memory_cursor_request(service_input)
if cursor_failure is not None:
return cursor_failure
projection_context = _projection_context(service_input.projection_readiness_context)
projection_ready = False
projection_empty = False
write_convergence_ready = False
if projection_context is not None:
projection_decision = decide_v3_projection_readiness(projection_context)
projection_ready = projection_decision.read_cutover_allowed
projection_empty = projection_decision.state == V3ProjectionReadinessState.READY_EMPTY
write_convergence_ready = projection_ready or _classify_write_convergence_ready(projection_decision.reason)
decision = decide_v3_compatibility(
V3CompatibilityContext(
uid=service_input.uid,
enrolled=True,
control_state=service_input.control_state,
default_memory_grant=service_input.default_memory_grant,
write_convergence_ready=write_convergence_ready,
projection_ready=projection_ready,
projection_empty=projection_empty,
requested_archive=service_input.requested_archive,
)
)
if decision.read_path != V3CompatibilityReadPath.MEMORY_COMPATIBILITY_PROJECTION:
return V3MemoryReadServiceResult(
http_status=decision.http_status,
read_plan='fail_closed' if decision.read_path == V3CompatibilityReadPath.FAIL_CLOSED else 'deny',
read_path=decision.read_path,
read_decision=decision.reason,
headers=decision.headers,
legacy_fallback_allowed=decision.legacy_fallback_allowed,
archive_default_available=decision.archive_available,
)
if decision.response_body_override is not None:
return V3MemoryReadServiceResult(
http_status=decision.http_status,
read_plan='memory_compatibility_projection',
read_path=decision.read_path,
read_decision=decision.reason,
headers=decision.headers,
body=decision.response_body_override,
legacy_fallback_allowed=decision.legacy_fallback_allowed,
)
headers_or_failure = _add_next_cursor_headers(decision.headers, service_input)
if isinstance(headers_or_failure, V3MemoryReadServiceResult):
return headers_or_failure
return V3MemoryReadServiceResult(
http_status=decision.http_status,
read_plan='memory_compatibility_projection',
read_path=decision.read_path,
read_decision=decision.reason,
headers=headers_or_failure,
body=service_input.page_body,
legacy_fallback_allowed=decision.legacy_fallback_allowed,
)