reeve 0.6.0 → 0.7.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 +176 -3
- data/README.md +42 -4
- data/lib/generators/reeve/install/templates/create_audit_entries.rb.tt +23 -1
- data/lib/generators/reeve/upgrade/templates/add_key_epochs_to_reeve_audit_entries.rb.tt +35 -0
- data/lib/generators/reeve/upgrade/upgrade_generator.rb +10 -25
- data/lib/generators/reeve/upgrade/upgrade_notes.rb +51 -0
- data/lib/reeve/audit/chain/canonical.rb +22 -7
- data/lib/reeve/audit/chain/epoch_rules.rb +159 -0
- data/lib/reeve/audit/chain/epochs.rb +77 -0
- data/lib/reeve/audit/chain/head.rb +29 -1
- data/lib/reeve/audit/chain/report.rb +45 -2
- data/lib/reeve/audit/chain/rotation.rb +124 -0
- data/lib/reeve/audit/chain/schema.rb +59 -0
- data/lib/reeve/audit/chain/verification.rb +28 -7
- data/lib/reeve/audit/chain/verifier.rb +17 -9
- data/lib/reeve/audit/chain/walk.rb +16 -2
- data/lib/reeve/audit/chain.rb +49 -10
- data/lib/reeve/audit/chain_epoch.rb +24 -0
- data/lib/reeve/audit/chain_record.rb +13 -8
- data/lib/reeve/audit.rb +14 -3
- data/lib/reeve/railtie.rb +2 -1
- data/lib/reeve/tasks.rb +29 -0
- data/lib/reeve/testing/checks/contract_version.rb +2 -2
- data/lib/reeve/version.rb +1 -1
- metadata +9 -2
|
@@ -23,7 +23,7 @@ module Reeve
|
|
|
23
23
|
BATCH_SIZE = 1_000
|
|
24
24
|
|
|
25
25
|
RECORD_MISSING = "chained rows exist but the chain record does not"
|
|
26
|
-
UNCHAINED_ROW = "a contract-3 row outside the chain, written after it started (by clock)"
|
|
26
|
+
UNCHAINED_ROW = "a contract-3+ row outside the chain, written after it started (by clock)"
|
|
27
27
|
|
|
28
28
|
def initialize(entry_class:, keyring:, batch_size: BATCH_SIZE)
|
|
29
29
|
@entry_class = entry_class
|
|
@@ -46,20 +46,26 @@ module Reeve
|
|
|
46
46
|
|
|
47
47
|
attr_reader :entry_class, :keyring, :batch_size, :breaks
|
|
48
48
|
|
|
49
|
+
# The epochs are read after the head: every row the walk visits committed, with any
|
|
50
|
+
# epoch it opened, before that read. They are checked against the head read *after*
|
|
51
|
+
# the walk, which is later than they were read, so an epoch opened by a rotation
|
|
52
|
+
# during the run is never mistaken for one placed beyond the head.
|
|
49
53
|
def verify_chain(record)
|
|
50
54
|
@breaks = []
|
|
51
|
-
|
|
55
|
+
epochs = EpochRules.read(keyring: keyring, entry_class: entry_class)
|
|
56
|
+
walk = walk(record, epochs)
|
|
52
57
|
check_record(record, walk)
|
|
53
58
|
head_at_end = record && read_head
|
|
54
59
|
check_beyond_head(head_at_end || record)
|
|
55
60
|
check_unchained(record)
|
|
61
|
+
epochs.check!(record, head_at_end || record, report: method(:add))
|
|
56
62
|
|
|
57
|
-
result(record, head_at_end, walk.last_sequence)
|
|
63
|
+
result(record, head_at_end, walk.last_sequence, epochs)
|
|
58
64
|
end
|
|
59
65
|
|
|
60
|
-
def walk(record)
|
|
66
|
+
def walk(record, epochs)
|
|
61
67
|
Walk.new(entry_class: entry_class, keyring: keyring, batch_size: batch_size,
|
|
62
|
-
link: record&.genesis_digest, report: method(:add))
|
|
68
|
+
link: record&.genesis_digest, report: method(:add), epochs: epochs)
|
|
63
69
|
.call(record&.head_sequence)
|
|
64
70
|
end
|
|
65
71
|
|
|
@@ -121,15 +127,17 @@ module Reeve
|
|
|
121
127
|
end
|
|
122
128
|
end
|
|
123
129
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
# `epoch` names the key epoch a break is about when it has no sequence (specs/003).
|
|
131
|
+
def add(kind, sequence, detail = nil, invocation_id = nil, key_id = nil, epoch: nil)
|
|
132
|
+
found = { kind: kind, sequence: sequence, detail: detail, invocation_id: invocation_id }
|
|
133
|
+
breaks << Verification::Break.new(**found, key_id: key_id, epoch: epoch)
|
|
127
134
|
end
|
|
128
135
|
|
|
129
|
-
def result(record, head_at_end, last_sequence)
|
|
136
|
+
def result(record, head_at_end, last_sequence, epochs)
|
|
130
137
|
Verification.new(
|
|
131
138
|
status: Verification.status_for(breaks), breaks: breaks,
|
|
132
139
|
covered: covered(record, last_sequence),
|
|
140
|
+
epochs: epochs.summaries, before_first_epoch: epochs.before_first_epoch,
|
|
133
141
|
head_at_start: record&.head_sequence, head_at_end: head_at_end&.head_sequence,
|
|
134
142
|
uncovered_now: count_uncovered, uncovered_at_genesis: record&.pre_chain_count,
|
|
135
143
|
unchained_now: count_unchained, unchained_at_genesis: record&.pre_chain_unchained_count
|
|
@@ -20,11 +20,13 @@ module Reeve
|
|
|
20
20
|
|
|
21
21
|
# `link` is the digest the first row must link to (the genesis), or nil when there
|
|
22
22
|
# is no chain record to take it from. `report` receives (kind, sequence, detail).
|
|
23
|
-
|
|
23
|
+
# `epochs` is the EpochRules the rows are checked against (specs/003 FR-011).
|
|
24
|
+
def initialize(entry_class:, keyring:, batch_size:, link:, report:, epochs:)
|
|
24
25
|
@entry_class = entry_class
|
|
25
26
|
@keyring = keyring
|
|
26
27
|
@batch_size = batch_size
|
|
27
28
|
@report = report
|
|
29
|
+
@epochs = epochs
|
|
28
30
|
@expected = 1
|
|
29
31
|
@last_sequence = 0
|
|
30
32
|
@link = link
|
|
@@ -39,7 +41,7 @@ module Reeve
|
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
42
|
-
attr_reader :entry_class, :keyring, :batch_size
|
|
44
|
+
attr_reader :entry_class, :keyring, :batch_size, :epochs
|
|
43
45
|
|
|
44
46
|
def visit(entry)
|
|
45
47
|
sequence = entry.chain_sequence
|
|
@@ -71,6 +73,10 @@ module Reeve
|
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def verify(entry, previous)
|
|
76
|
+
# A row whose key or epoch contradicts the epoch records is a break on its own; its
|
|
77
|
+
# digest would only repeat it.
|
|
78
|
+
return if epoch_break?(entry)
|
|
79
|
+
|
|
74
80
|
# With no chain record there is no genesis for the first row to link to. That is
|
|
75
81
|
# reported once, as chain_record_missing, not again here as a mismatch.
|
|
76
82
|
return if !@verify_first_link && entry.chain_sequence == 1
|
|
@@ -88,6 +94,14 @@ module Reeve
|
|
|
88
94
|
@report.call(:digest_mismatch, entry.chain_sequence, e.message)
|
|
89
95
|
end
|
|
90
96
|
|
|
97
|
+
def epoch_break?(entry)
|
|
98
|
+
kind, detail = epochs.judge(entry)
|
|
99
|
+
return false unless kind
|
|
100
|
+
|
|
101
|
+
@report.call(kind, entry.chain_sequence, detail)
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
|
|
91
105
|
def each_chained(limit, &block)
|
|
92
106
|
after = nil
|
|
93
107
|
loop do
|
data/lib/reeve/audit/chain.rb
CHANGED
|
@@ -38,7 +38,12 @@ module Reeve
|
|
|
38
38
|
class Chain
|
|
39
39
|
TABLE = "reeve_audit_chains"
|
|
40
40
|
|
|
41
|
-
COLUMNS = %w[chain_sequence chain_key_id chain_digest].freeze
|
|
41
|
+
COLUMNS = %w[chain_sequence chain_key_id chain_digest chain_epoch].freeze
|
|
42
|
+
|
|
43
|
+
EPOCHS_TABLE = "reeve_audit_chain_epochs"
|
|
44
|
+
|
|
45
|
+
# The first contract whose rows name their key epoch.
|
|
46
|
+
EPOCH_CONTRACT = 4
|
|
42
47
|
|
|
43
48
|
# A contract-3 row outside the chain: the walk only reads rows with a sequence, so a
|
|
44
49
|
# row without one is counted here or nowhere. Written once and used by both the
|
|
@@ -55,6 +60,12 @@ module Reeve
|
|
|
55
60
|
Verifier.new(entry_class: entry_class, keyring: keyring, batch_size: batch_size).call
|
|
56
61
|
end
|
|
57
62
|
|
|
63
|
+
# Makes the configured ledger key the key of the latest epoch (specs/003 FR-006) and
|
|
64
|
+
# returns a Rotation saying what happened. `bin/rails reeve:rotate_ledger_key` runs it.
|
|
65
|
+
def self.rotate_key!(accept_unverifiable: false, entry_class: Entry)
|
|
66
|
+
new(entry_class: entry_class).rotate_key!(accept_unverifiable: accept_unverifiable)
|
|
67
|
+
end
|
|
68
|
+
|
|
58
69
|
def initialize(entry_class: Entry, keyring: nil)
|
|
59
70
|
@entry_class = entry_class
|
|
60
71
|
@keyring = keyring
|
|
@@ -77,7 +88,9 @@ module Reeve
|
|
|
77
88
|
insert_once(normalized) do
|
|
78
89
|
head = Head.new(connection: connection, entry_class: entry_class)
|
|
79
90
|
head.start!(signer)
|
|
80
|
-
|
|
91
|
+
schema.explaining do
|
|
92
|
+
entry_class.transaction(requires_new: true) { link!(head, normalized, signer) }
|
|
93
|
+
end
|
|
81
94
|
end
|
|
82
95
|
end
|
|
83
96
|
end
|
|
@@ -95,6 +108,26 @@ module Reeve
|
|
|
95
108
|
end
|
|
96
109
|
end
|
|
97
110
|
|
|
111
|
+
# Starts the chain if it has not started — the genesis needs the key the epoch will
|
|
112
|
+
# open with — then decides under the head's lock, so a write and a rotation never
|
|
113
|
+
# interleave. A refusal writes nothing.
|
|
114
|
+
def rotate_key!(accept_unverifiable: false)
|
|
115
|
+
signer = keyring
|
|
116
|
+
return Rotation.refused(:no_key) unless signer.signing?
|
|
117
|
+
|
|
118
|
+
require_columns!
|
|
119
|
+
with_connection do |connection|
|
|
120
|
+
head = Head.new(connection: connection, entry_class: entry_class)
|
|
121
|
+
head.start!(signer)
|
|
122
|
+
schema.explaining do
|
|
123
|
+
entry_class.transaction(requires_new: true) do
|
|
124
|
+
Rotation.decide(Epochs.new(connection: connection), signer,
|
|
125
|
+
head_sequence: head.lock!, accept_unverifiable: accept_unverifiable)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
98
131
|
private
|
|
99
132
|
|
|
100
133
|
attr_reader :entry_class
|
|
@@ -111,10 +144,18 @@ module Reeve
|
|
|
111
144
|
# column defaults (`truncated: false`) and casts on assignment (an Integer principal_id
|
|
112
145
|
# becomes a String), and the instance is exactly what the database will be given.
|
|
113
146
|
# Digesting the hash a caller passed would sign values that are never stored.
|
|
147
|
+
#
|
|
148
|
+
# A row at contract 4 or later names the key epoch its key opened, opening the next one
|
|
149
|
+
# if the key has none (specs/003 FR-004); a contract-3 row from a custom recorder is
|
|
150
|
+
# chained exactly as 0.6.0 chained it.
|
|
114
151
|
def link!(head, normalized, signer)
|
|
115
152
|
sequence, previous = head.advance!
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
chain = { chain_sequence: sequence, chain_key_id: signer.signing_id, chain_epoch: nil }
|
|
154
|
+
if normalized[:contract_version].to_i >= EPOCH_CONTRACT
|
|
155
|
+
chain[:chain_epoch] = Epochs.new(connection: head.connection)
|
|
156
|
+
.for_signing!(signer, sequence: sequence)
|
|
157
|
+
end
|
|
158
|
+
entry = entry_class.new(normalized.merge(chain))
|
|
118
159
|
entry.chain_digest = Canonical.entry_digest(signer.signing_secret, entry,
|
|
119
160
|
previous_digest: previous)
|
|
120
161
|
entry.save!
|
|
@@ -136,13 +177,11 @@ module Reeve
|
|
|
136
177
|
# A table one contract behind the gem fails here, naming the fix, rather than as an
|
|
137
178
|
# "unknown attribute" or "no such column" from deep inside the write.
|
|
138
179
|
def require_columns!
|
|
139
|
-
|
|
140
|
-
|
|
180
|
+
schema.require_columns!
|
|
181
|
+
end
|
|
141
182
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"older audit-entry contract than this reeve writes (#{CONTRACT_VERSION}). Run " \
|
|
145
|
-
"`bin/rails generate reeve:upgrade` and `bin/rails db:migrate`."
|
|
183
|
+
def schema
|
|
184
|
+
@schema ||= Schema.new(entry_class)
|
|
146
185
|
end
|
|
147
186
|
|
|
148
187
|
def replayed(normalized)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reeve
|
|
4
|
+
module Audit
|
|
5
|
+
# One key epoch: the key that began signing the ledger at `first_sequence` (specs/003
|
|
6
|
+
# data-model "reeve_audit_chain_epochs").
|
|
7
|
+
#
|
|
8
|
+
# Written by Chain — when a key first signs, or by `bin/rails reeve:rotate_ledger_key` —
|
|
9
|
+
# through the ledger's own connection, inside the write that needs it. This model only
|
|
10
|
+
# reads them, for verification, and refuses to change one: an epoch is written once.
|
|
11
|
+
# As with ChainRecord, that protects against the library, not against raw SQL; the
|
|
12
|
+
# verifier reports an epoch changed outside reeve.
|
|
13
|
+
class ChainEpoch < ActiveRecord::Base
|
|
14
|
+
self.table_name = "reeve_audit_chain_epochs"
|
|
15
|
+
|
|
16
|
+
before_update { raise ActiveRecord::ReadOnlyRecord, "a key epoch is written once" }
|
|
17
|
+
before_destroy { throw :abort }
|
|
18
|
+
|
|
19
|
+
def readonly?
|
|
20
|
+
persisted? || super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -44,17 +44,22 @@ module Reeve
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# The record is written through the ledger's own connection (Chain::Head) and read
|
|
47
|
-
# through this model's
|
|
48
|
-
#
|
|
49
|
-
#
|
|
47
|
+
# through this model's — and so are the key epochs (ChainEpoch, specs/003 FR-019a). A
|
|
48
|
+
# host that moves the ledger to a database of its own and leaves either model behind
|
|
49
|
+
# would have verification read the wrong rows and report tampering on every run;
|
|
50
|
+
# refusing, with the fix, is the honest answer.
|
|
50
51
|
def self.require_same_database!(entry_class)
|
|
51
52
|
ledger_database = entry_class.connection_db_config.configuration_hash
|
|
52
|
-
|
|
53
|
+
[self, ChainEpoch].each do |model|
|
|
54
|
+
next if model.connection_db_config.configuration_hash == ledger_database
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
short = model.name.split("::").last
|
|
57
|
+
raise ConfigurationError,
|
|
58
|
+
"#{model.name} is connected to a different database than #{entry_class.name}. " \
|
|
59
|
+
"Connect ChainRecord and ChainEpoch the same way as the ledger (for example " \
|
|
60
|
+
"with the same connects_to) so the chain is read where it is written; #{short} " \
|
|
61
|
+
"is the one that is not."
|
|
62
|
+
end
|
|
58
63
|
end
|
|
59
64
|
end
|
|
60
65
|
end
|
data/lib/reeve/audit.rb
CHANGED
|
@@ -59,8 +59,14 @@ module Reeve
|
|
|
59
59
|
module Audit
|
|
60
60
|
# The version of the audit-entry shape, as documented in
|
|
61
61
|
# specs/001-guardrails-core/contracts/audit-entry.md (FR-015). Adding a nullable
|
|
62
|
-
# column is a MINOR change and leaves this alone
|
|
63
|
-
#
|
|
62
|
+
# column is a MINOR change and leaves this alone — unless what its absence means, or
|
|
63
|
+
# what an existing value covers, differs between rows written before and after it;
|
|
64
|
+
# removing or renaming a column, or changing what a value means, is MAJOR and bumps it.
|
|
65
|
+
#
|
|
66
|
+
# 4 — entries name their key epoch (specs/003). One nullable column, `chain_epoch`, but
|
|
67
|
+
# it is inside the digest, so `chain_digest` on a contract-4 row covers something a
|
|
68
|
+
# contract-3 digest does not, and a NULL epoch means "before key epochs" on a contract-3
|
|
69
|
+
# row where a chained contract-4 row always has one.
|
|
64
70
|
#
|
|
65
71
|
# 3 — entries are chained (specs/002). The three chain columns are nullable, so the
|
|
66
72
|
# shape change is additive, but what a NULL digest means changed: on a contract-3 row
|
|
@@ -71,7 +77,7 @@ module Reeve
|
|
|
71
77
|
# was written NULL on every row regardless of what was passed, so anything mapping
|
|
72
78
|
# version 1 rows could reasonably have read the column as "always empty". The shape
|
|
73
79
|
# did not change; what a value means did.
|
|
74
|
-
CONTRACT_VERSION =
|
|
80
|
+
CONTRACT_VERSION = 4
|
|
75
81
|
|
|
76
82
|
TABLE_NAME = "reeve_audit_entries"
|
|
77
83
|
|
|
@@ -108,9 +114,14 @@ require_relative "audit/chain/keyring"
|
|
|
108
114
|
require_relative "audit/chain/normalizer"
|
|
109
115
|
require_relative "audit/chain/canonical"
|
|
110
116
|
require_relative "audit/chain_record"
|
|
117
|
+
require_relative "audit/chain_epoch"
|
|
111
118
|
require_relative "audit/chain/verification"
|
|
112
119
|
require_relative "audit/chain/report"
|
|
113
120
|
require_relative "audit/chain/head"
|
|
121
|
+
require_relative "audit/chain/epochs"
|
|
122
|
+
require_relative "audit/chain/rotation"
|
|
123
|
+
require_relative "audit/chain/epoch_rules"
|
|
124
|
+
require_relative "audit/chain/schema"
|
|
114
125
|
require_relative "audit/chain/walk"
|
|
115
126
|
require_relative "audit/chain/verifier"
|
|
116
127
|
require_relative "audit/chain"
|
data/lib/reeve/railtie.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Reeve
|
|
4
|
-
# Registers `bin/rails reeve:verify_ledger
|
|
4
|
+
# Registers `bin/rails reeve:verify_ledger` and `bin/rails reeve:rotate_ledger_key`, and
|
|
5
|
+
# does nothing else.
|
|
5
6
|
#
|
|
6
7
|
# Loaded by lib/reeve.rb only when Rails::Railtie is already defined, so `require "reeve"`
|
|
7
8
|
# in a plain Ruby process still loads nothing beyond reeve's own files and its short stdlib
|
data/lib/reeve/tasks.rb
CHANGED
|
@@ -14,6 +14,7 @@ module Reeve
|
|
|
14
14
|
module Tasks
|
|
15
15
|
EXIT = { intact: 0, broken: 1, unverifiable: 2, no_chain: 2 }.freeze
|
|
16
16
|
FORMATS = %w[text json].freeze
|
|
17
|
+
ROTATE_EXIT = { rotated: 0, current: 0, refused: 1 }.freeze
|
|
17
18
|
|
|
18
19
|
module_function
|
|
19
20
|
|
|
@@ -35,6 +36,28 @@ module Reeve
|
|
|
35
36
|
EXIT.fetch(result.status)
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
# Makes the configured ledger key the latest epoch's key (specs/003, contracts/
|
|
40
|
+
# key-rotation.md), writes what happened to `out`, and returns the exit status: 0 rotated
|
|
41
|
+
# or already current, 1 refused.
|
|
42
|
+
#
|
|
43
|
+
# ACCEPT_UNVERIFIABLE=1 opens the epoch even though the outgoing epoch's key is not
|
|
44
|
+
# configured here — for a key that is lost, and only then.
|
|
45
|
+
def rotate_ledger_key(env: ENV, out: $stdout)
|
|
46
|
+
accept = accept_unverifiable_from(env)
|
|
47
|
+
require "reeve/audit"
|
|
48
|
+
rotation = Reeve::Audit::Chain.rotate_key!(accept_unverifiable: accept)
|
|
49
|
+
out.puts(rotation.to_s)
|
|
50
|
+
ROTATE_EXIT.fetch(rotation.status)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def accept_unverifiable_from(env)
|
|
54
|
+
raw = env.fetch("ACCEPT_UNVERIFIABLE", "")
|
|
55
|
+
return false if raw.empty?
|
|
56
|
+
return true if raw == "1"
|
|
57
|
+
|
|
58
|
+
raise ArgumentError, "ACCEPT_UNVERIFIABLE must be 1 or unset, got #{raw.inspect}"
|
|
59
|
+
end
|
|
60
|
+
|
|
38
61
|
def limit_from(env)
|
|
39
62
|
raw = env.fetch("LIMIT", Reeve::Audit::Chain::Verification::DEFAULT_LIMIT.to_s)
|
|
40
63
|
Integer(raw, 10).tap { |limit| raise ArgumentError if limit.negative? }
|
|
@@ -55,6 +78,12 @@ module Reeve
|
|
|
55
78
|
task verify_ledger: :environment do
|
|
56
79
|
exit Reeve::Tasks.verify_ledger
|
|
57
80
|
end
|
|
81
|
+
|
|
82
|
+
desc "Make config.ledger_key the ledger's current key epoch (exit 0 rotated or " \
|
|
83
|
+
"current, 1 refused; ACCEPT_UNVERIFIABLE=1 if the old key is lost)"
|
|
84
|
+
task rotate_ledger_key: :environment do
|
|
85
|
+
exit Reeve::Tasks.rotate_ledger_key
|
|
86
|
+
end
|
|
58
87
|
end
|
|
59
88
|
end
|
|
60
89
|
end
|
|
@@ -46,10 +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
|
+
chain_sequence chain_key_id chain_digest chain_epoch
|
|
50
50
|
].freeze
|
|
51
51
|
|
|
52
|
-
EXPECTED_VERSION =
|
|
52
|
+
EXPECTED_VERSION = 4
|
|
53
53
|
|
|
54
54
|
def initialize(tool: nil, expected: EXPECTED_VERSION, ledger: nil)
|
|
55
55
|
super(tool: tool, ledger: ledger)
|
data/lib/reeve/version.rb
CHANGED
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.7.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-23 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,19 +31,26 @@ 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_key_epochs_to_reeve_audit_entries.rb.tt
|
|
34
35
|
- lib/generators/reeve/upgrade/templates/add_ledger_chain_to_reeve_audit_entries.rb.tt
|
|
35
36
|
- lib/generators/reeve/upgrade/upgrade_generator.rb
|
|
37
|
+
- lib/generators/reeve/upgrade/upgrade_notes.rb
|
|
36
38
|
- lib/reeve.rb
|
|
37
39
|
- lib/reeve/audit.rb
|
|
38
40
|
- lib/reeve/audit/chain.rb
|
|
39
41
|
- lib/reeve/audit/chain/canonical.rb
|
|
42
|
+
- lib/reeve/audit/chain/epoch_rules.rb
|
|
43
|
+
- lib/reeve/audit/chain/epochs.rb
|
|
40
44
|
- lib/reeve/audit/chain/head.rb
|
|
41
45
|
- lib/reeve/audit/chain/keyring.rb
|
|
42
46
|
- lib/reeve/audit/chain/normalizer.rb
|
|
43
47
|
- lib/reeve/audit/chain/report.rb
|
|
48
|
+
- lib/reeve/audit/chain/rotation.rb
|
|
49
|
+
- lib/reeve/audit/chain/schema.rb
|
|
44
50
|
- lib/reeve/audit/chain/verification.rb
|
|
45
51
|
- lib/reeve/audit/chain/verifier.rb
|
|
46
52
|
- lib/reeve/audit/chain/walk.rb
|
|
53
|
+
- lib/reeve/audit/chain_epoch.rb
|
|
47
54
|
- lib/reeve/audit/chain_record.rb
|
|
48
55
|
- lib/reeve/audit/entry.rb
|
|
49
56
|
- lib/reeve/audit/isolated_recorder.rb
|