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: '0814efaa2ca66f0121dd6d783ec6383b09542f126628695cf2c375c056b5c2b1'
4
- data.tar.gz: d6df0516525b8ff2883f9f4074eb488ac6df00c74ee24994ab7c629a9a449a0e
3
+ metadata.gz: 50c80158ba856d35e47e3c72fdfbadb29a4a75b0e45b1e859f507dafc850ab4d
4
+ data.tar.gz: e8ca09283855617c11b3cef8da5bdeec38a1aaeea526f1558610d1702ee8f97e
5
5
  SHA512:
6
- metadata.gz: ad170b27af1c27a90430d7ce82d89554b5a4cd5709504535d84bfc3ae52046495a790acf29cc9665739c2486cc736c36cbd118cf588f4f9418d3e90f307182e3
7
- data.tar.gz: 8ee023a4360585eca2bd215dfc8601e4c9d2d96935a985c1bc8b0200e7953dbcd2f49d26032c35d23f8b5af69fa097a0d2c9c189291faa189e5892e22785405c
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) ? Legion::JSON.dump(tags) : nil,
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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Memory
7
- VERSION = '0.1.13'
7
+ VERSION = '0.1.14'
8
8
  end
9
9
  end
10
10
  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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity