activeadmin-graphql 0.4.0 → 0.4.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -3
  3. data/README.md +2 -2
  4. data/docs/changelogs/20260818111600_rfc-0002-problems.md +1 -1
  5. data/docs/changelogs/20260818114000_rfc-as-built-coverage.md +1 -1
  6. data/docs/changelogs/20260818114700_rfc-design-not-version.md +1 -1
  7. data/docs/changelogs/20260907141000_advisory-lockfile-refresh.md +1 -1
  8. data/docs/changelogs/20260921103000_mutation-attribute-allowlists.md +1 -1
  9. data/docs/changelogs/20260921111000_release-0-3-0.md +1 -1
  10. data/docs/changelogs/20260921112800_drop-standard-rubocop-direct.md +1 -1
  11. data/docs/changelogs/20260921120000_graphql-floor-and-activeadmin-4.md +1 -1
  12. data/docs/changelogs/20261010091415_graphql-resource-enabled-default.md +1 -1
  13. data/docs/changelogs/20261010103800_release-0-3-1.md +1 -1
  14. data/docs/changelogs/20261010110600_graphql-identity-dsl.md +4 -0
  15. data/docs/changelogs/20261010111800_release-0-3-2.md +1 -1
  16. data/docs/changelogs/20261012050051_graphql-surface-toggles.md +1 -1
  17. data/docs/changelogs/20261012064500_graphql-capabilities.md +1 -1
  18. data/docs/changelogs/20261012113500_release-0-3-3.md +1 -1
  19. data/docs/changelogs/20261012200000_host-customization-hooks.md +5 -1
  20. data/docs/changelogs/20261013080000_release-0-4-0.md +1 -1
  21. data/docs/changelogs/20261103145000_release-0-4-1.md +26 -0
  22. data/docs/graphql-api.md +107 -102
  23. data/docs/issues/20260907141000_engineering-and-dependency-audit.md +1 -1
  24. data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +1 -1
  25. data/docs/issues/20260921103000_mutation-attribute-allowlists.md +1 -1
  26. data/docs/issues/20260921104400_convention-alignment-audit.md +7 -7
  27. data/docs/issues/20260921114500_dependency-audit.md +13 -13
  28. data/docs/issues/20260921115500_file-length-metrics.md +1 -1
  29. data/docs/issues/20261010111159_rails-composite-primary-keys-alignment.md +13 -13
  30. data/docs/issues/20261010111300_partitions-and-shards-support.md +3 -3
  31. data/docs/issues/20261010111500_graphql-federation-support.md +3 -3
  32. data/docs/issues/20261012200000_host-customization-gaps.md +6 -2
  33. data/docs/issues/20261103145000_engineering-prose-release-audit.md +39 -0
  34. data/docs/plan/20260817201500_rfc-starter.md +1 -1
  35. data/docs/reviews/20261028_engineering-audit-release-preparation.md +24 -0
  36. data/docs/reviews/20261103145000_engineering-prose-release-audit.md +38 -0
  37. data/lib/active_admin/graphql/schema_builder/mutation_create.rb +20 -20
  38. data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +2 -4
  39. data/lib/active_admin/graphql/schema_builder/query_type_member.rb +1 -4
  40. data/lib/active_admin/graphql/schema_builder/query_type_registered.rb +1 -4
  41. data/lib/active_admin/graphql/version.rb +1 -1
  42. metadata +5 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei Makarov
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -16,28 +16,28 @@ after shipping inferred id columns and RFC 0007 identity DSL.
16
16
 
17
17
  ## Effects
18
18
 
19
- Compared gem behavior to the guide’s model, query, form, and parameter rules.
19
+ Compared gem behavior to the guide's model, query, form, and parameter rules.
20
20
 
21
21
  ActiveRecord composite primary key (self.primary_key = array):
22
22
 
23
- - Aligns: model declares composite key the same way as the guide’s
23
+ - Aligns: model declares composite key the same way as the guide's
24
24
  `self.primary_key = [:store_id, :sku]` style. Dummy `LibraryEdition` uses
25
25
  `[:book_code, :seq]`.
26
26
  - Aligns: internal batch and find paths convert that string id into an ordered
27
27
  value list and use Rails tuple/`Relation#find` style lookups
28
28
  (`ActiveAdmin::PrimaryKey.dataloader_tuple`, `ResourceQueryProxy` composite
29
- branch). That matches the guide’s `Product.find([3, "XYZ12345"])` and
29
+ branch). That matches the guide's `Product.find([3, "XYZ12345"])` and
30
30
  `where(primary_key => [[...], [...]])` idea at the Ruby API layer.
31
31
  - Diverges: GraphQL and the published docs encode composite `id` as a JSON
32
- object string (`{"book_code":"CPK","seq":7}`), not as the guide’s form/URL
32
+ object string (`{"book_code":"CPK","seq":7}`), not as the guide's form/URL
33
33
  underscore packing (`/books/2_25` with `params.extract_value(:id)`). JSON
34
34
  keeps column names explicit and avoids ambiguity when a key value contains
35
35
  `_`. Underscore packing is not accepted as GraphQL `id` today.
36
36
  - Diverges: GraphQL also accepts per-key arguments and `WhereInput` fields for
37
- composite models. The guide’s controller example extracts one `:id` param
37
+ composite models. The guide's controller example extracts one `:id` param
38
38
  into an array; we keep named keys as a GraphQL-friendly alternate.
39
39
  - Aligns with guide caution: `where(id: ...)` / `find_by(id:)` on composite
40
- models can mean an `:id` attribute, not the full primary key. The gem’s
40
+ models can mean an `:id` attribute, not the full primary key. The gem's
41
41
  GraphQL `id` field is the ActiveAdminResource identity string, not
42
42
  necessarily an ActiveRecord `:id` column. Member lookup uses primary-key
43
43
  columns (or configured identity), not a bare `where(id: graphql_id)` on
@@ -45,15 +45,15 @@ ActiveRecord composite primary key (self.primary_key = array):
45
45
 
46
46
  Keyless views and RFC 0007 `graphql { identity ... }`:
47
47
 
48
- - Outside the guide’s scope. The guide assumes a declared composite primary
48
+ - Outside the guide's scope. The guide assumes a declared composite primary
49
49
  key on the table/model. AlertEvent-style views with `primary_key = nil` and
50
50
  a virtual Ruby `id` are not Rails composite primary keys.
51
51
  - `identity :a, :b, :c, separator: ":"` is an application-level encoding so
52
52
  GraphQL can share the same string as a hand-rolled `def id; [...].join(":")`.
53
- That separator form is closer in spirit to Rails’ delimited URL id than our
53
+ That separator form is closer in spirit to Rails' delimited URL id than our
54
54
  JSON composite encoding, but the delimiter and segment rules are app-chosen,
55
- not `extract_value`’s underscore default.
56
- - Without `separator`, configured multi-column identity reuses the gem’s JSON
55
+ not `extract_value`'s underscore default.
56
+ - Without `separator`, configured multi-column identity reuses the gem's JSON
57
57
  object string so keyless and AR-composite GraphQL ids stay one family.
58
58
 
59
59
  Stock ActiveAdmin HTML member routes still need a composite-aware
@@ -64,8 +64,8 @@ make Rails form URLs use JSON.
64
64
 
65
65
  Leave GraphQL composite `id` as JSON for AR composite primary keys unless a
66
66
  later RFC deliberately adds underscore (or `extract_value`-compatible) accept
67
- and emit. Document that choice next to the Rails guide, not as “the same
68
- format Rails uses for casting composite ids” without qualification.
67
+ and emit. Document that choice next to the Rails guide, not as "the same
68
+ format Rails uses for casting composite ids" without qualification.
69
69
 
70
70
  No change required for RFC 0007 separator identity; it is the escape hatch
71
71
  when the model is not a Rails composite primary key.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei Makarov
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -23,7 +23,7 @@ as a product feature and untested in this gem.
23
23
  Searched lib, docs, rfcs, and spec for partition, shard, connects_to,
24
24
  multiple database, tenant routing, writing_role, and reading_role.
25
25
 
26
- Product code has no shard or partition API. Hits for “shard” and “partition”
26
+ Product code has no shard or partition API. Hits for "shard" and "partition"
27
27
  are Polyrun/CI test parallelism and agent IO-fault vocabulary, not databases.
28
28
 
29
29
  Dummy app uses one SQLite database (`spec/dummy/config/database.yml`). No
@@ -41,7 +41,7 @@ the Rails/ActiveAdmin request boundary (middleware, `connected_to`, custom
41
41
  finder). Do not invent gem DSL until a concrete host failure and RFC name the
42
42
  contract (shard key in GraphQL args, role switching, cross-shard batch limits).
43
43
 
44
- Optional later: document “unsupported / inherited only” next to identity in
44
+ Optional later: document "unsupported / inherited only" next to identity in
45
45
  `docs/graphql-api.md` when a reader asks again.
46
46
 
47
47
  ## Source
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei Makarov
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -34,8 +34,8 @@ tested here.
34
34
  ## Next
35
35
 
36
36
  Keep federation out of scope until a host names a concrete subgraph contract
37
- and an RFC. Prefer documenting “single-schema admin API; federation
38
- unsupported” if readers keep asking.
37
+ and an RFC. Prefer documenting "single-schema admin API; federation
38
+ unsupported" if readers keep asking.
39
39
 
40
40
  ## Source
41
41
 
@@ -1,5 +1,9 @@
1
1
  # Host customization gaps
2
2
 
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
3
7
  ## Decisions
4
8
 
5
9
  The gem owns mechanics: when a field is visible, null, or omitted; dry-run
@@ -12,7 +16,7 @@ profiles stay incompatible with graphql_capabilities.
12
16
 
13
17
  ## Effects
14
18
 
15
- RFCs 0009–0013 specify the contract. Implementation landed on the feature
19
+ RFCs 0009-0013 specify the contract. Implementation landed on the feature
16
20
  branch with request coverage for each hook.
17
21
 
18
22
  ## Next
@@ -23,4 +27,4 @@ capability defaults, then confirm helpers.
23
27
 
24
28
  ## Source
25
29
 
26
- RFCs 0009–0013. docs/changelogs/20261012200000_host-customization-hooks.md.
30
+ RFCs 0009-0013. docs/changelogs/20261012200000_host-customization-hooks.md.
@@ -0,0 +1,39 @@
1
+ # Engineering, prose, and release audit
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Keep version 0.4.1. Fold audit fixes into the unpublished cut and date the product changelog 2026-11-03.
10
+ - Treat member and registered_resource instance-read denials as missing (null), and update/destroy instance denials as not found, so clients cannot enumerate ids from the error message.
11
+ - Wrap resolve_create hooks in a nested transaction and run instance create authorization before commit.
12
+ - Keep release confirmation EOF as rejection, require a CHANGELOG version heading, and run tests on pull requests to main.
13
+ - Leave policy run-action over-reporting, composite find_members N+1, RecordSource unscoped load, and default-open collection filter policy as follow-up work (medium severity, not cut into 0.4.1).
14
+
15
+ ## Effects
16
+
17
+ Pipeline: GraphQL HTTP ingress -> schema -> resolve -> auth/policy -> ActiveRecord -> JSON egress. Queue, worker, learned-system, and physical plant modes skipped (absent). Resource and identification: gem execute log keeps namespace, operation name, and error count only.
18
+
19
+ Ranked fixes applied:
20
+
21
+ 1. Member and registered_resource instance-read deny returns null (high, observed).
22
+ 2. Update and destroy instance deny returns not found (high, observed).
23
+ 3. resolve_create hook deny rolls back nested transaction (high, observed).
24
+ 4. Release changelog heading specs and EOF confirmation coverage (medium, observed).
25
+ 5. ASCII punctuation in README, CHANGELOG, and graphql-api guide (house prose).
26
+ 6. Docs timestamp trees: Participants handle, Source after Later pass sections.
27
+
28
+ Checks: rubocop clean on touched files; rspec 197 examples, 0 failures; gem 0.4.1 builds and verifies.
29
+
30
+ ## Next
31
+
32
+ - Commit the worktree, then run make release for tag and gem push.
33
+ - Follow-up: filter PolicyAdapter run-action lists by per-action authorized?; batch composite find_members; scope RecordSource loads; optional default collection query policy.
34
+
35
+ ## Source
36
+
37
+ docs/reviews/20261103145000_engineering-prose-release-audit.md
38
+ CHANGELOG.md section 0.4.1 (2026-11-03)
39
+ docs/reviews/20261028_engineering-audit-release-preparation.md
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei Makarov
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -0,0 +1,24 @@
1
+ # Engineering audit and release preparation
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Treat end-of-input at the release confirmation prompt as rejection.
10
+ - Run tests for pull requests targeting `main`.
11
+
12
+ ## Effects
13
+
14
+ - `make test`, RBS validation, RuboCop, and the sharded suite pass.
15
+ - The 0.4.1 gem builds and passes `Gem::Package#verify`.
16
+ - A local Rails and ActiveAdmin host cold load used 1.063 CPU seconds and 92,422,144 bytes peak RSS. These are single-machine observations, not release budgets.
17
+
18
+ ## Next
19
+
20
+ - Review the worktree, commit it, and let the release workflow create the tag and publish the gem.
21
+
22
+ ## Source
23
+
24
+ Iterative engineering audit on 2026-10-28.
@@ -0,0 +1,38 @@
1
+ # Engineering and prose audit before 0.4.1 publish
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ Re-audit main for the unpublished 0.4.1 cut. Include member auth oracle shape, resolve_create transaction order, release confirmation and changelog gates, pull_request CI, and ASCII docs punctuation. Leave tag and RubyGems publish to make release after commit.
10
+
11
+ ## Effects
12
+
13
+ Pipeline: POST graphql -> GraphqlController#execute -> schema_for -> resolve -> AuthContext / ResourceQueryProxy -> ActiveRecord -> JSON. Boundary mode covered class vs instance authorization messages. Resource and identification modes: no new person identifiers in gem logs. Queue, worker, learned-system, and physical plant modes skipped (absent).
14
+
15
+ Ranked fixes applied:
16
+
17
+ 1. Null for denied member and registered_resource reads (high confidence, observed).
18
+ 2. not found for denied update and destroy (high confidence, observed).
19
+ 3. Nested transaction around resolve_create with instance authorize before commit (high confidence, observed).
20
+ 4. Changelog heading and confirmation specs; CHANGELOG dated 2026-11-03.
21
+ 5. ASCII punctuation pass on README, CHANGELOG, and docs/graphql-api.md.
22
+
23
+ Deferred (documented in the issue): PolicyAdapter run-action over-report, composite find_members N+1, RecordSource unscoped PK load, default-open collection q policy.
24
+
25
+ Checks observed in this session:
26
+
27
+ - bundle exec rubocop on touched Ruby files: 9 files, no offenses.
28
+ - bundle exec rspec: 197 examples, 0 failures (includes release_version_check and new auth examples).
29
+ - gem build activeadmin-graphql.gemspec and Gem::Package#verify for 0.4.1: ok.
30
+
31
+ ## Next
32
+
33
+ Commit, then make release.
34
+
35
+ ## Source
36
+
37
+ - docs/issues/20261103145000_engineering-prose-release-audit.md
38
+ - CHANGELOG.md section 0.4.1 (2026-11-03)
@@ -35,32 +35,32 @@ module ActiveAdmin
35
35
  input,
36
36
  attribute_names: aa_res.graphql_create_attribute_names
37
37
  )
38
- record = if (hook = aa_res.graphql_config.resolve_create_proc)
39
- hook.call(
40
- proxy: proxy,
41
- input: input,
42
- attributes: attrs,
43
- context: context,
44
- auth: auth,
45
- aa_resource: aa_res
46
- )
38
+ if (hook = aa_res.graphql_config.resolve_create_proc)
39
+ ActiveRecord::Base.transaction(requires_new: true) do
40
+ record = hook.call(
41
+ proxy: proxy,
42
+ input: input,
43
+ attributes: attrs,
44
+ context: context,
45
+ auth: auth,
46
+ aa_resource: aa_res
47
+ )
48
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
49
+ raise ::GraphQL::ExecutionError, "not authorized to create this record"
50
+ end
51
+ record
52
+ end
47
53
  else
48
- r = proxy.build_new(attrs)
49
- unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, r)
54
+ record = proxy.build_new(attrs)
55
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
50
56
  raise ::GraphQL::ExecutionError, "not authorized to create this record"
51
57
  end
52
58
 
53
- unless r.save
54
- raise MutationExecutionError.validation(r)
59
+ unless record.save
60
+ raise MutationExecutionError.validation(record)
55
61
  end
56
- r
57
- end
58
-
59
- unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
60
- raise ::GraphQL::ExecutionError, "not authorized to create this record"
62
+ record
61
63
  end
62
-
63
- record
64
64
  end
65
65
  end
66
66
  end
@@ -38,9 +38,8 @@ module ActiveAdmin
38
38
  )
39
39
  record = proxy.find_member(rid)
40
40
  raise ::GraphQL::ExecutionError, "not found" unless record
41
-
42
41
  unless auth.authorized?(aa_res, ActiveAdmin::Authorization::UPDATE, record)
43
- raise ::GraphQL::ExecutionError, "not authorized to update this record"
42
+ raise ::GraphQL::ExecutionError, "not found"
44
43
  end
45
44
 
46
45
  attrs = sb.assignable_slice_from_input(
@@ -105,9 +104,8 @@ module ActiveAdmin
105
104
  )
106
105
  record = proxy.find_member(rid)
107
106
  raise ::GraphQL::ExecutionError, "not found" unless record
108
-
109
107
  unless auth.authorized?(aa_res, ActiveAdmin::Authorization::DESTROY, record)
110
- raise ::GraphQL::ExecutionError, "not authorized to destroy this record"
108
+ raise ::GraphQL::ExecutionError, "not found"
111
109
  end
112
110
 
113
111
  if (hook = aa_res.graphql_config.resolve_destroy_proc)
@@ -79,10 +79,7 @@ module ActiveAdmin
79
79
  id_argument: id
80
80
  )
81
81
  return nil unless record
82
-
83
- unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
84
- raise ::GraphQL::ExecutionError, "not authorized to read this record"
85
- end
82
+ return nil unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
86
83
 
87
84
  record
88
85
  end
@@ -42,10 +42,7 @@ module ActiveAdmin
42
42
  graph_params: graph_params
43
43
  )
44
44
  return nil unless record
45
-
46
- unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
47
- raise ::GraphQL::ExecutionError, "not authorized to read this record"
48
- end
45
+ return nil unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
49
46
 
50
47
  record
51
48
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveAdmin
4
4
  module GraphQL
5
- VERSION = "0.4.0"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeadmin-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -384,6 +384,7 @@ files:
384
384
  - docs/changelogs/20261012113500_release-0-3-3.md
385
385
  - docs/changelogs/20261012200000_host-customization-hooks.md
386
386
  - docs/changelogs/20261013080000_release-0-4-0.md
387
+ - docs/changelogs/20261103145000_release-0-4-1.md
387
388
  - docs/dependencies/20260907141000_json-cve-2026-71847.md
388
389
  - docs/dependencies/20260907141000_sqlite3-ghsa-mwm8-39rw-8826.md
389
390
  - docs/dependencies/20260921114000_rails-json-parse-kwargs.md
@@ -400,7 +401,10 @@ files:
400
401
  - docs/issues/20261010111300_partitions-and-shards-support.md
401
402
  - docs/issues/20261010111500_graphql-federation-support.md
402
403
  - docs/issues/20261012200000_host-customization-gaps.md
404
+ - docs/issues/20261103145000_engineering-prose-release-audit.md
403
405
  - docs/plan/20260817201500_rfc-starter.md
406
+ - docs/reviews/20261028_engineering-audit-release-preparation.md
407
+ - docs/reviews/20261103145000_engineering-prose-release-audit.md
404
408
  - lib/active_admin/graphql.rb
405
409
  - lib/active_admin/graphql/auth_context.rb
406
410
  - lib/active_admin/graphql/capability.rb