reeve 0.5.0 → 0.6.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 +106 -1
- data/README.md +113 -6
- data/lib/generators/reeve/install/templates/create_audit_entries.rb.tt +41 -3
- data/lib/generators/reeve/install/templates/initializer.rb.tt +20 -0
- data/lib/generators/reeve/upgrade/templates/add_ledger_chain_to_reeve_audit_entries.rb.tt +51 -0
- data/lib/generators/reeve/upgrade/upgrade_generator.rb +40 -2
- data/lib/reeve/audit/chain/canonical.rb +129 -0
- data/lib/reeve/audit/chain/head.rb +94 -0
- data/lib/reeve/audit/chain/keyring.rb +53 -0
- data/lib/reeve/audit/chain/normalizer.rb +88 -0
- data/lib/reeve/audit/chain/report.rb +134 -0
- data/lib/reeve/audit/chain/verification.rb +110 -0
- data/lib/reeve/audit/chain/verifier.rb +160 -0
- data/lib/reeve/audit/chain/walk.rb +123 -0
- data/lib/reeve/audit/chain.rb +168 -0
- data/lib/reeve/audit/chain_record.rb +61 -0
- data/lib/reeve/audit/isolated_recorder.rb +5 -0
- data/lib/reeve/audit/recorder.rb +32 -19
- data/lib/reeve/audit.rb +24 -4
- data/lib/reeve/configuration.rb +66 -2
- data/lib/reeve/errors.rb +15 -0
- data/lib/reeve/ledger_key.rb +47 -0
- data/lib/reeve/railtie.rb +15 -0
- data/lib/reeve/tasks.rb +62 -0
- data/lib/reeve/testing/checks/contract_version.rb +2 -1
- data/lib/reeve/testing/checks/ledger_chain.rb +116 -0
- data/lib/reeve/testing/checks.rb +29 -4
- data/lib/reeve/testing/compliance_assertions.rb +3 -2
- data/lib/reeve/testing/compliance_suite.rb +2 -2
- data/lib/reeve/testing/ledger.rb +11 -1
- data/lib/reeve/testing/report.rb +8 -0
- data/lib/reeve/testing.rb +1 -1
- data/lib/reeve/version.rb +1 -1
- data/lib/reeve.rb +2 -0
- metadata +17 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
# Registers `bin/rails reeve:verify_ledger`, and does nothing else.
|
|
5
|
+
#
|
|
6
|
+
# Loaded by lib/reeve.rb only when Rails::Railtie is already defined, so `require "reeve"`
|
|
7
|
+
# in a plain Ruby process still loads nothing beyond reeve's own files and its short stdlib
|
|
8
|
+
# allowlist (spec/load_safety_spec.rb).
|
|
9
|
+
class Railtie < ::Rails::Railtie
|
|
10
|
+
rake_tasks do
|
|
11
|
+
require "reeve/tasks"
|
|
12
|
+
Reeve::Tasks.define
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/reeve/tasks.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Reeve
|
|
6
|
+
# `bin/rails reeve:verify_ledger` (specs/002 FR-017, contracts/ledger-chain.md "Rake").
|
|
7
|
+
#
|
|
8
|
+
# A Ruby file rather than a .rake one on purpose: reeve.gemspec packages lib/**/*.rb, and a
|
|
9
|
+
# .rake file would pass every spec in the working tree and be missing from the gem users
|
|
10
|
+
# install. Registered by Reeve::Railtie; callable without Rake for anything else that
|
|
11
|
+
# wants the same answer — a deploy gate, a scheduled job, a console.
|
|
12
|
+
#
|
|
13
|
+
# status = Reeve::Tasks.verify_ledger(env: { "LIMIT" => "0" })
|
|
14
|
+
module Tasks
|
|
15
|
+
EXIT = { intact: 0, broken: 1, unverifiable: 2, no_chain: 2 }.freeze
|
|
16
|
+
FORMATS = %w[text json].freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Verifies the ledger, writes the report to `out`, and returns the exit status:
|
|
21
|
+
# 0 intact, 1 broken, 2 could not verify (unverifiable, or no chain).
|
|
22
|
+
#
|
|
23
|
+
# LIMIT caps the breaks listed in the text report (default 100; the totals are never
|
|
24
|
+
# capped). FORMAT=json writes the whole result, every break, and ignores LIMIT.
|
|
25
|
+
def verify_ledger(env: ENV, out: $stdout)
|
|
26
|
+
format = env.fetch("FORMAT", "text")
|
|
27
|
+
unless FORMATS.include?(format)
|
|
28
|
+
raise ArgumentError, "FORMAT must be one of #{FORMATS.join(', ')}, got #{format.inspect}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
limit = limit_from(env)
|
|
32
|
+
require "reeve/audit"
|
|
33
|
+
result = Reeve::Audit::Chain.verify
|
|
34
|
+
out.puts(format == "json" ? JSON.generate(result.as_json) : result.to_s(limit: limit))
|
|
35
|
+
EXIT.fetch(result.status)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def limit_from(env)
|
|
39
|
+
raw = env.fetch("LIMIT", Reeve::Audit::Chain::Verification::DEFAULT_LIMIT.to_s)
|
|
40
|
+
Integer(raw, 10).tap { |limit| raise ArgumentError if limit.negative? }
|
|
41
|
+
rescue ArgumentError, TypeError
|
|
42
|
+
raise ArgumentError, "LIMIT must be a whole number of breaks to list, got #{raw.inspect}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The Rake task, defined on the current Rake application. Depends on :environment, so
|
|
46
|
+
# the host's initializer — and with it the ledger key — is loaded first.
|
|
47
|
+
#
|
|
48
|
+
# The DSL is borrowed by a throwaway object, so Reeve::Tasks itself never gains `task`,
|
|
49
|
+
# `sh` and the rest of Rake's methods.
|
|
50
|
+
def define
|
|
51
|
+
Object.new.extend(Rake::DSL).instance_exec do
|
|
52
|
+
namespace :reeve do
|
|
53
|
+
desc "Verify the audit ledger's chain " \
|
|
54
|
+
"(exit 0 intact, 1 broken, 2 could not verify; LIMIT=, FORMAT=json)"
|
|
55
|
+
task verify_ledger: :environment do
|
|
56
|
+
exit Reeve::Tasks.verify_ledger
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -46,9 +46,10 @@ module Reeve
|
|
|
46
46
|
invocation_id occurred_at agent_id agent_name principal_type principal_id
|
|
47
47
|
tool_name arguments outcome rule detail record_type record_ids record_count
|
|
48
48
|
truncated derived guard duration_ms metadata contract_version
|
|
49
|
+
chain_sequence chain_key_id chain_digest
|
|
49
50
|
].freeze
|
|
50
51
|
|
|
51
|
-
EXPECTED_VERSION =
|
|
52
|
+
EXPECTED_VERSION = 3
|
|
52
53
|
|
|
53
54
|
def initialize(tool: nil, expected: EXPECTED_VERSION, ledger: nil)
|
|
54
55
|
super(tool: tool, ledger: ledger)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Testing
|
|
5
|
+
module Checks
|
|
6
|
+
# Whether the ledger's chain holds (specs/002 FR-018).
|
|
7
|
+
#
|
|
8
|
+
# It writes entries of its own first — the first compliance tool, once per fixture
|
|
9
|
+
# principal — because a suite with transactional fixtures starts every example with
|
|
10
|
+
# an empty ledger, and a check that only looked would find nothing and say nothing.
|
|
11
|
+
# Then it verifies the whole ledger the run can see, not only its own rows: a break an
|
|
12
|
+
# earlier run left behind is reported, not hidden.
|
|
13
|
+
#
|
|
14
|
+
# * **intact** — passed; the message is the verification's own summary line, so it
|
|
15
|
+
# states the range covered and what was not.
|
|
16
|
+
# * **broken, unverifiable, or no chain after writing** — failed. The run signed its own
|
|
17
|
+
# entries, so a key it lacks or a recorder that did not chain is a configuration
|
|
18
|
+
# error, not an absence.
|
|
19
|
+
# * **no key configured** — failed, naming the setting.
|
|
20
|
+
# * **nothing to write or read** (no compliance tools, ledger not loaded) — skipped,
|
|
21
|
+
# saying the chain was not verified. Never passed.
|
|
22
|
+
#
|
|
23
|
+
# Reeve::Checks::LedgerChain.new.call
|
|
24
|
+
class LedgerChain < Base
|
|
25
|
+
NOT_VERIFIED = "the ledger chain was not verified"
|
|
26
|
+
|
|
27
|
+
def initialize(tools: nil, principals: nil, arguments: {}, invoke: nil, ledger: nil)
|
|
28
|
+
@tools = tools
|
|
29
|
+
@principals = principals
|
|
30
|
+
super(tool: nil, arguments: arguments, invoke: invoke, ledger: ledger)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call
|
|
34
|
+
return skipped("there is no compliance tool to write an entry with") if subject.nil?
|
|
35
|
+
return skipped(unreadable_reason) unless readable?
|
|
36
|
+
return no_key if Reeve.config.ledger_key.nil?
|
|
37
|
+
|
|
38
|
+
unwritten = write_entries
|
|
39
|
+
return unwritten_entry(unwritten) if unwritten
|
|
40
|
+
|
|
41
|
+
judge(ledger.verify_chain)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def subject
|
|
47
|
+
@subject ||= Array(@tools || Testing.compliance_tools).first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def principals
|
|
51
|
+
configured = Testing.compliance_principals? ? Testing.compliance_principals : nil
|
|
52
|
+
list = Array(@principals || configured)
|
|
53
|
+
list.empty? ? [nil] : list
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Each principal on its own, so one refusal does not stop the next. A denial, or the
|
|
57
|
+
# tool's own error, still leaves a ledger entry — the envelope records before it
|
|
58
|
+
# re-raises — which is all this needs. A failed ledger write leaves none, and is
|
|
59
|
+
# returned as the reason the chain could not be checked.
|
|
60
|
+
#
|
|
61
|
+
# Returns nil when entries were written, or the reason none were — including the case
|
|
62
|
+
# where every invocation failed before reaching reeve, which a chain left by an
|
|
63
|
+
# earlier run must not be allowed to pass for.
|
|
64
|
+
def write_entries
|
|
65
|
+
before = ledger.count
|
|
66
|
+
principals.each do |principal|
|
|
67
|
+
invoke.call(tool: subject, arguments: arguments, principal: principal)
|
|
68
|
+
rescue Reeve::AuditWriteError => e
|
|
69
|
+
return "#{e.class}: #{e.message}"
|
|
70
|
+
rescue StandardError
|
|
71
|
+
next
|
|
72
|
+
end
|
|
73
|
+
ledger.count == before ? "its invocations wrote no entry" : nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def readable?
|
|
77
|
+
ledger.available? && ledger.respond_to?(:verify_chain)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def unwritten_entry(reason)
|
|
81
|
+
failed("#{NOT_VERIFIED}: it wrote no entry of its own — #{reason}",
|
|
82
|
+
coverage: :unwritten)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def unreadable_reason
|
|
86
|
+
ledger.unavailable_reason || "this ledger cannot verify a chain"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def judge(verification)
|
|
90
|
+
return skipped("the audit ledger is not loaded") if verification.nil?
|
|
91
|
+
|
|
92
|
+
summary = verification.to_s
|
|
93
|
+
return passed(summary.lines.first.chomp, coverage: :intact) if verification.intact?
|
|
94
|
+
|
|
95
|
+
failed(summary, coverage: verification.status)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def no_key
|
|
99
|
+
failed("expected the ledger to be chained, but no ledger key is configured: set " \
|
|
100
|
+
"config.ledger_key (the generated initializer derives one from secret_key_base)",
|
|
101
|
+
coverage: :no_key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Every skip says, first, that the chain was not verified — then why.
|
|
105
|
+
def skipped(reason)
|
|
106
|
+
Result.skipped(check: check_name, message: "#{NOT_VERIFIED}: #{reason}",
|
|
107
|
+
details: { coverage: :unverified })
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def base_details
|
|
111
|
+
{}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/reeve/testing/checks.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "checks/redaction_holds"
|
|
|
9
9
|
require_relative "checks/principal_required"
|
|
10
10
|
require_relative "checks/contract_version"
|
|
11
11
|
require_relative "checks/endpoint_coverage"
|
|
12
|
+
require_relative "checks/ledger_chain"
|
|
12
13
|
|
|
13
14
|
module Reeve
|
|
14
15
|
module Testing
|
|
@@ -19,7 +20,7 @@ module Reeve
|
|
|
19
20
|
# the same violation reads identically from either — and from neither:
|
|
20
21
|
#
|
|
21
22
|
# report = Reeve::Checks.run_all(principals: [alice, bob])
|
|
22
|
-
# abort report.to_s
|
|
23
|
+
# abort report.to_s if report.failed? || report.nothing_checked?
|
|
23
24
|
#
|
|
24
25
|
# Nothing here loads a test framework (FR-026).
|
|
25
26
|
module Checks
|
|
@@ -31,12 +32,13 @@ module Reeve
|
|
|
31
32
|
RedactionHolds,
|
|
32
33
|
PrincipalRequired,
|
|
33
34
|
ContractVersion,
|
|
34
|
-
EndpointCoverage
|
|
35
|
+
EndpointCoverage,
|
|
36
|
+
LedgerChain
|
|
35
37
|
].freeze
|
|
36
38
|
|
|
37
39
|
# The checks that are asked once — about the ledger, or about the endpoint as a
|
|
38
40
|
# whole — rather than once per tool.
|
|
39
|
-
GLOBAL = [ContractVersion, EndpointCoverage].freeze
|
|
41
|
+
GLOBAL = [ContractVersion, EndpointCoverage, LedgerChain].freeze
|
|
40
42
|
|
|
41
43
|
# The compliance suite's engine (FR-018): every check, against every registered
|
|
42
44
|
# guarded tool, in one Report.
|
|
@@ -58,10 +60,12 @@ module Reeve
|
|
|
58
60
|
def self.run(check, principals:, tools: nil, arguments: {}, invoke: nil, ledger: nil)
|
|
59
61
|
if GLOBAL.include?(check)
|
|
60
62
|
return Report.new([build(check, tool: nil, principals: principals, tools: tools,
|
|
61
|
-
ledger: ledger).call])
|
|
63
|
+
invoke: invoke, ledger: ledger).call])
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
subjects = tools || Testing.compliance_tools
|
|
67
|
+
return Report.new([nothing_to_check(check, tools)]) if subjects.empty?
|
|
68
|
+
|
|
65
69
|
Report.new(
|
|
66
70
|
subjects.map do |tool|
|
|
67
71
|
build(check, tool: tool, principals: principals, arguments: arguments,
|
|
@@ -70,6 +74,25 @@ module Reeve
|
|
|
70
74
|
)
|
|
71
75
|
end
|
|
72
76
|
|
|
77
|
+
# A per-tool check over no tools establishes nothing, and an empty Report reads as
|
|
78
|
+
# passed — the green that means "did not look". So an empty population is one skip,
|
|
79
|
+
# saying which of the three ways the run came to have nothing in it.
|
|
80
|
+
def self.nothing_to_check(check, tools)
|
|
81
|
+
reason =
|
|
82
|
+
if !tools.nil? then "the run was given tools: []"
|
|
83
|
+
elsif Testing.compliance_tools_narrowed? then "compliance_tools is []"
|
|
84
|
+
else "the registry is empty and no endpoint inventory routes a tool"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Result.skipped(
|
|
88
|
+
check: check.check_name,
|
|
89
|
+
message: "no compliance tools to check, so #{check.check_name} established " \
|
|
90
|
+
"nothing — #{reason}",
|
|
91
|
+
details: { coverage: :none }
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
private_class_method :nothing_to_check
|
|
95
|
+
|
|
73
96
|
# Every per-tool check for one tool, instantiated but not run.
|
|
74
97
|
def self.for_tool(tool, principals:, arguments: {}, invoke: nil, ledger: nil)
|
|
75
98
|
(ALL - GLOBAL).map do |check|
|
|
@@ -91,6 +114,8 @@ module Reeve
|
|
|
91
114
|
when "GuardDeclared" then GuardDeclared.new(tool: tool, ledger: ledger)
|
|
92
115
|
when "ContractVersion" then ContractVersion.new(ledger: ledger)
|
|
93
116
|
when "EndpointCoverage" then EndpointCoverage.new(tools: tools, ledger: ledger)
|
|
117
|
+
when "LedgerChain" then LedgerChain.new(tools: tools, principals: principals,
|
|
118
|
+
invoke: invoke, ledger: ledger)
|
|
94
119
|
when "PrincipalRequired" then PrincipalRequired.new(**common)
|
|
95
120
|
when "CrossPrincipalLeak" then CrossPrincipalLeak.new(principals: Array(principals),
|
|
96
121
|
**common)
|
|
@@ -15,8 +15,9 @@ module Reeve
|
|
|
15
15
|
#
|
|
16
16
|
# One method per check, matching the RSpec shared example group one for one, so a red
|
|
17
17
|
# build names the guarantee that broke rather than reporting "compliance" as one
|
|
18
|
-
# undifferentiated failure. A check that could establish nothing either way
|
|
19
|
-
# skipped with its
|
|
18
|
+
# undifferentiated failure. A check that could establish nothing either way — no
|
|
19
|
+
# inventory declared, or no tools to run a per-tool check over — is skipped with its
|
|
20
|
+
# reason, never passed.
|
|
20
21
|
module ComplianceAssertions
|
|
21
22
|
include Assertions
|
|
22
23
|
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
#
|
|
13
13
|
# One example per check rather than one for the lot, so a red build names the guarantee
|
|
14
14
|
# that broke — "CrossPrincipalLeak" is an incident, "compliance" is a shrug. A check that
|
|
15
|
-
# could establish nothing either way (EndpointCoverage with no inventory
|
|
16
|
-
# its reason, rather than passed.
|
|
15
|
+
# could establish nothing either way (EndpointCoverage with no inventory, or a per-tool
|
|
16
|
+
# check with no tools to run over) is skipped, with its reason, rather than passed.
|
|
17
17
|
RSpec.shared_examples "a reeve-compliant server" do
|
|
18
18
|
Reeve::Checks::ALL.each do |check|
|
|
19
19
|
it "satisfies #{check.check_name} for every registered guarded tool" do
|
data/lib/reeve/testing/ledger.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Reeve
|
|
|
10
10
|
# the ledger is absent.
|
|
11
11
|
#
|
|
12
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
|
|
13
|
+
# methods as `ledger:` to any ledger-reading check; LedgerChain also asks for
|
|
14
|
+
# #verify_chain, and is skipped by a ledger that has none.
|
|
14
15
|
class Ledger
|
|
15
16
|
def self.default
|
|
16
17
|
new
|
|
@@ -67,6 +68,15 @@ module Reeve
|
|
|
67
68
|
entry_class.column_names.map(&:to_s)
|
|
68
69
|
end
|
|
69
70
|
|
|
71
|
+
# The chain's verification of the whole ledger (specs/002), or nil when the ledger is
|
|
72
|
+
# not loaded. The kit's only way to the chain: it names Reeve::Audit::Chain here, at
|
|
73
|
+
# call time, so `require "reeve/testing"` still loads with no ActiveRecord at all.
|
|
74
|
+
def verify_chain
|
|
75
|
+
return nil unless available? && Reeve::Audit.const_defined?(:Chain, false)
|
|
76
|
+
|
|
77
|
+
Reeve::Audit::Chain.verify(entry_class: entry_class)
|
|
78
|
+
end
|
|
79
|
+
|
|
70
80
|
private
|
|
71
81
|
|
|
72
82
|
def entry_class
|
data/lib/reeve/testing/report.rb
CHANGED
|
@@ -31,6 +31,14 @@ module Reeve
|
|
|
31
31
|
!results.empty? && results.all?(&:skipped?)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# A check had nothing to examine — a per-tool check run over no tools at all. passed?
|
|
35
|
+
# is still true then, since nothing failed; a test framework shows the skip, but a
|
|
36
|
+
# gate with no framework (a CI script, a deploy step) has only this to stop on. A skip
|
|
37
|
+
# for a missing inventory is not this: there the tools existed and were checked.
|
|
38
|
+
def nothing_checked?
|
|
39
|
+
results.any? { |result| result.skipped? && result.details[:coverage] == :none }
|
|
40
|
+
end
|
|
41
|
+
|
|
34
42
|
def failures
|
|
35
43
|
results.select(&:failed?)
|
|
36
44
|
end
|
data/lib/reeve/testing.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Reeve
|
|
|
18
18
|
#
|
|
19
19
|
# require "reeve/testing"
|
|
20
20
|
# report = Reeve::Checks.run_all(principals: [alice, bob])
|
|
21
|
-
# abort report.to_s
|
|
21
|
+
# abort report.to_s if report.failed? || report.nothing_checked?
|
|
22
22
|
module Testing
|
|
23
23
|
class << self
|
|
24
24
|
# The two fixture principals the compliance suite runs every tool against. Set once,
|
data/lib/reeve/version.rb
CHANGED
data/lib/reeve.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "reeve/version"
|
|
4
4
|
require_relative "reeve/errors"
|
|
5
|
+
require_relative "reeve/ledger_key"
|
|
5
6
|
require_relative "reeve/decision"
|
|
6
7
|
require_relative "reeve/configuration"
|
|
7
8
|
require_relative "reeve/context"
|
|
@@ -9,6 +10,7 @@ require_relative "reeve/scope_result"
|
|
|
9
10
|
require_relative "reeve/invocation"
|
|
10
11
|
require_relative "reeve/authorization"
|
|
11
12
|
require_relative "reeve/inventory"
|
|
13
|
+
require_relative "reeve/railtie" if defined?(Rails::Railtie)
|
|
12
14
|
|
|
13
15
|
# Reeve — per-record authorization and an append-only audit ledger for the MCP tools
|
|
14
16
|
# 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.
|
|
4
|
+
version: 0.6.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-10-
|
|
11
|
+
date: 2026-10-10 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)
|
|
@@ -31,9 +31,20 @@ files:
|
|
|
31
31
|
- lib/generators/reeve/install/templates/create_audit_entries.rb.tt
|
|
32
32
|
- lib/generators/reeve/install/templates/initializer.rb.tt
|
|
33
33
|
- lib/generators/reeve/upgrade/templates/add_contract_version_to_reeve_audit_entries.rb.tt
|
|
34
|
+
- lib/generators/reeve/upgrade/templates/add_ledger_chain_to_reeve_audit_entries.rb.tt
|
|
34
35
|
- lib/generators/reeve/upgrade/upgrade_generator.rb
|
|
35
36
|
- lib/reeve.rb
|
|
36
37
|
- lib/reeve/audit.rb
|
|
38
|
+
- lib/reeve/audit/chain.rb
|
|
39
|
+
- lib/reeve/audit/chain/canonical.rb
|
|
40
|
+
- lib/reeve/audit/chain/head.rb
|
|
41
|
+
- lib/reeve/audit/chain/keyring.rb
|
|
42
|
+
- lib/reeve/audit/chain/normalizer.rb
|
|
43
|
+
- lib/reeve/audit/chain/report.rb
|
|
44
|
+
- lib/reeve/audit/chain/verification.rb
|
|
45
|
+
- lib/reeve/audit/chain/verifier.rb
|
|
46
|
+
- lib/reeve/audit/chain/walk.rb
|
|
47
|
+
- lib/reeve/audit/chain_record.rb
|
|
37
48
|
- lib/reeve/audit/entry.rb
|
|
38
49
|
- lib/reeve/audit/isolated_recorder.rb
|
|
39
50
|
- lib/reeve/audit/query.rb
|
|
@@ -59,9 +70,12 @@ files:
|
|
|
59
70
|
- lib/reeve/inventory.rb
|
|
60
71
|
- lib/reeve/inventory/report.rb
|
|
61
72
|
- lib/reeve/invocation.rb
|
|
73
|
+
- lib/reeve/ledger_key.rb
|
|
62
74
|
- lib/reeve/minitest.rb
|
|
75
|
+
- lib/reeve/railtie.rb
|
|
63
76
|
- lib/reeve/rspec.rb
|
|
64
77
|
- lib/reeve/scope_result.rb
|
|
78
|
+
- lib/reeve/tasks.rb
|
|
65
79
|
- lib/reeve/testing.rb
|
|
66
80
|
- lib/reeve/testing/assertions.rb
|
|
67
81
|
- lib/reeve/testing/checks.rb
|
|
@@ -71,6 +85,7 @@ files:
|
|
|
71
85
|
- lib/reeve/testing/checks/cross_principal_leak.rb
|
|
72
86
|
- lib/reeve/testing/checks/endpoint_coverage.rb
|
|
73
87
|
- lib/reeve/testing/checks/guard_declared.rb
|
|
88
|
+
- lib/reeve/testing/checks/ledger_chain.rb
|
|
74
89
|
- lib/reeve/testing/checks/principal_required.rb
|
|
75
90
|
- lib/reeve/testing/checks/redaction_holds.rb
|
|
76
91
|
- lib/reeve/testing/checks/rule_present.rb
|