ecoportal-api-graphql 1.3.13 → 1.3.15

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -29
  3. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -42
  4. data/.ai-assistance/projects/TODO.md +170 -118
  5. data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -48
  6. data/.ai-assistance/skills/code-specs/SKILL.md +69 -69
  7. data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -63
  8. data/.ai-assistance/skills/project-cycle/SKILL.md +177 -177
  9. data/.ai-assistance/skills/refactor/SKILL.md +62 -62
  10. data/.ai-assistance/skills/spec-generation/SKILL.md +72 -72
  11. data/.gitignore +37 -37
  12. data/.gitlab-ci.yml +64 -12
  13. data/CHANGELOG.md +77 -0
  14. data/changelog.d/fixed-mr51-payload-item-mismatches.md +17 -0
  15. data/docs/worklog.md +0 -18
  16. data/lib/ecoportal/api/common/graphql/client.rb +0 -2
  17. data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
  18. data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
  19. data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
  20. data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
  21. data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
  22. data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
  23. data/lib/ecoportal/api/graphql/base/page.rb +17 -16
  24. data/lib/ecoportal/api/graphql/base/template.rb +35 -0
  25. data/lib/ecoportal/api/graphql/base.rb +37 -35
  26. data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
  27. data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
  28. data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
  29. data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
  30. data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
  31. data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
  32. data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
  33. data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
  34. data/lib/ecoportal/api/graphql/connection.rb +17 -17
  35. data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
  36. data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
  37. data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
  38. data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
  39. data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
  40. data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
  41. data/lib/ecoportal/api/graphql/fragment.rb +3 -0
  42. data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
  43. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
  44. data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
  45. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
  46. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
  47. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
  48. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
  49. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
  50. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
  51. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
  52. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
  53. data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
  54. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
  55. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
  56. data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
  57. data/lib/ecoportal/api/graphql/input.rb +24 -24
  58. data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
  59. data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
  60. data/lib/ecoportal/api/graphql/model/page.rb +16 -15
  61. data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
  62. data/lib/ecoportal/api/graphql/model/template.rb +15 -0
  63. data/lib/ecoportal/api/graphql/model.rb +32 -30
  64. data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
  65. data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
  66. data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
  67. data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
  68. data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
  69. data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
  70. data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
  71. data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
  72. data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
  73. data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
  74. data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
  75. data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
  76. data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
  77. data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
  78. data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
  79. data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
  80. data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
  81. data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
  82. data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
  83. data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
  84. data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
  85. data/lib/ecoportal/api/graphql/mutation.rb +20 -20
  86. data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
  87. data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
  88. data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
  89. data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
  90. data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
  91. data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
  92. data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
  93. data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
  94. data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
  95. data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
  96. data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
  97. data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
  98. data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
  99. data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
  100. data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
  101. data/lib/ecoportal/api/graphql/query.rb +27 -27
  102. data/lib/ecoportal/api/graphql.rb +9 -5
  103. data/lib/ecoportal/api/graphql_version.rb +1 -1
  104. data/tests/dump_schema.rb +88 -0
  105. data/tests/validate_queries.rb +34 -12
  106. metadata +11 -1
@@ -1,62 +1,62 @@
1
- # SKILL: Refactor
2
-
3
- **Purpose:** Identify, log, and (with developer confirmation) apply refactoring opportunities. Never apply changes without explicit confirmation.
4
-
5
- ---
6
-
7
- ## When to Use
8
-
9
- - You notice a structural issue while working on something else — log it, don't fix it silently.
10
- - The developer asks for a refactor audit of an area.
11
- - A cycle-end review surfaces cleanup opportunities.
12
-
13
- ---
14
-
15
- ## Criteria for a Refactor Candidate
16
-
17
- Flag something when it fails one or more of:
18
-
19
- - **Understandability:** Is it clear what this does and why?
20
- - **Dependency tracking:** Are dependencies implicit or tangled?
21
- - **Separation of concerns:** Is this class/method doing more than one thing?
22
- - **Simplicity:** Could this be simpler without losing correctness?
23
- - **Alignment:** Does this match the conventions of the rest of the codebase?
24
-
25
- ---
26
-
27
- ## Log Format
28
-
29
- Refactoring opportunities are logged in `.ai-assistance/code/refactoring/<area>.md`.
30
-
31
- Each entry:
32
-
33
- ```markdown
34
- ## [SHORT-ID] <Title>
35
-
36
- - **File(s):** `lib/ecoportal/...`
37
- - **Type:** [extract_class | extract_method | rename | simplify | reorganise | other]
38
- - **Priority:** [high | medium | low]
39
- - **Status:** [open | confirmed | applied | dismissed]
40
- - **Identified:** YYYY-MM-DD
41
-
42
- **Observation:**
43
- What the issue is, concisely.
44
-
45
- **Proposed change:**
46
- What you'd do to fix it. High-level — not code unless it's very short.
47
-
48
- **Risk:**
49
- Any backwards-compat or downstream risk if applied.
50
- ```
51
-
52
- ---
53
-
54
- ## Workflow
55
-
56
- 1. Identify opportunity → log it in the appropriate refactoring file.
57
- 2. Present a summary to the developer: "I've logged N refactoring opportunities in `.ai-assistance/code/refactoring/`. Want to review them?"
58
- 3. Developer confirms which ones to apply → mark as `confirmed`.
59
- 4. Apply one at a time, update specs if needed, mark as `applied`.
60
- 5. If dismissed by developer → mark as `dismissed` with a brief reason.
61
-
62
- **Never apply a refactor without a `confirmed` status.**
1
+ # SKILL: Refactor
2
+
3
+ **Purpose:** Identify, log, and (with developer confirmation) apply refactoring opportunities. Never apply changes without explicit confirmation.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - You notice a structural issue while working on something else — log it, don't fix it silently.
10
+ - The developer asks for a refactor audit of an area.
11
+ - A cycle-end review surfaces cleanup opportunities.
12
+
13
+ ---
14
+
15
+ ## Criteria for a Refactor Candidate
16
+
17
+ Flag something when it fails one or more of:
18
+
19
+ - **Understandability:** Is it clear what this does and why?
20
+ - **Dependency tracking:** Are dependencies implicit or tangled?
21
+ - **Separation of concerns:** Is this class/method doing more than one thing?
22
+ - **Simplicity:** Could this be simpler without losing correctness?
23
+ - **Alignment:** Does this match the conventions of the rest of the codebase?
24
+
25
+ ---
26
+
27
+ ## Log Format
28
+
29
+ Refactoring opportunities are logged in `.ai-assistance/code/refactoring/<area>.md`.
30
+
31
+ Each entry:
32
+
33
+ ```markdown
34
+ ## [SHORT-ID] <Title>
35
+
36
+ - **File(s):** `lib/ecoportal/...`
37
+ - **Type:** [extract_class | extract_method | rename | simplify | reorganise | other]
38
+ - **Priority:** [high | medium | low]
39
+ - **Status:** [open | confirmed | applied | dismissed]
40
+ - **Identified:** YYYY-MM-DD
41
+
42
+ **Observation:**
43
+ What the issue is, concisely.
44
+
45
+ **Proposed change:**
46
+ What you'd do to fix it. High-level — not code unless it's very short.
47
+
48
+ **Risk:**
49
+ Any backwards-compat or downstream risk if applied.
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Workflow
55
+
56
+ 1. Identify opportunity → log it in the appropriate refactoring file.
57
+ 2. Present a summary to the developer: "I've logged N refactoring opportunities in `.ai-assistance/code/refactoring/`. Want to review them?"
58
+ 3. Developer confirms which ones to apply → mark as `confirmed`.
59
+ 4. Apply one at a time, update specs if needed, mark as `applied`.
60
+ 5. If dismissed by developer → mark as `dismissed` with a brief reason.
61
+
62
+ **Never apply a refactor without a `confirmed` status.**
@@ -1,72 +1,72 @@
1
- # SKILL: Spec Generation
2
-
3
- **Purpose:** Add RSpec specs that guard backwards compatibility and document expected behaviour, placed in the correct spec folder mirroring the lib/ structure.
4
-
5
- ---
6
-
7
- ## When to Use
8
-
9
- - A change could break downstream consumers (primarily `eco-helpers`).
10
- - A public method or interface is being added, changed, or removed.
11
- - A bug is fixed — add a regression spec.
12
- - A new class is added that other code will depend on.
13
-
14
- ---
15
-
16
- ## Placement Rules
17
-
18
- Spec files **mirror `lib/` structure** under `spec/`:
19
-
20
- | Code file | Spec file |
21
- |-----------|----------|
22
- | `lib/ecoportal/api/graphql/model/action.rb` | `spec/ecoportal/api/graphql/model/action_spec.rb` |
23
- | `lib/ecoportal/api/graphql/input/location_structure/draft.rb` | `spec/ecoportal/api/graphql/input/location_structure/draft_spec.rb` |
24
-
25
- Create the folder path if it doesn't exist.
26
-
27
- ---
28
-
29
- ## Spec Requirements
30
-
31
- Each spec file for a changed area must have:
32
-
33
- 1. A description of the class/module under test.
34
- 2. At least one spec covering the public interface being changed.
35
- 3. For backwards-compat specs: a comment `# backwards-compat` so they're easily searched.
36
-
37
- **Minimum spec template:**
38
-
39
- ```ruby
40
- # frozen_string_literal: true
41
-
42
- RSpec.describe Ecoportal::API::Graphql::<ClassName> do
43
- # backwards-compat
44
- describe '#<method>' do
45
- subject { described_class.new(<args>) }
46
-
47
- it '<expected behaviour>' do
48
- expect(subject.<method>).to <matcher>
49
- end
50
- end
51
- end
52
- ```
53
-
54
- ---
55
-
56
- ## Linked to Code Specs for AI
57
-
58
- Whenever a spec file is created or updated for an area, check the corresponding `.ai-assistance/code/<area>.md` doc:
59
- - Does it mention this class/method?
60
- - Does it reflect the current expected behaviour?
61
-
62
- If not — update it. This redundancy is intentional: it saves AI agents from having to parse spec files to understand expected behaviour.
63
-
64
- ---
65
-
66
- ## Gemini Assist Integration
67
-
68
- When generating specs for a large area, you may use `gemini-assist` to:
69
- - Get a first-pass draft of spec cases given the source file.
70
- - Check for edge cases you may have missed.
71
-
72
- Always verify the generated specs run and match actual behaviour before committing.
1
+ # SKILL: Spec Generation
2
+
3
+ **Purpose:** Add RSpec specs that guard backwards compatibility and document expected behaviour, placed in the correct spec folder mirroring the lib/ structure.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - A change could break downstream consumers (primarily `eco-helpers`).
10
+ - A public method or interface is being added, changed, or removed.
11
+ - A bug is fixed — add a regression spec.
12
+ - A new class is added that other code will depend on.
13
+
14
+ ---
15
+
16
+ ## Placement Rules
17
+
18
+ Spec files **mirror `lib/` structure** under `spec/`:
19
+
20
+ | Code file | Spec file |
21
+ |-----------|----------|
22
+ | `lib/ecoportal/api/graphql/model/action.rb` | `spec/ecoportal/api/graphql/model/action_spec.rb` |
23
+ | `lib/ecoportal/api/graphql/input/location_structure/draft.rb` | `spec/ecoportal/api/graphql/input/location_structure/draft_spec.rb` |
24
+
25
+ Create the folder path if it doesn't exist.
26
+
27
+ ---
28
+
29
+ ## Spec Requirements
30
+
31
+ Each spec file for a changed area must have:
32
+
33
+ 1. A description of the class/module under test.
34
+ 2. At least one spec covering the public interface being changed.
35
+ 3. For backwards-compat specs: a comment `# backwards-compat` so they're easily searched.
36
+
37
+ **Minimum spec template:**
38
+
39
+ ```ruby
40
+ # frozen_string_literal: true
41
+
42
+ RSpec.describe Ecoportal::API::Graphql::<ClassName> do
43
+ # backwards-compat
44
+ describe '#<method>' do
45
+ subject { described_class.new(<args>) }
46
+
47
+ it '<expected behaviour>' do
48
+ expect(subject.<method>).to <matcher>
49
+ end
50
+ end
51
+ end
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Linked to Code Specs for AI
57
+
58
+ Whenever a spec file is created or updated for an area, check the corresponding `.ai-assistance/code/<area>.md` doc:
59
+ - Does it mention this class/method?
60
+ - Does it reflect the current expected behaviour?
61
+
62
+ If not — update it. This redundancy is intentional: it saves AI agents from having to parse spec files to understand expected behaviour.
63
+
64
+ ---
65
+
66
+ ## Gemini Assist Integration
67
+
68
+ When generating specs for a large area, you may use `gemini-assist` to:
69
+ - Get a first-pass draft of spec cases given the source file.
70
+ - Check for edge cases you may have missed.
71
+
72
+ Always verify the generated specs run and match actual behaviour before committing.
data/.gitignore CHANGED
@@ -1,37 +1,37 @@
1
- # it's a gem, ignore the lockfile
2
- Gemfile.lock
3
-
4
- .env
5
- .ai-assistance/local_paths.md
6
- # build artifacts
7
- *.gem
8
- /.bundle/
9
- /vendor/bundle
10
- /spec/reports/
11
- /tmp/
12
- /pkg/
13
-
14
- # docs
15
- /.yardoc
16
- /_yardoc/
17
- /coverage/
18
- /doc/
19
-
20
- # rspec failure tracking
21
- .rspec_status
22
- scratch.rb
23
- tests/.byebug_history
24
-
25
- .solargraph.yml
26
-
27
- # AI assistance — local-only files
28
- .ai-assistance/code/dep-graph.json
29
- .ai-assistance/tmp/
30
- .ai-assistance/local/
31
- .ai-assistance/bridge/inbox/*.draft.md
32
- .ai-assistance/bridge/inbox/*.local.md
33
- .ai-assistance/bridge/LOCK
34
- .ai-assistance/bridge/STATUS
35
- .ai-assistance/capabilities/.env
36
- .ai-assistance/scripts/*.env
37
- .ai-assistance/scripts/node_modules/
1
+ # it's a gem, ignore the lockfile
2
+ Gemfile.lock
3
+
4
+ .env
5
+ .ai-assistance/local_paths.md
6
+ # build artifacts
7
+ *.gem
8
+ /.bundle/
9
+ /vendor/bundle
10
+ /spec/reports/
11
+ /tmp/
12
+ /pkg/
13
+
14
+ # docs
15
+ /.yardoc
16
+ /_yardoc/
17
+ /coverage/
18
+ /doc/
19
+
20
+ # rspec failure tracking
21
+ .rspec_status
22
+ scratch.rb
23
+ tests/.byebug_history
24
+
25
+ .solargraph.yml
26
+
27
+ # AI assistance — local-only files
28
+ .ai-assistance/code/dep-graph.json
29
+ .ai-assistance/tmp/
30
+ .ai-assistance/local/
31
+ .ai-assistance/bridge/inbox/*.draft.md
32
+ .ai-assistance/bridge/inbox/*.local.md
33
+ .ai-assistance/bridge/LOCK
34
+ .ai-assistance/bridge/STATUS
35
+ .ai-assistance/capabilities/.env
36
+ .ai-assistance/scripts/*.env
37
+ .ai-assistance/scripts/node_modules/
data/.gitlab-ci.yml CHANGED
@@ -1,18 +1,24 @@
1
1
  # CI pipeline for ecoportal-api-graphql
2
2
  #
3
- # First automated check on merge requests. Runs the RSpec suite NON-BLOCKING
4
- # (allow_failure: true) — it reports pass/fail status without gating merges.
5
- # Rationale (2026-07-10): add visibility now; tighten to a blocking gate later
6
- # once the suite is reliably green on a clean runner.
3
+ # Two jobs, deliberately different in strictness:
7
4
  #
8
- # Live/AI specs (tagged :live) are excluded: they need ANTHROPIC_API_KEY + network.
9
- # They already self-skip when the key is absent, but ~live keeps the runner off
10
- # that path entirely.
5
+ # rspec — NON-BLOCKING (allow_failure: true): reports pass/fail without gating merges.
6
+ # Rationale (2026-07-10): visibility now; tighten to a gate later once the
7
+ # suite is reliably green on a clean runner.
8
+ # validate-queries — BLOCKING: renders EVERY query + mutation in the gem against a FRESHLY
9
+ # introspected schema and fails only on deterministic, non-negotiable
10
+ # breakage (see below). This is the gate the 2026-07-03 v1.3.10 prod hotfix
11
+ # (LocationStructure `updatedAt`) called for — those queries had no spec and
12
+ # first broke in a client runner.
13
+ #
14
+ # Live/AI specs (tagged :live) are excluded from rspec: they need ANTHROPIC_API_KEY + network.
15
+ # They already self-skip when the key is absent, but ~live keeps the runner off that path entirely.
11
16
  #
12
17
  # NOTE: graphlient resolves from a git fork (see Gemfile); ecoportal-api / -v2 come
13
18
  # from rubygems. If a required floor (e.g. ecoportal-api-v2 >= 3.3.3) is not yet
14
- # published, `bundle install` may fail on the runner because this job is
15
- # allow_failure, that surfaces as a warning rather than blocking the MR.
19
+ # published, `bundle install` may fail on the runner. For `rspec` that surfaces as a
20
+ # warning; for `validate-queries` it fails the pipeline the fix is to publish the
21
+ # floor gem, not to relax the gate.
16
22
 
17
23
  workflow:
18
24
  rules:
@@ -27,10 +33,8 @@ workflow:
27
33
  stages:
28
34
  - test
29
35
 
30
- rspec:
31
- stage: test
36
+ .ruby:
32
37
  image: ruby:3.2.2
33
- allow_failure: true # non-blocking: surface failures, do not gate merges
34
38
  variables:
35
39
  BUNDLE_PATH: vendor/bundle # cache installed gems between pipelines
36
40
  cache:
@@ -41,5 +45,53 @@ rspec:
41
45
  - ruby -v
42
46
  - gem install bundler
43
47
  - bundle install --jobs "$(nproc)" --retry 3
48
+
49
+ rspec:
50
+ extends: .ruby
51
+ stage: test
52
+ allow_failure: true # non-blocking: surface failures, do not gate merges
44
53
  script:
45
54
  - bundle exec rspec --tag ~live --format progress
55
+
56
+ # Offline validation of every gem query + mutation against the LIVE schema.
57
+ #
58
+ # Fails the pipeline on exactly two deterministic classes (see tests/validate_queries.rb):
59
+ # 1. FRAGMENT / RENDER FAILURES — the operation cannot even be rendered (dead fragment
60
+ # reference / unresolved constant). This is what broke the live LocationDraft, Action and
61
+ # ContractorEntity paths.
62
+ # 2. STRUCTURAL BUGS — "field must have selections" (object field selected bare) and
63
+ # "selections can't be made on scalars" (scalar over-selected).
64
+ # Everything else (unknown fields etc.) is reported but does NOT fail: it is usually schema
65
+ # staleness or union handling, and needs a human read.
66
+ #
67
+ # The schema is introspected AT PIPELINE TIME on purpose — a committed 6.3MB dump goes stale,
68
+ # and a stale schema both invents noise and hides real findings.
69
+ #
70
+ # Required CI/CD variables (Settings → CI/CD → Variables; mask the password):
71
+ # EP_CI_SERVER host of a NON-PRODUCTION environment (e.g. an internal/UAT host)
72
+ # EP_CI_ORG_ID organisation id on that host
73
+ # EP_CI_USER_EMAIL service account with READ access only — introspection needs no write scope
74
+ # EP_CI_USER_PASS its password (masked)
75
+ # Until all four are set the job does not run, so the gate is inert rather than red. Use a
76
+ # service account on a non-production org: the job only ever issues an introspection query,
77
+ # never touches records.
78
+ validate-queries:
79
+ extends: .ruby
80
+ stage: test
81
+ rules:
82
+ - if: '$EP_CI_SERVER && $EP_CI_ORG_ID && $EP_CI_USER_EMAIL && $EP_CI_USER_PASS'
83
+ variables:
84
+ BUNDLE_PATH: vendor/bundle
85
+ # The gem reads its connection from these names (Common::GraphQL::AuthService).
86
+ SERVER: $EP_CI_SERVER
87
+ ORGANIZATION_ID: $EP_CI_ORG_ID
88
+ USER_EMAIL: $EP_CI_USER_EMAIL
89
+ USER_PASS: $EP_CI_USER_PASS
90
+ script:
91
+ - bundle exec ruby tests/dump_schema.rb ci_schema.json
92
+ - bundle exec ruby tests/validate_queries.rb ci_schema.json
93
+ artifacts:
94
+ when: on_failure # keep the exact schema the failure was diagnosed against
95
+ expire_in: 1 week
96
+ paths:
97
+ - ci_schema.json
data/CHANGELOG.md CHANGED
@@ -2,6 +2,83 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.3.15] - 2026-07-30
6
+
7
+ **Backwards-compatible.** Every change here fixes a query or mutation document that the live
8
+ schema rejected — i.e. code paths that could not run at all — plus the CI gate that found them.
9
+ Cherry-picked onto the 1.3.x line from `main`, where they landed alongside **breaking** changes
10
+ that are deliberately NOT in this release (see "Deferred to 1.4.0" below).
11
+
12
+ ### Fixed
13
+
14
+ - **`FileUpload::Client` authenticated the v2 REST endpoints wrongly.** The ported S3
15
+ register/poll steps sent `Authorization: Bearer <oauth_token>`; those endpoints authenticate via
16
+ `X-ECOPORTAL-API-KEY`, so step 3 404'd and `#upload` never completed. `Ecoportal::API::GraphQL.new`
17
+ accepts an additive `api_key:` (independent of the GraphQL endpoint's own email/pass OAuth),
18
+ `HttpClient#api_key` is public, and the client raises a clear error when it is missing.
19
+ ⚠ **Not yet live-verified** — verify against a sandbox org before relying on it.
20
+ - **Three invalid union selections.** `Query::RegisterPresetViews` (`fieldConfigurations`),
21
+ `Query::PageWithForces` (`forces`) and `Query::PagesWorkflowCommands` (`workflow`) selected
22
+ fields directly on a union, which is invalid GraphQL ("Selections can't be made directly on
23
+ unions") — all three queries failed outright. They now select through the members' shared
24
+ interface (`FieldConfigurationInterface` / `BasePageInterface`). Response shapes are unchanged.
25
+ - **`fragment ForceFields` asked for a non-existent `id` on bindings.** `Force.bindings` is
26
+ `[BindingInterface]`, whose only fields are `name` / `referenceId` (+ per-member `reference`),
27
+ so every query spreading the fragment was invalid. Now selects on the interface directly.
28
+ NOTE the read/write asymmetry this exposes: `WorkflowEditBindingInput` / `removeBinding` require
29
+ a binding `id` that no read exposes, so `BindingCollection#delete!(binding)` has no source for it.
30
+ - **7 invalid `PageFields` spreads.** The four review-task mutations return `item: TaskInterface`
31
+ (not a page — the page is reachable via `subject`), `Template::{Create,Destroy}RelatedPage`
32
+ return `RelatedPages`, and `Template::UpdateInformation` returns the narrow `Template` type. New
33
+ `:PageTaskFields` / `:TemplateRelatedPages` / `:TemplateInformation` fragments and read-only
34
+ `Base/Model::Page::Task` + `Base/Model::Template::{RelatedPages,Information}` models; the payload
35
+ `item_class`es pointed at `Model::PageUnion`, which never matched the schema. The template
36
+ mutations that genuinely return a page (`Create`/`Publish`/`Unpublish`/`Update`) are unchanged.
37
+ - **6 payloads selected an `item` their type does not have.** `Page::CreateDraft` → `{ id }`,
38
+ `Page::{DeleteDraft,PublishDraft}` → `{ ok }`, `Page::BatchUpdateReviewTask` → `{ count }` (and
39
+ its own `Payload::Page::ReviewTaskBatch`), `Register::Destroy` → `{ ok }`, `PresetView::Destroy`
40
+ → `{ clientMutationId errors }`. `Payload::Page::Draft` gained `id`/`ok`, `Payload::Register`
41
+ gained `ok`.
42
+ - **`executeWorkflowCommands` was rendered with arguments it never accepted** — the schema takes a
43
+ single `input: ExecuteWorkflowCommandsInput!`, so the **page-level command bus could not run**.
44
+ Both classes' `#query` already built the right `input` hash; only the documents were wrong.
45
+ - **`Payload::OkPayload` now models `errors`** — it selected them but computed `success?` purely
46
+ from `ok`, silently ignoring a populated errors object.
47
+
48
+ ### Added (tooling — no runtime effect)
49
+
50
+ - **`validate-queries` CI job** (`.gitlab-ci.yml`): renders every query + mutation against a
51
+ freshly introspected schema and fails on render failures plus the always-invalid selection
52
+ classes ("must have selections", "can't be made on scalars", "can't be made directly on unions",
53
+ "can't be spread inside"). This gate is what found everything above.
54
+ - **`tests/dump_schema.rb`**: fresh introspection through the gem's own `HttpClient`. Requires
55
+ `SERVER` explicitly (the gem's default host is production). Schema dumps are never committed.
56
+
57
+ ### Deferred to 1.4.0 (breaking — intentionally absent here)
58
+
59
+ - The AI-summary rewire (`generateAiSummary` → `generatePageSummary`,
60
+ `submitAiSummaryFeedback` → `submitPageAiSummaryFeedback`, changed `#query` keywords,
61
+ `Base::AiSummaryVersion` field removals) and the removal of the `SmartFill` mutations
62
+ (`smartFillGenerate` / `smartFillSubmitFeedback` do not exist in the schema).
63
+ - The 2026-07-13 `WorkflowCommandInput` schema sync and the polymorphic read-model layer.
64
+
65
+ ## [1.3.14] - 2026-07-16
66
+
67
+ Re-issue of the **1.3.13 HttpClient resilience fix** under a fresh version. The `1.3.13`
68
+ artifact published to the gem server (2026-07-11) could not be relied upon to contain the
69
+ fix below — RubyGems versions are immutable and cannot be re-pushed, so the fix ships here
70
+ as `1.3.14`. Functionally identical to the *intended* `1.3.13` (this is `1.3.12` + the
71
+ resilience fix, nothing else). **Backwards-compatible.**
72
+
73
+ Consumers that need the 429/1015 resilience (e.g. bulk `cans-upsert` live runs) must floor
74
+ `ecoportal-api-graphql >= 1.3.14` — a `>= 1.3.13` floor is not sufficient.
75
+
76
+ ### Fixed
77
+
78
+ - Same fix as documented under `[1.3.13]` below: `Common::GraphQL::HttpClient#execute` routes
79
+ through the inherited `instrument → with_retry { rate_throttling }` pipeline, so a single
80
+ HTTP 429 (Cloudflare edge rate-limit `1015`) no longer aborts the run.
81
+
5
82
  ## [1.3.13] - 2026-07-12
6
83
 
7
84
  Reliability: the GraphQL `HttpClient` now inherits the **same request-resilience
@@ -0,0 +1,17 @@
1
+ - **Six mutations selected an `item` their payload type does not have** — invalid documents, so
2
+ none could run. Found by the first live run of the `validate-queries` CI gate (2026-07-30);
3
+ every replacement selection below is the schema's own shape, verified against a fresh prod
4
+ introspection:
5
+ - `Page::CreateDraft` → `CreatePageDraftPayload { id }` (the new draft's page id)
6
+ - `Page::DeleteDraft` / `Page::PublishDraft` → `{ ok }`
7
+ - `Page::BatchUpdateReviewTask` → `{ count }` (tasks updated). It also shared
8
+ `Payload::Page::ReviewTask`, whose type has an `item`; it now has its own
9
+ `Payload::Page::ReviewTaskBatch`.
10
+ - `Register::Destroy` → `DestroyRegisterPayload { ok }`
11
+ - `PresetView::Destroy` → `DestroyPresetViewPayload { clientMutationId errors }` — no payload
12
+ data at all, not even `ok`.
13
+ `Payload::Page::Draft` gained `id`/`ok` and `Payload::Register` gained `ok`; the create/update
14
+ variants of Register/PresetView still return `item` and are untouched. ★ Note `Logic::Payload`'s
15
+ inherited `embeds_one :item` materialises a HOLLOW item (all fields nil, and it writes
16
+ `'item' => {}` into the doc) when the response has no `item` — it does not return nil, so read
17
+ the scalars. 21 specs.
data/docs/worklog.md CHANGED
@@ -8,24 +8,6 @@ This file tracks cross-project session state and the overall repo status.
8
8
 
9
9
  ---
10
10
 
11
- ## ▶ SESSION 2026-07-10 — OD-001 decision-support delivered to ep-ai-standards (breadcrumb)
12
-
13
- Produced a decision-support doc mapping this repo's REAL GraphQL mutation surface onto
14
- ep-ai-standards' operation-class ladder for OD-001 (the GraphQL gateway PROD "low-risk"
15
- auto-approval threshold / provisional Dial A). Evidence-based from live introspection
16
- (`.ai-assistance/tmp/20260605T101224_live_ep_graphql_schema.graphql.json` -- 190 mutations)
17
- + code-specs + `lib/ecoportal/api/graphql/mutation/**`. Key findings: register-type axis is
18
- real + in-schema (`Register.registerStatus: TESTING|LIVE`, `base/register.rb:7`); org-type
19
- (sandpit/uat/demo/PROD) is NOT a schema field (infra/credential-level, `base/organization.rb`
20
- exposes only id/name/companyName/logoUrl); several mutations do not map cleanly (payload-hidden
21
- deletions in `updatePage`, bulk/cascade, cross-env UAT->PROD deploy, egress/email/credential
22
- mints). Delivered to ep-ai-standards' bridge inbox as
23
- `ecoportal-api-graphql-od001-mutation-mapping.md` (STATUS: PENDING) -- a PROPOSAL for the graphql
24
- team + Oscar to validate via an ep-ai-standards MR. NO code changed in this repo. Does not change
25
- OD-001's ruling. (OD-007 breadcrumb convention.)
26
-
27
- ---
28
-
29
11
  ## ▶ SESSION 2026-07-05 — 3 live act-gov crashes = ONE root cause (stale released gems); fixed-stack floors
30
12
 
31
13
  Three live crashes on act-gov (`-toocs-coding` + org-structure sync), all the **same class**: the
@@ -29,8 +29,6 @@ module Ecoportal
29
29
 
30
30
  puts "Configuring GraphQL Client onto '#{url}'"
31
31
 
32
- pp email
33
-
34
32
  super(
35
33
  url,
36
34
  headers: {
@@ -21,6 +21,12 @@ module Ecoportal
21
21
 
22
22
  attr_reader :host, :version
23
23
 
24
+ # Publicly exposes the configured api_key (inherited as `private` from
25
+ # `Common::Client`) — `FileUpload::Client` needs it directly to authenticate
26
+ # against the v2 REST endpoints, independent of this client's own `@version`
27
+ # (which stays `'graphql'` and therefore always builds a Bearer `key_token`).
28
+ public :api_key
29
+
24
30
  include Ecoportal::API::Common::GraphQL::AuthService
25
31
 
26
32
  def initialize(
@@ -1,17 +1,17 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- module Base
5
- # Represents one generation version of an AiSummary field.
6
- # Returned by generateAiSummary — tracks the prompt, output, token usage, and status.
7
- class AiSummaryVersion < Logic::BaseModel
8
- passkey :id
9
- passthrough :aiSummary, :status, :prompt
10
- passthrough :inputTokens, :outputTokens, :totalTokens
11
- passthrough :resourceType # "field" | "stage" | "page"
12
- passarray :ratings # [[user_id, score], ...]
13
- end
14
- end
15
- end
16
- end
17
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Base
5
+ # Represents one generation version of an AiSummary field.
6
+ # Returned by generateAiSummary — tracks the prompt, output, token usage, and status.
7
+ class AiSummaryVersion < Logic::BaseModel
8
+ passkey :id
9
+ passthrough :aiSummary, :status, :prompt
10
+ passthrough :inputTokens, :outputTokens, :totalTokens
11
+ passthrough :resourceType # "field" | "stage" | "page"
12
+ passarray :ratings # [[user_id, score], ...]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,15 +1,15 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- module Base
5
- class LocationClassificationType < Logic::BaseModel
6
- root!
7
-
8
- passkey :id
9
- passthrough :name
10
- passboolean :active
11
- end
12
- end
13
- end
14
- end
15
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Base
5
+ class LocationClassificationType < Logic::BaseModel
6
+ root!
7
+
8
+ passkey :id
9
+ passthrough :name
10
+ passboolean :active
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end