reeve 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aaf976c70abfbb4d56236acface7e1ef95a4e8c177452e4ef918ca22f45933d0
4
- data.tar.gz: 9076bbad0582db6c4cda3c38dc89f97840dc3a7e2dbb99ca98a16c5b642ece57
3
+ metadata.gz: 225a220bbfd6168a26fdf67a816c8a580ca57ba996f97c7d1c9317755252d497
4
+ data.tar.gz: a5633fe99dedbc03796c82abf560eca68187fa783e5a9ceef1fb764b9a47d351
5
5
  SHA512:
6
- metadata.gz: 7957d35a7e9df67acaa1c6babcd18b1d2340ffd63595fc88a11e16bd5c81cfa78ddb6b6cf17742f74b1f300d5e35058442d124fe77d6cc7051005e3ed4d5b837
7
- data.tar.gz: 543bb727d1abf718053d21a72fe3d3eb62b9e82ebe5a3658046b7d80eb8c897c91e76da7d6c55842f1ee0545b76af94a2d1127ffa2fc814a88bbadab3c827a01
6
+ metadata.gz: c9bccface22cb54d935720a927dd5aff7c61582403f6d0aeada860be4f178d110d4ac94801a84d6a15993c0abad8a6b14ee0c1058311809ddf5575ddb57d1741
7
+ data.tar.gz: e43b1e0a1fb8900a45cb998997b45152ab726b21961c2b86ea5dea0cc0adb3e0f9e015cf7aed8cdcd6e39aa28d7e9c827ff85d11653b8aa5fab769844b1cdecb
data/CHANGELOG.md CHANGED
@@ -4,6 +4,81 @@ All notable changes are recorded here. This project follows [Semantic
4
4
  Versioning](https://semver.org), with one rule specific to what it does — see
5
5
  [Versioning policy](#versioning-policy).
6
6
 
7
+ ## [0.3.0] - 2026-08-17
8
+
9
+ Three of the limitations 0.1.0 shipped knowingly are now closed, and the audit-entry
10
+ contract is unchanged — **no migration is required to take this release**.
11
+
12
+ Two of the "Known limitations" under 0.1.0 below no longer hold, and are left in place
13
+ because that section records what was true of that release:
14
+
15
+ - *"A transaction the host wraps around an invocation takes the ledger row with it"* —
16
+ `IsolatedRecorder` closes it where the database has concurrent writers.
17
+ - *"CI runs SQLite only"* — the suite now runs on PostgreSQL and MySQL too.
18
+
19
+ Still open from that list: an unscoped fetch by id disclosing existence, a scope-less type
20
+ needing to be tied to its policy, `AuditCoverage` proving one invocation rather than every
21
+ one, and the fast-mcp adapter needing Ruby 3.1+.
22
+
23
+ ### Added
24
+
25
+ - **`Reeve::Audit::IsolatedRecorder`** — the ledger row on a connection of its own, which
26
+ closes the R5 limitation the first release documented rather than fixed. The default
27
+ recorder writes in a savepoint: the trace survives a rollback by the *tool*, but a
28
+ transaction the *host* wrapped around the whole invocation takes the row with it, and
29
+ the calls most worth auditing are exactly the ones that rolled back. A transaction on
30
+ one connection cannot roll back an INSERT committed on another.
31
+
32
+ ```ruby
33
+ config.audit_recorder = Reeve::Audit::IsolatedRecorder
34
+ ```
35
+
36
+ Opt-in rather than the default, because it cannot work everywhere the default does: on
37
+ SQLite an open transaction holds the database write lock, so a second connection would
38
+ block until it timed out and fail every guarded call. It raises `ConfigurationError`
39
+ there rather than degrading quietly, and `IsolatedRecorder.available?` answers the
40
+ question in an initializer for a host that develops on SQLite and deploys on PostgreSQL.
41
+ The default recorder's warning now names it.
42
+
43
+ Worth deciding knowingly: this also keeps the row when the host rolls back an
44
+ invocation it meant to undo entirely. A ledger of what was *attempted* is the intended
45
+ reading of Constitution II, but it is a choice.
46
+
47
+ - **CI runs the suite on PostgreSQL and MySQL as well as SQLite.** The gem claims to work
48
+ on every database ActiveRecord supports — the ledger stores `invocation_id` as a string
49
+ rather than a native `uuid` precisely so that holds — and until now that claim had only
50
+ ever been executed against SQLite. The three engines disagree about exactly what this
51
+ gem leans on: what `t.json` becomes, what a boolean literal is, what
52
+ `change_column_default` does to a NOT NULL column, and how a savepoint behaves inside a
53
+ transaction the caller opened. Select an engine with `DB=postgresql` or `DB=mysql2`
54
+ (`DB=postgresql bundle install` first — the drivers are `install_if`-gated so the
55
+ default install needs no native client libraries).
56
+
57
+ All 545 examples pass on all three. The one portability defect it found was in the
58
+ `reeve:upgrade` spec rather than the library: a fixture row inserted `0` into boolean
59
+ columns, which is a boolean on SQLite and MySQL and a type error on PostgreSQL. The
60
+ `change_column_default` the upgrade migration depends on behaves identically on all
61
+ three, which was the specific thing worth knowing after shipping it against SQLite only.
62
+
63
+ - **`bin/rails generate reeve:upgrade`** — brings an existing ledger up to the current
64
+ audit-entry contract. It asks the table which columns it has and emits only the steps
65
+ that are missing, so it is a no-op on a current ledger and safe to run twice before
66
+ migrating. Replaces the hand-written `add_column` the 0.2.0 notes had to give.
67
+ ([#11](https://github.com/vicmaster/reeve/issues/11))
68
+ - `contracts/audit-entry.md` states the rule the upgrade path depends on: **changes to the
69
+ table must be additive**, and a new column must be nullable or carry a default that is
70
+ true of the rows already written. An append-only ledger has no honest value to backfill
71
+ into a historical row. A change that cannot be expressed additively is a new table, not
72
+ a new version of this one.
73
+
74
+ ### Fixed
75
+
76
+ - `Checks::ContractVersion` names `reeve:upgrade` when the table is missing a column. It
77
+ said `reeve:install`, which is wrong for exactly the case it fires in: the table exists,
78
+ so Rails resolves that migration by name and either emits nothing or offers to overwrite
79
+ one that has already run — which does not touch the database and destroys the record of
80
+ what was applied. The install generator now says the same thing on its way out.
81
+
7
82
  ## [0.2.0] - 2026-08-12
8
83
 
9
84
  Everything here came out of running 0.1.0 against a real Rails 8.1 application rather than
data/README.md CHANGED
@@ -7,7 +7,7 @@ A Ruby gem that makes it safe for a Rails application to expose MCP tools to AI
7
7
  declarative per-record authorization, an append-only audit ledger, and a testing kit that
8
8
  proves both hold.
9
9
 
10
- > **Status: 0.2.0.** Read the known limitations in
10
+ > **Status: 0.3.0.** Read the known limitations in
11
11
  > [CHANGELOG.md](CHANGELOG.md) before adopting it — particularly the one about a
12
12
  > transaction wrapped around an invocation, if your application wraps requests in one.
13
13
 
@@ -136,6 +136,20 @@ A call whose tool raised is still recorded — that trace is the one most worth
136
136
  call that cannot be recorded fails, unless the host has explicitly opted into
137
137
  `audit_failure_mode = :warn`.
138
138
 
139
+ If your application wraps invocations in a transaction — a controller that opens one per
140
+ request, a job runner — a rollback takes the ledger row with it, and exactly the calls
141
+ worth auditing are the ones that rolled back. Write on a connection the rollback cannot
142
+ reach:
143
+
144
+ ```ruby
145
+ config.audit_recorder = Reeve::Audit::IsolatedRecorder
146
+ ```
147
+
148
+ It needs a database with concurrent writers and refuses on SQLite, where an open
149
+ transaction holds the write lock; `IsolatedRecorder.available?` lets you branch if you
150
+ develop on one and deploy on the other. The default recorder warns when it detects the
151
+ case rather than failing silently.
152
+
139
153
  ## Provable in CI, in whichever framework you already use
140
154
 
141
155
  All the logic lives in framework-neutral checks. RSpec and Minitest are thin front-ends
@@ -39,6 +39,10 @@ module Reeve
39
39
  2. Run `bin/rails db:migrate` to create the audit ledger.
40
40
 
41
41
  Then add `guard_with SomePolicy` to a tool.
42
+
43
+ Upgrading an existing ledger later? Use `rails g reeve:upgrade`, not this
44
+ generator — Rails resolves a migration by name, so re-running install cannot
45
+ deliver a shape change to a table that already exists.
42
46
  NEXT
43
47
  end
44
48
  end
@@ -53,6 +53,25 @@ Reeve.configure do |config|
53
53
  # guarantee for availability.
54
54
  config.audit_failure_mode = :fail
55
55
 
56
+ # ---------------------------------------------------------------------------
57
+ # Does your application wrap the invocation in a transaction?
58
+ #
59
+ # A controller that opens one per request, a job runner, a test suite with
60
+ # transactional fixtures — if that transaction rolls back, it takes the ledger
61
+ # row with it, and the default recorder can only warn about it.
62
+ #
63
+ # config.audit_recorder = Reeve::Audit::IsolatedRecorder
64
+ #
65
+ # writes on a connection of its own, which a rollback on yours cannot reach.
66
+ # It needs a database with concurrent writers and refuses to run on SQLite, so
67
+ # a host that develops on SQLite and deploys on PostgreSQL branches:
68
+ #
69
+ # config.audit_recorder =
70
+ # Reeve::Audit::IsolatedRecorder.available? ? Reeve::Audit::IsolatedRecorder
71
+ # : Reeve::Audit::Recorder
72
+ #
73
+ # Left unset, which resolves to Reeve::Audit::Recorder.
74
+
56
75
  # Cap on how many record identifiers one entry stores. The true count is
57
76
  # always recorded, and the entry says when the list was truncated.
58
77
  config.max_recorded_ids = 1000
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Audit-entry contract 1 → 2. Generated by `bin/rails generate reeve:upgrade`.
4
+ #
5
+ # Adds the column that lets a row name the shape it was written under. Contract 1 wrote
6
+ # `metadata` NULL whatever the caller passed, and contract 2 writes what was passed, so
7
+ # without this column a NULL `metadata` is ambiguous: "never recorded" and "the caller
8
+ # passed none" look identical, separable only by knowing when the gem was deployed.
9
+ #
10
+ # `default: 1` is not a fallback, it is the truth about the rows already in the table:
11
+ # every one of them was written under contract 1. The default is then dropped, so a row
12
+ # inserted from here on has to say what it is — which matches the shape a fresh
13
+ # `reeve:install` creates, and keeps the two paths converged.
14
+ class AddContractVersionToReeveAuditEntries < ActiveRecord::Migration[7.0]
15
+ def change
16
+ add_column :reeve_audit_entries, :contract_version, :integer, null: false, default: 1
17
+ change_column_default :reeve_audit_entries, :contract_version, from: 1, to: nil
18
+ end
19
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Reeve
7
+ module Generators
8
+ # `bin/rails generate reeve:upgrade` — brings an existing ledger up to the audit-entry
9
+ # contract this version of the gem writes.
10
+ #
11
+ # Why this exists as a second generator rather than as a re-run of `reeve:install`:
12
+ # Rails resolves a migration by name, so `install` on a host that already has
13
+ # `create_reeve_audit_entries` reports `identical` and emits nothing, or — once the
14
+ # template has changed, which is exactly the upgrade case — offers to overwrite a
15
+ # migration that has already run. Overwriting an applied migration does not touch the
16
+ # database and destroys the record of what was applied, so the one thing a host must
17
+ # never be told to do about an out-of-date ledger is run the install generator again.
18
+ #
19
+ # What it emits is decided by the table, not by a version number the host might have
20
+ # recorded wrongly or not at all: each step declares the columns it adds, and a step
21
+ # whose columns are all present has already been applied. That makes running this on a
22
+ # current ledger a no-op it can report rather than a duplicate migration.
23
+ class UpgradeGenerator < Rails::Generators::Base
24
+ include ActiveRecord::Generators::Migration
25
+
26
+ TABLE = "reeve_audit_entries"
27
+
28
+ # The ladder, oldest first. One entry per contract bump that touches the table.
29
+ #
30
+ # `adds` is what makes a step detectable, so it must name every column the step
31
+ # creates. A step that changes a column without adding one cannot be detected this
32
+ # way and needs its own predicate — see the additive-only rule in
33
+ # contracts/audit-entry.md, which exists so that stays hypothetical: an append-only
34
+ # ledger cannot be backfilled, so a column that is not additive has no honest value
35
+ # to give the rows already written.
36
+ STEPS = [
37
+ {
38
+ contract: 2,
39
+ adds: %w[contract_version],
40
+ template: "add_contract_version_to_reeve_audit_entries.rb.tt",
41
+ destination: "add_contract_version_to_reeve_audit_entries.rb"
42
+ }
43
+ ].freeze
44
+
45
+ source_root File.expand_path("templates", __dir__)
46
+
47
+ desc "Brings an existing reeve audit ledger up to the current audit-entry contract."
48
+
49
+ def verify_ledger_exists
50
+ return if table_exists?
51
+
52
+ say <<~MISSING
53
+
54
+ There is no #{TABLE} table to upgrade.
55
+
56
+ This generator is for a ledger that already exists. For a new one:
57
+
58
+ bin/rails generate reeve:install
59
+ bin/rails db:migrate
60
+ MISSING
61
+
62
+ # Not an exception: "you wanted the other generator" is a normal thing to get
63
+ # wrong, and a backtrace would suggest reeve broke.
64
+ @halted = true
65
+ end
66
+
67
+ def create_upgrade_migrations
68
+ return if @halted
69
+
70
+ pending = STEPS.reject { |step| applied?(step) }
71
+ return @nothing_to_do = true if pending.empty?
72
+
73
+ pending.each do |step|
74
+ migration_template(step[:template], "db/migrate/#{step[:destination]}")
75
+ end
76
+
77
+ @emitted = pending
78
+ end
79
+
80
+ def report_next_step
81
+ return if @halted
82
+
83
+ say(@nothing_to_do ? up_to_date_message : pending_message)
84
+ end
85
+
86
+ private
87
+
88
+ def up_to_date_message
89
+ <<~CURRENT
90
+
91
+ #{TABLE} is already at audit-entry contract #{STEPS.last[:contract]}. Nothing to do.
92
+ CURRENT
93
+ end
94
+
95
+ def pending_message
96
+ <<~NEXT
97
+
98
+ #{@emitted.size} migration#{'s' if @emitted.size != 1} written, taking the ledger
99
+ to audit-entry contract #{@emitted.last[:contract]}. Run:
100
+
101
+ bin/rails db:migrate
102
+
103
+ Existing rows are stamped with the contract they were actually written under,
104
+ not with the current one.
105
+ NEXT
106
+ end
107
+
108
+ # Asked of the table rather than of a version the host recorded: the table is the
109
+ # thing being changed, and it is the only party that cannot be out of date about
110
+ # itself.
111
+ def applied?(step)
112
+ (step[:adds] - existing_columns).empty?
113
+ end
114
+
115
+ def existing_columns
116
+ @existing_columns ||= connection.columns(TABLE).map(&:name)
117
+ end
118
+
119
+ def table_exists?
120
+ connection.table_exists?(TABLE)
121
+ rescue StandardError
122
+ false
123
+ end
124
+
125
+ def connection
126
+ ActiveRecord::Base.connection
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reeve
4
+ module Audit
5
+ # The ledger row, written on a connection of its own.
6
+ #
7
+ # `Recorder` writes in a savepoint, which protects the trace from a transaction the
8
+ # *tool* opens and rolls back. It cannot protect the trace from a transaction the
9
+ # *host* opened around the whole invocation — a controller that wraps each request in
10
+ # one, a job runner, a test suite with transactional fixtures. A savepoint released
11
+ # into that transaction goes down with it, and the envelope has no way to learn
12
+ # otherwise: the write reported success, the invocation returned records, and no row
13
+ # survives to say so. Constitution II says every call leaves a trace; on one
14
+ # connection that is not achievable, only detectable, which is why `Recorder` warns.
15
+ #
16
+ # A second connection is the only thing that actually closes it. A transaction on
17
+ # connection A cannot roll back an INSERT committed on connection B, so the row lands
18
+ # whatever the host's transaction does next.
19
+ #
20
+ # config.audit_recorder = Reeve::Audit::IsolatedRecorder
21
+ #
22
+ # == Why this is not the default
23
+ #
24
+ # It cannot be. On SQLite the host's open transaction holds the database write lock,
25
+ # so the second connection blocks until it times out and every guarded call fails —
26
+ # the isolation is not merely unavailable there, it is actively worse than the
27
+ # savepoint. Rather than degrade quietly on the database most hosts develop against,
28
+ # this refuses to run on SQLite and says why. `Recorder` remains the default, and a
29
+ # host that needs durability under a wrapping transaction opts in on a database where
30
+ # opting in means something.
31
+ #
32
+ # It also costs a connection per pool, and moves the ledger write outside the host's
33
+ # transaction in both directions: the row survives a rollback, which is the point,
34
+ # and it also survives a rollback of an invocation the host *meant* to undo entirely.
35
+ # A ledger of what was attempted is the intended reading of Constitution II, but it is
36
+ # a choice, and it should be made rather than inherited.
37
+ class IsolatedRecorder < Recorder
38
+ UNSUPPORTED_ADAPTERS = %w[sqlite sqlite3].freeze
39
+
40
+ class << self
41
+ def record(attributes)
42
+ new.record(attributes)
43
+ end
44
+
45
+ # True when a second connection can actually be used here. Lets a host branch in
46
+ # an initializer — SQLite in development, PostgreSQL in production — rather than
47
+ # discovering the answer on the first guarded call.
48
+ def available?
49
+ verify_supported!
50
+ true
51
+ rescue Reeve::Error
52
+ false
53
+ end
54
+
55
+ def verify_supported!
56
+ adapter = IsolatedEntry.adapter_name
57
+ return unless UNSUPPORTED_ADAPTERS.include?(adapter.to_s.downcase)
58
+
59
+ raise ConfigurationError,
60
+ "Reeve::Audit::IsolatedRecorder cannot be used on #{adapter}: an open " \
61
+ "transaction holds the database write lock, so a second connection " \
62
+ "blocks until it times out and every guarded call fails. Use " \
63
+ "Reeve::Audit::Recorder here — it warns when a host transaction is " \
64
+ "wrapped around an invocation — and configure this recorder only where " \
65
+ "the database supports concurrent writers."
66
+ end
67
+ end
68
+
69
+ def initialize(config: nil)
70
+ super(entry_class: IsolatedEntry, config: config)
71
+ end
72
+
73
+ # Checked out and returned around each write rather than held: the pool exists to
74
+ # keep the ledger off the host's connection, not to keep a connection per thread
75
+ # alive for the life of the process.
76
+ def record(attributes)
77
+ self.class.verify_supported!
78
+ IsolatedEntry.connection_pool.with_connection { super }
79
+ end
80
+
81
+ private
82
+
83
+ # The savepoint `Recorder` uses is what makes the trace survive the *tool's* own
84
+ # rollback, and that is still worth having here: this connection is not in a
85
+ # transaction the host controls, but the tool body may have opened one on the main
86
+ # connection, and a single INSERT wants the same all-or-nothing treatment either
87
+ # way. Nothing about it is inherited from the host's transaction.
88
+ #
89
+ # The warning the parent emits is correctly silent here: it asks whether *this*
90
+ # connection has a transaction open, and this one never does.
91
+ def enclosing_transaction?
92
+ false
93
+ end
94
+ end
95
+
96
+ # The same table and the same rules, on a pool of its own.
97
+ #
98
+ # A subclass rather than a second model so that validations, readonly-once-persisted
99
+ # and the destroy guard are defined in exactly one place. The table has no
100
+ # inheritance column, so ActiveRecord adds no type condition and this reads and writes
101
+ # the same rows `Entry` does.
102
+ class IsolatedEntry < Entry
103
+ class << self
104
+ def adapter_name
105
+ establish_own_connection!
106
+ connection_pool.db_config.adapter
107
+ rescue StandardError => e
108
+ raise Error, "could not reach the audit ledger on its own connection: " \
109
+ "#{e.class}: #{e.message}"
110
+ end
111
+
112
+ def connection_pool
113
+ establish_own_connection!
114
+ super
115
+ end
116
+
117
+ private
118
+
119
+ # Idempotent, and deliberately lazy: a Rails initializer naming this recorder runs
120
+ # before the database is necessarily reachable, so the pool is built on first use
121
+ # rather than at configure time.
122
+ def establish_own_connection!
123
+ return if @own_connection
124
+
125
+ establish_connection(::ActiveRecord::Base.connection_db_config)
126
+ @own_connection = true
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -26,10 +26,14 @@ module Reeve
26
26
  # A genuinely independent write needs a second connection, and that is not portable:
27
27
  # on SQLite the enclosing transaction holds the write lock, so a second connection
28
28
  # blocks until it times out. Rather than fail every call on the databases where
29
- # isolation is impossible, the recorder detects the enclosing transaction and warns
30
- # that the guarantee is suspended for that call. A host that needs durability under a
31
- # wrapping transaction supplies its own `audit_recorder` — writing to a separate
32
- # connection, a queue, or an append-only log which is what that setting is for.
29
+ # isolation is impossible, this recorder detects the enclosing transaction and warns
30
+ # that the guarantee is suspended for that call.
31
+ #
32
+ # `Reeve::Audit::IsolatedRecorder` is the answer where the database has concurrent
33
+ # writers: same rows, same redaction, on a pool of its own, so a rollback on the
34
+ # host's connection cannot reach it. It is opt-in rather than the default precisely
35
+ # because it cannot work everywhere this one does. A host with a different answer
36
+ # again — a queue, an append-only log — still supplies its own `audit_recorder`.
33
37
  #
34
38
  # The other known limit, unchanged: on a single connection there is a narrow window
35
39
  # where the tool's data commits and the ledger write then fails. The caller learns by
@@ -161,8 +165,9 @@ module Reeve
161
165
 
162
166
  message = "reeve: invocation #{invocation_id} was recorded inside a transaction " \
163
167
  "the host opened around it, so the ledger row will be rolled back with " \
164
- "it. Configure Reeve.config.audit_recorder with a recorder that writes " \
165
- "outside this transaction if the trace must survive."
168
+ "it. Set Reeve.config.audit_recorder = Reeve::Audit::IsolatedRecorder " \
169
+ "to write on a connection the transaction cannot reach (needs a " \
170
+ "database with concurrent writers; not SQLite)."
166
171
  logger = config.logger
167
172
  logger ? logger.warn(message) : Kernel.warn(message)
168
173
  end
data/lib/reeve/audit.rb CHANGED
@@ -92,6 +92,7 @@ end
92
92
  require_relative "audit/redactor"
93
93
  require_relative "audit/entry"
94
94
  require_relative "audit/recorder"
95
+ require_relative "audit/isolated_recorder"
95
96
  require_relative "audit/query"
96
97
 
97
98
  Reeve::Audit.install!
@@ -79,10 +79,14 @@ module Reeve
79
79
  )
80
80
  end
81
81
 
82
+ # Names the generator that actually fixes this. It used to say `reeve:install`,
83
+ # which is wrong for the case this failure describes: the table exists, so Rails
84
+ # resolves that migration by name and either emits nothing or offers to overwrite
85
+ # one that has already run.
82
86
  def missing_columns(missing)
83
87
  failed(
84
88
  "expected the ledger to implement audit-entry contract version #{@expected}, " \
85
- "but #{TABLE} is missing: #{missing.join(', ')} — run `rails g reeve:install` " \
89
+ "but #{TABLE} is missing: #{missing.join(', ')} — run `rails g reeve:upgrade` " \
86
90
  "and migrate",
87
91
  version: @expected, missing: missing
88
92
  )
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.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.0
4
+ version: 0.3.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-12 00:00:00.000000000 Z
11
+ date: 2026-08-17 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)
@@ -30,9 +30,12 @@ files:
30
30
  - lib/generators/reeve/install/install_generator.rb
31
31
  - lib/generators/reeve/install/templates/create_audit_entries.rb.tt
32
32
  - lib/generators/reeve/install/templates/initializer.rb.tt
33
+ - lib/generators/reeve/upgrade/templates/add_contract_version_to_reeve_audit_entries.rb.tt
34
+ - lib/generators/reeve/upgrade/upgrade_generator.rb
33
35
  - lib/reeve.rb
34
36
  - lib/reeve/audit.rb
35
37
  - lib/reeve/audit/entry.rb
38
+ - lib/reeve/audit/isolated_recorder.rb
36
39
  - lib/reeve/audit/query.rb
37
40
  - lib/reeve/audit/recorder.rb
38
41
  - lib/reeve/audit/redactor.rb