admin_suite 0.4.0 → 0.6.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/CHANGELOG.md +203 -18
  4. data/CONTRIBUTING.md +9 -4
  5. data/README.md +21 -5
  6. data/app/assets/vendor/chart.umd.min.js +14 -0
  7. data/app/controllers/admin_suite/application_controller.rb +12 -11
  8. data/app/controllers/admin_suite/mcp_controller.rb +36 -0
  9. data/app/controllers/admin_suite/resources_controller.rb +71 -17
  10. data/app/helpers/admin_suite/base_helper.rb +187 -10
  11. data/app/javascript/admin_suite_application.js +3 -0
  12. data/app/javascript/controllers/admin_suite/chart_controller.js +173 -0
  13. data/app/views/admin_suite/panels/_chart.html.erb +158 -18
  14. data/app/views/admin_suite/resources/index.html.erb +25 -5
  15. data/app/views/admin_suite/shared/_sidebar.html.erb +14 -8
  16. data/app/views/layouts/admin_suite/application.html.erb +6 -0
  17. data/config/routes.rb +8 -0
  18. data/lib/admin/base/filter_builder.rb +48 -5
  19. data/lib/admin/base/resource.rb +46 -32
  20. data/lib/admin_suite/auth/host_user.rb +42 -0
  21. data/lib/admin_suite/auth/strategy.rb +1 -1
  22. data/lib/admin_suite/auth.rb +15 -0
  23. data/lib/admin_suite/authorization_context.rb +28 -0
  24. data/lib/admin_suite/configuration.rb +66 -8
  25. data/lib/admin_suite/engine.rb +1 -1
  26. data/lib/admin_suite/legacy_custom_renderer_procs.rb +1 -1
  27. data/lib/admin_suite/mcp/authorization.rb +68 -0
  28. data/lib/admin_suite/mcp/serializer.rb +138 -0
  29. data/lib/admin_suite/mcp/tools/aggregate.rb +55 -0
  30. data/lib/admin_suite/mcp/tools/describe_resources.rb +63 -0
  31. data/lib/admin_suite/mcp/tools/get_record.rb +41 -0
  32. data/lib/admin_suite/mcp/tools/list_records.rb +67 -0
  33. data/lib/admin_suite/mcp.rb +53 -0
  34. data/lib/admin_suite/query.rb +71 -0
  35. data/lib/admin_suite/renderer_registry.rb +11 -0
  36. data/lib/admin_suite/ui/dashboard_definition.rb +6 -0
  37. data/lib/admin_suite/ui/show_value_formatter.rb +2 -2
  38. data/lib/admin_suite/version.rb +1 -1
  39. data/lib/admin_suite.rb +23 -7
  40. data/lib/generators/admin_suite/install/templates/admin_suite.rb +5 -2
  41. data/test/controllers/resources_controller_test.rb +19 -13
  42. data/test/integration/association_linking_test.rb +292 -0
  43. data/test/integration/authentication_test.rb +10 -0
  44. data/test/integration/authorization_test.rb +20 -3
  45. data/test/integration/chart_panel_test.rb +491 -0
  46. data/test/integration/dashboard_test.rb +9 -2
  47. data/test/integration/index_query_characterization_test.rb +229 -0
  48. data/test/integration/index_table_test.rb +289 -0
  49. data/test/integration/mcp_aggregate_test.rb +44 -0
  50. data/test/integration/mcp_authorization_test.rb +102 -0
  51. data/test/integration/mcp_endpoint_test.rb +89 -0
  52. data/test/integration/mcp_get_record_test.rb +62 -0
  53. data/test/integration/mcp_instrumentation_test.rb +107 -0
  54. data/test/integration/mcp_list_records_test.rb +75 -0
  55. data/test/integration/mcp_parity_test.rb +155 -0
  56. data/test/integration/mcp_release_test.rb +131 -0
  57. data/test/integration/navigation_sections_test.rb +21 -0
  58. data/test/integration/read_only_resource_test.rb +128 -2
  59. data/test/integration/searchable_select_search_test.rb +369 -0
  60. data/test/integration/show_hide_blank_test.rb +195 -0
  61. data/test/integration/toggle_test.rb +106 -0
  62. data/test/lib/auth_host_user_test.rb +52 -0
  63. data/test/lib/auth_http_basic_test.rb +10 -0
  64. data/test/lib/auth_test.rb +20 -3
  65. data/test/lib/authorization_context_test.rb +127 -0
  66. data/test/lib/definition_loader_test.rb +12 -0
  67. data/test/lib/engine_defaults_test.rb +1 -2
  68. data/test/lib/form_field_renderer_test.rb +83 -11
  69. data/test/lib/format_table_cell_test.rb +49 -0
  70. data/test/lib/index_includes_test.rb +223 -0
  71. data/test/lib/mcp_serializer_test.rb +107 -0
  72. data/test/lib/query_test.rb +91 -0
  73. data/test/lib/removed_deprecations_test.rb +16 -0
  74. data/test/lib/renderer_test.rb +23 -7
  75. data/test/publish_workflow_test.rb +63 -0
  76. data/test/test_helper.rb +73 -8
  77. metadata +76 -9
  78. data/lib/admin_suite/renderers/legacy_gleania.rb +0 -230
  79. data/test/lib/legacy_renderer_deprecation_test.rb +0 -35
  80. data/test/lib/resource_exportable_deprecation_test.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 816ba44b1d87d7cc1ca92ccfcff24dc15a3c5eaadc23679de61a1977ab007a26
4
- data.tar.gz: 89690a043f1fdb3e9a175d6fa32da6ba8b1d605c46b631ac23dbac6c9b04f630
3
+ metadata.gz: 964267328a54fb0d55bfb035fab32ba694b620b0f5cbdb45194e770a3138885f
4
+ data.tar.gz: eec49bd217460a272eb6ce1add0fd9ac487764a37e304bdcde105044f7eed80a
5
5
  SHA512:
6
- metadata.gz: 9aec218fbb03dbb9550bd127eb38aab19709bf13a1874c186a878e3b8f32c4618b0698d879a5d685ae105146a9d4e8c446e4e2e3533c253d40f551b6f00597a9
7
- data.tar.gz: 63f991346017892d5f0b2e10327cbedc5c86ba2739a5dfb9680c525d62c458a64b2341fc5a0861313391b7898946c5073152ac6ef5182ebfeb3ea2955957a604
6
+ metadata.gz: 5875a1f28a12afb57b1e43c38db7f4362f6ef896f2b0de7f0af2d949945ac1c60e17f0e24a5b3f967f753fdeac8960470f2076421ef31156f3fb64b03285fbd2
7
+ data.tar.gz: 76e74fce525a385fad34c84511f2affbfeca6df6ea147e81dfdaa393051c3b98915055f1b620040a140c6ce5e336823fc66928d2a0b01ca9017881914d530185
data/.gitignore CHANGED
@@ -10,3 +10,10 @@
10
10
  /test/dummy/storage/
11
11
  admin_suite-*.gem
12
12
  /.bundle/
13
+
14
+ # Agent scratch: git worktrees created per-phase, and subagent-driven-development
15
+ # ledgers/briefs/review diffs. Never part of the gem — `spec.files` is an
16
+ # allowlist, so these could not ship, but an `add -A` from the repo root would
17
+ # otherwise commit an entire nested worktree.
18
+ /.claude/
19
+ /.superpowers/
data/CHANGELOG.md CHANGED
@@ -5,12 +5,196 @@ 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
- ## [Unreleased]
8
+ ## [0.6.1] - 2026-09-12
9
+
10
+ ### Fixed
11
+ - Automated gem publishing now uses RubyGems Trusted Publishing (GitHub Actions
12
+ OIDC) instead of a long-lived API key that required an interactive MFA code.
13
+ - Publishing is restricted to successful CI for main-branch pushes from this
14
+ repository, or manual dispatch on main, using the protected `release` environment.
15
+
16
+ ### Upgrade notes
17
+ - Includes the changes listed under 0.6.0 below, which was not published to
18
+ RubyGems. Upgrading from 0.5.0 requires the 0.6.0 host authentication,
19
+ authorization, and renderer migrations.
20
+
21
+ ## [0.6.0] - 2026-09-09
22
+
23
+ ### Breaking
24
+ - `read_only` resources now reject declared member and bulk actions as well as
25
+ CRUD and toggle routes (404, no model mutation).
26
+ - Authorization hooks now accept `context:` instead of `controller:`. Use
27
+ `context.surface` (`:web` or `:mcp`), `context.controller` (web only), and
28
+ `context.request`. Old signatures fail at assignment with migration guidance.
29
+ - Removed the deprecated `exportable` no-op and the four built-in Gleania
30
+ renderer implementations. Hosts must remove `exportable` calls and provide
31
+ their own renderer classes before upgrading. Existing registered host keys work.
32
+ - MCP requires a real actor and an explicit `config.authorize` hook. A nil hook
33
+ exposes no tools/data; authenticated web requests retain their existing behavior.
34
+ - Default DSL page sizes above the configured cap are now capped on the web index too.
35
+
36
+ ### Added
37
+ - Operator MCP at `<mount>/mcp`, using the official SDK's stateless HTTP transport.
38
+ Four read tools: `describe_resources`, `list_records`, `get_record`, `aggregate`.
39
+ Discovery includes portal, section, declared fields, and declared actions as
40
+ metadata (actions are not callable). Reads serialize declared fields only;
41
+ association panels require explicit columns and return bounded rows. No write
42
+ tools, LLM calls or chat UI in this release.
43
+ - `:host_user` authentication strategy with a host-provided user resolver; shared
44
+ actor normalization across surfaces. Existing HTTP Basic and SSO still work.
45
+ - Resource DSL `mcp false` opt-out and `config.mcp.enabled` / `max_page_size` settings.
46
+ - `admin_suite.mcp.tool_call` notifications with tool, resource, action,
47
+ actor_type, actor_id, request_id, filters, q, allowed, error, result_count and
48
+ duration_ms. Hosts own request-log persistence.
49
+
50
+ ### Fixed
51
+ - MCP index serialization evaluates column content lambdas (matching the web
52
+ index) and coerces times and association records to JSON primitives instead of
53
+ `to_s` heap addresses.
54
+ - MCP/default pagination cannot bypass the configured cap by omitting `per_page`
55
+ or passing invalid/non-positive values. The response states the applied limit.
56
+ - Anonymous MCP calls are rejected even under the development escape hatch;
57
+ `get_record` additionally checks record-level policy before serialization.
58
+ - Foreign browser origins are rejected. Notifications return 202; unsupported
59
+ GET streams and session deletion return 405.
60
+
61
+ ### Changed
62
+ - Extracted `AdminSuite::Query`; the web index and MCP share filter/search/sort,
63
+ eager-loading and page-size behavior, covered by parity/characterization tests.
64
+ - Migration guide: `../_vault/products/admin_suite/docs/mcp.md`.
65
+
66
+ ## [0.5.0] - 2026-08-01
67
+
68
+ ### Added
69
+ - **Charts.** `chart_panel` now renders a real Chart.js chart (Chart.js
70
+ 4.5.1, vendored at `app/assets/vendor/chart.umd.min.js` — no gem
71
+ dependency, no CDN, works air-gapped/under a strict CSP), loaded only on
72
+ pages that actually render a chart with data. New `type:` option —
73
+ `:bar` (default), `:line`, `:area` (a Chart.js line chart with
74
+ `fill: true`), or `:doughnut`; an unrecognized value falls back to
75
+ `:bar` and logs a warning rather than raising, regardless of the input's
76
+ type (Symbol, String, Integer, Boolean, ...). New `height:` option (see
77
+ Changed below for the new default). The existing CSS bar chart remains
78
+ the no-JS degraded state — nothing is hidden until Chart.js has actually
79
+ built the real chart. See
80
+ `../_vault/products/admin_suite/docs/charts.md`.
81
+ - Index rows are now clickable end to end via the already-existing (but
82
+ previously unwired) `ClickActionsController`: clicking anywhere on a row
83
+ other than an interactive element (link, button, form input) navigates to
84
+ the record's show page.
85
+ - A 25/50/100 per-page selector in the index's existing filter form,
86
+ clamped server-side to a maximum of 100 regardless of what the
87
+ `per_page` query param requests, falling back to the resource's
88
+ `paginate(n)` value when the param is absent or invalid (non-numeric,
89
+ zero, negative, or array-shaped).
90
+ - `column :name, align: :right` (also `:center`, `:left`) DSL option,
91
+ emitting the matching Tailwind alignment class on the `<td>`.
92
+ - The index's `<thead>` is now `sticky top-0`, and its scroll wrapper is
93
+ now bounded (`max-h-[70vh] overflow-y-auto`, alongside the existing
94
+ `overflow-x-auto`) so the header actually pins **within that scroll
95
+ region** while scrolling a long page of results — not to the browser
96
+ viewport — instead of scrolling away with the page.
97
+ - `index do includes :company, :line_items end` — eager-loads the named
98
+ associations on the index's filtered/sorted/searched scope, before
99
+ pagination. Skips silently on a scope that doesn't support `#includes`;
100
+ logs and degrades (unoptimized, still renders) if `#includes` itself
101
+ raises (e.g. a bad association name). See
102
+ `../_vault/products/admin_suite/docs/resources.md`.
103
+ - A gem-provided JSON search endpoint, `GET <mount>/:portal/:resource/search?q=`,
104
+ for `searchable_select` — hosts no longer need to hand-build one. Enforces
105
+ authentication and `config.authorize` (`action: :read`), only searches the
106
+ resource's declared `searchable` fields, hard-caps at 25 results, and
107
+ returns `[]` (never the whole table) for a blank/missing `q`. New field
108
+ option `resource:` resolves a `searchable_select` field's search URL to
109
+ this endpoint automatically; a String `collection:` still overrides it
110
+ unconditionally. See `../_vault/products/admin_suite/docs/fields.md`.
111
+ - `hide_blank: true` on show `fields:` panels (sidebar or main column) hides
112
+ a field's row entirely instead of rendering a label with an empty value.
113
+ Deliberately stricter than `.blank?`: only `nil`/`""`/`[]`/`{}` are
114
+ hidden — whitespace-only strings, `false`, and `0`/`0.0` are kept, since
115
+ none of those are "no data," and `false`/`0` are today's real rendered
116
+ values for a lot of fields. See
117
+ `../_vault/products/admin_suite/docs/resources.md`.
118
+
119
+ ### Changed
120
+ - **Host-visible:** `belongs_to`-shaped values now render as links to the
121
+ associated record's own admin show page — in both index columns
122
+ (previously a raw `#<Company:0x...>` inspect string) and association
123
+ tables (previously plain text). Falls back to plain text (never a raw
124
+ inspect string, never a 500) when no resource is registered for the
125
+ associated class, the record is unpersisted, or its display title
126
+ raises.
127
+ - **Host-visible:** the chart panel's default height changed from a 64px
128
+ strip to **192px**. 64px was enough for a sparkline-style bar row but not
129
+ a real chart with axes/labels; both the degraded bars and the live canvas
130
+ read the same height value, so there's no shift between the two, but
131
+ every existing dashboard's chart panels are now taller unless `height:`
132
+ is set explicitly. `chart_panel`'s markup for hosts overriding
133
+ `config.partials[:panel_chart]` has also changed shape (new Stimulus
134
+ mount + data attributes) — see the charts doc.
135
+ - **Host-visible:** resources declaring `paginate(n)` now render that many
136
+ rows per page instead of Pagy's own default of 20 — see the Fixed entry
137
+ below. Operators may see different row counts and pagination boundaries
138
+ than they're used to.
139
+ - `column.css_class` (populated by the DSL's `class:` option) is now
140
+ applied to the rendered `<td>`. Previously accepted but never read.
141
+ - A column value that is `nil` now renders as `—`, matching every other
142
+ surface in the gem, instead of a blank cell. Applies to both plain
143
+ scalar columns and `belongs_to`-shaped association columns.
144
+ - **Host-visible:** the `pagy` dependency range is now `>= 9.0, < 10`
145
+ (previously `>= 6.0, < 11.0`). `paginate_collection` calls `pagy(scope,
146
+ limit:)` (see the Fixed entry above) — `limit:` is Pagy 9.x's vars key,
147
+ silently ignored on Pagy 6-8, and Pagy 10 reworked the backend API
148
+ again. A host resolving Pagy 6-8 for another reason will now get a real
149
+ bundler resolution conflict at `bundle update` instead of quietly
150
+ reintroducing the "paginate(n) ignored, 20 rows forever" bug this
151
+ release fixes.
152
+ - `turbo-rails` is now an explicit gem dependency. The engine has always
153
+ hard-depended on it (`turbo_frame_tag` in the resource views;
154
+ `format.turbo_stream`/`turbo_stream.replace` in the `toggle` action) —
155
+ this declares what was already true, it does not add a new
156
+ requirement.
157
+
158
+ ### Fixed
159
+ - `paginate_collection` called `pagy(scope, items: n)`. Pagy's vars key
160
+ is `limit:`, not `items:` — `items:` silently did nothing, so **every
161
+ resource's `paginate(n)` DSL value has been ignored since the gem's
162
+ first commit**, with every index quietly paginating at Pagy's own
163
+ default of 20 regardless of what was declared. This is a long-standing
164
+ latent bug, not a regression introduced by this release; declared page
165
+ sizes now take effect.
166
+ - A chart panel's `data:` proc raising, returning junk rows (non-Hash
167
+ entries mixed into the array), or returning rows with a non-numeric
168
+ `value` (Boolean, Hash, Array, nil, non-numeric String) all previously
169
+ 500'd the dashboard. All three now degrade instead: a raising proc logs
170
+ and shows "No chart data."; junk rows are dropped; junk values render as
171
+ a zero-height bar/slice. String-keyed data rows (e.g. from a JSONB
172
+ column) are also now tolerated the same way `data_table` already
173
+ handles them — previously they rendered blank/zero bars silently rather
174
+ than the real value.
175
+ - A chart panel's `height:` option 500'd the dashboard for any value that
176
+ survives `presence` but doesn't respond to `#to_i` (`true`, a Symbol, a
177
+ non-empty Array or Hash — e.g. `height: :tall` or `height: [200]`). Now
178
+ totally coerced the same way `data:` row values are, falling back to the
179
+ default 192px for anything that doesn't genuinely parse as a number.
180
+ `color:` had the same gap (pre-existing since 0.4.0): any value without
181
+ `#to_sym` (an Integer, Boolean, ...) 500'd the dashboard instead of
182
+ falling through to the existing unknown-color default (indigo).
183
+
184
+ ### Deprecated
185
+ - **Unchanged, reiterated for clarity:** the four Gleania-specific renderer
186
+ keys (`:prompt_template_preview`, `:messages_preview`,
187
+ `:tool_args_preview`, `:turn_messages_preview`) and
188
+ `Resource.exportable(*formats)` remain deprecated (as of 0.4.0) and
189
+ **still work in 0.5.0**. Their removal, originally targeted at 0.5.0, is
190
+ now targeted at **0.6.0**, pending the Gleania and TrustGrowth host
191
+ migrations. The runtime deprecation warnings (and their source comments)
192
+ have been retargeted to say 0.6.0 as part of this release.
9
193
 
10
194
  ## [0.4.0] - 2026-08-01
11
195
 
12
196
  ### Added
13
- - `AdminSuite::Renderer` base class for show-panel renderers, with shared
197
+ - `AdminSuite::Renderer` base class for panel renderers, with shared
14
198
  primitives (`json_block`, `code_block`, `key_value_list`, `data_table`,
15
199
  `badge`, `empty_state`). Host renderers live in `app/admin/renderers/*.rb`
16
200
  as `Admin::Renderers::<Key>Renderer` and are autoloaded from a panel's
@@ -49,12 +233,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
49
233
  paper only, with no runtime signal.
50
234
  - The built-in `:prompt_template_preview`, `:messages_preview`,
51
235
  `:tool_args_preview` and `:turn_messages_preview` renderers. They warn once
52
- per key per process and are **removed in 0.5.0**.
236
+ per key per process and are **removed in 0.5.0** (removal retargeted to
237
+ 0.6.0 — see [0.5.0]).
53
238
  - `Resource.exportable(*formats)` — a no-op that warns once per resource
54
- class, naming the class, and is **removed in 0.5.0**. It was write-only
55
- in every prior release (never had a reader, never drove any export
56
- behavior); calling it is now harmless instead of raising `NoMethodError`
57
- at resource-definition time.
239
+ class, naming the class, and is **removed in 0.5.0** (removal retargeted
240
+ to 0.6.0 see [0.5.0]). It was write-only in every prior release (never
241
+ had a reader, never drove any export behavior); calling it is now
242
+ harmless instead of raising `NoMethodError` at resource-definition time.
58
243
 
59
244
  ### Fixed
60
245
  - A host `Admin::Renderers::<Key>Renderer` class (or an explicit
@@ -97,6 +282,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
97
282
 
98
283
  ## [0.3.0] - 2026-07-31
99
284
 
285
+ ### Added
286
+ - Pluggable auth strategies: `AdminSuite::Auth.register`, built-in
287
+ `:http_basic` (ENV or `config.auth_options` credentials, constant-time
288
+ comparison, blank credentials deny).
289
+ - `config.authorize` is now enforced for every resource action with the
290
+ contract `->(actor:, action:, resource:, record:, controller:)`,
291
+ action ∈ :read/:create/:update/:destroy/:execute.
292
+ - `config.skip_host_before_actions` (default `[:require_authentication]`)
293
+ replaces the hardcoded host-filter skip.
294
+
100
295
  ### Changed (BREAKING)
101
296
  - AdminSuite now **fails closed**: with no authentication configured, every
102
297
  engine request responds 403. Configure `config.auth_strategy = :http_basic`
@@ -108,17 +303,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
108
303
  dedicated bulk-action lookup. Declared member and bulk actions remain
109
304
  allowed on `read_only` resources by design.
110
305
  - `execute_action` with an unknown action name now responds 404 instead of
111
- redirecting with an "Action not found." alert.
112
-
113
- ### Added
114
- - Pluggable auth strategies: `AdminSuite::Auth.register`, built-in
115
- `:http_basic` (ENV or `config.auth_options` credentials, constant-time
116
- comparison, blank credentials deny).
117
- - `config.authorize` is now enforced for every resource action with the
118
- contract `->(actor:, action:, resource:, record:, controller:)`,
119
- action ∈ :read/:create/:update/:destroy/:execute.
120
- - `config.skip_host_before_actions` (default `[:require_authentication]`)
121
- replaces the hardcoded host-filter skip.
306
+ redirecting with an "Action not found" alert.
122
307
 
123
308
  ### Fixed
124
309
  - `config.current_actor` is now consulted at most once per request
data/CONTRIBUTING.md CHANGED
@@ -28,7 +28,7 @@ See `../_vault/products/admin_suite/docs/development.md` for detailed informatio
28
28
  ### CI Checks
29
29
 
30
30
  All pull requests must pass the following checks before merging:
31
- - **Tests**: Automated test suite runs on Ruby 3.2 and 3.3
31
+ - **Tests**: Automated test suite runs on the latest available patch releases of Ruby 3.2, 3.3, 3.4, and 4.0
32
32
  - **Coverage**: Code coverage is automatically generated and uploaded to Codecov
33
33
  - **Code Review**: At least one maintainer approval required
34
34
 
@@ -40,10 +40,15 @@ See `../_vault/products/admin_suite/docs/releasing.md` for information on how re
40
40
 
41
41
  Releases are automated via GitHub Actions when changes are merged to `main` with a version bump.
42
42
 
43
- ### Required Secrets for Maintainers
43
+ ### Publishing Setup for Maintainers
44
44
 
45
- The repository requires the following secrets to be configured:
46
- - **`RUBYGEMS_API_KEY`**: Required for automated gem publishing to RubyGems
45
+ RubyGems Trusted Publishing authenticates GitHub Actions with short-lived OIDC
46
+ credentials; no `RUBYGEMS_API_KEY` secret is needed. The gem's trusted publisher
47
+ must match `techwright-lab/admin_suite`, workflow `publish.yml`, and environment
48
+ `release`. Restrict the GitHub `release` environment to the `main` branch.
49
+ See the canonical releasing guide linked above for setup and verification.
50
+
51
+ Optional repository secret:
47
52
  - **`CODECOV_TOKEN`**: Optional, for uploading code coverage reports to Codecov (workflow continues without it)
48
53
 
49
54
  ## Questions?
data/README.md CHANGED
@@ -11,6 +11,7 @@ across other products.
11
11
  - **Portals**: group resources by portal + section, optional per-portal dashboards
12
12
  - **Resources DSL**: index (columns/filters/stats), form fields, show panels/associations, actions
13
13
  - **Docs viewer**: renders `*.md` from your host app filesystem at `/docs`
14
+ - **Admin MCP**: four read tools derived from resource definitions, under operator authentication and explicit authorization
14
15
  - **UI**: baseline CSS + engine Tailwind build; host overrides optional
15
16
 
16
17
  ## Documentation
@@ -43,20 +44,35 @@ bin/rails g admin_suite:install --mount-path=/internal/admin
43
44
 
44
45
  ### Secure it (recommended)
45
46
 
46
- Set `config.authenticate` so only authorized users can access AdminSuite:
47
+ Resolve the host user and authorize admin access:
47
48
 
48
49
  ```ruby
49
50
  # config/initializers/admin_suite.rb
50
51
  AdminSuite.configure do |config|
51
- config.authenticate = ->(controller) do
52
- user = controller.respond_to?(:current_user) ? controller.current_user : nil
53
- controller.head(:forbidden) unless user&.admin?
54
- end
52
+ config.auth_strategy = :host_user
53
+ config.auth_options = { resolve: ->(controller) { controller.current_user } }
54
+ config.authorize = ->(actor:, action:, resource:, record:, context:) { actor&.admin? }
55
55
  end
56
56
  ```
57
57
 
58
58
  Read more: `../_vault/products/admin_suite/docs/configuration.md`
59
59
 
60
+ Set `config.authorize` to decide *what* an authenticated actor may do:
61
+
62
+ ```ruby
63
+ config.authorize = ->(actor:, action:, resource:, record:, context:) {
64
+ # action is :read, :create, :update, :destroy, or :execute
65
+ true
66
+ }
67
+ ```
68
+
69
+ A `false` or `nil` return is `403` (fail closed). Leaving the hook `nil`
70
+ keeps authentication as the only web gate; MCP serves no tools or data. Resources marked `read_only` reject
71
+ CRUD, `toggle`, and named execute/bulk actions regardless of this hook.
72
+
73
+ The MCP endpoint is `<mount>/mcp`. See
74
+ `../_vault/products/admin_suite/docs/mcp.md` for setup, tool contracts and migration.
75
+
60
76
  ### Add portals (navigation metadata)
61
77
 
62
78
  ```ruby