robot_lab-audit 0.2.7 → 0.2.8
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/.envrc +3 -0
- data/CHANGELOG.md +4 -0
- data/Rakefile +0 -96
- data/lib/robot_lab/audit/event_log.rb +11 -6
- data/lib/robot_lab/audit/hook.rb +9 -0
- data/lib/robot_lab/audit/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef178bcab0fa1ddd03a137fb4c2bb97032d66e3e78120a171970c87bcc5ca6af
|
|
4
|
+
data.tar.gz: 0eeb18834c2ef6a1355e528308ef31805d86c48e1557359b75d1372d8ef5e52f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77e6579549b5b4d05cc34162cdae304cd1fa1c38ce6d5257cb3c4344f62bef4fa6adaaa6bd2afddbecaf6dd7b953048c44229e8b3841ff6bb601c7c3fbb11097
|
|
7
|
+
data.tar.gz: 0d1b28b9d41dc6d312f2777752664b91f5f197d765c26bf17d3ca84a42f09cb25e049e4ddad141ced0f6ae2d18e9c687d5e4160a0945ad08f9f83ead419520f2
|
data/.envrc
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.8] - 2026-09-09
|
|
11
|
+
|
|
12
|
+
Released in lockstep with `robot_lab` core v0.2.8: this gem now resolves the released core gem from RubyGems instead of the local sibling checkout (local-path development remains available via `BUNDLE_GEMFILE=Gemfile.local`). Also in this release: gem lifecycle tasks (build/install/release) moved from the Rakefile to the asgard task runner.
|
|
13
|
+
|
|
10
14
|
### Added
|
|
11
15
|
- `.loki` Asgard task file: `test`, `rubocop`, `rubocop_fix`, `flog`, `flay`, `quality`, `build`, `install`, `release`, and `console` tasks via the Asgard task runner
|
|
12
16
|
- `flay_check` Rake task: structural code duplication gate (mass threshold 50); integrated into the `quality` Rake task
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'bundler/gem_tasks'
|
|
4
3
|
require 'rake/testtask'
|
|
5
4
|
|
|
6
5
|
Rake::TestTask.new(:test) do |t|
|
|
@@ -24,101 +23,6 @@ task :test_file, [:file] do |_t, args|
|
|
|
24
23
|
ruby "test/#{args[:file]}"
|
|
25
24
|
end
|
|
26
25
|
|
|
27
|
-
desc 'Check code complexity with Flog (warn >=20, fail >=50)'
|
|
28
|
-
task :flog_check do
|
|
29
|
-
require 'flog'
|
|
30
|
-
|
|
31
|
-
method_warn = 20.0
|
|
32
|
-
method_fail = 50.0
|
|
33
|
-
|
|
34
|
-
flogger = Flog.new(all: true)
|
|
35
|
-
flogger.flog(*Dir.glob('lib/**/*.rb'))
|
|
36
|
-
|
|
37
|
-
warnings = []
|
|
38
|
-
failures = []
|
|
39
|
-
|
|
40
|
-
flogger.each_by_score do |method, score|
|
|
41
|
-
next if method.end_with?('#none')
|
|
42
|
-
|
|
43
|
-
if score > method_fail
|
|
44
|
-
failures << "#{format('%.1f', score)}: #{method}"
|
|
45
|
-
elsif score > method_warn
|
|
46
|
-
warnings << "#{format('%.1f', score)}: #{method}"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
unless warnings.empty?
|
|
51
|
-
puts "\nFlog warnings (#{method_warn}–#{method_fail}) — target for future refactoring:"
|
|
52
|
-
warnings.each { |v| puts " #{v}" }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
if failures.empty?
|
|
56
|
-
puts "\nFlog: no methods exceed the failure threshold (>=#{method_fail})"
|
|
57
|
-
else
|
|
58
|
-
puts "\nFlog failures (>=#{method_fail}) — must be refactored:"
|
|
59
|
-
failures.each { |v| puts " #{v}" }
|
|
60
|
-
abort "\nFlog quality gate failed: #{failures.size} method(s) exceed #{method_fail}"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
desc 'Check for structural code duplication with Flay (mass >= 50)'
|
|
65
|
-
task :flay_check do
|
|
66
|
-
require 'flay'
|
|
67
|
-
|
|
68
|
-
mass_threshold = 50
|
|
69
|
-
|
|
70
|
-
flay = Flay.new({ mass: mass_threshold, diff: false, verbose: false, summary: false, timeout: 60 })
|
|
71
|
-
flay.process(*Dir.glob('lib/**/*.rb'))
|
|
72
|
-
flay.analyze
|
|
73
|
-
|
|
74
|
-
if flay.hashes.empty?
|
|
75
|
-
puts "\nFlay: no structural duplication detected (mass >= #{mass_threshold})"
|
|
76
|
-
else
|
|
77
|
-
puts "\nFlay found structural duplication (mass >= #{mass_threshold}):"
|
|
78
|
-
flay.report
|
|
79
|
-
abort "\nFlay quality gate failed: #{flay.hashes.length} pattern(s) detected"
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
desc 'Run all quality checks: tests (with coverage), RuboCop, Flog, and Flay'
|
|
84
|
-
task :quality do
|
|
85
|
-
gates = [
|
|
86
|
-
['Tests + Coverage', 'bundle exec rake test'],
|
|
87
|
-
['RuboCop', 'bundle exec rubocop'],
|
|
88
|
-
['Flog Complexity', 'bundle exec rake flog_check'],
|
|
89
|
-
['Flay Duplication', 'bundle exec rake flay_check']
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
results = gates.map do |label, command|
|
|
93
|
-
puts "\n#{'=' * 60}"
|
|
94
|
-
puts "Quality Gate: #{label}"
|
|
95
|
-
puts '=' * 60
|
|
96
|
-
[label, system(command) ? :pass : :fail]
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
green = ->(s) { "\e[32m#{s}\e[0m" }
|
|
100
|
-
red = ->(s) { "\e[31m#{s}\e[0m" }
|
|
101
|
-
width = results.map { |label, _| label.length }.max
|
|
102
|
-
|
|
103
|
-
puts "\n#{'=' * 60}"
|
|
104
|
-
puts 'Quality Gate Summary'
|
|
105
|
-
puts '=' * 60
|
|
106
|
-
results.each do |label, status|
|
|
107
|
-
badge = status == :pass ? green.call('PASS') : red.call('FAIL')
|
|
108
|
-
puts " [#{badge}] #{label.ljust(width)}"
|
|
109
|
-
end
|
|
110
|
-
puts '-' * 60
|
|
111
|
-
|
|
112
|
-
passed = results.count { |_, s| s == :pass }
|
|
113
|
-
failed = results.count { |_, s| s == :fail }
|
|
114
|
-
tally = "#{passed} passed, #{failed} failed"
|
|
115
|
-
puts " #{failed.zero? ? green.call(tally) : red.call(tally)}"
|
|
116
|
-
puts '=' * 60
|
|
117
|
-
|
|
118
|
-
abort "\n#{red.call('Quality gate failed.')}" unless failed.zero?
|
|
119
|
-
puts "\n#{green.call('All quality gates passed.')}"
|
|
120
|
-
end
|
|
121
|
-
|
|
122
26
|
namespace :docs do
|
|
123
27
|
desc 'Build MkDocs documentation'
|
|
124
28
|
task :build do
|
|
@@ -44,6 +44,9 @@ module RobotLab
|
|
|
44
44
|
@db.execute_batch(SCHEMA)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# :reek:LongParameterList -- one required keyword per SCHEMA column, on
|
|
48
|
+
# purpose: a caller that forgets a column raises ArgumentError instead
|
|
49
|
+
# of silently inserting NULL.
|
|
47
50
|
def record_network_run(run_id:, network_name:, input:, result:, error_class:,
|
|
48
51
|
error_message:, started_at:, finished_at:, duration_ms:)
|
|
49
52
|
@db.execute(
|
|
@@ -56,6 +59,9 @@ module RobotLab
|
|
|
56
59
|
)
|
|
57
60
|
end
|
|
58
61
|
|
|
62
|
+
# :reek:LongParameterList -- one required keyword per SCHEMA column, on
|
|
63
|
+
# purpose: a caller that forgets a column raises ArgumentError instead
|
|
64
|
+
# of silently inserting NULL.
|
|
59
65
|
def record_event(run_id:, event_type:, robot_name:, tool_name:, input:, output:,
|
|
60
66
|
error_class:, error_message:, started_at:, finished_at:, duration_ms:)
|
|
61
67
|
@db.execute(
|
|
@@ -70,12 +76,12 @@ module RobotLab
|
|
|
70
76
|
|
|
71
77
|
def network_runs(limit: 100)
|
|
72
78
|
rows = @db.execute('SELECT * FROM network_runs ORDER BY started_at DESC LIMIT ?', [limit])
|
|
73
|
-
rows.map { |r| row_to_hash(
|
|
79
|
+
rows.map { |r| row_to_hash(NETWORK_RUN_COLUMNS, r) }
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def events_for(run_id)
|
|
77
83
|
rows = @db.execute('SELECT * FROM audit_events WHERE run_id = ? ORDER BY started_at', [run_id])
|
|
78
|
-
rows.map { |r| row_to_hash(
|
|
84
|
+
rows.map { |r| row_to_hash(AUDIT_EVENT_COLUMNS, r) }
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
def recent_errors(limit: 50)
|
|
@@ -83,7 +89,7 @@ module RobotLab
|
|
|
83
89
|
'SELECT * FROM audit_events WHERE error_class IS NOT NULL ORDER BY started_at DESC LIMIT ?',
|
|
84
90
|
[limit]
|
|
85
91
|
)
|
|
86
|
-
rows.map { |r| row_to_hash(
|
|
92
|
+
rows.map { |r| row_to_hash(AUDIT_EVENT_COLUMNS, r) }
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
NETWORK_RUN_COLUMNS = %i[id run_id network_name input result error_class
|
|
@@ -93,9 +99,8 @@ module RobotLab
|
|
|
93
99
|
|
|
94
100
|
private
|
|
95
101
|
|
|
96
|
-
def row_to_hash(
|
|
97
|
-
|
|
98
|
-
cols.zip(row).to_h
|
|
102
|
+
def row_to_hash(columns, row)
|
|
103
|
+
columns.zip(row).to_h
|
|
99
104
|
end
|
|
100
105
|
end
|
|
101
106
|
end
|
data/lib/robot_lab/audit/hook.rb
CHANGED
|
@@ -5,6 +5,12 @@ require 'json'
|
|
|
5
5
|
|
|
6
6
|
module RobotLab
|
|
7
7
|
module Audit
|
|
8
|
+
# :reek:RepeatedConditional -- each callback independently no-ops when
|
|
9
|
+
# event_log is nil, so the hook can be registered globally (RobotLab.on)
|
|
10
|
+
# before Audit.enable ever runs, or scoped to one robot/network without
|
|
11
|
+
# ever calling enable at all. A Null Object would remove the repeated
|
|
12
|
+
# check but couples every caller to a different `Hook.event_log` contract
|
|
13
|
+
# (an object always present vs. nil-means-disabled); not worth it here.
|
|
8
14
|
class Hook < RobotLab::Hook
|
|
9
15
|
self.namespace = :audit
|
|
10
16
|
|
|
@@ -62,6 +68,9 @@ module RobotLab
|
|
|
62
68
|
|
|
63
69
|
private
|
|
64
70
|
|
|
71
|
+
# :reek:LongParameterList -- private helper with exactly two call
|
|
72
|
+
# sites, each passing every argument explicitly; a wrapper object
|
|
73
|
+
# would add indirection without making either call site clearer.
|
|
65
74
|
def record_audit_event(event_type, ctx, started, tool_name, input, output, error)
|
|
66
75
|
event_log.record_event(
|
|
67
76
|
run_id: current_run_id,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: robot_lab-audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
90
|
- !ruby/object:Gem::Version
|
|
91
91
|
version: '0'
|
|
92
92
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
93
|
+
rubygems_version: 4.0.20
|
|
94
94
|
specification_version: 4
|
|
95
95
|
summary: SQLite-backed execution audit log for RobotLab via the Hook system.
|
|
96
96
|
test_files: []
|