rails-ai-context 5.35.1 → 5.36.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +375 -1
  3. data/CONTRIBUTING.md +2 -1
  4. data/docs/COMPATIBILITY.md +15 -1
  5. data/docs/SECURITY.md +70 -40
  6. data/docs/TOOLS.md +23 -10
  7. data/docs/TROUBLESHOOTING.md +33 -6
  8. data/lib/rails_ai_context/database_family/mysql_runtime.rb +19 -5
  9. data/lib/rails_ai_context/database_family/postgresql_plan.rb +35 -16
  10. data/lib/rails_ai_context/database_family/postgresql_runtime.rb +43 -8
  11. data/lib/rails_ai_context/database_family/sqlite_runtime.rb +47 -13
  12. data/lib/rails_ai_context/database_family.rb +36 -10
  13. data/lib/rails_ai_context/introspector.rb +12 -0
  14. data/lib/rails_ai_context/introspectors/declared_constant.rb +19 -7
  15. data/lib/rails_ai_context/introspectors/eager_load.rb +12 -2
  16. data/lib/rails_ai_context/introspectors/job_introspector.rb +60 -20
  17. data/lib/rails_ai_context/introspectors/model_introspector.rb +18 -23
  18. data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +10 -1
  19. data/lib/rails_ai_context/introspectors/performance_introspector.rb +0 -1
  20. data/lib/rails_ai_context/introspectors/schema_introspector.rb +12 -1
  21. data/lib/rails_ai_context/payload.rb +12 -3
  22. data/lib/rails_ai_context/secret_columns.rb +120 -72
  23. data/lib/rails_ai_context/serializers/markdown_serializer.rb +1 -1
  24. data/lib/rails_ai_context/sql_gate.rb +183 -38
  25. data/lib/rails_ai_context/tools/get_context.rb +4 -4
  26. data/lib/rails_ai_context/tools/get_controllers.rb +5 -1
  27. data/lib/rails_ai_context/tools/get_conventions.rb +10 -5
  28. data/lib/rails_ai_context/tools/get_schema.rb +35 -11
  29. data/lib/rails_ai_context/tools/onboard.rb +3 -6
  30. data/lib/rails_ai_context/tools/query.rb +13 -3
  31. data/lib/rails_ai_context/tools/runtime_info.rb +23 -14
  32. data/lib/rails_ai_context/tools/safe_call.rb +2 -3
  33. data/lib/rails_ai_context/tools/session_context.rb +2 -3
  34. data/lib/rails_ai_context/version.rb +1 -1
  35. metadata +1 -23
  36. data/.rspec +0 -3
  37. data/.rubocop.yml +0 -16
  38. data/.ruby-version +0 -1
  39. data/AGENTS.md +0 -15
  40. data/CLAUDE.md +0 -3
  41. data/CODE_OF_CONDUCT.md +0 -44
  42. data/CONTEXT.md +0 -242
  43. data/Rakefile +0 -80
  44. data/docs/RELEASING.md +0 -70
  45. data/docs/_config.yml +0 -15
  46. data/docs/_includes/head-custom.html +0 -16
  47. data/docs/adr/0001-own-listener-registration.md +0 -9
  48. data/docs/adr/0002-static-tier-declared-not-detected.md +0 -5
  49. data/docs/adr/0003-shared-tool-cache-semantics.md +0 -31
  50. data/docs/adr/0004-install-e2e-stays-example-rich.md +0 -28
  51. data/docs/adr/0008-query-runs-only-where-it-can-guard.md +0 -38
  52. data/docs/adr/0009-dbhub-shapes-in-ruby.md +0 -26
  53. data/docs/adr/0010-one-config-tool-with-sections.md +0 -32
  54. data/docs/agents/domain.md +0 -38
  55. data/docs/agents/issue-tracker.md +0 -45
  56. data/docs/agents/triage-labels.md +0 -15
  57. data/server.json +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 534fe60bd5d8bcd7bc0e2df04cbaed12794c259bc663e969625dd6ccd2437237
4
- data.tar.gz: 0a5d91ff87c7c960539e6e60f3fea7d238cf3fcd79dc56905fea7a248b9d912a
3
+ metadata.gz: 8ea836548c12d9e6a022ca0e9cb78f471c2144d5b16197ab9493808209c12f74
4
+ data.tar.gz: 1037665358df3ef107d83530dcec2371728b76df61852c51387b2797eb5bac51
5
5
  SHA512:
6
- metadata.gz: e44f58f7a5728ab9cbb04429f49cfe4abd685ce37c6fa6cd2b59e30afdb27443a11db5e9a07733bc135572a5876993a1449ef04bfba206c15f6ff71b4b870baf
7
- data.tar.gz: d689733a334294b5f22a9ca839ee6171f881b2bcd7fdf3818097166ce8a2989ef5ceefd702b357d436c4796e7a3f93c8a11ce4adb3de85f9a8fe7191193153c5
6
+ metadata.gz: 007d9bbd31542ad5caa6847ce83443fa3d793483cbb93aefcd37dc03c4f4cbf536e51dfd4735ddcf27e0f6dda46b052a95f7f6317d2685f2b2cb8769da349819
7
+ data.tar.gz: a4815b990a54d309fe3b5a4ce97ab8b7fca7a50841a70825d3aa02378915fe77f0f5465259e3484efb2a8268e959f4877bde376a4617ee461e36e94292eb4301
data/CHANGELOG.md CHANGED
@@ -5,6 +5,380 @@ 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.0] - 2026-10-14
9
+
10
+ ### Security
11
+
12
+ - **On PostgreSQL a name on the read-only VOLATILE list vouches for the server's
13
+ own function and an extension's C function alone.** The list trusted a name in
14
+ any schema, so a VOLATILE function the app defined under one of those names
15
+ ran. While the database holds a VOLATILE function of a listed name in another
16
+ language outside `pg_catalog`, every call of the name is now refused, and the
17
+ refusal says so. 5.35.2 and earlier versions had the gap.
18
+ - **On PostgreSQL 18 a set operation over a secret column is refused, as it is
19
+ on older servers.** PostgreSQL 18 plans a set operation over the tables
20
+ themselves, and the plan check that refuses a secret column passed through a
21
+ set operation did not read that shape. A set operation now counts every column
22
+ its inputs hand up as a use of that column. 5.35.2 and earlier versions had
23
+ the gap on PostgreSQL 18.
24
+
25
+ ### Changed
26
+
27
+ - **On SQLite a call of `changes()`, `total_changes()` or
28
+ `last_insert_rowid()` is refused also where the app has a table of the same
29
+ name.** The counter answers for the connection that runs the statement,
30
+ which is `rails_query`'s own. 5.35.1 and 5.35.2 ran such a call when a table
31
+ carried the counter's name. Read the counter on the app's own connection, in
32
+ `bin/rails runner` or the console.
33
+ - **The packaged gem holds only what a user of the gem runs or reads.** That is
34
+ `lib/`, `exe/`, `app/`, `config/`, the README, the changelog, the license,
35
+ `SECURITY.md`, the user docs in `docs/`, and the files those docs link to
36
+ (`CONTRIBUTING.md` and three ADRs), so every link works in the unpacked gem.
37
+ 5.35.2 also packed the files that serve the repository alone: `AGENTS.md`,
38
+ `CLAUDE.md`, `CONTEXT.md`, `CODE_OF_CONDUCT.md`, `docs/RELEASING.md`, the
39
+ other ADRs, `docs/agents/`, the docs site config, `.rspec`, `.rubocop.yml`,
40
+ `.ruby-version`, `Rakefile` and `server.json`. A tool that indexes an app's
41
+ bundle could read the two assistant files as instructions about the app.
42
+
43
+ ### Fixed
44
+
45
+ - **On PostgreSQL a read with `TABLESAMPLE` runs.** The name of the sampling
46
+ method (`BERNOULLI`, `SYSTEM`) was read as a call of a VOLATILE function, and
47
+ the statement was refused. The statement never calls it, so it is no longer
48
+ read as a call. A VOLATILE function called anywhere in the statement, one
49
+ named like a sampling method included, is still refused. 5.35.2 refused the
50
+ read.
51
+ - **On PostgreSQL an `EXISTS` inside an `EXISTS` over a table with a secret
52
+ column runs.** The plan puts a Result node over the inner scan, and the plan
53
+ check counted every column of that scan as passed through, so the statement
54
+ was refused with a sentence saying it passed the secret column on. A Result
55
+ node is now read like a join: a column counts only where the Result names
56
+ it. A plain read of a few columns under a condition that holds no column
57
+ (`WHERE EXISTS (...)`) runs for the same reason. 5.35.2 refused them.
58
+ - **On PostgreSQL `EXPLAIN (GENERIC_PLAN)` answers the plan.** The plan check
59
+ planned the statement under it without the option, so a statement with `$1` in
60
+ it answered an SQL error. It is now planned with the option, read as the
61
+ server reads it: its last value counts, quoted or not. A server older than
62
+ PostgreSQL 16 has no such option and still answers its error. 5.35.2 answered
63
+ the error.
64
+ - **On PostgreSQL the `pg_sequences` view can be read.** It reads each
65
+ sequence's value through `pg_sequence_last_value`, a VOLATILE function that
66
+ writes nothing, and the statement was refused. The function now joins the
67
+ list of read-only VOLATILE functions. `nextval`, `setval`, `currval` and
68
+ `lastval` are still refused. 5.35.2 refused the view.
69
+ - **A query leaves a transaction the app opened with a raw `BEGIN` open, with
70
+ its writes.** Active Record does not count such a transaction, so on
71
+ PostgreSQL the query sent its own `BEGIN` and then `ROLLBACK`, which ended
72
+ the app's transaction and threw its writes away; the answer also missed the
73
+ rows the transaction had written. PostgreSQL now reads the server's own
74
+ transaction status and guards inside a savepoint there, under the same
75
+ read-only transaction and statement timeout. On MySQL and MariaDB the answer
76
+ is the refusal an open transaction gets ("MySQL cannot make a transaction
77
+ that is already open read-only"), where 5.35.2 answered the server's error
78
+ 1568, and no `BEGIN`, which would commit the app's transaction, is sent. On
79
+ a SQLite file the statement runs on the app's own connection and reads the
80
+ transaction's rows, where 5.35.2 answered from the file without them.
81
+ 5.35.2 and every earlier version ended the transaction on PostgreSQL.
82
+ - **On PostgreSQL a statement another session cancels is answered as the SQL
83
+ error it is.** A `pg_cancel_backend` from another session raises the same
84
+ error class as the statement timeout, and 5.35.2 answered "Query exceeded <N>
85
+ second timeout". The server's message tells the two apart, so a cancel now
86
+ answers `SQL error: PG::QueryCanceled: ERROR: canceling statement due to user
87
+ request`. A real timeout keeps its sentence. A server that writes its messages
88
+ in another language still answers a cancel as the timeout.
89
+ - **A query that reads a setting `rails_query` sets for each statement says so,
90
+ and gives the session's own value.** `SHOW statement_timeout` answered the
91
+ bound the tool set for the statement (`2s`) with nothing to say it was not the
92
+ session's (`50s`), and so did `transaction_read_only` on PostgreSQL, and the
93
+ statement limit (`max_execution_time` or `max_statement_time`) and
94
+ `lock_wait_timeout` on MySQL and MariaDB, by `@@` (the name backquoted or
95
+ not), by `SHOW VARIABLES` or by MariaDB's
96
+ `information_schema.SESSION_VARIABLES`. The answer now ends with a note, for
97
+ example "statement_timeout here is the value rails_query sets for each
98
+ statement it runs. The session's own: statement_timeout 50s." A name used as
99
+ an alias or a column, one in a string or a comment, and a global value on
100
+ MySQL and MariaDB get no note. 5.35.2 and earlier gave no note.
101
+ - **On PostgreSQL 13 and 14 a temporary table runs, with its secret column
102
+ filtered.** Before PostgreSQL 15 a plan names the session's temporary schema
103
+ by its number (`pg_temp_3`), where the column reads name it `pg_temp`, so
104
+ 5.35.2 refused every statement over a temporary table there as one whose
105
+ columns could not be read. A whole row of a temporary table that holds a
106
+ secret column stays refused.
107
+ - **On SQLite a column whose own name starts with `pragma_` runs.** A
108
+ `pragma_*` name is now refused where SQLite reads it as the pragma function:
109
+ called, or where a table stands (after `FROM`, `JOIN` or `IN`, after a comma
110
+ of a `FROM` list, inside a parenthesis that opens one, or after a schema and a
111
+ dot there), bare, quoted or as a string. Anywhere else it is a column's or an
112
+ alias's name, and the statement runs. 5.35.1 and 5.35.2 refused such a name
113
+ wherever it stood, so `SELECT id FROM t WHERE pragma_flag = 1` was refused.
114
+ - **On SQLite a secret column name covers only the columns SQLite reads it as.**
115
+ SQLite folds ASCII letters alone, so `"é"` and `"É"` are two columns, and the
116
+ secret column rule now compares names that way there. With `"É"` in
117
+ `query_redacted_columns`, `SELECT "é" FROM t` runs and `SELECT *` filters `É`
118
+ alone; every earlier version refused `é` too and filtered both. A name is
119
+ still read with every letter folded for what it means, so a column that folds
120
+ to a built-in name or one the heuristic flags stays secret, and so does one a
121
+ configured name folds to when no column spells that name as SQLite folds it.
122
+ PostgreSQL, MySQL and MariaDB fold every letter as before.
123
+ - **On SQLite a statement that names a view over a pragma function or a
124
+ counter that tells of the connection is refused, by the view and the
125
+ function.** The view's definition runs on `rails_query`'s own connection, so
126
+ `CREATE VIEW v AS SELECT name FROM pragma_database_list` answered without
127
+ the app's attached databases, and a view calling `changes()` answered for the
128
+ wrong connection, both as a success. 5.35.1 and 5.35.2 refused the function
129
+ written in the statement alone. A view over a pragma function that describes
130
+ the schema or the database file, such as `pragma_table_info`, runs.
131
+ - **Every preset names each database's adapter and lists the databases.** On
132
+ `preset: standard`, which runs no `multi_database` introspector, the static
133
+ tier called the primary's adapter `unknown` in `rails_onboard` and in the
134
+ generated files, named no adapter beside a second database, and left the `-
135
+ Databases:` line out of CLAUDE.md and `.claude/rules/rails-context.md`. The
136
+ context now carries the app databases list on every preset, and where the
137
+ `multi_database` introspector failed, so these answer what `preset: full`
138
+ answers. 5.35.2 and earlier versions had the gap.
139
+ - **A model whose class body raises is named with what it raised.** Ruby
140
+ registers a class before its body runs, so a model file that raised partway
141
+ down its class was listed as `[VERIFIED]` with only what ran before the
142
+ raise, and with no word that loading it failed. It is now named with the
143
+ error, as a file that raises before its class opens has been since 5.35.2.
144
+ 5.35.2 and earlier versions listed it.
145
+ - **A booted model keeps its STI hierarchy while nothing has queried its
146
+ database.** A model whose table is in a second database, and so not in
147
+ `db/schema.rb`, lost its STI parent, children and type column until a first
148
+ query connected that database. The table is now read as not known to lack the
149
+ type column, as the static tier reads it, and a model with no STI parent or
150
+ child still answers no STI. An abstract base, such as the class a second
151
+ database connects through, is no STI parent. 5.35.2 and earlier versions had
152
+ the gap.
153
+ - **The static mailer list adds the actions a mailer inherits from the app's own
154
+ mailers.** A `ShopMailer < BaseMailer` listed only the actions in its own
155
+ file, where Rails delivers the parent's public methods too and the booted tier
156
+ lists them. Each name now takes the visibility the nearest class gives it, so
157
+ a method the child makes private, under `private` or with `private :name`,
158
+ stays out. The methods of `ApplicationMailer` itself stay out too, as the
159
+ booted tier leaves them out, and on both tiers a mailer with no action of its
160
+ own names where they come from only for a parent past the app's own mailers,
161
+ such as `Devise::Mailer`, and none past `ApplicationMailer`. 5.35.2 and
162
+ earlier versions had the gap.
163
+ - **`rails_get_schema` says when two databases share `db/schema.rb`.** Rails
164
+ dumps both the first database and the one named `primary` to that file, so it
165
+ holds whichever was dumped last. A table of the other database was called
166
+ missing from the connected one, with advice to run `rails db:migrate`, which
167
+ changes nothing. The answer now says "_main and primary both dump to
168
+ db/schema.rb, so it may describe primary. Not in main: events._", and asked
169
+ for that table it says the same, with no word of a migration. 5.35.2 and
170
+ earlier versions had the gap.
171
+ - **The conventions section says how many entries each of its lists of five
172
+ leaves out.** `rails_get_config(section: "conventions")` shows the first five
173
+ authorization checks, denials, flash notices, flash alerts and not-found
174
+ patterns, and now ends a longer list with `- _...and N more._`. Every earlier
175
+ version cut each list at five and said nothing about the rest.
176
+ - **`rails_runtime_info` shows Sidekiq's queue counts as the app's only when
177
+ Sidekiq is the Active Job adapter.** Under another adapter the jobs section
178
+ says live queue stats are only read from Sidekiq and names that adapter. When
179
+ the app also has Sidekiq workers of its own, Sidekiq's counts follow under a
180
+ `### Sidekiq` heading that says they are those workers' counts. Since 4.3.1
181
+ any app with Sidekiq loaded got Sidekiq's counts under the adapter's name,
182
+ where they read as that adapter's queue.
183
+
184
+ ## [5.35.2] - 2026-10-14
185
+
186
+ ### Security
187
+
188
+ - **On MySQL and MariaDB a quote character inside a string does not change
189
+ which databases a statement is read to name.** The secret column rule reads
190
+ the columns of every database a statement names. In 5.35.1, the first release
191
+ with that read, a quote character inside a string could keep the rule from
192
+ seeing that the statement named another database. A secret column of a table
193
+ or of a view in that database then came back. Each name before a dot is now
194
+ read backward from its dot, so nothing written before the name changes how it
195
+ is read. The refusals are the ones 5.35.1 gives. This gap is in 5.35.1
196
+ alone: earlier versions read no other database, which the 5.35.1 notes cover.
197
+ - **On MySQL and MariaDB a `[` outside a string or a quoted name is refused.**
198
+ MariaDB reads `[name]` as a quoted name when the session's sql_mode holds
199
+ `MSSQL`, and the statement checks read a bracket as an ordinary character. In
200
+ a session with that mode the checks and the server could read one statement
201
+ two ways. `rails_query` does not read the session's mode, so it now refuses
202
+ the bracket on both servers. 5.35.1 and every earlier version had the gap,
203
+ on an app whose connection runs in that mode.
204
+ - **On MySQL and MariaDB a query leaves no value behind on the app's
205
+ connection.** A user variable assignment (`:=`) and `LAST_INSERT_ID` called
206
+ with an argument each set a value that stayed on the pooled connection after
207
+ the query returned, which a read-only transaction does not stop. Both are now
208
+ refused. 5.35.1 and every earlier version ran them.
209
+ - **On PostgreSQL what a foreign scan sends to the remote server is read like
210
+ what the plan runs itself.** A condition, a sort or a group on a secret
211
+ column of a `postgres_fdw` foreign table ran on the remote server, where the
212
+ plan checks did not read it. So a statement could test the column's value,
213
+ also through a view that renames the column and under a `column_name`
214
+ option. The remote statement the plan prints is now read, and such a
215
+ statement answers "Blocked: the query's plan uses the secret column
216
+ `<column>`". Fetching the column returns `[FILTERED]` as before. A statement
217
+ whose plan holds a foreign scan costs one more catalog read. Only the remote
218
+ statement of `postgres_fdw` is read: another foreign data wrapper prints
219
+ keys of its own. 5.35.1 and every earlier version had the gap.
220
+ - **On PostgreSQL a join that the remote server runs is traced to its
221
+ tables.** When `postgres_fdw` sends a whole join to the remote server, the
222
+ plan names the tables with their aliases in one line of the scan, which the
223
+ plan checks did not read, so the secret column rule did not reach those
224
+ tables. The tables and aliases of that line are now read like any scan's: a
225
+ secret column is `[FILTERED]` in place and a whole row is refused, as for a
226
+ local table. 5.35.1 and every earlier version had the gap.
227
+ - **On PostgreSQL the check for a secret column passed through a subquery
228
+ reads a name with two spaces in a row or a dotted capital I.** In 5.35.1
229
+ that check missed a secret column whose name holds either one. A statement
230
+ that passes such a column through a subquery, a CTE or a set operation is
231
+ now refused as for any other secret column. 5.35.0 refused it, so the gap is
232
+ in 5.35.1 alone.
233
+ - **On SQLite a virtual table built on a relation that is not read is refused
234
+ however its definition is written.** 5.35.1 read a relation's name in the
235
+ `content=` option of a full-text table and in the table a vocabulary lists.
236
+ A virtual table that named the relation another way ran, and returned the
237
+ rows or words of a refused view, a shadow table or the page modules. The
238
+ module and every argument can now name a relation, except the columns a
239
+ full-text table (fts3, fts4, fts5) declares and the type an `fts5vocab`
240
+ table ends with (`row`, `col` or `instance`). An argument with `=` is read
241
+ whole, whatever its name, and comments are taken out first. The module is
242
+ read after the table's own name, so a name that spells a module changes
243
+ nothing: 5.35.1 read such a name as the module, and the rule for a virtual
244
+ table that holds a secret column did not reach that table. The answers are
245
+ the ones 5.35.1 gives. The gap needs a virtual table the app itself created.
246
+ 5.35.0 and earlier returned such a table's rows too.
247
+ - **On SQLite a relation whose name holds an apostrophe is matched where it
248
+ is written in single quotes.** SQLite reads a name in single quotes where a
249
+ table is expected, with the apostrophe doubled inside. The secret column
250
+ rule spelled a name in double quotes and in backticks and missed that
251
+ spelling, so a statement or a definition that used it was not held to the
252
+ rule for that relation. 5.35.1 and every earlier version had the gap, on an
253
+ app with a relation so named.
254
+
255
+ ### Changed
256
+
257
+ The three refusals below go out under a patch number. Each closes a guard gap,
258
+ and the maintainer chose 5.35.2 for them, outside step 1 of `docs/RELEASING.md`.
259
+
260
+ - **On MySQL and MariaDB a statement with a user variable assignment (`:=`) is
261
+ refused.** The answer is "Blocked: a user variable assignment (`:=`) is not
262
+ run. It leaves a value on the pooled connection after the query returns,
263
+ which a read-only transaction does not stop." A statement that numbers its
264
+ rows with `@n := @n + 1` ran before and is refused now. Use a window
265
+ function, `ROW_NUMBER() OVER (...)`, which MySQL 8 and MariaDB 10.2 and
266
+ later have. A `:=` inside a string or a quoted name is data and runs, with
267
+ one case apart: a statement with a backslash before a quote inside a string.
268
+ The second reading of a backslash ends the string at that quote, and where
269
+ that leaves the `:=` outside a string the statement is refused. Write the
270
+ quote doubled.
271
+ - **On MySQL and MariaDB `LAST_INSERT_ID` with an argument is refused.** The
272
+ answer is "Blocked: LAST_INSERT_ID with an argument is not run. It sets a
273
+ value that stays on the pooled connection after the query returns, which a
274
+ read-only transaction does not stop. LAST_INSERT_ID() with no argument
275
+ runs." The call is matched on the raw text, like the session functions, so
276
+ it is refused inside a string too.
277
+ - **On MySQL and MariaDB a statement with a `[` outside a string or a quoted
278
+ name is refused.** The answer is "Blocked: a `[` outside a string or a
279
+ quoted name is not run. MariaDB reads it as the start of a quoted name when
280
+ the session's sql_mode holds MSSQL, and rails_query does not read the
281
+ session's mode. Quote a name in backticks." MySQL, and MariaDB outside that
282
+ mode, run no statement that holds one. In a session with that mode, write
283
+ `` `name` `` for `[name]`. A `[` inside a string or a backticked name runs,
284
+ with one case apart: a statement with a backslash before a quote inside a
285
+ string, as in `'O\'Brien'` beside `REGEXP '[a-z]'`. The second reading of a
286
+ backslash ends the string at that quote, and where that leaves the `[`
287
+ outside a string the statement is refused. Such a statement ran before.
288
+ Write the quote doubled.
289
+
290
+ ### Fixed
291
+
292
+ - **A write that follows a `WITH` clause is refused as the write it is.**
293
+ `WITH ... INSERT INTO ...` answered "Blocked: SELECT INTO creates a table".
294
+ It now answers "Blocked: contains INSERT", and `REPLACE` and `MERGE` after a
295
+ `WITH` clause are named the same way. A real `SELECT ... INTO` keeps its
296
+ sentence.
297
+ - **A refusal names a view, a table and a relation as the database spells
298
+ them.** It named them case folded, with a run of spaces as one space, so a
299
+ view called `Hq Odd View` was named `hq odd view`, which no relation is
300
+ called. Two names that differ by a run of spaces alone are one name to the
301
+ rule, and the refusal names the first of them it read.
302
+ - **A view refused over a table that is not read is said to name it.** The
303
+ answer ended "its definition reads `<table>`, which is not read", and the
304
+ rule tests whether the definition names the table. It now ends "its
305
+ definition names `<table>`, which is not read".
306
+ - **The answer for a view that leads too deep drops its count.** It reads "The
307
+ view `<view>` leads through more databases than rails_query follows, so the
308
+ secret columns it could read cannot be told and no statement that names it
309
+ runs." The "(3 rounds)" after "follows" told a user nothing.
310
+ - **The note for a row limit of one says "1 row shown".** It said "1 rows
311
+ shown; the query returned more".
312
+ - **On PostgreSQL a sequence can be queried.** `SELECT last_value FROM
313
+ <sequence>` and `SELECT * FROM <sequence>` ran on 5.35.0, and 5.35.1 answered
314
+ that the sequence's columns could not be read. A sequence is now read like a
315
+ table that holds no secret column.
316
+ - **On PostgreSQL a whole row of a table with no secret column runs when
317
+ another table shares its alias or its name with a number after it.** 5.35.1
318
+ refused such a statement when a subquery used the same alias for a table
319
+ that holds a secret column, or when a table named like `name_2` stood beside
320
+ it, and the refusal named the other table. 5.35.0 ran it. The number the
321
+ planner adds to an alias is now taken off for the members of an Append or a
322
+ Merge Append alone, and a refusal over a whole row names the relation whose
323
+ row the statement reads.
324
+ - **On PostgreSQL a block comment inside a block comment is read as the server
325
+ reads it.** PostgreSQL nests block comments, and the gate ended one at the
326
+ first `*/`, so `SELECT 1 /* a /* b */ c */ AS n` answered PostgreSQL's
327
+ syntax error. It now runs.
328
+ - **On SQLite a statement that runs past `query_timeout` answers the timeout
329
+ when a writer takes the lock while it runs.** It answered that the database
330
+ was locked and the query did not run. The query process now takes its read
331
+ lock before it prepares the statement, so no lock stops the statement from
332
+ there on. The locked answer is given for a lock the query process is still
333
+ waiting on when it gives up or when the deadline passes.
334
+ - **On a SQLite file database a statement that names something the app's
335
+ connection alone holds runs on that connection.** While that connection
336
+ holds a temp object or an attachment, a statement runs there when it names a
337
+ temp object, `sqlite_temp_master` or `sqlite_temp_schema`, the `temp` schema
338
+ or an attached database, or calls a pragma function (`pragma_table_info(...)`,
339
+ `pragma_table_list` and the rest) or `dbstat`, since an argument can name any
340
+ of them.
341
+ The query process has none of them, so it answered an empty list, the rows of
342
+ a main table of the same name, or "unknown database". The answer now ends
343
+ with the note that the query ran without a time limit. A name is matched
344
+ anywhere in the statement, in any letter case and inside a string too.
345
+ Finding this out takes two more reads before a statement on a file database
346
+ where a query process can run: eight where there were six.
347
+ - **The full generated file lists the first database's recent migrations
348
+ under its own name.** With a first database not named `primary` and another
349
+ one that is, the heading read `### Recent Migrations: primary`. It now names
350
+ the first database, as in `### Recent Migrations: animals`.
351
+ - **The database marks and the first database's own name do not depend on the
352
+ preset.** On `preset: standard`, or any `introspectors` list without
353
+ `multi_database`, `rails_get_schema`, `rails_onboard`,
354
+ `rails_performance_check` and the generated files lost the mark of a
355
+ database another environment configures, such as `cache (production only)`,
356
+ and called a first database with another name `primary`. They now read the
357
+ app databases list there, as on `full`, and `rails_onboard` names a
358
+ secondary's adapter.
359
+ - **The model, controller, job, mailer and channel lists hold a class only
360
+ while its own constant names it.** A class whose constant was removed stays
361
+ loaded until the garbage collector takes it, so a list could hold it in one
362
+ run and lose it in the next. Such a class is now left out. A constant still
363
+ waiting to autoload counts as naming its class. The lists of a class's
364
+ children follow the same rule: a model's STI children, the classes a base job
365
+ or mailer is inherited by, and the abstract bases.
366
+ - **A model file that raises before its class opens costs that model alone.**
367
+ A failing `require` at the top of the file, or a raise above the class line,
368
+ made the whole models section answer an error. The file is now named with
369
+ what it raised, and the other models stay listed.
370
+ - **The refusal over a secret column says where `query_allowed_columns` is
371
+ set on either install.** It said to add the name "in an initializer", which
372
+ does nothing on a standalone install. It now ends "add "x" to
373
+ query_allowed_columns in .rails-ai-context.yml (or in
374
+ config/initializers/rails_ai_context.rb where the app bundles the gem)."
375
+ - **`rails_runtime_info` says when table sizes or index usage could not be
376
+ read.** A read that failed or ran out of time left its part of the database
377
+ section out with no word. The section now says "_Table sizes were not read:
378
+ the read failed or took longer than query_timeout._", and at `detail:
379
+ "full"` "_Index usage was not read: the read failed or took longer than
380
+ query_timeout._"
381
+
8
382
  ## [5.35.1] - 2026-10-13
9
383
 
10
384
  ### Security
@@ -8242,7 +8616,7 @@ Every introspector that scans `.rb` files has been migrated from regex-based pat
8242
8616
 
8243
8617
  ### Added - 8 new introspectors closing RAILS_NERVOUS_SYSTEM.md gaps
8244
8618
 
8245
- An audit against [`RAILS_NERVOUS_SYSTEM.md`](RAILS_NERVOUS_SYSTEM.md) identified 9 framework sections where introspection was missing or partial. This release ships the 8 introspectors needed to close them (the 9th - §11 Query interface - was already covered by `:conventions` via `load_async` / `.async_*` scanning). All are wired into `PRESETS[:full]`. The gem now exposes **39 introspectors** (up from 31).
8619
+ An audit against `RAILS_NERVOUS_SYSTEM.md` identified 9 framework sections where introspection was missing or partial. This release ships the 8 introspectors needed to close them (the 9th - §11 Query interface - was already covered by `:conventions` via `load_async` / `.async_*` scanning). All are wired into `PRESETS[:full]`. The gem now exposes **39 introspectors** (up from 31).
8246
8620
 
8247
8621
  - **`InitializerIntrospector` (`:initializers`, §2).** Enumerates `Rails.application.initializers` - every initializer's name, owner, declared `before:` / `after:` ordering edges, and the `source_location` of its block. Also summarizes each `config/initializers/*.rb` file (initializer count + the top `config.*` setters it touches) so AI can jump straight to user-owned boot code.
8248
8622
  - **`AutoloadIntrospector` (`:autoload`, §3).** Zeitwerk presence, both `Rails.autoloaders.main` and `.once` with their collapsed dirs + ignored paths + root dirs, raw `autoload_paths` / `autoload_once_paths` / `eager_load_paths`, the resolved `eager_load` boolean, plus custom inflection rules extracted from `config/initializers/*.rb` (`inflect.acronym` / `.plural` / `.singular` / `.irregular` / `.uncountable` / `.human`). Paths are root-relative.
data/CONTRIBUTING.md CHANGED
@@ -106,9 +106,10 @@ RAC_TEST_MYSQL_URL=trilogy://root:secret@127.0.0.1:3306/rac \
106
106
  BUNDLE_GEMFILE=gemfiles/database_servers.gemfile bundle exec rspec spec/lib/rails_ai_context/database_family/database_servers_spec.rb
107
107
  ```
108
108
 
109
- The specs create and drop tables and views whose names start with `rac_`, so point them at a scratch database.
109
+ The specs create and drop tables and views whose names start with `rac_`, and leave the `postgres_fdw` extension installed on PostgreSQL, so point them at a scratch database.
110
110
  CI's `database_servers` job runs them on PostgreSQL 16 in every leg. Rails 8.0 runs two legs: MySQL 8.0 with MariaDB 10.6, and MySQL 8.4 with MariaDB 11.4. Rails 7.0 runs MySQL 8.0 with MariaDB 10.6, and Rails 8.1 runs MySQL 8.4 with MariaDB 11.4.
111
111
  Rails 7.0 has no Trilogy adapter, so its leg runs PostgreSQL and mysql2 only.
112
+ The `postgresql_versions` job runs the PostgreSQL specs on PostgreSQL 13, 14, 15, 17 and 18 on Rails 8.0, since the plan checks read the plan the server prints.
112
113
  The job builds mysql2 against MariaDB's client library (`libmariadb-dev`, through `BUNDLE_BUILD__MYSQL2`), which signs in to MySQL and to MariaDB. A MySQL client from 9.0 on cannot sign in to MariaDB.
113
114
 
114
115
  ### Static fixture
@@ -312,6 +312,17 @@ Proof sources:
312
312
  the tool's own checks timed on Ruby 3.4 for statements up to a megabyte
313
313
  and on Ruby 3.1 for statements of 50 kilobytes; and the unit suite and the
314
314
  E2E harness on Linux, macOS and Windows.
315
+ In the v5.35.2 closing review: the statement files of the v5.35.1 QA sent
316
+ through the tool again on PostgreSQL 16, MySQL 8.4, MariaDB 11.4 and SQLite,
317
+ on the v5.35.1 code and on the fixed code, with every changed answer traced
318
+ to its fix; and for each fix an example that fails on the v5.35.1 code, on a
319
+ real server where the fix is a server's.
320
+ In the v5.36.0 release QA: for each fix an example that fails on the v5.35.2
321
+ code, on a real server where the fix is a server's; `rails_query` through
322
+ its tool call in CI on PostgreSQL 16, MySQL 8.0 and 8.4 and MariaDB 10.6 and
323
+ 11.4 on Rails 7.0 (mysql2), 8.0 and 8.1 (mysql2 and Trilogy), and on
324
+ PostgreSQL 13, 14, 15, 17 and 18 on Rails 8.0; and the unit suite and the
325
+ E2E harness on Linux, macOS and Windows.
315
326
  2. Non-crash coverage for every built-in tool including `get_view` in
316
327
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
317
328
  (ivar cross-check, render-form detection, partial interfaces) verified
@@ -358,7 +369,10 @@ row estimates and index usage say they are not available there. CI's
358
369
  8.4 and MariaDB 10.6 and 11.4 on Rails 8.0, through mysql2 and Trilogy. It also
359
370
  runs PostgreSQL 16 with MySQL 8.0 and MariaDB 10.6 on Rails 7.0, which has no
360
371
  Trilogy adapter, and with MySQL 8.4 and MariaDB 11.4 on Rails 8.1; see
361
- [CONTRIBUTING.md](../CONTRIBUTING.md#real-database-servers).
372
+ [CONTRIBUTING.md](../CONTRIBUTING.md#real-database-servers). The
373
+ `postgresql_versions` job runs the PostgreSQL examples on PostgreSQL 13, 14,
374
+ 15, 17 and 18 as well, since the plan checks read the plan the server prints and
375
+ its shape changes between major versions.
362
376
 
363
377
  Postgres itself - live query safety (read-only transactions, the
364
378
  `BLOCKED_FUNCTIONS` guard against `pg_read_file`/`dblink`/`COPY ... PROGRAM`,