reeve 0.0.1 → 0.1.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/CHANGELOG.md +112 -0
- data/README.md +191 -17
- data/lib/generators/reeve/install/install_generator.rb +46 -0
- data/lib/generators/reeve/install/templates/create_audit_entries.rb.tt +69 -0
- data/lib/generators/reeve/install/templates/initializer.rb.tt +65 -0
- data/lib/reeve/audit/entry.rb +48 -0
- data/lib/reeve/audit/query.rb +84 -0
- data/lib/reeve/audit/recorder.rb +163 -0
- data/lib/reeve/audit/redactor.rb +67 -0
- data/lib/reeve/audit.rb +88 -0
- data/lib/reeve/authorization/adapter.rb +71 -0
- data/lib/reeve/authorization/adapters/plain.rb +65 -0
- data/lib/reeve/authorization/adapters/pundit.rb +84 -0
- data/lib/reeve/authorization/authorizer.rb +23 -0
- data/lib/reeve/authorization/current.rb +66 -0
- data/lib/reeve/authorization/declaration.rb +73 -0
- data/lib/reeve/authorization/guard.rb +97 -0
- data/lib/reeve/authorization/registry.rb +118 -0
- data/lib/reeve/authorization/scoper.rb +399 -0
- data/lib/reeve/authorization.rb +76 -0
- data/lib/reeve/configuration.rb +158 -0
- data/lib/reeve/context.rb +104 -0
- data/lib/reeve/decision.rb +111 -0
- data/lib/reeve/errors.rb +89 -0
- data/lib/reeve/fast_mcp.rb +24 -0
- data/lib/reeve/integrations/fast_mcp/context_builder.rb +48 -0
- data/lib/reeve/integrations/fast_mcp/tool_extension.rb +55 -0
- data/lib/reeve/invocation.rb +260 -0
- data/lib/reeve/minitest.rb +19 -0
- data/lib/reeve/rspec.rb +18 -0
- data/lib/reeve/scope_result.rb +104 -0
- data/lib/reeve/testing/assertions.rb +76 -0
- data/lib/reeve/testing/checks/audit_coverage.rb +46 -0
- data/lib/reeve/testing/checks/base.rb +155 -0
- data/lib/reeve/testing/checks/contract_version.rb +73 -0
- data/lib/reeve/testing/checks/cross_principal_leak.rb +132 -0
- data/lib/reeve/testing/checks/guard_declared.rb +33 -0
- data/lib/reeve/testing/checks/principal_required.rb +60 -0
- data/lib/reeve/testing/checks/redaction_holds.rb +142 -0
- data/lib/reeve/testing/checks/rule_present.rb +55 -0
- data/lib/reeve/testing/checks.rb +95 -0
- data/lib/reeve/testing/compliance_assertions.rb +34 -0
- data/lib/reeve/testing/compliance_suite.rb +23 -0
- data/lib/reeve/testing/ledger.rb +82 -0
- data/lib/reeve/testing/matchers/audit_every_call.rb +39 -0
- data/lib/reeve/testing/matchers/base.rb +84 -0
- data/lib/reeve/testing/matchers/deny_access_for.rb +38 -0
- data/lib/reeve/testing/matchers/pass_reeve_check.rb +32 -0
- data/lib/reeve/testing/matchers.rb +33 -0
- data/lib/reeve/testing/report.rb +58 -0
- data/lib/reeve/testing/result.rb +49 -0
- data/lib/reeve/testing.rb +60 -0
- data/lib/reeve/version.rb +1 -1
- data/lib/reeve.rb +9 -3
- metadata +52 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
# The checks' read-only window onto the audit ledger.
|
|
6
|
+
#
|
|
7
|
+
# It exists so that `require "reeve/testing"` stays loadable with no ActiveRecord in
|
|
8
|
+
# the process (FR-020, SC-008): nothing here mentions +Reeve::Audit::Entry+ until a
|
|
9
|
+
# check actually asks a question, and every question has an answer for the case where
|
|
10
|
+
# the ledger is absent.
|
|
11
|
+
#
|
|
12
|
+
# A host with its own non-ActiveRecord recorder can pass any object with these six
|
|
13
|
+
# methods as `ledger:` to any ledger-reading check.
|
|
14
|
+
class Ledger
|
|
15
|
+
def self.default
|
|
16
|
+
new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def available?
|
|
20
|
+
return false if entry_class.nil?
|
|
21
|
+
|
|
22
|
+
entry_class.table_exists?
|
|
23
|
+
rescue StandardError
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Why not, in the words a developer can act on.
|
|
28
|
+
def unavailable_reason
|
|
29
|
+
return nil if available?
|
|
30
|
+
if entry_class.nil?
|
|
31
|
+
return "the audit ledger is not loaded — add `require \"reeve/audit\"` to your " \
|
|
32
|
+
"test helper, or pass a `ledger:` of your own"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
"the #{entry_class.table_name} table does not exist — run `rails g reeve:install` " \
|
|
36
|
+
"and migrate"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A position in the ledger, taken before an invocation so the entries that
|
|
40
|
+
# invocation wrote can be told apart from everything already there.
|
|
41
|
+
def marker
|
|
42
|
+
return nil unless available?
|
|
43
|
+
|
|
44
|
+
entry_class.maximum(:id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def entries_after(marker)
|
|
48
|
+
return [] unless available?
|
|
49
|
+
|
|
50
|
+
scope = entry_class.order(:id)
|
|
51
|
+
marker.nil? ? scope.to_a : scope.where("id > ?", marker).to_a
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def count
|
|
55
|
+
available? ? entry_class.count : 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def contract_version
|
|
59
|
+
return nil unless entry_class.respond_to?(:contract_version)
|
|
60
|
+
|
|
61
|
+
entry_class.contract_version
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def columns
|
|
65
|
+
return [] unless available?
|
|
66
|
+
|
|
67
|
+
entry_class.column_names.map(&:to_s)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def entry_class
|
|
73
|
+
return @entry_class if defined?(@entry_class)
|
|
74
|
+
|
|
75
|
+
@entry_class =
|
|
76
|
+
if Reeve.const_defined?(:Audit, false) && Reeve::Audit.const_defined?(:Entry, false)
|
|
77
|
+
Reeve::Audit::Entry
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
module Matchers
|
|
6
|
+
# FR-017, from RSpec:
|
|
7
|
+
#
|
|
8
|
+
# it { is_expected.to audit_every_call }
|
|
9
|
+
# it { is_expected.to audit_every_call.for_principal(alice).with(query: "AC") }
|
|
10
|
+
#
|
|
11
|
+
# Point it at the host's own call site to prove that *that* goes through the
|
|
12
|
+
# envelope, which is where an audit bypass actually lives:
|
|
13
|
+
#
|
|
14
|
+
# it { is_expected.to audit_every_call.invoked_by(MyServer.method(:dispatch)) }
|
|
15
|
+
class AuditEveryCall < Base
|
|
16
|
+
def self.check
|
|
17
|
+
Checks::AuditCoverage
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def for_principal(principal)
|
|
21
|
+
@principal = principal
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def description
|
|
26
|
+
"audit every call"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def check_for(tool)
|
|
32
|
+
Checks::AuditCoverage.new(
|
|
33
|
+
tool: tool, principal: @principal || default_principal, **options
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
module Matchers
|
|
6
|
+
# What every reeve matcher is: a check, and nothing else.
|
|
7
|
+
#
|
|
8
|
+
# +failure_message+ returns the check's own +message+ verbatim. No matcher composes
|
|
9
|
+
# a sentence, reformats one, or adds context (FR-019) — that is the whole reason the
|
|
10
|
+
# same violation reads identically here, in Minitest, and in a rake task.
|
|
11
|
+
#
|
|
12
|
+
# Written against RSpec's matcher protocol by duck-typing rather than by including
|
|
13
|
+
# +RSpec::Matchers::DSL+, so nothing under lib/reeve/testing needs RSpec loaded in
|
|
14
|
+
# order to be *parsed*; only a suite that uses these needs it present.
|
|
15
|
+
class Base
|
|
16
|
+
attr_reader :result
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@arguments = {}
|
|
20
|
+
@invoke = nil
|
|
21
|
+
@ledger = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The arguments the tool is called with.
|
|
25
|
+
def with(**arguments)
|
|
26
|
+
@arguments = arguments
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# How the tool is called. Point this at a host's own invoker to prove that *it*
|
|
31
|
+
# goes through the envelope.
|
|
32
|
+
def invoked_by(callable)
|
|
33
|
+
@invoke = callable
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reading(ledger)
|
|
38
|
+
@ledger = ledger
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def matches?(subject)
|
|
43
|
+
@result = check_for(subject).call
|
|
44
|
+
@result.passed?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# No `does_not_match?`: RSpec negates `matches?` for us, and a check that had a
|
|
48
|
+
# separate negative path could drift from its positive one.
|
|
49
|
+
def failure_message
|
|
50
|
+
result.message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def failure_message_when_negated
|
|
54
|
+
"expected #{result.check} to fail, but it passed: #{result.message}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def description
|
|
58
|
+
self.class.check.check_name
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def supports_block_expectations?
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
attr_reader :arguments, :invoke, :ledger
|
|
68
|
+
|
|
69
|
+
def check_for(_subject)
|
|
70
|
+
raise NotImplementedError, "#{self.class} must build its check"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def options
|
|
74
|
+
{ arguments: arguments, invoke: invoke, ledger: ledger }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The principal a matcher falls back to when the example did not name one.
|
|
78
|
+
def default_principal
|
|
79
|
+
Testing.compliance_principals.first
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
module Matchers
|
|
6
|
+
# FR-016, from RSpec:
|
|
7
|
+
#
|
|
8
|
+
# it { is_expected.to deny_access_for(stranger).with(query: "AC") }
|
|
9
|
+
#
|
|
10
|
+
# Backed by CrossPrincipalLeak in its :nothing expectation — this principal may
|
|
11
|
+
# receive no record at all. A denial satisfies it; so does an allowed call that
|
|
12
|
+
# returns an empty scope, because from the agent's side those are the same thing,
|
|
13
|
+
# and FR-006 requires that they stay the same thing.
|
|
14
|
+
class DenyAccessFor < Base
|
|
15
|
+
def self.check
|
|
16
|
+
Checks::CrossPrincipalLeak
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(principal)
|
|
20
|
+
super()
|
|
21
|
+
@principal = principal
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def description
|
|
25
|
+
"deny access for #{@principal.inspect}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def check_for(tool)
|
|
31
|
+
Checks::CrossPrincipalLeak.new(
|
|
32
|
+
tool: tool, principals: [@principal], expect: :nothing, **options
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
module Matchers
|
|
6
|
+
# The escape hatch that keeps SC-009 honest: every one of the seven checks is
|
|
7
|
+
# assertable from RSpec, not only the two with names of their own.
|
|
8
|
+
#
|
|
9
|
+
# expect(Reeve::Checks::RedactionHolds.new(tool: T, principal: alice))
|
|
10
|
+
# .to pass_reeve_check
|
|
11
|
+
class PassReeveCheck < Base
|
|
12
|
+
def self.check
|
|
13
|
+
Checks::Base
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
"pass its reeve check"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def failure_message_when_negated
|
|
21
|
+
"expected #{result.check} to fail, but it passed: #{result.message}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def check_for(check)
|
|
27
|
+
check
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "matchers/base"
|
|
4
|
+
require_relative "matchers/deny_access_for"
|
|
5
|
+
require_relative "matchers/audit_every_call"
|
|
6
|
+
require_relative "matchers/pass_reeve_check"
|
|
7
|
+
|
|
8
|
+
module Reeve
|
|
9
|
+
module Testing
|
|
10
|
+
# The RSpec front-end.
|
|
11
|
+
#
|
|
12
|
+
# require "reeve/rspec"
|
|
13
|
+
# RSpec.configure { |c| c.include Reeve::Testing::Matchers }
|
|
14
|
+
#
|
|
15
|
+
# RSpec.describe InvoiceSearchTool do
|
|
16
|
+
# it { is_expected.to deny_access_for(stranger).with(query: "AC") }
|
|
17
|
+
# it { is_expected.to audit_every_call }
|
|
18
|
+
# end
|
|
19
|
+
module Matchers
|
|
20
|
+
def deny_access_for(principal)
|
|
21
|
+
DenyAccessFor.new(principal)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def audit_every_call
|
|
25
|
+
AuditEveryCall.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def pass_reeve_check
|
|
29
|
+
PassReeveCheck.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
# The aggregate of one compliance run (FR-018).
|
|
6
|
+
#
|
|
7
|
+
# +to_s+ is written to be the whole output of a failing CI step: a one-line verdict
|
|
8
|
+
# followed by every failure in full. A report that has to be cross-referenced with
|
|
9
|
+
# something else is a report nobody reads at 3am.
|
|
10
|
+
class Report
|
|
11
|
+
attr_reader :results
|
|
12
|
+
|
|
13
|
+
def initialize(results)
|
|
14
|
+
@results = results.freeze
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def passed?
|
|
19
|
+
failures.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def failed?
|
|
23
|
+
!passed?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def failures
|
|
27
|
+
results.reject(&:passed?)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def passes
|
|
31
|
+
results.select(&:passed?)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def size
|
|
35
|
+
results.size
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
[summary, *failures.map { |result| detail(result) }].join("\n")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
"#<Reeve::Testing::Report #{summary}>"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def summary
|
|
49
|
+
"reeve compliance: #{size} #{size == 1 ? 'check' : 'checks'}, " \
|
|
50
|
+
"#{passes.size} passed, #{failures.size} failed"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def detail(result)
|
|
54
|
+
"\nFAIL #{result.check}\n #{result.message}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
# What a check hands back: whether the guarantee held, the sentence explaining it, and
|
|
6
|
+
# the structured facts behind the sentence.
|
|
7
|
+
#
|
|
8
|
+
# The message is built here and nowhere else. A front-end — an RSpec matcher, a
|
|
9
|
+
# Minitest assertion, a rake task — reads +message+ and prints it; none of them
|
|
10
|
+
# composes their own (FR-019). That is what makes the same violation read identically
|
|
11
|
+
# from all three.
|
|
12
|
+
class Result
|
|
13
|
+
attr_reader :check, :message, :details
|
|
14
|
+
|
|
15
|
+
def self.passed(check:, message:, details: {})
|
|
16
|
+
new(check: check, passed: true, message: message, details: details)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.failed(check:, message:, details: {})
|
|
20
|
+
new(check: check, passed: false, message: message, details: details)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(check:, passed:, message:, details: {})
|
|
24
|
+
@check = check.to_s
|
|
25
|
+
@passed = passed ? true : false
|
|
26
|
+
@message = message.to_s
|
|
27
|
+
@details = details.freeze
|
|
28
|
+
freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def passed?
|
|
32
|
+
@passed
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def failed?
|
|
36
|
+
!@passed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inspect
|
|
44
|
+
"#<Reeve::Testing::Result #{check} #{passed? ? 'passed' : 'failed'} " \
|
|
45
|
+
"#{message.inspect}>"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../reeve"
|
|
4
|
+
require_relative "testing/result"
|
|
5
|
+
require_relative "testing/report"
|
|
6
|
+
require_relative "testing/ledger"
|
|
7
|
+
require_relative "testing/checks"
|
|
8
|
+
|
|
9
|
+
module Reeve
|
|
10
|
+
# The testing kit: framework-neutral checks, and thin front-ends over them.
|
|
11
|
+
#
|
|
12
|
+
# require "reeve/testing" # the checks, and nothing else
|
|
13
|
+
# require "reeve/rspec" # + matchers and the shared example group
|
|
14
|
+
# require "reeve/minitest" # + assertions and the compliance test case
|
|
15
|
+
#
|
|
16
|
+
# This file loads no test framework and no ActiveRecord, so the checks are runnable from
|
|
17
|
+
# a rake task, a CI script or a boot-time assertion in staging (FR-020, FR-026):
|
|
18
|
+
#
|
|
19
|
+
# require "reeve/testing"
|
|
20
|
+
# report = Reeve::Checks.run_all(principals: [alice, bob])
|
|
21
|
+
# abort report.to_s unless report.passed?
|
|
22
|
+
module Testing
|
|
23
|
+
class << self
|
|
24
|
+
# The two fixture principals the compliance suite runs every tool against. Set once,
|
|
25
|
+
# in the host's test helper, and both front-ends' compliance suites pick it up:
|
|
26
|
+
#
|
|
27
|
+
# Reeve::Testing.compliance_principals = -> { [users(:alice), users(:bob)] }
|
|
28
|
+
#
|
|
29
|
+
# A callable rather than a value, because in a Rails test suite the fixtures do not
|
|
30
|
+
# exist yet at the moment the helper is loaded.
|
|
31
|
+
attr_writer :compliance_principals
|
|
32
|
+
|
|
33
|
+
def compliance_principals
|
|
34
|
+
source = @compliance_principals || Reeve.config.compliance_principals
|
|
35
|
+
raise ConfigurationError, missing_principals_message if source.nil?
|
|
36
|
+
|
|
37
|
+
principals = Array(source.respond_to?(:call) ? source.call : source)
|
|
38
|
+
raise ConfigurationError, missing_principals_message if principals.size < 2
|
|
39
|
+
|
|
40
|
+
principals
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def compliance_principals?
|
|
44
|
+
!(@compliance_principals || Reeve.config.compliance_principals).nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reset!
|
|
48
|
+
@compliance_principals = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def missing_principals_message
|
|
54
|
+
"the reeve compliance suite needs two fixture principals with disjoint records. " \
|
|
55
|
+
"Set them in your test helper:\n\n " \
|
|
56
|
+
"Reeve::Testing.compliance_principals = -> { [users(:alice), users(:bob)] }"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/reeve/version.rb
CHANGED
data/lib/reeve.rb
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "reeve/version"
|
|
4
|
+
require_relative "reeve/errors"
|
|
5
|
+
require_relative "reeve/decision"
|
|
6
|
+
require_relative "reeve/configuration"
|
|
7
|
+
require_relative "reeve/context"
|
|
8
|
+
require_relative "reeve/scope_result"
|
|
9
|
+
require_relative "reeve/invocation"
|
|
10
|
+
require_relative "reeve/authorization"
|
|
4
11
|
|
|
5
12
|
# Reeve — per-record authorization and an append-only audit ledger for the MCP tools
|
|
6
13
|
# a Rails application exposes to AI agents.
|
|
7
14
|
#
|
|
8
|
-
# 0.0.1 is a placeholder release reserving the gem name
|
|
9
|
-
# See https://github.com/vicmaster/reeve
|
|
15
|
+
# The published 0.0.1 is a placeholder release reserving the gem name; the kernel below
|
|
16
|
+
# is unreleased work in progress. See https://github.com/vicmaster/reeve.
|
|
10
17
|
module Reeve
|
|
11
|
-
class Error < StandardError; end
|
|
12
18
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reeve
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Victor Velazquez
|
|
@@ -27,7 +27,57 @@ files:
|
|
|
27
27
|
- CHANGELOG.md
|
|
28
28
|
- LICENSE.txt
|
|
29
29
|
- README.md
|
|
30
|
+
- lib/generators/reeve/install/install_generator.rb
|
|
31
|
+
- lib/generators/reeve/install/templates/create_audit_entries.rb.tt
|
|
32
|
+
- lib/generators/reeve/install/templates/initializer.rb.tt
|
|
30
33
|
- lib/reeve.rb
|
|
34
|
+
- lib/reeve/audit.rb
|
|
35
|
+
- lib/reeve/audit/entry.rb
|
|
36
|
+
- lib/reeve/audit/query.rb
|
|
37
|
+
- lib/reeve/audit/recorder.rb
|
|
38
|
+
- lib/reeve/audit/redactor.rb
|
|
39
|
+
- lib/reeve/authorization.rb
|
|
40
|
+
- lib/reeve/authorization/adapter.rb
|
|
41
|
+
- lib/reeve/authorization/adapters/plain.rb
|
|
42
|
+
- lib/reeve/authorization/adapters/pundit.rb
|
|
43
|
+
- lib/reeve/authorization/authorizer.rb
|
|
44
|
+
- lib/reeve/authorization/current.rb
|
|
45
|
+
- lib/reeve/authorization/declaration.rb
|
|
46
|
+
- lib/reeve/authorization/guard.rb
|
|
47
|
+
- lib/reeve/authorization/registry.rb
|
|
48
|
+
- lib/reeve/authorization/scoper.rb
|
|
49
|
+
- lib/reeve/configuration.rb
|
|
50
|
+
- lib/reeve/context.rb
|
|
51
|
+
- lib/reeve/decision.rb
|
|
52
|
+
- lib/reeve/errors.rb
|
|
53
|
+
- lib/reeve/fast_mcp.rb
|
|
54
|
+
- lib/reeve/integrations/fast_mcp/context_builder.rb
|
|
55
|
+
- lib/reeve/integrations/fast_mcp/tool_extension.rb
|
|
56
|
+
- lib/reeve/invocation.rb
|
|
57
|
+
- lib/reeve/minitest.rb
|
|
58
|
+
- lib/reeve/rspec.rb
|
|
59
|
+
- lib/reeve/scope_result.rb
|
|
60
|
+
- lib/reeve/testing.rb
|
|
61
|
+
- lib/reeve/testing/assertions.rb
|
|
62
|
+
- lib/reeve/testing/checks.rb
|
|
63
|
+
- lib/reeve/testing/checks/audit_coverage.rb
|
|
64
|
+
- lib/reeve/testing/checks/base.rb
|
|
65
|
+
- lib/reeve/testing/checks/contract_version.rb
|
|
66
|
+
- lib/reeve/testing/checks/cross_principal_leak.rb
|
|
67
|
+
- lib/reeve/testing/checks/guard_declared.rb
|
|
68
|
+
- lib/reeve/testing/checks/principal_required.rb
|
|
69
|
+
- lib/reeve/testing/checks/redaction_holds.rb
|
|
70
|
+
- lib/reeve/testing/checks/rule_present.rb
|
|
71
|
+
- lib/reeve/testing/compliance_assertions.rb
|
|
72
|
+
- lib/reeve/testing/compliance_suite.rb
|
|
73
|
+
- lib/reeve/testing/ledger.rb
|
|
74
|
+
- lib/reeve/testing/matchers.rb
|
|
75
|
+
- lib/reeve/testing/matchers/audit_every_call.rb
|
|
76
|
+
- lib/reeve/testing/matchers/base.rb
|
|
77
|
+
- lib/reeve/testing/matchers/deny_access_for.rb
|
|
78
|
+
- lib/reeve/testing/matchers/pass_reeve_check.rb
|
|
79
|
+
- lib/reeve/testing/report.rb
|
|
80
|
+
- lib/reeve/testing/result.rb
|
|
31
81
|
- lib/reeve/version.rb
|
|
32
82
|
homepage: https://github.com/vicmaster/reeve
|
|
33
83
|
licenses:
|
|
@@ -37,6 +87,7 @@ metadata:
|
|
|
37
87
|
source_code_uri: https://github.com/vicmaster/reeve
|
|
38
88
|
changelog_uri: https://github.com/vicmaster/reeve/blob/main/CHANGELOG.md
|
|
39
89
|
bug_tracker_uri: https://github.com/vicmaster/reeve/issues
|
|
90
|
+
rubygems_mfa_required: 'true'
|
|
40
91
|
post_install_message:
|
|
41
92
|
rdoc_options: []
|
|
42
93
|
require_paths:
|