lex-audit 0.1.0 → 0.1.2

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: 678a76434f7d1af470a6c1302b6079dbe86f4096f5e64fff11ffe3bc80d6ab92
4
- data.tar.gz: e873a54a8b05dee40f5d078f9f5c80e99a332b6479a98ee7a26f4faae6f983d8
3
+ metadata.gz: e06daee7efba7ee9c8f7883c2e59922baaa602e22094f28d94d05335dfa5a42d
4
+ data.tar.gz: 1deb7352830d90b9a2df09a73e514e5bd7149ae6b4122d3156c531db0e47e9a1
5
5
  SHA512:
6
- metadata.gz: e0783cc5cc4634ad85ae9d350d05c2d40f350a180689457f2285ace36ef3f227e5a722ffb2bb5ef5596d99d03b6d015e82c3a99728d80de46ff9d0c8074bfcae
7
- data.tar.gz: f45707d4627e116e12dd303bb26b85abc984a8cb9b342f9c836e89a33d2cd73839e15c2990fa1630ca4956fd01e42c260e892a224ed91b44d80a1c6d586b72c1
6
+ metadata.gz: 51b37ec39294af491b19f7c2ed2049b07586c5290cb37949bf435e23dd323e12a86f28c7e8996aa44c86d22c33ab6d3484b192d1b814feb9813648d1ce992a5b
7
+ data.tar.gz: a638436adb02bcb28da8cfc113d761c32ec157eb4759f61c50feafaa02ee630aff6b92620079d639ab7dc5f4861e7bd5d870f64a58b0dc306ee0ab1d347b8c70
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2] - 2026-03-21
4
+
5
+ ### Added
6
+ - context_snapshot field for working memory state capture in audit entries
7
+ - context_snapshot included in SHA-256 hash chain for tamper evidence
8
+ - Backward-compatible verify with mixed snapshot/non-snapshot records
9
+
10
+ ## [0.1.1] - 2026-03-20
11
+
12
+ ### Added
13
+ - `Runners::ApprovalQueue` with submit, approve, reject, list_pending, and show_approval methods
14
+ - Lazy Sequel model definition to avoid schema introspection at require time
15
+ - Audit event publishing via transport messages when available
16
+
3
17
  ## [0.1.0] - 2026-03-16
4
18
 
5
19
  ### Added
data/CLAUDE.md CHANGED
@@ -61,7 +61,7 @@ Legion::Extensions::Audit
61
61
 
62
62
  ```bash
63
63
  bundle install
64
- bundle exec rspec # 29 examples, 0 failures
64
+ bundle exec rspec # 26 examples, 0 failures
65
65
  bundle exec rubocop # 0 offenses
66
66
  ```
67
67
 
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Audit
6
+ module Runners
7
+ module ApprovalQueue
8
+ extend self
9
+
10
+ def submit(approval_type:, payload:, requester_id:, tenant_id: nil, **)
11
+ define_approval_queue_model
12
+ json_payload = if defined?(Legion::JSON)
13
+ Legion::JSON.dump({ data: payload })
14
+ else
15
+ require 'json'
16
+ ::JSON.dump({ data: payload })
17
+ end
18
+
19
+ record = Legion::Extensions::Audit::Runners::ApprovalQueue::ApprovalQueue.create(
20
+ approval_type: approval_type,
21
+ payload: json_payload,
22
+ requester_id: requester_id,
23
+ status: 'pending',
24
+ tenant_id: tenant_id,
25
+ created_at: Time.now.utc
26
+ )
27
+ publish_event('approval_needed', record)
28
+ { success: true, approval_id: record.id, status: 'pending' }
29
+ end
30
+
31
+ def approve(id:, reviewer_id:, **)
32
+ define_approval_queue_model
33
+ record = Legion::Extensions::Audit::Runners::ApprovalQueue::ApprovalQueue[id]
34
+ return { success: false, reason: :not_found } unless record
35
+ return { success: false, reason: :already_decided } unless record.status == 'pending'
36
+
37
+ record.update(status: 'approved', reviewer_id: reviewer_id, reviewed_at: Time.now.utc)
38
+ publish_event('approval_decided', record)
39
+ { success: true, approval_id: id, status: 'approved' }
40
+ end
41
+
42
+ def reject(id:, reviewer_id:, **)
43
+ define_approval_queue_model
44
+ record = Legion::Extensions::Audit::Runners::ApprovalQueue::ApprovalQueue[id]
45
+ return { success: false, reason: :not_found } unless record
46
+ return { success: false, reason: :already_decided } unless record.status == 'pending'
47
+
48
+ record.update(status: 'rejected', reviewer_id: reviewer_id, reviewed_at: Time.now.utc)
49
+ publish_event('approval_decided', record)
50
+ { success: true, approval_id: id, status: 'rejected' }
51
+ end
52
+
53
+ def list_pending(tenant_id: nil, limit: 50, **)
54
+ define_approval_queue_model
55
+ dataset = Legion::Extensions::Audit::Runners::ApprovalQueue::ApprovalQueue.where(status: 'pending').order(Sequel.desc(:created_at))
56
+ dataset = dataset.where(tenant_id: tenant_id) if tenant_id
57
+ dataset = dataset.limit(limit)
58
+ { success: true, approvals: dataset.all.map(&:values), count: dataset.count }
59
+ end
60
+
61
+ def show_approval(id:, **)
62
+ define_approval_queue_model
63
+ record = Legion::Extensions::Audit::Runners::ApprovalQueue::ApprovalQueue[id]
64
+ return { success: false, reason: :not_found } unless record
65
+
66
+ { success: true, approval: record.values }
67
+ end
68
+
69
+ private
70
+
71
+ def define_approval_queue_model
72
+ return if Legion::Extensions::Audit::Runners::ApprovalQueue.const_defined?(:ApprovalQueue, false)
73
+
74
+ db = Legion::Data::Connection.sequel
75
+ return unless db&.table_exists?(:approval_queue)
76
+
77
+ Legion::Extensions::Audit::Runners::ApprovalQueue.const_set(
78
+ :ApprovalQueue,
79
+ Class.new(Sequel::Model(db[:approval_queue])) do
80
+ set_primary_key :id
81
+ end
82
+ )
83
+ end
84
+
85
+ def publish_event(event_type, record)
86
+ return unless defined?(Legion::Extensions::Audit::Transport::Messages::Audit)
87
+
88
+ Legion::Extensions::Audit::Transport::Messages::Audit.new(
89
+ event_type: event_type,
90
+ principal_id: record.respond_to?(:requester_id) ? record.requester_id : record[:requester_id],
91
+ action: event_type == 'approval_needed' ? 'submit' : record.status,
92
+ resource: "approval_queue:#{record.id}",
93
+ detail: { approval_type: record.approval_type, approval_id: record.id }
94
+ ).publish
95
+ rescue StandardError => e
96
+ Legion::Logging.warn "[audit] failed to publish #{event_type}: #{e.message}" if defined?(Legion::Logging)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -16,25 +16,29 @@ module Legion
16
16
  prev_hash = prev ? prev.record_hash : GENESIS_HASH
17
17
 
18
18
  created_at = opts[:created_at] ? Time.parse(opts[:created_at].to_s) : Time.now.utc
19
+ snapshot_json = opts[:context_snapshot] ? Legion::JSON.dump(opts[:context_snapshot]) : nil
20
+
19
21
  content = "#{prev_hash}|#{event_type}|#{principal_id}|#{action}|#{resource}|#{created_at.utc.iso8601}"
22
+ content = "#{content}|#{snapshot_json}" if snapshot_json
20
23
  record_hash = Digest::SHA256.hexdigest(content)
21
24
 
22
25
  detail_json = opts[:detail] ? Legion::JSON.dump(opts[:detail]) : nil
23
26
 
24
27
  record = Legion::Data::Model::AuditLog.create(
25
- event_type: event_type,
26
- principal_id: principal_id,
27
- principal_type: opts[:principal_type] || 'system',
28
- action: action,
29
- resource: resource,
30
- source: opts[:source] || 'unknown',
31
- node: opts[:node] || 'unknown',
32
- status: opts[:status] || 'success',
33
- duration_ms: opts[:duration_ms],
34
- detail: detail_json,
35
- record_hash: record_hash,
36
- prev_hash: prev_hash,
37
- created_at: created_at
28
+ event_type: event_type,
29
+ principal_id: principal_id,
30
+ principal_type: opts[:principal_type] || 'system',
31
+ action: action,
32
+ resource: resource,
33
+ source: opts[:source] || 'unknown',
34
+ node: opts[:node] || 'unknown',
35
+ status: opts[:status] || 'success',
36
+ duration_ms: opts[:duration_ms],
37
+ detail: detail_json,
38
+ context_snapshot: snapshot_json,
39
+ record_hash: record_hash,
40
+ prev_hash: prev_hash,
41
+ created_at: created_at
38
42
  )
39
43
 
40
44
  { success: true, audit_id: record.id, record_hash: record_hash }
@@ -50,6 +54,7 @@ module Legion
50
54
 
51
55
  dataset.each do |record|
52
56
  content = "#{prev_hash}|#{record.event_type}|#{record.principal_id}|#{record.action}|#{record.resource}|#{record.created_at.utc.iso8601}"
57
+ content = "#{content}|#{record.context_snapshot}" if record.respond_to?(:context_snapshot) && record.context_snapshot
53
58
  expected = Digest::SHA256.hexdigest(content)
54
59
  unless record.record_hash == expected
55
60
  broken_at = record.id
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Audit
6
- VERSION = '0.1.0'
6
+ VERSION = '0.1.2'
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/audit/version'
4
+ require 'legion/extensions/audit/runners/approval_queue'
4
5
 
5
6
  module Legion
6
7
  module Extensions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -102,16 +102,17 @@ extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
104
  - ".github/workflows/ci.yml"
105
+ - ".gitignore"
105
106
  - ".rspec"
106
107
  - ".rubocop.yml"
107
108
  - CHANGELOG.md
108
109
  - CLAUDE.md
109
110
  - Gemfile
110
- - Gemfile.lock
111
111
  - README.md
112
112
  - lex-audit.gemspec
113
113
  - lib/legion/extensions/audit.rb
114
114
  - lib/legion/extensions/audit/actors/audit_writer.rb
115
+ - lib/legion/extensions/audit/runners/approval_queue.rb
115
116
  - lib/legion/extensions/audit/runners/audit.rb
116
117
  - lib/legion/extensions/audit/transport/exchanges/audit.rb
117
118
  - lib/legion/extensions/audit/transport/messages/audit.rb
data/Gemfile.lock DELETED
@@ -1,86 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- lex-audit (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- addressable (2.8.9)
10
- public_suffix (>= 2.0.2, < 8.0)
11
- ast (2.4.3)
12
- bigdecimal (4.0.1)
13
- diff-lcs (1.6.2)
14
- json (2.19.1)
15
- json-schema (6.2.0)
16
- addressable (~> 2.8)
17
- bigdecimal (>= 3.1, < 5)
18
- language_server-protocol (3.17.0.5)
19
- lint_roller (1.1.0)
20
- mcp (0.8.0)
21
- json-schema (>= 4.1)
22
- parallel (1.27.0)
23
- parser (3.3.10.2)
24
- ast (~> 2.4.1)
25
- racc
26
- prism (1.9.0)
27
- public_suffix (7.0.5)
28
- racc (1.8.1)
29
- rainbow (3.1.1)
30
- rake (13.3.1)
31
- regexp_parser (2.11.3)
32
- rspec (3.13.2)
33
- rspec-core (~> 3.13.0)
34
- rspec-expectations (~> 3.13.0)
35
- rspec-mocks (~> 3.13.0)
36
- rspec-core (3.13.6)
37
- rspec-support (~> 3.13.0)
38
- rspec-expectations (3.13.5)
39
- diff-lcs (>= 1.2.0, < 2.0)
40
- rspec-support (~> 3.13.0)
41
- rspec-mocks (3.13.8)
42
- diff-lcs (>= 1.2.0, < 2.0)
43
- rspec-support (~> 3.13.0)
44
- rspec-support (3.13.7)
45
- rubocop (1.85.1)
46
- json (~> 2.3)
47
- language_server-protocol (~> 3.17.0.2)
48
- lint_roller (~> 1.1.0)
49
- mcp (~> 0.6)
50
- parallel (~> 1.10)
51
- parser (>= 3.3.0.2)
52
- rainbow (>= 2.2.2, < 4.0)
53
- regexp_parser (>= 2.9.3, < 3.0)
54
- rubocop-ast (>= 1.49.0, < 2.0)
55
- ruby-progressbar (~> 1.7)
56
- unicode-display_width (>= 2.4.0, < 4.0)
57
- rubocop-ast (1.49.1)
58
- parser (>= 3.3.7.2)
59
- prism (~> 1.7)
60
- rubocop-rspec (3.9.0)
61
- lint_roller (~> 1.1)
62
- rubocop (~> 1.81)
63
- ruby-progressbar (1.13.0)
64
- sequel (5.102.0)
65
- bigdecimal
66
- sqlite3 (2.9.2-arm64-darwin)
67
- sqlite3 (2.9.2-x86_64-linux-gnu)
68
- unicode-display_width (3.2.0)
69
- unicode-emoji (~> 4.1)
70
- unicode-emoji (4.2.0)
71
-
72
- PLATFORMS
73
- arm64-darwin-25
74
- x86_64-linux
75
-
76
- DEPENDENCIES
77
- lex-audit!
78
- rake
79
- rspec
80
- rubocop
81
- rubocop-rspec
82
- sequel
83
- sqlite3
84
-
85
- BUNDLED WITH
86
- 2.6.9