lex-agentic-memory 0.1.13 → 0.1.14
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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50c80158ba856d35e47e3c72fdfbadb29a4a75b0e45b1e859f507dafc850ab4d
|
|
4
|
+
data.tar.gz: e8ca09283855617c11b3cef8da5bdeec38a1aaeea526f1558610d1702ee8f97e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c7d4a3b2ba13f3639fcb6a6b8729ddef7e0e5286eaf2fb7a68fe5d4d395f6306e8dccf5d1a3c7ed2f884a49e2ddcb812f2377f0bca36384ad6231804d90a40b
|
|
7
|
+
data.tar.gz: d55c7a895b14acb28209eafba19b83059a1a89e84335d0e130968d941814c3af4ee66bb6d4c95a0d5c655bbe117b753f3240eee80f00cbbdccaa3e7479d322fd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.14] - 2026-03-26
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `PostgresStore#serialize_trace` and `#map_update_fields` now strip null bytes (`\x00`) from all string fields before INSERT/UPDATE. PostgreSQL text columns reject null bytes, causing `string contains null byte` errors when content from external sources (e.g., Teams Graph API) contains embedded nulls
|
|
7
|
+
|
|
3
8
|
## [0.1.13] - 2026-03-26
|
|
4
9
|
|
|
5
10
|
### Fixed
|
|
@@ -286,28 +286,28 @@ module Legion
|
|
|
286
286
|
trace_id: trace[:trace_id],
|
|
287
287
|
agent_id: @agent_id,
|
|
288
288
|
tenant_id: @tenant_id,
|
|
289
|
-
trace_type: trace[:trace_type].to_s,
|
|
290
|
-
content: payload.is_a?(Hash) ? Legion::JSON.dump(payload) : payload.to_s,
|
|
289
|
+
trace_type: sanitize_pg_string(trace[:trace_type].to_s),
|
|
290
|
+
content: sanitize_pg_string(payload.is_a?(Hash) ? Legion::JSON.dump(payload) : payload.to_s),
|
|
291
291
|
significance: conf,
|
|
292
292
|
confidence: conf,
|
|
293
|
-
associations: assocs.is_a?(Array) ? Legion::JSON.dump(assocs) : '[]',
|
|
294
|
-
domain_tags: tags.is_a?(Array)
|
|
293
|
+
associations: sanitize_pg_string(assocs.is_a?(Array) ? Legion::JSON.dump(assocs) : '[]'),
|
|
294
|
+
domain_tags: sanitize_pg_string(tags.is_a?(Array) ? Legion::JSON.dump(tags) : nil),
|
|
295
295
|
strength: trace[:strength],
|
|
296
296
|
peak_strength: trace[:peak_strength],
|
|
297
297
|
base_decay_rate: trace[:base_decay_rate],
|
|
298
298
|
emotional_valence: ev.is_a?(Numeric) ? ev.to_f : 0.0,
|
|
299
299
|
emotional_intensity: trace[:emotional_intensity],
|
|
300
|
-
origin: trace[:origin].to_s,
|
|
301
|
-
source_agent_id: trace[:source_agent_id],
|
|
302
|
-
storage_tier: trace[:storage_tier].to_s,
|
|
300
|
+
origin: sanitize_pg_string(trace[:origin].to_s),
|
|
301
|
+
source_agent_id: sanitize_pg_string(trace[:source_agent_id]),
|
|
302
|
+
storage_tier: sanitize_pg_string(trace[:storage_tier].to_s),
|
|
303
303
|
last_reinforced: trace[:last_reinforced],
|
|
304
304
|
last_decayed: trace[:last_decayed],
|
|
305
305
|
reinforcement_count: trace[:reinforcement_count],
|
|
306
306
|
unresolved: trace[:unresolved] || false,
|
|
307
307
|
consolidation_candidate: trace[:consolidation_candidate] || false,
|
|
308
|
-
parent_trace_id: trace[:parent_trace_id],
|
|
309
|
-
encryption_key_id: trace[:encryption_key_id],
|
|
310
|
-
partition_id: trace[:partition_id],
|
|
308
|
+
parent_trace_id: sanitize_pg_string(trace[:parent_trace_id]),
|
|
309
|
+
encryption_key_id: sanitize_pg_string(trace[:encryption_key_id]),
|
|
310
|
+
partition_id: sanitize_pg_string(trace[:partition_id]),
|
|
311
311
|
created_at: trace[:created_at] || Time.now.utc,
|
|
312
312
|
accessed_at: Time.now.utc
|
|
313
313
|
}
|
|
@@ -359,13 +359,13 @@ module Legion
|
|
|
359
359
|
|
|
360
360
|
row[col] = case col
|
|
361
361
|
when :content
|
|
362
|
-
v.is_a?(Hash) ? Legion::JSON.dump(v) : v.to_s
|
|
362
|
+
sanitize_pg_string(v.is_a?(Hash) ? Legion::JSON.dump(v) : v.to_s)
|
|
363
363
|
when :associations
|
|
364
|
-
v.is_a?(Array) ? Legion::JSON.dump(v) : '[]'
|
|
364
|
+
sanitize_pg_string(v.is_a?(Array) ? Legion::JSON.dump(v) : '[]')
|
|
365
365
|
when :domain_tags
|
|
366
|
-
v.is_a?(Array) ? Legion::JSON.dump(v) : nil
|
|
366
|
+
sanitize_pg_string(v.is_a?(Array) ? Legion::JSON.dump(v) : nil)
|
|
367
367
|
when :trace_type, :origin, :storage_tier
|
|
368
|
-
v.to_s
|
|
368
|
+
sanitize_pg_string(v.to_s)
|
|
369
369
|
else
|
|
370
370
|
v
|
|
371
371
|
end
|
|
@@ -390,6 +390,12 @@ module Legion
|
|
|
390
390
|
[]
|
|
391
391
|
end
|
|
392
392
|
|
|
393
|
+
def sanitize_pg_string(value)
|
|
394
|
+
return value unless value.is_a?(String)
|
|
395
|
+
|
|
396
|
+
value.delete("\x00")
|
|
397
|
+
end
|
|
398
|
+
|
|
393
399
|
def log_warn(message)
|
|
394
400
|
Legion::Logging.warn "[memory:postgres_store] #{message}" if defined?(Legion::Logging)
|
|
395
401
|
end
|
|
@@ -185,6 +185,53 @@ RSpec.describe Legion::Extensions::Agentic::Memory::Trace::Helpers::PostgresStor
|
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
+
# --- null byte sanitization ---
|
|
189
|
+
|
|
190
|
+
describe 'null byte sanitization' do
|
|
191
|
+
it 'strips null bytes from string content and stores successfully' do
|
|
192
|
+
trace = trace_helper.new_trace(type: :episodic, content_payload: "hello\x00world")
|
|
193
|
+
result = store.store(trace)
|
|
194
|
+
expect(result).not_to be_nil
|
|
195
|
+
|
|
196
|
+
retrieved = store.retrieve(trace[:trace_id])
|
|
197
|
+
expect(retrieved[:content_payload]).to eq('helloworld')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'strips null bytes from hash content payloads' do
|
|
201
|
+
trace = trace_helper.new_trace(type: :episodic, content_payload: { text: "has\x00null" })
|
|
202
|
+
result = store.store(trace)
|
|
203
|
+
expect(result).not_to be_nil
|
|
204
|
+
|
|
205
|
+
row = db[:memory_traces].where(trace_id: trace[:trace_id]).first
|
|
206
|
+
expect(row[:content]).not_to include("\x00")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'strips null bytes from domain_tags' do
|
|
210
|
+
trace = trace_helper.new_trace(type: :episodic, content_payload: 'clean', domain_tags: ["tag\x00bad"])
|
|
211
|
+
store.store(trace)
|
|
212
|
+
|
|
213
|
+
row = db[:memory_traces].where(trace_id: trace[:trace_id]).first
|
|
214
|
+
expect(row[:domain_tags]).not_to include("\x00")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'stores cleanly when no null bytes are present' do
|
|
218
|
+
trace = trace_helper.new_trace(type: :episodic, content_payload: 'no nulls here')
|
|
219
|
+
result = store.store(trace)
|
|
220
|
+
expect(result).not_to be_nil
|
|
221
|
+
|
|
222
|
+
retrieved = store.retrieve(trace[:trace_id])
|
|
223
|
+
expect(retrieved[:content_payload]).to eq('no nulls here')
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'strips null bytes during partial update' do
|
|
227
|
+
store.store(semantic_trace)
|
|
228
|
+
store.update(semantic_trace[:trace_id], content_payload: { text: "up\x00dated" })
|
|
229
|
+
|
|
230
|
+
row = db[:memory_traces].where(trace_id: semantic_trace[:trace_id]).first
|
|
231
|
+
expect(row[:content]).not_to include("\x00")
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
188
235
|
# --- retrieve_by_type ---
|
|
189
236
|
|
|
190
237
|
describe '#retrieve_by_type' do
|