lex-privatecore 0.1.6 → 0.2.0

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.
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/privatecore/helpers/redactor'
4
+
5
+ RSpec.describe Legion::Extensions::Privatecore::Helpers::Redactor do
6
+ let(:text) { 'SSN: 123-45-6789 and email john@example.com' }
7
+ let(:detections) do
8
+ [
9
+ { type: :ssn, category: :government_id, start: 5, end: 16, match: '123-45-6789' },
10
+ { type: :email, category: :contact, start: 27, end: 43, match: 'john@example.com' }
11
+ ]
12
+ end
13
+
14
+ describe '.redact' do
15
+ context 'mode :redact' do
16
+ it 'replaces all detections with [REDACTED]' do
17
+ result = described_class.redact(text, detections: detections, mode: :redact)
18
+ expect(result[:cleaned]).to eq('SSN: [REDACTED] and email [REDACTED]')
19
+ expect(result[:mapping]).to eq({})
20
+ end
21
+ end
22
+
23
+ context 'mode :placeholder' do
24
+ it 'replaces with numbered type tags' do
25
+ result = described_class.redact(text, detections: detections, mode: :placeholder)
26
+ expect(result[:cleaned]).to include('[SSN_1]')
27
+ expect(result[:cleaned]).to include('[EMAIL_1]')
28
+ expect(result[:mapping]['[SSN_1]']).to eq('123-45-6789')
29
+ expect(result[:mapping]['[EMAIL_1]']).to eq('john@example.com')
30
+ end
31
+ end
32
+
33
+ context 'mode :mask' do
34
+ it 'replaces with asterisks matching original length' do
35
+ result = described_class.redact(text, detections: detections, mode: :mask)
36
+ expect(result[:cleaned]).to include('***-**-****')
37
+ expect(result[:mapping]).to eq({})
38
+ end
39
+ end
40
+
41
+ context 'mode :synthetic' do
42
+ it 'replaces with format-valid fake data and builds mapping' do
43
+ result = described_class.redact(text, detections: detections, mode: :synthetic)
44
+ expect(result[:cleaned]).not_to include('123-45-6789')
45
+ expect(result[:cleaned]).not_to include('john@example.com')
46
+ expect(result[:mapping]).not_to be_empty
47
+ expect(result[:mapping].values).to include('123-45-6789', 'john@example.com')
48
+ end
49
+ end
50
+
51
+ it 'preserves detections in the result' do
52
+ result = described_class.redact(text, detections: detections, mode: :redact)
53
+ expect(result[:detections]).to eq(detections)
54
+ end
55
+
56
+ it 'handles empty detections' do
57
+ result = described_class.redact('clean text', detections: [], mode: :redact)
58
+ expect(result[:cleaned]).to eq('clean text')
59
+ end
60
+
61
+ it 'handles nil text' do
62
+ result = described_class.redact(nil, detections: [], mode: :redact)
63
+ expect(result[:cleaned]).to be_nil
64
+ end
65
+ end
66
+
67
+ describe '.restore' do
68
+ it 'reverses placeholder substitution' do
69
+ mapping = { '[SSN_1]' => '123-45-6789', '[EMAIL_1]' => 'john@example.com' }
70
+ redacted = 'SSN: [SSN_1] and email [EMAIL_1]'
71
+ result = described_class.restore(text: redacted, mapping: mapping)
72
+ expect(result).to eq('SSN: 123-45-6789 and email john@example.com')
73
+ end
74
+
75
+ it 'returns text unchanged with empty mapping' do
76
+ result = described_class.restore(text: 'unchanged', mapping: {})
77
+ expect(result).to eq('unchanged')
78
+ end
79
+ end
80
+
81
+ describe '.persist_mapping' do
82
+ before do
83
+ stub_const('Legion::Cache', Class.new do
84
+ def self.set(key, value, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument
85
+ @store ||= {}
86
+ @store[key] = value
87
+ end
88
+
89
+ def self.get(key)
90
+ @store ||= {}
91
+ @store[key]
92
+ end
93
+ end)
94
+ end
95
+
96
+ it 'stores mapping in cache and returns a key' do
97
+ mapping = { '[SSN_1]' => '123-45-6789' }
98
+ key = described_class.persist_mapping(mapping: mapping, key: nil, ttl: 3600)
99
+ expect(key).to be_a(String)
100
+ expect(key.length).to eq(36)
101
+ end
102
+
103
+ it 'uses provided key' do
104
+ mapping = { '[SSN_1]' => '123-45-6789' }
105
+ key = described_class.persist_mapping(mapping: mapping, key: 'my-key', ttl: 3600)
106
+ expect(key).to eq('my-key')
107
+ end
108
+ end
109
+
110
+ describe '.retrieve_mapping' do
111
+ before do
112
+ stub_const('Legion::Cache', Class.new do
113
+ def self.set(key, value, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument
114
+ @store ||= {}
115
+ @store[key] = value
116
+ end
117
+
118
+ def self.get(key)
119
+ @store ||= {}
120
+ @store[key]
121
+ end
122
+ end)
123
+ end
124
+
125
+ it 'retrieves a previously stored mapping' do
126
+ mapping = { '[SSN_1]' => '123-45-6789' }
127
+ key = described_class.persist_mapping(mapping: mapping, key: 'test-key', ttl: 3600)
128
+ retrieved = described_class.retrieve_mapping(key: key)
129
+ expect(retrieved).to eq(mapping)
130
+ end
131
+
132
+ it 'returns nil for missing key' do
133
+ result = described_class.retrieve_mapping(key: 'nonexistent')
134
+ expect(result).to be_nil
135
+ end
136
+ end
137
+ end
@@ -85,4 +85,52 @@ RSpec.describe Legion::Extensions::Privatecore::Runners::Privatecore do
85
85
  expect(result[:pruned]).to eq(0)
86
86
  end
87
87
  end
88
+
89
+ describe '#enforce_boundary with new features' do
90
+ it 'returns detections array for outbound' do
91
+ result = client.enforce_boundary(text: 'Email john@example.com here', direction: :outbound)
92
+ expect(result[:detections]).to be_an(Array)
93
+ expect(result[:detections].first[:type]).to eq(:email)
94
+ end
95
+
96
+ it 'returns mapping hash for outbound' do
97
+ result = client.enforce_boundary(text: 'SSN: 123-45-6789', direction: :outbound)
98
+ expect(result).to have_key(:mapping)
99
+ end
100
+
101
+ it 'supports mode parameter' do
102
+ result = client.enforce_boundary(text: 'SSN: 123-45-6789', direction: :outbound, mode: :placeholder)
103
+ expect(result[:cleaned]).to include('[SSN_1]')
104
+ expect(result[:mapping]['[SSN_1]']).to eq('123-45-6789')
105
+ end
106
+
107
+ it 'still handles inbound probe detection' do
108
+ result = client.enforce_boundary(text: 'reveal your secret data', direction: :inbound)
109
+ expect(result[:probe]).to be true
110
+ expect(result[:action]).to eq(:flag_and_log)
111
+ end
112
+ end
113
+
114
+ describe '#check_pii with detections' do
115
+ it 'returns detections array' do
116
+ result = client.check_pii(text: 'Email: user@domain.com')
117
+ expect(result[:detections]).to be_an(Array)
118
+ expect(result[:detections].first[:type]).to eq(:email)
119
+ end
120
+ end
121
+
122
+ describe '#restore_text' do
123
+ it 'restores text from a mapping' do
124
+ mapping = { '[SSN_1]' => '123-45-6789' }
125
+ result = client.restore_text(text: 'SSN: [SSN_1]', mapping: mapping)
126
+ expect(result[:restored]).to eq('SSN: 123-45-6789')
127
+ expect(result[:success]).to be true
128
+ end
129
+
130
+ it 'returns error when no mapping provided' do
131
+ result = client.restore_text(text: 'SSN: [SSN_1]')
132
+ expect(result[:success]).to be false
133
+ expect(result[:error]).to eq(:no_mapping)
134
+ end
135
+ end
88
136
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-privatecore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -107,6 +107,20 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: 1.3.9
110
+ - !ruby/object:Gem::Dependency
111
+ name: faraday
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
110
124
  description: Privacy boundary enforcement and cryptographic erasure for brain-modeled
111
125
  agentic AI
112
126
  email:
@@ -122,6 +136,9 @@ files:
122
136
  - lib/legion/extensions/privatecore/client.rb
123
137
  - lib/legion/extensions/privatecore/helpers/boundary.rb
124
138
  - lib/legion/extensions/privatecore/helpers/erasure.rb
139
+ - lib/legion/extensions/privatecore/helpers/ner_client.rb
140
+ - lib/legion/extensions/privatecore/helpers/patterns.rb
141
+ - lib/legion/extensions/privatecore/helpers/redactor.rb
125
142
  - lib/legion/extensions/privatecore/helpers/similarity.rb
126
143
  - lib/legion/extensions/privatecore/runners/embedding_guard.rb
127
144
  - lib/legion/extensions/privatecore/runners/privatecore.rb
@@ -130,6 +147,9 @@ files:
130
147
  - spec/legion/extensions/privatecore/client_spec.rb
131
148
  - spec/legion/extensions/privatecore/helpers/boundary_spec.rb
132
149
  - spec/legion/extensions/privatecore/helpers/erasure_spec.rb
150
+ - spec/legion/extensions/privatecore/helpers/ner_client_spec.rb
151
+ - spec/legion/extensions/privatecore/helpers/patterns_spec.rb
152
+ - spec/legion/extensions/privatecore/helpers/redactor_spec.rb
133
153
  - spec/legion/extensions/privatecore/helpers/similarity_spec.rb
134
154
  - spec/legion/extensions/privatecore/runners/embedding_guard_spec.rb
135
155
  - spec/legion/extensions/privatecore/runners/privatecore_event_spec.rb