tork-governance 0.1.0 → 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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +69 -359
- data/Rakefile +1 -32
- data/lib/tork/version.rb +1 -1
- data/lib/tork_governance/client.rb +119 -0
- data/lib/tork_governance/middleware/grape.rb +240 -0
- data/lib/tork_governance/middleware/rails.rb +163 -0
- data/lib/tork_governance/middleware/sinatra.rb +147 -0
- data/lib/tork_governance/pii.rb +110 -0
- data/lib/tork_governance/receipt.rb +75 -0
- data/lib/tork_governance/version.rb +5 -0
- data/lib/tork_governance.rb +48 -0
- data/test_runner.rb +242 -0
- data/tork_governance.gemspec +43 -0
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 216554d503f29e742fd17648fe812add23b32f509c5af2a3df6620ff278e0c84
|
|
4
|
+
data.tar.gz: 3389e8d1f22910eed0a302963837a152bf541303690938d816ef7257b31be926
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8943faa1f06adf99915b6d3d2080a4890b9b42d399ad3e2464cd3e5a4caf90e58c88b730a59491caefe8672dcacc9fd54ed7afb8a5d38b8c3631a99da72f1942
|
|
7
|
+
data.tar.gz: 284b06a25065b00b0eddb0e581f376d15a097d58adfddeefab49eb2e579cc63593e85d2d991121e13ee89d461f564a48d2d1f147c59a67b75f6e8d55fa221821
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tork Network Pty Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
# Tork
|
|
1
|
+
# Tork Governance Ruby SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
On-device AI governance with PII detection, redaction, and cryptographic receipts for Ruby applications.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/tork-governance)
|
|
6
|
-
[](https://github.com/torkjacobs/tork-ruby-sdk/actions)
|
|
7
6
|
[](https://opensource.org/licenses/MIT)
|
|
8
7
|
|
|
9
8
|
## Installation
|
|
10
9
|
|
|
11
|
-
Add
|
|
10
|
+
Add to your Gemfile:
|
|
12
11
|
|
|
13
12
|
```ruby
|
|
14
13
|
gem 'tork-governance'
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
bundle install
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Or install it yourself as:
|
|
16
|
+
Or install directly:
|
|
24
17
|
|
|
25
18
|
```bash
|
|
26
19
|
gem install tork-governance
|
|
@@ -29,397 +22,114 @@ gem install tork-governance
|
|
|
29
22
|
## Quick Start
|
|
30
23
|
|
|
31
24
|
```ruby
|
|
32
|
-
require '
|
|
25
|
+
require 'tork_governance'
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
Tork.configure do |config|
|
|
36
|
-
config.api_key = 'tork_your_api_key'
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Evaluate content
|
|
40
|
-
result = Tork.evaluate(prompt: "What is the capital of France?")
|
|
41
|
-
puts result['data']['passed'] # => true
|
|
42
|
-
```
|
|
27
|
+
tork = TorkGovernance::Client.new
|
|
43
28
|
|
|
44
|
-
|
|
29
|
+
# Detect and redact PII
|
|
30
|
+
result = tork.govern("My SSN is 123-45-6789 and email is john@example.com")
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Tork.configure do |config|
|
|
50
|
-
config.api_key = 'tork_your_api_key'
|
|
51
|
-
config.base_url = 'https://api.tork.network/v1' # Default
|
|
52
|
-
config.timeout = 30 # Request timeout in seconds
|
|
53
|
-
config.max_retries = 3 # Max retry attempts
|
|
54
|
-
config.retry_base_delay = 0.5 # Base delay for exponential backoff
|
|
55
|
-
config.raise_on_rate_limit = true # Raise exception on rate limit
|
|
56
|
-
config.logger = Logger.new(STDOUT) # Enable logging
|
|
57
|
-
end
|
|
32
|
+
puts result.output # "My SSN is [SSN_REDACTED] and email is [EMAIL_REDACTED]"
|
|
33
|
+
puts result.pii.types # ['ssn', 'email']
|
|
34
|
+
puts result.receipt.id # Cryptographic receipt ID
|
|
58
35
|
```
|
|
59
36
|
|
|
60
|
-
|
|
37
|
+
## Supported Frameworks (2 Adapters)
|
|
61
38
|
|
|
62
|
-
|
|
39
|
+
### Web Frameworks
|
|
40
|
+
- **Rails** - Middleware and controller integration
|
|
41
|
+
- **Grape** - API middleware and helpers
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
export TORK_API_KEY=tork_your_api_key
|
|
66
|
-
```
|
|
43
|
+
## Framework Examples
|
|
67
44
|
|
|
68
|
-
###
|
|
45
|
+
### Rails Middleware
|
|
69
46
|
|
|
70
47
|
```ruby
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
### Content Evaluation
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
client = Tork::Client.new(api_key: 'tork_your_api_key')
|
|
83
|
-
|
|
84
|
-
# Basic evaluation
|
|
85
|
-
result = client.evaluate(prompt: "Hello, how are you?")
|
|
86
|
-
|
|
87
|
-
# Evaluation with response
|
|
88
|
-
result = client.evaluate(
|
|
89
|
-
prompt: "What is 2+2?",
|
|
90
|
-
response: "The answer is 4."
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# Evaluation with specific policy
|
|
94
|
-
result = client.evaluate(
|
|
95
|
-
prompt: "Process this request",
|
|
96
|
-
policy_id: "pol_abc123"
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Evaluation with specific checks
|
|
100
|
-
result = client.evaluations.create(
|
|
101
|
-
prompt: "Contact me at john@example.com",
|
|
102
|
-
checks: ['pii', 'toxicity', 'moderation']
|
|
103
|
-
)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### PII Detection & Redaction
|
|
107
|
-
|
|
108
|
-
```ruby
|
|
109
|
-
# Detect PII
|
|
110
|
-
result = client.evaluations.detect_pii(
|
|
111
|
-
content: "My email is john@example.com and SSN is 123-45-6789"
|
|
112
|
-
)
|
|
113
|
-
# => { "has_pii" => true, "types" => ["email", "ssn"] }
|
|
114
|
-
|
|
115
|
-
# Redact PII
|
|
116
|
-
result = client.evaluations.redact_pii(
|
|
117
|
-
content: "Call me at 555-123-4567",
|
|
118
|
-
replacement: "mask"
|
|
119
|
-
)
|
|
120
|
-
# => { "redacted" => "Call me at ***-***-****" }
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Jailbreak Detection
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
result = client.evaluations.detect_jailbreak(
|
|
127
|
-
prompt: "Ignore previous instructions and..."
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
if result['data']['is_jailbreak']
|
|
131
|
-
puts "Jailbreak attempt detected!"
|
|
132
|
-
puts "Techniques: #{result['data']['techniques']}"
|
|
133
|
-
end
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Policy Management
|
|
137
|
-
|
|
138
|
-
```ruby
|
|
139
|
-
policies = client.policies
|
|
140
|
-
|
|
141
|
-
# List all policies
|
|
142
|
-
all_policies = policies.list(page: 1, per_page: 20)
|
|
143
|
-
|
|
144
|
-
# Get a specific policy
|
|
145
|
-
policy = policies.get('pol_abc123')
|
|
146
|
-
|
|
147
|
-
# Create a new policy
|
|
148
|
-
new_policy = policies.create(
|
|
149
|
-
name: "Content Safety Policy",
|
|
150
|
-
description: "Block harmful content",
|
|
151
|
-
rules: [
|
|
152
|
-
{
|
|
153
|
-
type: "block",
|
|
154
|
-
condition: "toxicity > 0.8",
|
|
155
|
-
action: "reject",
|
|
156
|
-
message: "Content flagged as toxic"
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
type: "redact",
|
|
160
|
-
condition: "pii.detected",
|
|
161
|
-
action: "mask"
|
|
162
|
-
}
|
|
163
|
-
],
|
|
164
|
-
enabled: true
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
# Update a policy
|
|
168
|
-
policies.update('pol_abc123', name: "Updated Policy Name")
|
|
169
|
-
|
|
170
|
-
# Enable/Disable a policy
|
|
171
|
-
policies.enable('pol_abc123')
|
|
172
|
-
policies.disable('pol_abc123')
|
|
173
|
-
|
|
174
|
-
# Delete a policy
|
|
175
|
-
policies.delete('pol_abc123')
|
|
176
|
-
|
|
177
|
-
# Test a policy
|
|
178
|
-
test_result = policies.test('pol_abc123',
|
|
179
|
-
content: "Test content here",
|
|
180
|
-
context: { user_role: "admin" }
|
|
181
|
-
)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Batch Evaluation
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
items = [
|
|
188
|
-
{ prompt: "First prompt" },
|
|
189
|
-
{ prompt: "Second prompt", response: "Second response" },
|
|
190
|
-
{ prompt: "Third prompt" }
|
|
191
|
-
]
|
|
192
|
-
|
|
193
|
-
results = client.evaluations.batch(items, policy_id: 'pol_abc123')
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### RAG Validation
|
|
197
|
-
|
|
198
|
-
```ruby
|
|
199
|
-
chunks = [
|
|
200
|
-
{ content: "Document chunk 1", source: "doc1.pdf", page: 1 },
|
|
201
|
-
{ content: "Document chunk 2", source: "doc2.pdf", page: 3 }
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
result = client.evaluations.validate_rag(
|
|
205
|
-
chunks: chunks,
|
|
206
|
-
query: "What is the company policy?"
|
|
207
|
-
)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Metrics & Analytics
|
|
211
|
-
|
|
212
|
-
```ruby
|
|
213
|
-
metrics = client.metrics
|
|
214
|
-
|
|
215
|
-
# Get Torking X score for an evaluation
|
|
216
|
-
score = metrics.torking_x(evaluation_id: 'eval_abc123')
|
|
217
|
-
|
|
218
|
-
# Get usage statistics
|
|
219
|
-
usage = metrics.usage(period: 'month')
|
|
220
|
-
|
|
221
|
-
# Get policy performance
|
|
222
|
-
performance = metrics.policy_performance(policy_id: 'pol_abc123')
|
|
223
|
-
|
|
224
|
-
# Get violation statistics
|
|
225
|
-
violations = metrics.violations(period: 'week', group_by: 'type')
|
|
226
|
-
|
|
227
|
-
# Get dashboard summary
|
|
228
|
-
dashboard = metrics.dashboard
|
|
229
|
-
|
|
230
|
-
# Get latency metrics
|
|
231
|
-
latency = metrics.latency(period: 'day', percentiles: [50, 95, 99])
|
|
232
|
-
|
|
233
|
-
# Export metrics
|
|
234
|
-
export = metrics.export(
|
|
235
|
-
type: 'usage',
|
|
236
|
-
start_date: '2024-01-01',
|
|
237
|
-
end_date: '2024-01-31',
|
|
238
|
-
format: 'csv'
|
|
239
|
-
)
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Error Handling
|
|
243
|
-
|
|
244
|
-
```ruby
|
|
245
|
-
begin
|
|
246
|
-
result = client.evaluate(prompt: "Test content")
|
|
247
|
-
rescue Tork::AuthenticationError => e
|
|
248
|
-
puts "Invalid API key: #{e.message}"
|
|
249
|
-
rescue Tork::RateLimitError => e
|
|
250
|
-
puts "Rate limited. Retry after #{e.retry_after} seconds"
|
|
251
|
-
rescue Tork::ValidationError => e
|
|
252
|
-
puts "Validation failed: #{e.message}"
|
|
253
|
-
puts "Details: #{e.details}"
|
|
254
|
-
rescue Tork::PolicyViolationError => e
|
|
255
|
-
puts "Policy violation: #{e.message}"
|
|
256
|
-
puts "Violations: #{e.violations}"
|
|
257
|
-
rescue Tork::NotFoundError => e
|
|
258
|
-
puts "Resource not found: #{e.message}"
|
|
259
|
-
rescue Tork::ServerError => e
|
|
260
|
-
puts "Server error: #{e.message}"
|
|
261
|
-
rescue Tork::TimeoutError => e
|
|
262
|
-
puts "Request timed out"
|
|
263
|
-
rescue Tork::ConnectionError => e
|
|
264
|
-
puts "Connection failed"
|
|
265
|
-
rescue Tork::Error => e
|
|
266
|
-
puts "Tork error: #{e.message}"
|
|
267
|
-
end
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## Rails Integration
|
|
271
|
-
|
|
272
|
-
### Initializer
|
|
273
|
-
|
|
274
|
-
Create `config/initializers/tork.rb`:
|
|
275
|
-
|
|
276
|
-
```ruby
|
|
277
|
-
Tork.configure do |config|
|
|
278
|
-
config.api_key = Rails.application.credentials.tork_api_key
|
|
279
|
-
config.logger = Rails.logger
|
|
280
|
-
config.timeout = 30
|
|
48
|
+
# config/application.rb
|
|
49
|
+
module MyApp
|
|
50
|
+
class Application < Rails::Application
|
|
51
|
+
config.middleware.use TorkGovernance::Middleware::Rails,
|
|
52
|
+
protected_paths: ['/api/'],
|
|
53
|
+
skip_paths: ['/api/health']
|
|
54
|
+
end
|
|
281
55
|
end
|
|
282
56
|
```
|
|
283
57
|
|
|
284
|
-
### Controller Example
|
|
285
|
-
|
|
286
58
|
```ruby
|
|
287
|
-
|
|
59
|
+
# In controllers
|
|
60
|
+
class ChatController < ApplicationController
|
|
288
61
|
def create
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
policy_id: current_user.organization.policy_id
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
if result['data']['passed']
|
|
295
|
-
@message = Message.create!(content: params[:content])
|
|
296
|
-
render json: @message
|
|
297
|
-
else
|
|
298
|
-
render json: {
|
|
299
|
-
error: 'Content blocked',
|
|
300
|
-
violations: result['data']['violations']
|
|
301
|
-
}, status: :unprocessable_entity
|
|
302
|
-
end
|
|
303
|
-
rescue Tork::RateLimitError => e
|
|
304
|
-
render json: { error: 'Rate limited' }, status: :too_many_requests
|
|
62
|
+
tork_result = request.env['tork.result']
|
|
63
|
+
render json: { status: 'ok', receipt_id: tork_result&.receipt&.id }
|
|
305
64
|
end
|
|
306
65
|
end
|
|
307
66
|
```
|
|
308
67
|
|
|
309
|
-
###
|
|
68
|
+
### Grape API Middleware
|
|
310
69
|
|
|
311
70
|
```ruby
|
|
312
|
-
|
|
313
|
-
queue_as :default
|
|
71
|
+
require 'tork_governance/middleware/grape'
|
|
314
72
|
|
|
315
|
-
|
|
316
|
-
|
|
73
|
+
class API < Grape::API
|
|
74
|
+
use TorkGovernance::Middleware::Grape,
|
|
75
|
+
protected_paths: ['/api/'],
|
|
76
|
+
skip_paths: ['/api/health']
|
|
317
77
|
|
|
318
|
-
|
|
319
|
-
prompt: message.content,
|
|
320
|
-
checks: ['toxicity', 'pii']
|
|
321
|
-
)
|
|
78
|
+
helpers TorkGovernance::Middleware::GrapeHelpers
|
|
322
79
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
moderation_result: result['data']
|
|
327
|
-
)
|
|
80
|
+
post '/chat' do
|
|
81
|
+
result = tork_result
|
|
82
|
+
receipt_id = tork_receipt_id
|
|
328
83
|
|
|
329
|
-
|
|
84
|
+
if tork_blocked?
|
|
85
|
+
error!({ error: 'Content blocked' }, 403)
|
|
330
86
|
end
|
|
87
|
+
|
|
88
|
+
{ status: 'ok', receipt_id: receipt_id }
|
|
331
89
|
end
|
|
332
90
|
end
|
|
333
91
|
```
|
|
334
92
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
Enable detailed logging for debugging:
|
|
93
|
+
### Grape Helper Methods
|
|
338
94
|
|
|
339
95
|
```ruby
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
Tork.configure do |config|
|
|
343
|
-
config.api_key = 'tork_your_api_key'
|
|
344
|
-
config.logger = Logger.new(STDOUT)
|
|
345
|
-
end
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## Retry Behavior
|
|
96
|
+
helpers TorkGovernance::Middleware::GrapeHelpers
|
|
349
97
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Customize retry behavior:
|
|
359
|
-
|
|
360
|
-
```ruby
|
|
361
|
-
Tork.configure do |config|
|
|
362
|
-
config.max_retries = 5
|
|
363
|
-
config.retry_base_delay = 1.0
|
|
364
|
-
config.retry_max_delay = 60.0
|
|
365
|
-
end
|
|
98
|
+
# Available helpers:
|
|
99
|
+
tork_result # Get full governance result
|
|
100
|
+
tork_receipt_id # Get receipt ID
|
|
101
|
+
tork_redacted_content # Get redacted content
|
|
102
|
+
tork_blocked? # Check if request was blocked
|
|
103
|
+
tork_redacted? # Check if content was redacted
|
|
104
|
+
require_tork_governance! # Raises 403 if blocked
|
|
366
105
|
```
|
|
367
106
|
|
|
368
|
-
##
|
|
369
|
-
|
|
370
|
-
The SDK is thread-safe. Each `Tork::Client` instance maintains its own connection pool.
|
|
107
|
+
## Configuration
|
|
371
108
|
|
|
372
109
|
```ruby
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
client.evaluate(prompt: "Thread #{i} content")
|
|
379
|
-
end
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
threads.each(&:join)
|
|
110
|
+
TorkGovernance.configure(
|
|
111
|
+
api_key: ENV['TORK_API_KEY'],
|
|
112
|
+
policy_version: '1.0.0',
|
|
113
|
+
default_action: :redact
|
|
114
|
+
)
|
|
383
115
|
```
|
|
384
116
|
|
|
385
|
-
##
|
|
386
|
-
|
|
387
|
-
After checking out the repo:
|
|
388
|
-
|
|
389
|
-
```bash
|
|
390
|
-
# Install dependencies
|
|
391
|
-
bundle install
|
|
392
|
-
|
|
393
|
-
# Run tests
|
|
394
|
-
bundle exec rspec
|
|
117
|
+
## PII Detection
|
|
395
118
|
|
|
396
|
-
|
|
397
|
-
bundle exec rubocop
|
|
119
|
+
Detects 50+ PII types including:
|
|
398
120
|
|
|
399
|
-
|
|
400
|
-
|
|
121
|
+
| Category | Types |
|
|
122
|
+
|----------|-------|
|
|
123
|
+
| **US** | SSN, EIN, ITIN, Passport, Driver's License |
|
|
124
|
+
| **Australia** | TFN, ABN, ACN, Medicare |
|
|
125
|
+
| **Financial** | Credit Card, Bank Account, SWIFT/BIC |
|
|
126
|
+
| **Universal** | Email, IP Address, URL, Phone, DOB |
|
|
401
127
|
|
|
402
|
-
|
|
403
|
-
bundle exec rake build
|
|
404
|
-
|
|
405
|
-
# Install locally
|
|
406
|
-
bundle exec rake install
|
|
407
|
-
```
|
|
128
|
+
## Documentation
|
|
408
129
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
1. Fork the repository
|
|
412
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
413
|
-
3. Commit your changes (`git commit -am 'Add amazing feature'`)
|
|
414
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
415
|
-
5. Open a Pull Request
|
|
130
|
+
- [Full Documentation](https://docs.tork.network)
|
|
131
|
+
- [API Reference](https://docs.tork.network/api/ruby)
|
|
416
132
|
|
|
417
133
|
## License
|
|
418
134
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
## Support
|
|
422
|
-
|
|
423
|
-
- **Documentation**: [docs.tork.network](https://docs.tork.network)
|
|
424
|
-
- **Email**: support@tork.network
|
|
425
|
-
- **Issues**: [GitHub Issues](https://github.com/torkjacobs/tork-ruby-sdk/issues)
|
|
135
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
data/Rakefile
CHANGED
|
@@ -1,38 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/gem_tasks"
|
|
4
3
|
require "rspec/core/rake_task"
|
|
5
|
-
require "rubocop/rake_task"
|
|
6
4
|
|
|
7
5
|
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
-
RuboCop::RakeTask.new
|
|
9
6
|
|
|
10
|
-
task default:
|
|
11
|
-
|
|
12
|
-
desc "Run tests with coverage"
|
|
13
|
-
task :coverage do
|
|
14
|
-
ENV["COVERAGE"] = "true"
|
|
15
|
-
Rake::Task[:spec].invoke
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
desc "Generate documentation"
|
|
19
|
-
task :doc do
|
|
20
|
-
sh "yard doc --output-dir doc lib/**/*.rb"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
desc "Open documentation in browser"
|
|
24
|
-
task :doc_server do
|
|
25
|
-
sh "yard server --reload"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
desc "Build and install gem locally"
|
|
29
|
-
task :local_install do
|
|
30
|
-
sh "gem build tork-governance.gemspec"
|
|
31
|
-
sh "gem install tork-governance-#{Tork::VERSION}.gem"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
desc "Release gem to RubyGems"
|
|
35
|
-
task :publish do
|
|
36
|
-
sh "gem build tork-governance.gemspec"
|
|
37
|
-
sh "gem push tork-governance-#{Tork::VERSION}.gem"
|
|
38
|
-
end
|
|
7
|
+
task default: :spec
|
data/lib/tork/version.rb
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TorkGovernance
|
|
4
|
+
# Governance result
|
|
5
|
+
class GovernResult
|
|
6
|
+
attr_reader :action, :output, :pii, :receipt
|
|
7
|
+
|
|
8
|
+
def initialize(action:, output:, pii:, receipt:)
|
|
9
|
+
@action = action
|
|
10
|
+
@output = output
|
|
11
|
+
@pii = pii
|
|
12
|
+
@receipt = receipt
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def allowed?
|
|
16
|
+
action == ACTIONS[:allow]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def denied?
|
|
20
|
+
action == ACTIONS[:deny]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def redacted?
|
|
24
|
+
action == ACTIONS[:redact]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_h
|
|
28
|
+
{
|
|
29
|
+
action: action,
|
|
30
|
+
output: output,
|
|
31
|
+
pii: {
|
|
32
|
+
has_pii: pii.has_pii?,
|
|
33
|
+
types: pii.types,
|
|
34
|
+
count: pii.count
|
|
35
|
+
},
|
|
36
|
+
receipt: receipt.to_h
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Main Tork governance client
|
|
42
|
+
class Client
|
|
43
|
+
attr_reader :api_key, :policy_version, :default_action, :stats
|
|
44
|
+
|
|
45
|
+
def initialize(api_key: nil, policy_version: "1.0.0", default_action: ACTIONS[:redact])
|
|
46
|
+
@api_key = api_key
|
|
47
|
+
@policy_version = policy_version
|
|
48
|
+
@default_action = default_action
|
|
49
|
+
@stats = {
|
|
50
|
+
total_calls: 0,
|
|
51
|
+
total_pii_detected: 0,
|
|
52
|
+
total_processing_ns: 0,
|
|
53
|
+
action_counts: Hash.new(0)
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Apply governance to content
|
|
58
|
+
#
|
|
59
|
+
# @param input [String] the content to govern
|
|
60
|
+
# @return [GovernResult] the governance result
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# client = TorkGovernance::Client.new
|
|
64
|
+
# result = client.govern("My email is test@example.com")
|
|
65
|
+
# puts result.output # "My email is [EMAIL_REDACTED]"
|
|
66
|
+
# puts result.receipt.id # "rcpt_..."
|
|
67
|
+
def govern(input)
|
|
68
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
69
|
+
|
|
70
|
+
# Detect PII
|
|
71
|
+
pii = PIIDetector.detect(input)
|
|
72
|
+
|
|
73
|
+
# Determine action and output
|
|
74
|
+
if pii.has_pii?
|
|
75
|
+
action = default_action
|
|
76
|
+
output = action == ACTIONS[:redact] ? pii.redacted_text : input
|
|
77
|
+
else
|
|
78
|
+
action = ACTIONS[:allow]
|
|
79
|
+
output = input
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
processing_time_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
|
|
83
|
+
|
|
84
|
+
# Generate receipt
|
|
85
|
+
receipt = Receipt.generate(
|
|
86
|
+
input: input,
|
|
87
|
+
output: output,
|
|
88
|
+
action: action,
|
|
89
|
+
pii_types: pii.types,
|
|
90
|
+
pii_count: pii.count,
|
|
91
|
+
policy_version: policy_version,
|
|
92
|
+
processing_time_ns: processing_time_ns
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Update stats
|
|
96
|
+
@stats[:total_calls] += 1
|
|
97
|
+
@stats[:total_pii_detected] += 1 if pii.has_pii?
|
|
98
|
+
@stats[:total_processing_ns] += processing_time_ns
|
|
99
|
+
@stats[:action_counts][action] += 1
|
|
100
|
+
|
|
101
|
+
GovernResult.new(
|
|
102
|
+
action: action,
|
|
103
|
+
output: output,
|
|
104
|
+
pii: pii,
|
|
105
|
+
receipt: receipt
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Reset statistics
|
|
110
|
+
def reset_stats
|
|
111
|
+
@stats = {
|
|
112
|
+
total_calls: 0,
|
|
113
|
+
total_pii_detected: 0,
|
|
114
|
+
total_processing_ns: 0,
|
|
115
|
+
action_counts: Hash.new(0)
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|