reeve 0.3.0 → 0.5.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.
@@ -3,7 +3,7 @@
3
3
  module Reeve
4
4
  module Testing
5
5
  module Matchers
6
- # The escape hatch that keeps SC-009 honest: every one of the seven checks is
6
+ # The escape hatch that keeps SC-009 honest: every check is
7
7
  # assertable from RSpec, not only the two with names of their own.
8
8
  #
9
9
  # expect(Reeve::Checks::RedactionHolds.new(tool: T, principal: alice))
@@ -15,6 +15,8 @@ module Reeve
15
15
  freeze
16
16
  end
17
17
 
18
+ # Nothing failed. A skip is not a failure — and it is printed, so it is not
19
+ # mistaken for a pass either.
18
20
  def passed?
19
21
  failures.empty?
20
22
  end
@@ -23,20 +25,31 @@ module Reeve
23
25
  !passed?
24
26
  end
25
27
 
28
+ # Every result was a skip: the run established nothing either way. The front-ends
29
+ # report this as a skipped test rather than a green one.
30
+ def skipped?
31
+ !results.empty? && results.all?(&:skipped?)
32
+ end
33
+
26
34
  def failures
27
- results.reject(&:passed?)
35
+ results.select(&:failed?)
28
36
  end
29
37
 
30
38
  def passes
31
39
  results.select(&:passed?)
32
40
  end
33
41
 
42
+ def skips
43
+ results.select(&:skipped?)
44
+ end
45
+
34
46
  def size
35
47
  results.size
36
48
  end
37
49
 
38
50
  def to_s
39
- [summary, *failures.map { |result| detail(result) }].join("\n")
51
+ [summary, *failures.map { |result| detail("FAIL", result) },
52
+ *skips.map { |result| detail("SKIP", result) }].join("\n")
40
53
  end
41
54
 
42
55
  def inspect
@@ -46,12 +59,15 @@ module Reeve
46
59
  private
47
60
 
48
61
  def summary
49
- "reeve compliance: #{size} #{size == 1 ? 'check' : 'checks'}, " \
50
- "#{passes.size} passed, #{failures.size} failed"
62
+ line = "reeve compliance: #{size} #{size == 1 ? 'check' : 'checks'}, " \
63
+ "#{passes.size} passed, #{failures.size} failed"
64
+ skips.empty? ? line : "#{line}, #{skips.size} skipped"
51
65
  end
52
66
 
53
- def detail(result)
54
- "\nFAIL #{result.check}\n #{result.message}"
67
+ # Every line of a multi-line message indented, so it reads as one block under its
68
+ # heading rather than as stray lines of the report.
69
+ def detail(heading, result)
70
+ "\n#{heading} #{result.check}\n #{result.message.gsub("\n", "\n ")}"
55
71
  end
56
72
  end
57
73
  end
@@ -9,31 +9,51 @@ module Reeve
9
9
  # Minitest assertion, a rake task — reads +message+ and prints it; none of them
10
10
  # composes their own (FR-019). That is what makes the same violation read identically
11
11
  # from all three.
12
+ #
13
+ # A result is passed, failed, or skipped. Skipped is for a guarantee the run could not
14
+ # establish either way — endpoint coverage with no inventory declared. Reporting that
15
+ # as a pass would be the green that means "we did not look"; reporting it as a failure
16
+ # would turn every build red over a question the host never asked. A skip is printed,
17
+ # and shows up yellow in both test frameworks, so it is neither.
12
18
  class Result
13
- attr_reader :check, :message, :details
19
+ STATUSES = %i[passed failed skipped].freeze
20
+
21
+ attr_reader :check, :message, :details, :status
14
22
 
15
23
  def self.passed(check:, message:, details: {})
16
- new(check: check, passed: true, message: message, details: details)
24
+ new(check: check, status: :passed, message: message, details: details)
17
25
  end
18
26
 
19
27
  def self.failed(check:, message:, details: {})
20
- new(check: check, passed: false, message: message, details: details)
28
+ new(check: check, status: :failed, message: message, details: details)
29
+ end
30
+
31
+ def self.skipped(check:, message:, details: {})
32
+ new(check: check, status: :skipped, message: message, details: details)
21
33
  end
22
34
 
23
- def initialize(check:, passed:, message:, details: {})
35
+ def initialize(check:, status:, message:, details: {})
36
+ unless STATUSES.include?(status)
37
+ raise ArgumentError, "status must be one of #{STATUSES.inspect}, got #{status.inspect}"
38
+ end
39
+
24
40
  @check = check.to_s
25
- @passed = passed ? true : false
41
+ @status = status
26
42
  @message = message.to_s
27
43
  @details = details.freeze
28
44
  freeze
29
45
  end
30
46
 
31
47
  def passed?
32
- @passed
48
+ status == :passed
33
49
  end
34
50
 
35
51
  def failed?
36
- !@passed
52
+ status == :failed
53
+ end
54
+
55
+ def skipped?
56
+ status == :skipped
37
57
  end
38
58
 
39
59
  def to_s
@@ -41,7 +61,7 @@ module Reeve
41
61
  end
42
62
 
43
63
  def inspect
44
- "#<Reeve::Testing::Result #{check} #{passed? ? 'passed' : 'failed'} " \
64
+ "#<Reeve::Testing::Result #{check} #{status} " \
45
65
  "#{message.inspect}>"
46
66
  end
47
67
  end
data/lib/reeve/testing.rb CHANGED
@@ -40,16 +40,45 @@ module Reeve
40
40
  principals
41
41
  end
42
42
 
43
+ # The tools the suite walks. Unset means every tool reeve knows about — the classes
44
+ # that included the DSL, guarded or not, plus every class the endpoint inventory
45
+ # routes to, which need not have included anything. That default is what makes an
46
+ # unguarded tool visible rather than merely absent.
47
+ attr_writer :compliance_tools
48
+
49
+ def compliance_tools
50
+ source = @compliance_tools || Reeve.config.compliance_tools
51
+ return Reeve.registry.tool_classes | inventory_tools if source.nil?
52
+
53
+ Array(source.respond_to?(:call) ? source.call : source)
54
+ end
55
+
56
+ # The host said which tools it is certifying, so a run is a subset by its own account.
57
+ def compliance_tools_narrowed?
58
+ !(@compliance_tools || Reeve.config.compliance_tools).nil?
59
+ end
60
+
43
61
  def compliance_principals?
44
62
  !(@compliance_principals || Reeve.config.compliance_principals).nil?
45
63
  end
46
64
 
47
65
  def reset!
48
66
  @compliance_principals = nil
67
+ @compliance_tools = nil
49
68
  end
50
69
 
51
70
  private
52
71
 
72
+ # An inventory that cannot be read adds nothing here; EndpointCoverage fails on it
73
+ # by name, which is a better place to learn about it than an exception in every
74
+ # per-tool check.
75
+ def inventory_tools
76
+ inventory = Reeve.config.inventory
77
+ inventory ? inventory.routed_tools : []
78
+ rescue StandardError
79
+ []
80
+ end
81
+
53
82
  def missing_principals_message
54
83
  "the reeve compliance suite needs two fixture principals with disjoint records. " \
55
84
  "Set them in your test helper:\n\n " \
data/lib/reeve/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reeve
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/reeve.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "reeve/context"
8
8
  require_relative "reeve/scope_result"
9
9
  require_relative "reeve/invocation"
10
10
  require_relative "reeve/authorization"
11
+ require_relative "reeve/inventory"
11
12
 
12
13
  # Reeve — per-record authorization and an append-only audit ledger for the MCP tools
13
14
  # a Rails application exposes to AI agents.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reeve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Velazquez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-17 00:00:00.000000000 Z
11
+ date: 2026-10-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Reeve makes it safe for a Rails application to expose MCP (Model Context Protocol)
@@ -56,6 +56,8 @@ files:
56
56
  - lib/reeve/fast_mcp.rb
57
57
  - lib/reeve/integrations/fast_mcp/context_builder.rb
58
58
  - lib/reeve/integrations/fast_mcp/tool_extension.rb
59
+ - lib/reeve/inventory.rb
60
+ - lib/reeve/inventory/report.rb
59
61
  - lib/reeve/invocation.rb
60
62
  - lib/reeve/minitest.rb
61
63
  - lib/reeve/rspec.rb
@@ -67,6 +69,7 @@ files:
67
69
  - lib/reeve/testing/checks/base.rb
68
70
  - lib/reeve/testing/checks/contract_version.rb
69
71
  - lib/reeve/testing/checks/cross_principal_leak.rb
72
+ - lib/reeve/testing/checks/endpoint_coverage.rb
70
73
  - lib/reeve/testing/checks/guard_declared.rb
71
74
  - lib/reeve/testing/checks/principal_required.rb
72
75
  - lib/reeve/testing/checks/redaction_holds.rb