rails-ai-context 5.36.0 → 5.36.1

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: 8ea836548c12d9e6a022ca0e9cb78f471c2144d5b16197ab9493808209c12f74
4
- data.tar.gz: 1037665358df3ef107d83530dcec2371728b76df61852c51387b2797eb5bac51
3
+ metadata.gz: 7ae3eb5f18027a0abe6cf4a8872f9ecce9fb792109ab243b9b7edbba3558cb6d
4
+ data.tar.gz: c6c7b740f4e18e494a371215c99c05bad848ed43ebf1f84f24020ad4348612ac
5
5
  SHA512:
6
- metadata.gz: 007d9bbd31542ad5caa6847ce83443fa3d793483cbb93aefcd37dc03c4f4cbf536e51dfd4735ddcf27e0f6dda46b052a95f7f6317d2685f2b2cb8769da349819
7
- data.tar.gz: a4815b990a54d309fe3b5a4ce97ab8b7fca7a50841a70825d3aa02378915fe77f0f5465259e3484efb2a8268e959f4877bde376a4617ee461e36e94292eb4301
6
+ metadata.gz: fe22c7af7f32d7aa16abdc3b6534653614f3e38fc25a1406c5e29ab9dbb0cf609878fd633210cb3006352007b156d5eaba887db3d1e9ad53cec0ae57357622c0
7
+ data.tar.gz: 6b0aff79f555f43322eab5c3c81ebcc8a7477a8674891d1f3d2988a47cd23044db5383aacbbd6db3ec15cdeb882dc37b5af4e9cb98c73f1150ba8c73d55cf283
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.36.1] - 2026-10-15
9
+
10
+ ### Fixed
11
+
12
+ - **On PostgreSQL the refusal of a name on the read-only VOLATILE list says the
13
+ list trusts the server's own function or a C function.** 5.36.0 answered "an
14
+ extension's C function", narrower than the rule it applies: any C function of
15
+ that name is trusted, since only a superuser can create one.
16
+ - **The docs say which reads of a `rails_query` call run with no time limit.**
17
+ `docs/TOOLS.md` and `docs/SECURITY.md` said every statement a call sends is
18
+ bounded, and the reads of a setting's own value for the settings note, new in
19
+ 5.36.0, are not. They wait on no lock. `docs/SECURITY.md` also said `PRAGMA
20
+ query_only` is refused by name: a `PRAGMA` statement is no read, and the
21
+ `pragma_query_only` function is the one refused by name.
22
+ - **`docs/TOOLS.md` says when `rails_get_mailers` names where a mailer's actions
23
+ come from.** A mailer with no action of its own names a gem base it reaches
24
+ without passing `ApplicationMailer`, such as `Devise::Mailer`. It names none
25
+ past `ApplicationMailer` and never Rails' own base, on both tiers, as 5.36.0
26
+ does. The page said any gem base was named.
27
+
8
28
  ## [5.36.0] - 2026-10-14
9
29
 
10
30
  ### Security
@@ -323,6 +323,10 @@ Proof sources:
323
323
  11.4 on Rails 7.0 (mysql2), 8.0 and 8.1 (mysql2 and Trilogy), and on
324
324
  PostgreSQL 13, 14, 15, 17 and 18 on Rails 8.0; and the unit suite and the
325
325
  E2E harness on Linux, macOS and Windows.
326
+ In the v5.36.1 release QA: every issue repro of 5.36.0 run again on the
327
+ published code on PostgreSQL 13 to 18, MySQL 8.0 and 8.4 and MariaDB 10.6
328
+ and 11.4, the booted and static mailer lists compared on one app, and the
329
+ unit suite and the E2E harness on Linux, macOS and Windows.
326
330
  2. Non-crash coverage for every built-in tool including `get_view` in
327
331
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
328
332
  (ivar cross-check, render-form detection, partial interfaces) verified
data/docs/SECURITY.md CHANGED
@@ -85,7 +85,7 @@ The textual layer cannot see a whole-row serialisation (`SELECT row_to_json(u.*)
85
85
  - holds a **whole-row reference** (`alias.*`) to a relation that has a secret column - this closes `row_to_json(u)`, `to_json(u)`, `u::text`, `array_agg(u)`, `json_agg(u)`, `hstore(u)`, `concat(u.*)`, `string_agg(u::text, ...)`, a bare `SELECT u`, and the same through a view, subquery or CTE. An alias is read as the plan prints it, so two aliases that differ only by case are two relations. The planner numbers an alias a statement uses twice (`p_1` for the `p` inside a subquery), and that name stands for its own relation alone. The number is taken off only for the members of an Append or a Merge Append, which is how the partitions of one table are named. The refusal names the relation whose row the statement reads;
86
86
  - **uses a secret column** anywhere but as a plain pass-through output - a `ROW(u.*)` the planner expanded into named columns, or a `WHERE` / `ORDER BY` oracle, an ordinal `ORDER BY 3` over a wildcard included. The statement a `postgres_fdw` foreign scan sends to the remote server (the plan's `Remote SQL`) is read the same way, so a condition, a sort or a group on a secret column of a foreign table is refused where the remote server would run it. A column that carries another name on the remote server (the `column_name` option) is told by one more catalog read, made only for a plan that holds a foreign scan. A stated limit: only the remote statement of `postgres_fdw` is read, and another foreign data wrapper prints keys of its own. A join that `postgres_fdw` runs on the remote server has one scan for all its tables, which names them with their aliases in its `Relations` line: they are read from there, so a whole row of one is refused and a column of one is traced and filtered like a local table's;
87
87
  - **passes a secret column** through an intermediate the plan's top node does not name - a `MATERIALIZED` CTE, a subquery scan, or a set operation (`INTERSECT` / `EXCEPT`) - so its value would reach a result column that the output-provenance redaction (Layer 4) cannot pin. These are refused, since no redaction could reach them. A column that a scan hands to an aggregate, a join or a Result node is counted only when that node names it, so `count(*)` over a table with a secret column runs, and so does an `EXISTS` inside an `EXISTS`, whose plan puts a Result over the inner scan;
88
- - names any **VOLATILE function** (`pg_proc.provolatile = 'v'`) outside a short read-only allowlist. `pg_proc` marks a function VOLATILE when its answer may change within one statement, which is as true of a reader of live server state as of a writer, so VOLATILE alone does not mean "changes state". The allowlist holds only built-in functions and extension C functions that write nothing: value generators (`random`, `clock_timestamp`, `gen_random_uuid`, `timeofday`, `uuid_generate_v4`, and `pg_sleep*`, which the statement timeout bounds); the size readers (`pg_total_relation_size`, `pg_relation_size`, `pg_table_size`, `pg_indexes_size`, `pg_database_size`, `pg_tablespace_size`); the lock-table snapshot readers (`pg_lock_status` - what the `pg_locks` view reads - `pg_blocking_pids`, `pg_safe_snapshot_blocking_pids`); and the recovery / WAL-position readers (`pg_is_in_recovery`, `pg_is_wal_replay_paused`, `pg_get_wal_replay_pause_state`, `pg_current_wal_lsn`, `pg_current_wal_insert_lsn`, `pg_current_wal_flush_lsn`, `pg_last_wal_receive_lsn`, `pg_last_wal_replay_lsn`, `pg_last_xact_replay_timestamp`); and `pg_sequence_last_value`, which reads a sequence's stored value the way `SELECT last_value FROM a_sequence` does and is what the `pg_sequences` view reads. A name on the list vouches for the server's own function and an extension's C function alone, such as `uuid_generate_v4`: while the database holds a VOLATILE function of that name in another language outside `pg_catalog`, every call of the name is refused. The functions the statement's text names are checked beside those the plan prints, since a plan prints no row of a `VALUES` list and no `LIMIT` expression. The name after `TABLESAMPLE` is a sampling method, whose handler the statement never calls, so it is not read as a call; the method's arguments and its `REPEATABLE` seed are, as the plan prints them. Anything else VOLATILE is not run, and the refusal says plainly that the function is VOLATILE and outside the read-only list, or, for a name on the list, that the database holds another VOLATILE function of that name. This closes the admin-function family without a name list that has to grow with every PostgreSQL release.
88
+ - names any **VOLATILE function** (`pg_proc.provolatile = 'v'`) outside a short read-only allowlist. `pg_proc` marks a function VOLATILE when its answer may change within one statement, which is as true of a reader of live server state as of a writer, so VOLATILE alone does not mean "changes state". The allowlist holds only built-in functions and extension C functions that write nothing: value generators (`random`, `clock_timestamp`, `gen_random_uuid`, `timeofday`, `uuid_generate_v4`, and `pg_sleep*`, which the statement timeout bounds); the size readers (`pg_total_relation_size`, `pg_relation_size`, `pg_table_size`, `pg_indexes_size`, `pg_database_size`, `pg_tablespace_size`); the lock-table snapshot readers (`pg_lock_status` - what the `pg_locks` view reads - `pg_blocking_pids`, `pg_safe_snapshot_blocking_pids`); and the recovery / WAL-position readers (`pg_is_in_recovery`, `pg_is_wal_replay_paused`, `pg_get_wal_replay_pause_state`, `pg_current_wal_lsn`, `pg_current_wal_insert_lsn`, `pg_current_wal_flush_lsn`, `pg_last_wal_receive_lsn`, `pg_last_wal_replay_lsn`, `pg_last_xact_replay_timestamp`); and `pg_sequence_last_value`, which reads a sequence's stored value the way `SELECT last_value FROM a_sequence` does and is what the `pg_sequences` view reads. A name on the list vouches for the server's own function or a C function alone, such as an extension's `uuid_generate_v4`: while the database holds a VOLATILE function of that name in another language outside `pg_catalog`, every call of the name is refused. The functions the statement's text names are checked beside those the plan prints, since a plan prints no row of a `VALUES` list and no `LIMIT` expression. The name after `TABLESAMPLE` is a sampling method, whose handler the statement never calls, so it is not read as a call; the method's arguments and its `REPEATABLE` seed are, as the plan prints them. Anything else VOLATILE is not run, and the refusal says plainly that the function is VOLATILE and outside the read-only list, or, for a name on the list, that the database holds another VOLATILE function of that name. This closes the admin-function family without a name list that has to grow with every PostgreSQL release.
89
89
 
90
90
  Every statement except `SHOW` is planned, and an `EXPLAIN` is planned as the statement it explains. `EXPLAIN (GENERIC_PLAN)` is planned with that option too, read as the server reads it (its last value, quoted or not), so a statement with `$1` in it is planned as PostgreSQL 16 and later plan it. A statement whose plan cannot be read does not run: when planning times out the answer is the timeout, when planning fails it is the SQL error, and when the server returns no plan the statement is refused.
91
91
 
@@ -148,7 +148,7 @@ What bounds each statement a `rails_query` call sends:
148
148
  | The query, and `explain: true` | `statement_timeout`, a wait on a lock included | the session's statement limit, and `lock_wait_timeout` for a wait on a lock | the child process's deadline |
149
149
  | The reads beside it: the schema read behind the secret column rule, and on PostgreSQL the plan, its `pg_proc` lookup and the column trace | `statement_timeout`, a wait on a lock included | the session's statement limit, and `lock_wait_timeout` for a wait on a lock | no statement limit; a wait on another writer ends at the connection's busy timeout |
150
150
 
151
- The query has `query_timeout` to itself. The reads beside it share a second `query_timeout`, and each is charged the time it ran, so the statements of a call take about twice `query_timeout` at most. Building the app's context, which a cold call does first, is charged to neither, and so are the gate's checks on the statement's text, which the statement length limit bounds. The schema read is a few statements whatever the number of tables: four at most on MySQL and MariaDB, five on PostgreSQL and six on SQLite. On a SQLite file database where a query process can run, two more reads ask what the app's own connection alone holds, so eight reads come before the statement there. On PostgreSQL a plan that holds a foreign scan adds one catalog read. A statement or a view definition that names another schema or database adds the reads of that one.
151
+ The query has `query_timeout` to itself. The reads beside it share a second `query_timeout`, and each is charged the time it ran, so the statements of a call take about twice `query_timeout` at most. On PostgreSQL, MySQL and MariaDB the reads with no bound are of a setting's own value for the settings note, which wait on no lock. Building the app's context, which a cold call does first, is charged to neither, and so are the gate's checks on the statement's text, which the statement length limit bounds. The schema read is a few statements whatever the number of tables: four at most on MySQL and MariaDB, five on PostgreSQL and six on SQLite. On a SQLite file database where a query process can run, two more reads ask what the app's own connection alone holds, so eight reads come before the statement there. On PostgreSQL a plan that holds a foreign scan adds one catalog read. A statement or a view definition that names another schema or database adds the reads of that one.
152
152
 
153
153
  A read that runs out of that time ends the call with an error that says which side of the statement it was on:
154
154
 
@@ -174,7 +174,7 @@ On PostgreSQL and MySQL the query executes inside a transaction, then rolls back
174
174
 
175
175
  On a connection that is already inside a transaction (a tool called from a transactional test, a mounted server inside a request's transaction, a transaction the app opened with a raw `BEGIN` that Active Record does not count) the guard leaves that transaction open as it found it, with its writes. PostgreSQL guards inside a savepoint, chosen by the server's own transaction status, so the outer transaction stays writable. MySQL and MariaDB cannot make an open transaction read-only: the server refuses `SET TRANSACTION READ ONLY` inside one, and a `BEGIN` there would commit it, so no `BEGIN` is sent, the statement is not sent, and the answer is "Query failed: MySQL cannot make a transaction that is already open read-only, so the statement was not sent. Run it outside the transaction." SQLite runs the statement on the app's own connection, so it reads the rows of the open transaction. It runs under `PRAGMA query_only = ON`, and the pragma is put back as it was found.
176
176
 
177
- A statement that reads a setting the guard sets for it answers the guard's value: `statement_timeout` and `transaction_read_only` on PostgreSQL, the statement limit (`max_execution_time` or `max_statement_time`) and `lock_wait_timeout` on MySQL and MariaDB. When the statement reads one of them (`SHOW name`, `@@name`, `@@SESSION.name`, `@@LOCAL.name` or `current_setting('name')`, the name backquoted or not), or a `SHOW VARIABLES` row or MariaDB's `information_schema.SESSION_VARIABLES` names one, the answer ends with a note that says so and gives the session's own value, for example "statement_timeout here is the value rails_query sets for each statement it runs. The session's own: statement_timeout 50s." A name used as an alias or a column, one in a string or a comment, and a global value on MySQL and MariaDB get no note. SQLite's `PRAGMA query_only` is refused by name.
177
+ A statement that reads a setting the guard sets for it answers the guard's value: `statement_timeout` and `transaction_read_only` on PostgreSQL, the statement limit (`max_execution_time` or `max_statement_time`) and `lock_wait_timeout` on MySQL and MariaDB. When the statement reads one of them (`SHOW name`, `@@name`, `@@SESSION.name`, `@@LOCAL.name` or `current_setting('name')`, the name backquoted or not), or a `SHOW VARIABLES` row or MariaDB's `information_schema.SESSION_VARIABLES` names one, the answer ends with a note that says so and gives the session's own value, for example "statement_timeout here is the value rails_query sets for each statement it runs. The session's own: statement_timeout 50s." A name used as an alias or a column, one in a string or a comment, and a global value on MySQL and MariaDB get no note. On SQLite a `PRAGMA` statement is no read, and the `pragma_query_only` function is refused by name.
178
178
 
179
179
  An adapter with no family (SQL Server, Oracle, CockroachDB and any other) is refused: `rails_query` answers with the adapter's name and sends no statement, because it cannot give that database a read-only transaction and a timeout it has verified. The family is read from the database's configuration, so the refusal comes before any connection is opened or verified. CockroachDB's adapter is built on PostgreSQL's, but the adapter's name alone chooses the family, so it is refused too.
180
180
 
data/docs/TOOLS.md CHANGED
@@ -654,7 +654,7 @@ ViewComponent/Phlex components: props, slots, previews, sidecar assets, usage ex
654
654
 
655
655
  ### `rails_get_mailers`
656
656
 
657
- ActionMailer mailers: every mailer class with its delivery actions and delivery method. The actions include the public methods a mailer inherits from the app's own mailers below `ApplicationMailer`, on both tiers. A mailer that declares no action of its own - one taking them from a gem base or a mixin, or one called through class methods - is listed with where its actions come from. A base other mailers inherit from is not one: it leaves the listing, and a line above names the ones left out. Asked for by name, a base answers with what every mailer below it inherits - its layout, helpers, defaults, callbacks and mixins as the app wrote them, the methods it defines - and which mailers inherit it.
657
+ ActionMailer mailers: every mailer class with its delivery actions and delivery method. The actions include the public methods a mailer inherits from the app's own mailers below `ApplicationMailer`, on both tiers. A mailer that declares no action of its own - one taking them from a gem base or a mixin, or one called through class methods - is listed with where its actions come from when that is a gem base it reaches without passing `ApplicationMailer`, such as `Devise::Mailer`, and never Rails' own base. A base other mailers inherit from is not one: it leaves the listing, and a line above names the ones left out. Asked for by name, a base answers with what every mailer below it inherits - its layout, helpers, defaults, callbacks and mixins as the app wrote them, the methods it defines - and which mailers inherit it.
658
658
 
659
659
  Each mailer also shows its own `default`, `layout`, `helper` and action and
660
660
  deliver callbacks as written, the template formats of each action under
@@ -746,7 +746,7 @@ Safe read-only SQL with layered security: the SQL gate, a PostgreSQL plan check
746
746
 
747
747
  It runs on PostgreSQL, MySQL (with MariaDB) and SQLite, each with its own read-only transaction and statement timeout. On any other adapter it answers with that adapter's name and runs nothing. A statement that runs past `query_timeout` is answered as a timeout and any other database error as an SQL error. Both are error responses: `isError` over MCP and a non-zero exit on the CLI. So is a database no connection reaches, which the answer names with what to do about it. So are a locked SQLite database and the refusal in production.
748
748
 
749
- On PostgreSQL, MySQL and MariaDB every statement a call sends has a time limit. The query has `query_timeout` to itself and the reads beside it share a second one, so the statements of a call take about twice `query_timeout` at most. On SQLite the query has `query_timeout`, and the reads beside it run on the app's own connection with no statement limit. A SQLite statement that names something the app's connection alone holds, such as a temp table or an attached database, runs on that connection, and its answer says it ran without a time limit. The [security model](SECURITY.md#layer-2---database-level-read-only) says what bounds each statement and lists the stated limits.
749
+ On PostgreSQL, MySQL and MariaDB every statement a call sends has a time limit, apart from the reads of a setting's own value for the settings note, which wait on no lock. The query has `query_timeout` to itself and the reads beside it share a second one, so the statements of a call take about twice `query_timeout` at most. On SQLite the query has `query_timeout`, and the reads beside it run on the app's own connection with no statement limit. A SQLite statement that names something the app's connection alone holds, such as a temp table or an attached database, runs on that connection, and its answer says it ran without a time limit. The [security model](SECURITY.md#layer-2---database-level-read-only) says what bounds each statement and lists the stated limits.
750
750
 
751
751
  On MySQL, MariaDB and SQLite a statement that names a view whose definition names a secret column is refused. So is a `*` inside a subquery when the statement names a table that holds a secret column, and on SQLite `IN` before a table's name (`x IN t`). On PostgreSQL a materialized view whose definition names a secret column is refused. On SQLite a virtual table that holds a secret column, a shadow table and the page modules are refused, and only the pragma functions that describe the schema or the database file run.
752
752
 
@@ -822,14 +822,17 @@ module RailsAiContext
822
822
  parent_class: inherited_from(walked, outside&.name) }.reject { |_k, v| v.nil? || v == [] }
823
823
  end
824
824
 
825
- # The parent past the app's own mailers that a mailer with no action of its own takes them from. None past the app
826
- # base, whose methods and everything above it are no actions, and none from Rails itself.
827
- def inherited_from(walked, outside)
828
- return nil if walked.any? { |name| ActionResolver.app_base_name?(name, kind: :mailer) }
825
+ # The parent past the app's own mailers that a mailer with no action of its own takes its actions from. None past
826
+ # the app base, whose methods and everything above it are no actions, and none from Rails' own base.
827
+ def inherited_from(walked, parent_name)
828
+ return nil if walked.any? { |name| app_mailer_base?(name) }
829
829
 
830
- name = outside.to_s.delete_prefix("::")
831
- name unless name.empty? || name.start_with?("ActionMailer::", "AbstractController::") ||
832
- ActionResolver.app_base_name?(name, kind: :mailer)
830
+ name = parent_name.to_s.delete_prefix("::")
831
+ name unless name.empty? || name.start_with?("ActionMailer::", "AbstractController::") || app_mailer_base?(name)
832
+ end
833
+
834
+ def app_mailer_base?(name)
835
+ ActionResolver.app_base_name?(name, kind: :mailer)
833
836
  end
834
837
 
835
838
  def callable_class_methods(names)
@@ -1191,7 +1194,7 @@ module RailsAiContext
1191
1194
  # gives it; tagged STATIC, since a gem's base or a mixin can add actions only the booted tier sees.
1192
1195
  def mailer_entry(klass, ancestors)
1193
1196
  # The app base's public methods are no actions, as the booted tier subtracts them; its callbacks still filter.
1194
- action_chain = [ klass, *ancestors.take_while { |declaring| !ActionResolver.app_base_name?(declaring.name, kind: :mailer) } ]
1197
+ action_chain = [ klass, *ancestors.take_while { |declaring| !app_mailer_base?(declaring.name) } ]
1195
1198
  visibility = {}
1196
1199
  action_chain.each do |declaring|
1197
1200
  own = ActionResolver.own_methods(declaring.methods, declaring.name).select { |m| m[:scope] == :instance }
@@ -128,6 +128,8 @@ module RailsAiContext
128
128
  FROM_LIST_CLAUSES = %w[from join on using].freeze
129
129
  CLAUSE_WORDS = %w[select from join on where group order having limit values using returning set union intersect except].freeze
130
130
 
131
+ # A double-quoted bare word, which MySQL under ANSI_QUOTES reads as a name and otherwise as a string.
132
+ BARE_QUOTED_WORD = /\A"\w+"\z/
131
133
  # How a statement reads a session setting by name: SHOW, @@, @@SESSION. or @@LOCAL. (backquoted or not), current_setting('..').
132
134
  SETTING_READ = /(?:\bshow\s+(?!global\b)|@@(?!global\s*\.)(?:(?:session|local)\s*\.\s*)?|current_setting\s*\(\s*')`?(\w+)/i
133
135
  # A list of the session's settings, which names each in its rows or in the filter the statement puts on it.
@@ -335,9 +337,9 @@ module RailsAiContext
335
337
  def self.settings_read(sql, family, rows)
336
338
  text = comment_free(sql, family)
337
339
  code = mask_quoted(text, family) { true }
338
- # MySQL reads a double-quoted span as a string, or under ANSI_QUOTES as a name, which only a bare word can be here.
340
+ # On MySQL a double-quoted span other than a bare word is a string.
339
341
  strings_blanked = mask_quoted(text, family) do |kind, span|
340
- kind == :string || (family.mysql? && span.start_with?('"') && !span.match?(/\A"\w+"\z/))
342
+ kind == :string || (family.mysql? && span.start_with?('"') && !span.match?(BARE_QUOTED_WORD))
341
343
  end
342
344
  read = text.enum_for(:scan, SETTING_READ).filter_map { $~[1] if code[$~.begin(0)] == text[$~.begin(0)] }
343
345
  read += text.scan(/\w+/) + rows.flatten.map(&:to_s) if strings_blanked.match?(SETTINGS_LIST)
@@ -467,7 +469,7 @@ module RailsAiContext
467
469
  return never_read_refusal(m.captures.compact.first)
468
470
  end
469
471
  if (m = listed(SYSTEM_SCHEMAS, mask_quoted(cleaned, :string)))
470
- text_only = ->(kind, span) { kind == :string || (span.start_with?('"') && !span.match?(/\A"\w+"\z/)) }
472
+ text_only = ->(kind, span) { kind == :string || (span.start_with?('"') && !span.match?(BARE_QUOTED_WORD)) }
471
473
  return "#{never_read_refusal(m[1])} A name before a dot is read as that schema, so an alias named `#{m[1]}` needs another name." if
472
474
  listed(SYSTEM_SCHEMAS, mask_quoted(cleaned, &text_only))
473
475
 
@@ -964,7 +966,7 @@ module RailsAiContext
964
966
  end
965
967
  if (fn = volatile_function(called)) && PG_VOLATILE_ALLOWLIST.include?(fn)
966
968
  "Blocked: the database holds a VOLATILE function named #{fn} that rails_query cannot vouch for, so rails_query runs no " \
967
- "call of that name. Its list of read-only VOLATILE functions trusts the name for the server's own function or an extension's C function alone."
969
+ "call of that name. Its list of read-only VOLATILE functions trusts the name for the server's own function or a C function alone."
968
970
  elsif fn
969
971
  "Blocked: function #{fn} is VOLATILE (pg_proc.provolatile = 'v') and not on " \
970
972
  "rails_query's list of read-only VOLATILE functions, so it is not run. Some VOLATILE " \
@@ -1078,7 +1080,7 @@ module RailsAiContext
1078
1080
  exprs.each_with_object(Set.new) { |expr, names| expr.scan(/([A-Za-z_]\w*)\s*\(/) { |(fn)| names << fn.downcase } }
1079
1081
  end
1080
1082
 
1081
- # A name on the read-only list vouches for the server's own function and C code alone: while the database holds a
1083
+ # A name on the read-only list vouches for the server's own function or a C function alone: while the database holds a
1082
1084
  # VOLATILE function of that name in another language outside pg_catalog, every call of the name is refused.
1083
1085
  def volatile_function(called)
1084
1086
  return nil if called.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAiContext
4
- VERSION = "5.36.0"
4
+ VERSION = "5.36.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-ai-context
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.36.0
4
+ version: 5.36.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crisnahine