activeadmin-graphql 0.3.2 → 0.4.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/app/controllers/active_admin/graphql_controller.rb +19 -4
  4. data/docs/changelogs/20261010091415_graphql-resource-enabled-default.md +34 -0
  5. data/docs/changelogs/20261012050051_graphql-surface-toggles.md +40 -0
  6. data/docs/changelogs/20261012064500_graphql-capabilities.md +35 -0
  7. data/docs/changelogs/20261012113500_release-0-3-3.md +45 -0
  8. data/docs/changelogs/20261012200000_host-customization-hooks.md +27 -0
  9. data/docs/changelogs/20261013080000_release-0-4-0.md +48 -0
  10. data/docs/graphql-api.md +173 -10
  11. data/docs/issues/20261012200000_host-customization-gaps.md +26 -0
  12. data/lib/active_admin/graphql/capability.rb +35 -0
  13. data/lib/active_admin/graphql/capability_definition_dsl.rb +69 -0
  14. data/lib/active_admin/graphql/collection_query_policy.rb +103 -0
  15. data/lib/active_admin/graphql/integration.rb +43 -1
  16. data/lib/active_admin/graphql/member_action_capability.rb +14 -0
  17. data/lib/active_admin/graphql/member_action_dry_run.rb +121 -0
  18. data/lib/active_admin/graphql/policy_adapter.rb +94 -0
  19. data/lib/active_admin/graphql/policy_set_cache.rb +10 -6
  20. data/lib/active_admin/graphql/resource_capability_dsl.rb +32 -0
  21. data/lib/active_admin/graphql/resource_capability_maps.rb +77 -0
  22. data/lib/active_admin/graphql/resource_config.rb +19 -2
  23. data/lib/active_admin/graphql/resource_definition_dsl.rb +22 -10
  24. data/lib/active_admin/graphql/resource_methods.rb +7 -1
  25. data/lib/active_admin/graphql/resource_surface_dsl.rb +48 -0
  26. data/lib/active_admin/graphql/run_action_capability.rb +62 -0
  27. data/lib/active_admin/graphql/run_action_control_extension.rb +72 -0
  28. data/lib/active_admin/graphql/run_action_field.rb +103 -0
  29. data/lib/active_admin/graphql/run_action_mutation_config.rb +8 -0
  30. data/lib/active_admin/graphql/run_action_mutation_dsl.rb +18 -0
  31. data/lib/active_admin/graphql/run_action_preview.rb +20 -0
  32. data/lib/active_admin/graphql/schema_builder/build.rb +52 -3
  33. data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +7 -0
  34. data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +9 -0
  35. data/lib/active_admin/graphql/schema_builder/mutation_create.rb +1 -0
  36. data/lib/active_admin/graphql/schema_builder/mutation_member.rb +13 -0
  37. data/lib/active_admin/graphql/schema_builder/mutation_type_builder.rb +15 -10
  38. data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +2 -0
  39. data/lib/active_admin/graphql/schema_builder/query_type.rb +2 -1
  40. data/lib/active_admin/graphql/schema_builder/query_type_collection.rb +3 -0
  41. data/lib/active_admin/graphql/schema_builder/query_type_member.rb +1 -0
  42. data/lib/active_admin/graphql/schema_builder/query_type_policies.rb +9 -63
  43. data/lib/active_admin/graphql/schema_builder/resolvers.rb +46 -7
  44. data/lib/active_admin/graphql/schema_builder/resources.rb +34 -0
  45. data/lib/active_admin/graphql/schema_builder/types_object.rb +19 -8
  46. data/lib/active_admin/graphql/schema_builder/visibility.rb +22 -7
  47. data/lib/active_admin/graphql/schema_builder/wire.rb +4 -1
  48. data/lib/active_admin/graphql/schema_field.rb +97 -6
  49. data/lib/active_admin/graphql/version.rb +1 -1
  50. data/lib/active_admin/graphql.rb +9 -0
  51. metadata +21 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95e56a00a0c78ea55f44c186012af72c1ba6ff80ada2adbf905ce9b2d190c47d
4
- data.tar.gz: 1e1dc89e1ffd41d05c6f2962a83857188a239ba677e6f492dbc304a09bb123b9
3
+ metadata.gz: c03a6abe474dea238c1dec40f75bb03b2f3203e4874d4639e2327a0562c9a9ce
4
+ data.tar.gz: 3bb9bd9cf8b55e796cf87cf8f51dca817b41b5ae9a515132ff0e56a218afe6db
5
5
  SHA512:
6
- metadata.gz: 02533503de4c86355e28bc7212115b043a9d7da293db40d1134251f7892539fca8257d6c03140c110d12da8e4de1b4112668c7e6fc2d721273c990d9fff930e3
7
- data.tar.gz: 6e13f9a69b6cc7df900cf9883ab60600be21bfea50196209c7324306030d74784cb3e66082ffc3ec225192c3c0c385fffb3f24840061e9f12415b60479de9d3c
6
+ metadata.gz: 9e0a6930217123a83432a072410be640d618371c062eb83e27c86786461218f4d48d524edd9a7c00749914ac3c216d015d565fbc60a7f5fcdf7fc0d2f57d48b7
7
+ data.tar.gz: 639363956ab8b19349df04e7a5c5f9f6610705206b3d7d8f82838cde74b4dc10ab5e7b53f993def3c2de016885d1a1ecd267698252b877f655c87b49ef1a8f9f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,32 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.4.0 (2026-10-13)
4
+
5
+ - Add field `capability_mode` (`:omit` / `:null`) and namespace hooks
6
+ `graphql_field_capability_mode_default` and `graphql_field_visible` (RFC 0009).
7
+ - Add `member_action_mutation … dry_run: true` with `preview`, `extra_params`,
8
+ `ok` / `refuse`, and optional `graphql_run_action_audit` (RFC 0010).
9
+ - Add `graphql_collection_query_policy` and
10
+ `CollectionQueryPolicy.require_visible_fields!`, inferring filter grants from
11
+ resource field capabilities by default (RFC 0011).
12
+ - Add `graphql_member_action_capability_default`,
13
+ `graphql_batch_action_capability_default`,
14
+ `graphql_collection_action_capability_default`, and `capability:` on
15
+ `batch_action_mutation` / `member_action_mutation` /
16
+ `collection_action_mutation` (RFC 0012).
17
+ - Add `dry_run:` / `confirm:` / `preview:` on `SchemaField`, preview
18
+ short-circuit via `RunActionControlExtension`, and
19
+ `RunActionField.define` DSL sugar for custom run-action fields (RFC 0013).
20
+
21
+ ## 0.3.3 (2026-10-12)
22
+
23
+ - Add `graphql { capability ... }` and namespace `graphql_capabilities` so one named grant can gate many fields, queries, and mutations (RFC 0008).
24
+ - Apply `fields:` capabilities to wired `belongs_to` associations; reject unknown capability field names and named visibility `profiles:` combined with `graphql_capabilities`.
25
+ - Add namespace setting `graphql_resource_enabled_default` (`true` by default) and `graphql { enable! }` so apps can opt resources into the schema instead of opting out with `disable!`.
26
+ - Add namespace toggles for `registered_resource` and `belongs_to` wiring (`graphql_registered_resource_enabled`, `graphql_belongs_to_associations_enabled`; both `true` by default).
27
+ - Add `graphql_policies_enabled` / `graphql_policies_enabled_default` for policy field publishing and per-resource seeds, plus `graphql_policy_adapter` (default `ActiveAdmin::GraphQL::PolicyAdapter`) for payload construction.
28
+ - Add per-resource surface defaults and DSL (`graphql_queries_enabled_default`, `graphql_mutations_enabled_default`, `graphql_run_actions_enabled_default`, plus `enable_*!` / `disable_*!`) so apps can trim list/show, CRUD, run-action, and policy fields without disabling the whole resource.
29
+ - Treat `queries_enabled` as the read surface: omit `registered_resource` union membership when queries are off; fail schema build when `Query` would have no fields.
4
30
 
5
31
  ## 0.3.2 (2026-10-10)
6
32
 
@@ -51,16 +51,31 @@ module ActiveAdmin
51
51
 
52
52
  def graphql_context
53
53
  ns = active_admin_namespace
54
- {
54
+ context = {
55
55
  auth: ActiveAdmin::GraphQL::AuthContext.new(
56
56
  user: current_active_admin_user,
57
57
  namespace: ns
58
58
  ),
59
59
  namespace: ns,
60
60
  request: request,
61
- current_user: current_active_admin_user,
62
- visibility_profile: graphql_visibility_profile_for(ns)
63
- }.compact
61
+ current_user: current_active_admin_user
62
+ }
63
+ # Named visibility profiles cache a fixed context; per-request capabilities
64
+ # need the live request context (dynamic visibility). Skip the profile key.
65
+ unless ns.graphql_capabilities.respond_to?(:call)
66
+ profile = graphql_visibility_profile_for(ns)
67
+ context[:visibility_profile] = profile if profile
68
+ end
69
+ capabilities = graphql_capabilities_for(ns, context)
70
+ context[:capabilities] = capabilities unless capabilities.nil?
71
+ context
72
+ end
73
+
74
+ def graphql_capabilities_for(ns, context)
75
+ hook = ns.graphql_capabilities
76
+ return nil unless hook.respond_to?(:call)
77
+
78
+ ActiveAdmin::GraphQL::Capability.normalize_set(hook.call(context))
64
79
  end
65
80
 
66
81
  def graphql_visibility_profile_for(ns)
@@ -0,0 +1,34 @@
1
+ # GraphQL resource enabled default
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add namespace setting `graphql_resource_enabled_default`, default `true`, so
10
+ 0.3.x behavior stays: turning on the namespace endpoint still includes every
11
+ ActiveRecord resource unless `graphql { disable! }` is set.
12
+
13
+ Apps that want support-style opt-in set the setting to `false`. Then a resource
14
+ enters the schema only when registration runs `graphql { }` or
15
+ `graphql { enable! }`. `disable!` still omits.
16
+
17
+ `ResourceConfig` takes the initial `enabled` flag from the namespace setting.
18
+ Entering a `graphql` block sets `enabled = true` before the block body runs so
19
+ an empty block is a conscious opt-in.
20
+
21
+ ## Effects
22
+
23
+ New request specs cover default true, default false without a block, empty
24
+ block, `enable!`, and `disable!`.
25
+
26
+ ## Next
27
+
28
+ Folded into 0.3.3 release metadata.
29
+
30
+ ## Source
31
+
32
+ CHANGELOG 0.3.3. docs/graphql-api.md namespace settings and per-resource
33
+ DSL. spec/requests/graphql_resource_enabled_default_spec.rb.
34
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -0,0 +1,40 @@
1
+ # GraphQL surface toggles
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add namespace and per-resource controls for schema surfaces that were always on
10
+ once a resource was enabled. Defaults stay `true` so 0.3.x behavior is
11
+ unchanged.
12
+
13
+ Namespace-only switches: `graphql_registered_resource_enabled`,
14
+ `graphql_belongs_to_associations_enabled`, `graphql_policies_enabled`.
15
+ Policy payloads use `graphql_policy_adapter` (default `PolicyAdapter`).
16
+ Per-resource policy seed is `graphql_policies_enabled_default`.
17
+
18
+ Per-resource seeds from namespace defaults: queries, mutations (CRUD),
19
+ run-actions, and policies. Resource DSL exposes matching `enable_*!` /
20
+ `disable_*!` methods. `queries_enabled` also gates `registered_resource`
21
+ membership. Schema build raises if `Query` would have no fields.
22
+
23
+ ## Effects
24
+
25
+ Schema builder skips policy query fields, registered_resource union, belongs_to
26
+ wiring, list/show fields, CRUD mutations, and run-action mutations when the
27
+ matching flag is off. Request specs cover the new settings and DSL, including
28
+ empty-Query rejection and registered_resource gating by queries.
29
+
30
+ ## Next
31
+
32
+ Folded into 0.3.3 release metadata.
33
+
34
+ ## Source
35
+
36
+ CHANGELOG 0.3.3. docs/graphql-api.md.
37
+ spec/requests/graphql_surface_toggles_spec.rb.
38
+ spec/requests/graphql_policy_surface_spec.rb.
39
+ spec/requests/graphql_query_surface_contract_spec.rb.
40
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -0,0 +1,35 @@
1
+ # GraphQL capabilities
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add RFC 0008 capability grants. Resource DSL `capability` associates many
10
+ object fields, queries, and mutations with one name. Namespace
11
+ `graphql_capabilities` fills `context[:capabilities]` per request. SchemaField
12
+ checks capability in `visible?` and `authorized?`. Capabilities do not replace
13
+ ActiveAdmin authorization. Subscriptions stay out of scope.
14
+
15
+ ## Effects
16
+
17
+ Gated members fail closed without the grant. With `graphql_visibility`,
18
+ introspection omits them. Aggregate run-action resolvers also check
19
+ per-action capability keys (`member_*`, `batch_*`, `collection_*`).
20
+ `belongs_to` association fields honor `fields:` capabilities. Schema build
21
+ rejects unknown capability field names and rejects named visibility profiles
22
+ combined with `graphql_capabilities` (dynamic visibility required).
23
+
24
+ ## Next
25
+
26
+ Folded into 0.3.3 release metadata. Follow-ons remain: policy payloads listing
27
+ field capabilities, mutation input attribute maps, and transport gates if
28
+ subscriptions ship later.
29
+
30
+ ## Source
31
+
32
+ RFC 0008. CHANGELOG 0.3.3. docs/graphql-api.md. spec/unit/capability_spec.rb,
33
+ spec/unit/capability_dsl_spec.rb, spec/requests/graphql_capabilities_spec.rb,
34
+ spec/requests/graphql_capabilities_edge_spec.rb.
35
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -0,0 +1,45 @@
1
+ # Release 0.3.3 metadata
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Cut 0.3.3 after published 0.3.2. User-visible work since the 0.3.2 tag:
10
+ `graphql_resource_enabled_default` and `enable!` opt-in, surface toggles for
11
+ queries, mutations, run-actions, policies, `registered_resource`, and
12
+ `belongs_to`, plus GraphQL capabilities (RFC 0008). Defaults stay `true` so
13
+ 0.3.x schema shape is unchanged unless an app opts in to narrower surfaces.
14
+
15
+ CHANGELOG Unreleased bullets moved under 0.3.3 dated 2026-10-12.
16
+
17
+ Version constant is 0.3.3. Path gem pins in Gemfile.lock and appraisal
18
+ lockfiles match.
19
+
20
+ Internal `stringify_attrs` safe-navigation cleanup stays out of CHANGELOG.md.
21
+
22
+ make release (usr/bin/release.rb) still owns gem build, gem push, git tag, and
23
+ GitHub release. This pass only prepares metadata.
24
+
25
+ ## Effects
26
+
27
+ lib/active_admin/graphql/version.rb VERSION is 0.3.3.
28
+
29
+ CHANGELOG surface-toggle and capability bullets live under 0.3.3.
30
+
31
+ PATH specs for activeadmin-graphql in Gemfile.lock,
32
+ gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby4.gemfile.lock, and
33
+ gemfiles/rails8ruby34.gemfile.lock are 0.3.3.
34
+
35
+ ## Next
36
+
37
+ Run make release from a clean tree when ready to push the gem and tag 0.3.3.
38
+
39
+ ## Source
40
+
41
+ docs/changelogs/20261010091415_graphql-resource-enabled-default.md
42
+ docs/changelogs/20261012050051_graphql-surface-toggles.md
43
+ docs/changelogs/20261012064500_graphql-capabilities.md
44
+ rfcs/0008-graphql-capabilities.md
45
+ usr/bin/release.rb
@@ -0,0 +1,27 @@
1
+ # Host customization hooks
2
+
3
+ ## Decisions
4
+
5
+ Ship generic namespace hooks and DSL for field capability null mode, member-action
6
+ dry-run with preview, collection query policy, default run-action capabilities,
7
+ and confirm/dry-run/preview plus RunActionField.define for custom mutations.
8
+ Product allowlists and auth grants stay in the host application.
9
+
10
+ ## Effects
11
+
12
+ RFCs 0009 through 0013 claimed and drafted. Implementation under 0.4.0 in
13
+ CHANGELOG.md. Request specs cover null-mode introspection, dry-run short-circuit,
14
+ collection filter refusal, default capability omit, and confirm_required.
15
+
16
+ ## Next
17
+
18
+ Metadata prepared in docs/changelogs/20261013080000_release-0-4-0.md. Run make
19
+ release when ready to push the gem and tag. Host apps can drop prepends that
20
+ duplicated these mechanics after upgrading.
21
+
22
+ ## Source
23
+
24
+ RFCs 0009–0013. Specs under spec/requests/graphql_capability_mode_spec.rb,
25
+ graphql_member_action_dry_run_spec.rb, graphql_collection_query_policy_spec.rb,
26
+ graphql_run_action_capability_default_spec.rb,
27
+ graphql_run_action_confirm_spec.rb.
@@ -0,0 +1,48 @@
1
+ # Release 0.4.0 metadata
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Cut 0.4.0 after published 0.3.3. User-visible work since the 0.3.3 tag:
10
+ field capability null mode and visibility hooks (RFC 0009), member-action
11
+ dry-run with preview and optional audit (RFC 0010), collection query policy
12
+ with filter grants inferred from field capabilities (RFC 0011), default
13
+ run-action capabilities and per-mutation `capability:` (RFC 0012), and
14
+ confirm / dry-run / preview plus `RunActionField.define` for custom
15
+ run-action fields (RFC 0013). Product allowlists and auth grants stay in the
16
+ host application.
17
+
18
+ CHANGELOG Unreleased bullets moved under 0.4.0 dated 2026-10-13.
19
+
20
+ Version constant is 0.4.0. Path gem pins in Gemfile.lock and appraisal
21
+ lockfiles match.
22
+
23
+ make release (usr/bin/release.rb) still owns gem build, gem push, git tag, and
24
+ GitHub release. This pass only prepares metadata.
25
+
26
+ ## Effects
27
+
28
+ lib/active_admin/graphql/version.rb VERSION is 0.4.0.
29
+
30
+ CHANGELOG host-customization bullets live under 0.4.0.
31
+
32
+ PATH specs for activeadmin-graphql in Gemfile.lock,
33
+ gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby4.gemfile.lock, and
34
+ gemfiles/rails8ruby34.gemfile.lock are 0.4.0.
35
+
36
+ ## Next
37
+
38
+ Run make release from a clean tree when ready to push the gem and tag 0.4.0.
39
+
40
+ ## Source
41
+
42
+ docs/changelogs/20261012200000_host-customization-hooks.md
43
+ rfcs/0009-field-capability-mode.md
44
+ rfcs/0010-member-action-dry-run.md
45
+ rfcs/0011-collection-query-policy.md
46
+ rfcs/0012-run-action-capability-defaults.md
47
+ rfcs/0013-run-action-confirm-dry-run.md
48
+ usr/bin/release.rb
data/docs/graphql-api.md CHANGED
@@ -33,6 +33,12 @@ ActiveAdmin.setup do |config|
33
33
  admin.graphql = true
34
34
  # Optional — default is "graphql", i.e. POST /admin/graphql
35
35
  # admin.graphql_path = "graphql"
36
+ # Optional — set false so only resources with a graphql block enter the schema
37
+ # admin.graphql_resource_enabled_default = false
38
+ # Optional — trim always-on surfaces for a narrow API
39
+ # admin.graphql_policies_enabled = false
40
+ # admin.graphql_registered_resource_enabled = false
41
+ # admin.graphql_belongs_to_associations_enabled = false
36
42
  end
37
43
  end
38
44
  ```
@@ -45,6 +51,33 @@ Run `bin/rails active_admin:install` or reload routes if needed so
45
51
  Each namespace inherits these options (defaults in parentheses):
46
52
 
47
53
  * `graphql` — set to `true` to mount the endpoint (`false` by default).
54
+ * `graphql_resource_enabled_default` — whether each registered ActiveRecord
55
+ resource is included in the schema without a `graphql` block (`true` by
56
+ default, matching 0.3.x). Set to `false` for opt-in: only resources that call
57
+ `graphql { }` (or `graphql { enable! }`) appear in the schema. `disable!`
58
+ still omits a resource.
59
+ * `graphql_queries_enabled_default` — seed for per-resource read fields
60
+ (`true` by default): named list/show fields and membership in
61
+ `registered_resource` / `ActiveAdminRegisteredResource`. Set `false` and use
62
+ `enable_queries!` for opt-in reads. Object types may still exist for mutations.
63
+ * `graphql_mutations_enabled_default` — seed for per-resource CRUD mutations
64
+ (`true` by default). Set `false` and use `enable_mutations!` for opt-in writes.
65
+ * `graphql_run_actions_enabled_default` — seed for batch/member/collection
66
+ action mutations (`true` by default).
67
+ * `graphql_policies_enabled` — publish `activeadmin_policies` query fields and
68
+ allow per-object policy fields (`true` by default).
69
+ * `graphql_policies_enabled_default` — seed for per-resource `policies_enabled`
70
+ (`true` by default). Use `enable_policies!` / `disable_policies!` per resource.
71
+ * `graphql_policy_adapter` — class that builds `activeadmin_policies` payloads
72
+ (`ActiveAdmin::GraphQL::PolicyAdapter` by default). Set a subclass (or constant
73
+ `String`) to replace payload logic; the default adapter still honors
74
+ `graphql_policy_actions` / `graphql_policy_action_mapper` /
75
+ `graphql_policy_extra` / `graphql_policy_transform`.
76
+ * `graphql_registered_resource_enabled` — include `registered_resource` and the
77
+ `ActiveAdminRegisteredResource` union when at least one queryable resource
78
+ exists (`true` by default).
79
+ * `graphql_belongs_to_associations_enabled` — wire non-polymorphic `belongs_to`
80
+ fields between registered resource types (`true` by default).
48
81
  * `graphql_path` — path segment under the namespace mount (`"graphql"`).
49
82
  * `graphql_multiplex_max` — maximum operations per multiplexed JSON array
50
83
  request (`20`).
@@ -70,6 +103,27 @@ Each namespace inherits these options (defaults in parentheses):
70
103
  default `context[:visibility_profile]` for each HTTP request (symbols accepted).
71
104
  * `graphql_schema_visible` — optional `Proc` `(context, meta) -> Boolean` when
72
105
  `graphql_visibility` is enabled; see Schema visibility below.
106
+ * `graphql_capabilities` — optional `Proc` `(context) -> Enumerable` of capability
107
+ names for the request (RFC 0008). Normalized into `context[:capabilities]`.
108
+ Cannot be combined with named `graphql_visibility` `profiles:`; schema build
109
+ forces `dynamic: true` and omits `visibility_profile` from the request context
110
+ so introspection uses the live capability set.
111
+ * `graphql_field_capability_mode_default` — `:omit` or `:null` when a field has a
112
+ capability but no per-field mode (RFC 0009). Default `:omit`.
113
+ * `graphql_field_visible` — optional `Proc` `(context, meta) -> Boolean` to omit
114
+ fields after capability checks (RFC 0009). Meta includes `graphql_type_name`,
115
+ `field_name`, and `resource` when known.
116
+ * `graphql_run_action_audit` — optional `Proc` `(context, meta) -> void` after
117
+ member-action dry-run, refused preview, or completed run (RFC 0010).
118
+ * `graphql_collection_query_policy` — optional `Proc` `(context, meta) -> void`
119
+ at the start of collection resolve (RFC 0011). Meta includes `aa_resource`,
120
+ `q`, `scope`, `order`, and `filter`.
121
+ * `graphql_member_action_capability_default` — optional capability symbol applied
122
+ to member-action mutation fields when the resource map has no entry (RFC 0012).
123
+ * `graphql_batch_action_capability_default` — same for the aggregate batch-action
124
+ field and per-action authorize checks (RFC 0012).
125
+ * `graphql_collection_action_capability_default` — same for collection-action
126
+ mutation fields (RFC 0012).
73
127
  * `graphql_configure_schema` — `Proc` receiving the schema class after build
74
128
  (extensions, extra types, and so on).
75
129
 
@@ -199,21 +253,103 @@ UI, including any `authorize!` or `authorize_resource!` calls.
199
253
 
200
254
  See the ActiveAdmin guide [Authorization adapter](https://activeadmin.info/13-authorization-adapter) for adapter setup; the same rules apply to GraphQL.
201
255
 
256
+ Policy preflight fields (`activeadmin_policies`, `activeadmin_policies_for`, and
257
+ per-object `activeadmin_policies`) are published when `graphql_policies_enabled`
258
+ is true. Payloads come from `graphql_policy_adapter`. The default
259
+ `ActiveAdmin::GraphQL::PolicyAdapter` asks the authorization adapter which
260
+ actions are allowed and applies the optional `graphql_policy_*` hooks. Assign a
261
+ subclass that implements `policy_set` (and optionally `include_resource?`) to
262
+ replace the payload builder.
263
+
264
+ ### Capabilities (RFC 0008)
265
+
266
+ Associate many object fields, queries, and mutations with one named capability
267
+ inside `graphql do`. The same capability name can cover a mixed set of members:
268
+
269
+ ```ruby
270
+ ActiveAdmin.register Post do
271
+ graphql do
272
+ capability :pii do
273
+ fields :body
274
+ end
275
+
276
+ capability :editor do
277
+ fields :title
278
+ queries :index, :show
279
+ mutations :create, :update, :destroy
280
+ end
281
+
282
+ capability :publish, member_actions: %i[publish]
283
+ end
284
+ end
285
+ ```
286
+
287
+ Kwargs (`fields:`, `queries:`, `mutations:`, `member_actions:`,
288
+ `batch_actions:`, `collection_actions:`) work without a block. Query aliases:
289
+ `index` / `list` → collection, `show` → member. Mutation alias: `delete` →
290
+ `destroy`. Run-action sugar builds keys like `member_publish`.
291
+
292
+ Grant capabilities per request with `graphql_capabilities`. Map OAuth scopes or
293
+ roles there; the gem stores a symbol set on `context[:capabilities]`:
294
+
295
+ ```ruby
296
+ admin.graphql_capabilities = lambda { |ctx|
297
+ Array(ctx[:current_user]&.oauth_scopes)
298
+ }
299
+ ```
300
+
301
+ A gated member fails closed when the capability is missing from that set:
302
+ resolve returns null for the field (graphql-ruby unauthorized field). Enable
303
+ `graphql_visibility` so introspection and validation also omit ungated members
304
+ when the field mode is `:omit` (default). Use `capability_mode: :null` (or
305
+ `capability :pii, fields: %i[body], mode: :null`) so the field stays in the
306
+ schema and resolves to `null` without the grant (RFC 0009).
307
+
308
+ `fields:` applies to generated attributes and wired `belongs_to` associations.
309
+ Unknown field names raise at schema build. Capabilities do not replace the
310
+ authorization adapter.
311
+
312
+ Custom `SchemaField` definitions may pass `capability: :name` and
313
+ `capability_mode: :null` the same way. Set `graphql_field_visible` for a second
314
+ omit gate (for example a request allowlist). Do not use named visibility
315
+ `profiles:` together with `graphql_capabilities` (cached profile context ignores
316
+ per-request grants).
317
+
318
+ Collection list fields may call a host `graphql_collection_query_policy`. The
319
+ helper `ActiveAdmin::GraphQL::CollectionQueryPolicy.require_visible_fields!`
320
+ rejects Ransack `q` keys for hidden fields and requires capabilities from the
321
+ resource field map by default (needed for `capability_mode: :null` columns).
322
+ Optional `field_grants` overrides inference; `infer_field_grants: false` turns
323
+ it off (RFC 0011).
324
+
202
325
  ## Schema overview
203
326
 
204
- The `Query` type includes one Relay-style connection field per resource route
205
- (for example `posts`), plus a singular field for a single record (for example
206
- `post`). Optional `graphql_field` entries on `register_page` appear here too,
207
- plus `registered_resource` (returning the `ActiveAdminRegisteredResource` union)
208
- when at least one resource is exposed.
327
+ With the default surface flags (`true`), the `Query` type includes one
328
+ Relay-style connection field per resource route (for example `posts`), plus a
329
+ singular field for a single record (for example `post`). Optional
330
+ `graphql_field` entries on `register_page` appear here too, plus
331
+ `registered_resource` (returning the `ActiveAdminRegisteredResource` union)
332
+ when at least one queryable resource exists.
333
+
334
+ List and show fields require per-resource `queries_enabled` (seeded from
335
+ `graphql_queries_enabled_default`). Turning queries off still leaves the object
336
+ type available for mutations. `registered_resource` and the union also require
337
+ `graphql_registered_resource_enabled`. Policy preflight fields require
338
+ `graphql_policies_enabled`. Schema build fails when `Query` would have no
339
+ fields: no list/show, no policies, no `registered_resource`, and no page
340
+ `graphql_field`.
209
341
 
210
342
  Every resource object type implements the `ActiveAdminResource` interface
211
343
  (`id: ID!`) so clients can share fragments or rely on a common shape.
344
+ Non-polymorphic `belongs_to` fields between registered types are wired when
345
+ `graphql_belongs_to_associations_enabled` is true.
212
346
 
213
347
  The `Mutation` type is present when at least one operation exists: per-resource
214
- `create_*`, `update_*`, and `delete_*` when those controller actions exist, plus
215
- `*_batch_action`, `*_member_action`, and `*_collection_action` fields when the
216
- resource defines the corresponding batch, member, or collection actions.
348
+ `create_*`, `update_*`, and `delete_*` when those controller actions exist and
349
+ `mutations_enabled` is true, plus `*_batch_action`, `*_member_action`, and
350
+ `*_collection_action` fields when the resource defines the corresponding batch,
351
+ member, or collection actions and `run_actions_enabled` is true. Both flags are
352
+ seeded from the matching namespace defaults.
217
353
 
218
354
  Object type names default to the model name (for example `Post`). Typed input
219
355
  objects use assignable columns (see the per-resource section below):
@@ -405,7 +541,16 @@ ActiveAdmin.register Post do
405
541
  end
406
542
  ```
407
543
 
544
+ * `enable!` — include this resource when `graphql_resource_enabled_default` is
545
+ `false`. An empty `graphql` block has the same effect.
408
546
  * `disable!` — resource is not included in Query or Mutation.
547
+ * `enable_queries!` / `disable_queries!` — list/show fields and
548
+ `registered_resource` membership for this resource.
549
+ * `enable_mutations!` / `disable_mutations!` — create/update/delete mutations.
550
+ * `enable_run_actions!` / `disable_run_actions!` — batch/member/collection
551
+ action mutations.
552
+ * `enable_policies!` / `disable_policies!` — object `activeadmin_policies` and
553
+ inclusion in the `activeadmin_policies` resources matrix.
409
554
  * `identity` — declare GraphQL id columns (and optional `separator`, `encode`,
410
555
  `decode`) when the model has no ActiveRecord primary key and no `id` column
411
556
  (RFC 0007).
@@ -456,8 +601,12 @@ graphql do
456
601
  type(Members::MemberRunPayload)
457
602
  end
458
603
 
459
- member_action_mutation(:send_invoice) do
604
+ member_action_mutation(:send_invoice, dry_run: true, capability: :support_act) do
460
605
  type(Members::SendInvoicePayload)
606
+ preview do |record, **|
607
+ record.may_send_invoice? ? ok("would_run:send_invoice:#{record.id}") : refuse("not_ready")
608
+ end
609
+ extra_params :note
461
610
  arguments do
462
611
  argument :note, GraphQL::Types::String, required: false, camelize: false
463
612
  end
@@ -467,7 +616,21 @@ end
467
616
 
468
617
  Call the named field as `posts_member_send_invoice(id: "...", note: "…")`. Extra
469
618
  arguments are merged with `params` for the controller and passed as keyword args
470
- to a custom `resolve` block.
619
+ to a custom `resolve` block. With `dry_run: true`, the field gains a Boolean
620
+ `dry_run` argument: preview runs first, dry-run returns without
621
+ `run_member_action`, and a refused preview never runs the action (RFC 0010).
622
+ `extra_params` allowlists which GraphQL kwargs forward into the AA request
623
+ (`id` cannot be rebound). `capability:` on batch / member / collection mutation
624
+ DSL overrides the resource map; otherwise the matching
625
+ `graphql_*_action_capability_default` applies (RFC 0012).
626
+
627
+ Custom mutations in `graphql_configure_schema` may use
628
+ `ActiveAdmin::GraphQL::RunActionField.define` (or `SchemaField` keywords
629
+ `dry_run:`, `confirm:`, `preview:`) for confirm/dry-run wiring (RFC 0013). When
630
+ `confirm` is required and both flags are false, the field returns
631
+ `body: "confirm_required"`. When `preview` is set, dry-run and refused preview
632
+ return without calling `resolve`; without `preview`, dry-run still calls
633
+ `resolve` with `dry_run: true`.
471
634
 
472
635
  With no symbol argument, `member_action_mutation { … }` configures only the
473
636
  aggregate `posts_member_action` field. With a symbol, it configures the
@@ -0,0 +1,26 @@
1
+ # Host customization gaps
2
+
3
+ ## Decisions
4
+
5
+ The gem owns mechanics: when a field is visible, null, or omitted; dry-run
6
+ argument wiring; filter-key policy hooks; default member-action capability;
7
+ confirm/dry_run injection on custom fields. Hosts own product grants, domain
8
+ preview bodies, and audit storage.
9
+
10
+ Capabilities remain grant names in context[:capabilities]. Named visibility
11
+ profiles stay incompatible with graphql_capabilities.
12
+
13
+ ## Effects
14
+
15
+ RFCs 0009–0013 specify the contract. Implementation landed on the feature
16
+ branch with request coverage for each hook.
17
+
18
+ ## Next
19
+
20
+ Publish a release. Hosts migrate off local prepends in the order: capability
21
+ null mode and dry-run DSL, then collection policy, then field_visible and
22
+ capability defaults, then confirm helpers.
23
+
24
+ ## Source
25
+
26
+ RFCs 0009–0013. docs/changelogs/20261012200000_host-customization-hooks.md.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ # Named grants carried in GraphQL +context[:capabilities]+ (RFC 0008).
6
+ module Capability
7
+ module_function
8
+
9
+ def normalize_set(raw)
10
+ return nil if raw.nil?
11
+
12
+ Set.new(
13
+ Array(raw).filter_map do |item|
14
+ next if item.nil?
15
+
16
+ name = item.to_s.strip
17
+ next if name.empty?
18
+
19
+ name.to_sym
20
+ end
21
+ )
22
+ end
23
+
24
+ def granted?(context, capability_name)
25
+ return true if capability_name.nil?
26
+
27
+ caps = context[:capabilities]
28
+ return false if caps.nil?
29
+
30
+ caps = normalize_set(caps) unless caps.is_a?(Set)
31
+ caps.include?(capability_name.to_sym)
32
+ end
33
+ end
34
+ end
35
+ end