eco-helpers 3.2.14 → 3.2.22

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +207 -0
  3. data/lib/eco/api/usecases/CLAUDE.md +78 -0
  4. data/lib/eco/api/usecases/default/pages.rb +30 -0
  5. data/lib/eco/api/usecases/graphql/CLAUDE.md +120 -0
  6. data/lib/eco/api/usecases/graphql/compat/ooze_redirect/dirty_array.rb +22 -0
  7. data/lib/eco/api/usecases/graphql/compat/ooze_redirect/field_patches.rb +241 -0
  8. data/lib/eco/api/usecases/graphql/compat/ooze_redirect/force_compat.rb +73 -0
  9. data/lib/eco/api/usecases/graphql/compat/ooze_redirect.rb +234 -0
  10. data/lib/eco/api/usecases/graphql/compat/parity/comparison.rb +70 -0
  11. data/lib/eco/api/usecases/graphql/compat/parity/harness.rb +102 -0
  12. data/lib/eco/api/usecases/graphql/compat/parity/run_result.rb +96 -0
  13. data/lib/eco/api/usecases/graphql/compat.rb +11 -0
  14. data/lib/eco/api/usecases/graphql/helpers/CLAUDE.md +79 -0
  15. data/lib/eco/api/usecases/graphql/helpers/location/command/end_points/optimizations.rb +4 -4
  16. data/lib/eco/api/usecases/graphql/helpers/pages/copying.rb +71 -0
  17. data/lib/eco/api/usecases/graphql/helpers/pages/creatable.rb +78 -0
  18. data/lib/eco/api/usecases/graphql/helpers/pages/filters.rb +114 -0
  19. data/lib/eco/api/usecases/graphql/helpers/pages/ooze_handlers.rb +112 -0
  20. data/lib/eco/api/usecases/graphql/helpers/pages/rescuable.rb +52 -0
  21. data/lib/eco/api/usecases/graphql/helpers/pages/shortcuts.rb +186 -0
  22. data/lib/eco/api/usecases/graphql/helpers/pages/typed_fields_pairing.rb +303 -0
  23. data/lib/eco/api/usecases/graphql/helpers/pages.rb +21 -0
  24. data/lib/eco/api/usecases/graphql/helpers.rb +1 -0
  25. data/lib/eco/api/usecases/graphql/samples/CLAUDE.md +76 -0
  26. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +1 -1
  27. data/lib/eco/api/usecases/graphql/samples/pages/CLAUDE.md +59 -0
  28. data/lib/eco/api/usecases/graphql/samples/pages/org_page/base.rb +41 -0
  29. data/lib/eco/api/usecases/graphql/samples/pages/org_page/dsl.rb +8 -0
  30. data/lib/eco/api/usecases/graphql/samples/pages/org_page.rb +7 -0
  31. data/lib/eco/api/usecases/graphql/samples/pages/page/base.rb +148 -0
  32. data/lib/eco/api/usecases/graphql/samples/pages/page/dsl.rb +38 -0
  33. data/lib/eco/api/usecases/graphql/samples/pages/page.rb +7 -0
  34. data/lib/eco/api/usecases/graphql/samples/pages/register/base.rb +181 -0
  35. data/lib/eco/api/usecases/graphql/samples/pages/register/migration_case.rb +132 -0
  36. data/lib/eco/api/usecases/graphql/samples/pages/register/target_oozes_update_case.rb +163 -0
  37. data/lib/eco/api/usecases/graphql/samples/pages/register.rb +8 -0
  38. data/lib/eco/api/usecases/graphql/samples/pages/template/base.rb +70 -0
  39. data/lib/eco/api/usecases/graphql/samples/pages/template/command_emitter.rb +139 -0
  40. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/builder.rb +126 -0
  41. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/format_map.rb +108 -0
  42. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/parser.rb +98 -0
  43. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build.rb +17 -0
  44. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/applier.rb +141 -0
  45. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/drift_report.rb +104 -0
  46. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/loop.rb +155 -0
  47. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/recording_executor.rb +58 -0
  48. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/sync_readiness.rb +178 -0
  49. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/verifier.rb +141 -0
  50. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy.rb +21 -0
  51. data/lib/eco/api/usecases/graphql/samples/pages/template.rb +11 -0
  52. data/lib/eco/api/usecases/graphql/samples/pages.rb +9 -0
  53. data/lib/eco/api/usecases/graphql/samples.rb +1 -0
  54. data/lib/eco/api/usecases/graphql.rb +1 -0
  55. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +4 -0
  56. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +13 -3
  57. data/lib/eco/version.rb +1 -1
  58. metadata +52 -15
  59. data/.gitignore +0 -23
  60. data/.idea/.gitignore +0 -10
  61. data/.markdownlint.json +0 -4
  62. data/.rspec +0 -3
  63. data/.rubocop.yml +0 -103
  64. data/.ruby-version +0 -1
  65. data/.yardopts +0 -10
  66. data/Gemfile +0 -8
  67. data/Rakefile +0 -38
  68. data/eco-helpers.gemspec +0 -63
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d67a16095de2e32c2c627214b0254df6d2685e0591ab6295082736e52494c4d3
4
- data.tar.gz: 60835a688189d8feda9bdc6198bbdb0cfaa9e9f95e5c7521f36cedbec706c1b0
3
+ metadata.gz: 2f79a58827dc2789ddfcc56e108d93520af92c15152b94338ca51e73cc388210
4
+ data.tar.gz: e15e69fdb8fa6bd37202aad7f01bebcda80c9aadacc23333a98846a27aeddffd
5
5
  SHA512:
6
- metadata.gz: 0c1ded6a88ad0c6394e96cb511fddb5c5ac29635307affc1577d5eeb210f01ad8dd78edf78e449b9bca765a8754aa31083abb72beb60746ed21741e523878e6c
7
- data.tar.gz: a18f9c81c2430ba8251bdfc34e6e4e1d3da0fd3cbe4647226942469d8da1f71e00aa7e21e3162d9d89d492f98e800642c0132edc30a305515df67764c397de91
6
+ metadata.gz: 14faa786d74eb1b096cea772f79903df7f16e0dd903ce8586621f4524413795f0720bad631b0ddbc892cee01d12e6445f2af5ff6ff808d5754bb8593959f5d9a
7
+ data.tar.gz: b8b28cba91173036b005e3aa9a8e88e0143a932e1342c4aa91f085c73d4e031191e5f09c8f71928547392e41291c8b0128d0455b1b646ab93422247b9cac57b9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,213 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [3.2.22] - 2026-08-14
6
+
7
+ ### Fixed
8
+
9
+ - Republish of `3.2.21`: its `lib/eco/version.rb` shipped with doubled carriage returns
10
+ (`
11
+ ` line endings), causing a `warning: encountered
5
12
  in middle of line` on every
13
+ load. Functionally identical otherwise; `3.2.21` will be yanked.
14
+
15
+ ## [3.2.21] - 2026-08-14
16
+
17
+ ### Fixed
18
+
19
+ - Packaging-only republish of `3.2.19` with an allowlisted gemspec (backported from `3.3.0`).
20
+ Versions `3.2.16`-`3.2.19` shipped internal repository content (`.ai-assistance/` tooling,
21
+ `.claude/settings.json`) to rubygems.org via the old denylist `spec.files`; `3.2.19` is
22
+ yanked and `3.2.16`/`3.2.18` are queued for deletion by RubyGems support. `3.2.21` is the
23
+ identical `lib/` code packaged clean, so constraints like `'~> 3.2.0', '>= 3.2.19'` keep
24
+ resolving on the 3.2 line (its graphql dependency stays `~> 1.3`, satisfied by the clean
25
+ `1.3.16`). The version number `3.2.20` is intentionally skipped: it exists as a
26
+ tagged-but-deliberately-unpublished version (see the 3.3.0-era changelog corrections).
27
+
28
+ ## [3.2.19] - 2026-07-16
29
+
30
+ Farmers / `cans-upsert` reliability adoption + an ooze KPI counter fix. **Backwards-compatible.**
31
+ Cut from the `v3.2.18` tag (not `master`, which carries the native GraphQL activity/dashboard
32
+ readers depending on the unreleased gem `1.4.0`), so this ships needing only the published
33
+ `ecoportal-api-graphql 1.3.14`.
34
+
35
+ ### Changed
36
+
37
+ - **Floor `ecoportal-api-graphql` to `>= 1.3.14`** (was `>= 1.3.11`) — pulls the HttpClient
38
+ 429/1015 resilience fix, so bulk `cans-upsert` / register-update live runs are no longer
39
+ aborted by a single Cloudflare edge rate-limit.
40
+
41
+ ### Fixed
42
+
43
+ - **Ooze update KPI counters now count GraphQL updates.** `RegisterUpdateCase` tallied
44
+ `updated`/`failed` only when the result `is_a?(Ecoportal::API::Common::Response)`, but the
45
+ GraphQL compat layer returns an `Ecoportal::API::GraphQL::Compat::Response` — it duck-types
46
+ `success?`/`status` yet is not in that class hierarchy, so every GraphQL update was silently
47
+ uncounted (`Updated 0 (attempted: N)`, `Failed 0`) even when the write applied. The guard is
48
+ now a duck-type (`respond_to?(:success?)`); `false`/`nil` returns (dry-run / no-op) still skip.
49
+ **Note:** this fixes the *report* only — the updates themselves were already applying.
50
+
51
+ ## [3.2.18] - 2026-07-10
52
+
53
+ Version-identity + regression-guard release. **No behaviour change** vs the fixed `3.2.17` build —
54
+ this bump exists so the LocationDraft fix can be pinned/verified by version, and adds an offline
55
+ guard so the dead-fragment class of bug can't reach production again.
56
+
57
+ ### Why this bump
58
+
59
+ `3.2.17` shipped as **two different builds under one version**: the version was bumped to `3.2.17`
60
+ on 2026-07-04 (`243822b9`), then the live-crash fix `352a9657` (LocationDraft dead-fragment
61
+ convention) landed on 2026-07-05 — keeping the same `3.2.17` label. Because `3.2.17` is installed
62
+ from git/path (unpublished), a consumer bundled in that ~26h window reports `3.2.17` yet lacks the
63
+ fix. This crashed the **live act-gov** org-structure sync again on 2026-07-09 with the exact
64
+ `uninitialized constant …Fragment::LocationDraft (NameError)`. Bumping to `3.2.18` makes the fixed
65
+ build unambiguously identifiable: any consumer on `>= 3.2.18` provably has the fix.
66
+
67
+ ### Added
68
+
69
+ - **Regression-guard spec for the dead-fragment convention.**
70
+ `spec/…/helpers/location/command/end_points/optimizations_spec.rb` scans every file under
71
+ `usecases/graphql/` and fails if any references a fragment via the removed `___Const__Fragment` /
72
+ `::Fragment::<Name>` constant convention (the exact NameError that crashed act-gov), and asserts
73
+ the three Location command payload procs still route fragments through the `spread :Name` registry
74
+ DSL. It is a SOURCE lint, not a full offline render: rendering needs the graphlient fork's
75
+ `to_query_string`/`spread` DSL, but eco-helpers' own bundle resolves stock graphlient `0.8.0` (the
76
+ fork is pinned only in the deployment/gem bundles). If eco-helpers later pins the fork in dev/test,
77
+ promote this to a real render guard like the gem's `tests/validate_queries.rb`.
78
+
79
+ ## [3.2.17] - 2026-07-04
80
+
81
+ Native GraphQL template build+maintenance + ooze-native migration (strangler-fig): pure,
82
+ non-breaking additions built ALONGSIDE the OozeRedirect shim. No `OozeSamples::*` name is
83
+ flipped/redirected and the shim is untouched. **Not yet published** — the template Deploy loop binds
84
+ to the gem's `Diff` module (gem v1.3.11), which is tagged but unpublished.
85
+
86
+ ### Fixed
87
+
88
+ - **`-no-email` double-send guard typo.** `Location::Command::Service::TreeUpdate#email_digest` set
89
+ `{worfklow: {no_email: true}}` (misspelled key) after sending, so the "don't send the digest twice"
90
+ guard never took effect. Corrected to `workflow`. (Note: the primary `-no-email` check on the same
91
+ method already used the correct key, so a single `-no-email` run was already suppressed.)
92
+ - **Location command optimizations — dead fragment convention (live crash).**
93
+ `Helpers::Location::Command::EndPoints::Optimizations` referenced fragments via the removed
94
+ `___Const__Fragment` constant convention (`___Ecoportal__API__GraphQL__Fragment__LocationDraft` /
95
+ `…__LocationsError`), which the gem's registry-based fragments no longer expose as Ruby constants →
96
+ `uninitialized constant … Fragment::LocationDraft (NameError)` when building a locations-draft
97
+ `addCommands`/`create`/`publish` request. Crashed the live act-gov tagtree / org-structure sync.
98
+ Now `spread :LocationDraft` / `spread :LocationsError`, matching the gem.
99
+
100
+ ### Dependencies
101
+
102
+ - **Raised floors to the fixed stack: `ecoportal-api-graphql >= 1.3.11`, `ecoportal-api-v2 >= 3.3.3`.**
103
+ Stops the stale gem 1.3.9 (missing the LocationStructure `updatedAt`/`createdAt` selection fixes from
104
+ 1.3.10) and the buggy v2 3.3.2 (Ruby-3.x `DoubleModel` cascade `TypeError`) from resolving on
105
+ consumers — both crashed the live act-gov integration.
106
+
107
+ ### Added
108
+
109
+ - **`Helpers::Pages::OozeHandlers`** — native re-expression of `OozeSamples::Helpers::OozeHandlers`.
110
+ `merge_values(origin, append, type:, delimiter:)` dispatches on the GraphQL DataField TYPE STRING
111
+ (`PlainText`/`RichText` → newline; `Select`/`CrossReference`/`People`/`Checklist`/`ActionsList`/
112
+ `File`/`ImageGallery`/`Geo`/`Law` → delimiter join; `Date`/`Number`/`Gauge` → keep origin) instead
113
+ of `Ecoportal::API::V2::Page::Component::*` classes. `merge_arrays`/`array_indexes` ported verbatim.
114
+ Included into `Samples::Pages::Register::Base`, which also gains `#merge_field_values(field, append)`
115
+ (reads the field's own `#type` + current `#value`).
116
+ - **`Samples::Pages::Register::TargetOozesUpdateCase`** — native counterpart of
117
+ `OozeSamples::TargetOozesUpdateCase`, subclassing the native `Register::Base`. Reads target page ids
118
+ from `options[:source][:file]` (first CSV column, header dropped), batches them, previews with
119
+ duplicate detection + proceed prompt, fetches each via `graphql.pages.get`, dedups by id, and hands
120
+ each page to `process_page`. `#each_page` (register-search) is disabled here to prevent an
121
+ accidental org scan.
122
+ - **`Compat::Parity`** A/B parity harness — `RunResult` (canonical KPI + normalised per-page update
123
+ payload snapshot), `Comparison` (KPI/page-set/payload diffs + `equivalent?` verdict + report), and
124
+ `Harness` (orchestrates a legacy vs native run and compares; `.record` captures a run's observables).
125
+ Comparison logic is pure/offline-runnable; live capture of the two runs needs a test org + creds.
126
+ - **`Helpers::Pages::TypedFieldsPairing`** — native re-expression of
127
+ `OozeSamples::HelpersMigration::TypedFieldsPairing`. Pairs data fields across two page models by
128
+ SAME type + SAME label, in two screenings (exact label, then mild: case-insensitive a-z with
129
+ bracketed content removed). Groups/dispatches by the GraphQL DataField TYPE STRING
130
+ (`PlainText`/`Select`/`Date`/... per `DataField::TYPE_MAP`) rather than v2 snake_case component
131
+ types; `EXCLUDED_TYPES` = `%w[TagField Chart FrequencyRateChart]` (never-pair). Tracks unpaired
132
+ src/dst fields + multi-candidate sources, with `resolve`/report helpers. Legacy class untouched.
133
+ This is the cross-template field-pairing (migration), DISTINCT from `OozeHandlers#merge_values`
134
+ (value-merge of one already-paired field).
135
+ - **`Helpers::Pages::Copying`** — native counterpart of
136
+ `OozeSamples::HelpersMigration::Copying`. `copy_generic_paired_fields(src, dst)` runs
137
+ `TypedFieldsPairing` and copies each pair's content; `copy_field_content(src, dst)` dispatches on
138
+ the GraphQL DataField type string (`PlainText`/`Number`/`Date`/`Gauge` → `value=`, `RichText` →
139
+ `content=`, `Select` → `select_option`, `CrossReference` → `page_ids=`; other types left untouched).
140
+ The v2 regex/JSON hooked-field mapping layer (`copy_hooked_fields`) is NOT ported (later increment).
141
+ - **`Samples::Pages::Register::MigrationCase`** — native counterpart of
142
+ `OozeSamples::RegisterMigrationCase`, subclassing the native `Register::Base` and mixing in
143
+ `Copying`. Iterates a source register, drafts a NEW entry of `TEMPLATE_ID` (via native `Creatable`),
144
+ pairs the source fields to the draft's fields and copies content across, runs the unpaired /
145
+ multi-pair reports, and hands the draft to `#custom_processing` for finalising. Per-page persistence
146
+ is the native create path (not the v2 batch queue). Native-only; no OozeSamples name flipped.
147
+
148
+ Template automatic build & maintenance (Phase 5): native, offline-safe orchestration on top of the
149
+ released gem's `Diff::Deploy` / `Diff::VersionDiff` / `Builder::Template`. Additive, no case flipped.
150
+
151
+ - **`Samples::Pages::Template::Deploy`** — deploy → verify → monitor loop for template (workflow)
152
+ changes, driven by a gem `Diff::Deploy` command batch. Dry-run is the DEFAULT; live apply is gated
153
+ behind an explicit `commit: true` + executor. All components are session-less and offline-runnable
154
+ (the apply / re-read seams are injected).
155
+ - `Deploy::Applier` — wraps a gem `Diff::Deploy` plan. Dry-run records the batch it WOULD send;
156
+ `commit: true` applies it via any `execute_workflow_commands`-shaped executor (gem `Builder::Page`
157
+ facade adapted by `TargetBoundExecutor`). Refuses to apply while the plan has `unsupported`
158
+ changes unless `allow_partial: true` — nothing is guessed.
159
+ - `Deploy::RecordingExecutor` — offline stand-in that records batches and (with a `mutator`) replays
160
+ them onto an in-memory doc, so the pre/post drift check is runnable with no API call.
161
+ - `Deploy::DriftReport` — compares the APPLIED delta (a pre/post self-version `Diff::VersionDiff` of
162
+ the re-read target) to the INTENDED delta (the source diff the batch came from) on an id-free shape
163
+ signature; reports `missing` (under-applied) and `unexpected` (side-effect) changes honestly.
164
+ - `Deploy::Verifier` — pluggable post-deploy verification. `QaVerifier` wires the `ecoportal-qa`
165
+ check framework through a single seam when the gem is on the load path (NOT a hard dependency);
166
+ otherwise `NullVerifier` keeps the loop runnable and is honest that qa was unavailable.
167
+ - `Deploy::SyncReadiness` — given a register subset + its active template doc, reports which entries
168
+ CAN sync (all REQUIRED fields present, non-empty, and correctly typed) vs not, with per-entry
169
+ reasons.
170
+ - `Deploy::Loop` — orchestrates apply → drift → verify → sync-readiness and yields an aggregate
171
+ `Result` (`ok?` gate + human `report`). The applied-diff builder defaults to the gem's
172
+ `Diff::VersionDiff` (resolved lazily) and is injectable for offline use.
173
+ - **`Samples::Pages::Template::CsvBuild`** — CSV → template BUILD pipeline reusing the existing
174
+ `Template::CommandEmitter` + the released gem `Builder::Template#create(commands:)`.
175
+ - `CsvBuild::FormatMap` — the SINGLE, isolated place the assumed columnar format lives (column names
176
+ + option delimiters), mapping a CSV row to a format-agnostic `RowSpec`. ⚠ The exact ~300-template
177
+ CSV column format is due ~mid-July 2026 and is NOT yet confirmed — only this file (and its spec)
178
+ changes when the real format lands.
179
+ - `CsvBuild::Parser` — groups rows into a stage → section → field tree preserving first-seen order.
180
+ - `CsvBuild::Builder` — replays the tree onto `CommandEmitter` (same placeholder-id threading), emits
181
+ the ordered `WorkflowCommand` batch (select options → `addSelectFieldOption`), and stamps a hidden
182
+ per-section anchor field for the identity convention (hidden-field + description). Offline/dry-run
183
+ by default (`#commands`, `#preview`); `#create!(template_facade)` is the live path (needs creds).
184
+ - `Template::CommandEmitter#field` gains optional `description:`/`required:` pass-throughs (additive).
185
+ - **HONEST LIMITATIONS / deferred:**
186
+ - The released gem (1.3.9) does NOT yet ship `Diff::Deploy` / `Diff::VersionDiff` (unreleased on the
187
+ gem's main). The deploy loop is written against those APIs but touches them only via lazy/injected
188
+ seams, so it loads and its specs run offline today; a live drift check needs those classes released.
189
+ - The released gem's `addField` input drops any key outside
190
+ placeholderId/fieldType/label/stageId/sectionId/column, so the `description` identity token is NOT
191
+ yet persisted through addField — only the hidden anchor field's presence survives. Wiring is in
192
+ place so identity flows end-to-end once the gem's addField gains a description key.
193
+ - Live deploy/apply and live template creation both need credentials + a test org (the sandbox
194
+ cannot reach live). qa verification is stubbed behind `QaVerifier`'s single seam until qa is pinned.
195
+
196
+ ## [3.2.16] - 2026-07-02
197
+
198
+ APIv2→GraphQL cutover — the OozeRedirect compat layer that runs the maintained ooze integrations
199
+ (toocs-coding, cans-upsert, supplier-documents) on GraphQL. Requires ecoportal-api-graphql >= 1.3.9.
200
+
201
+ ### Fixed / Changed
202
+
203
+ - **OozeRedirect slimmed onto the base OozeSamples loop** — removed the `with_each_entry` /
204
+ `update_ooze` / `process_ooze` overrides that duplicated and silently bypassed the base loop
205
+ (KPIs, dedup, queue, `dry_run_feedback`). Fetches go through `ooze → apiv2.pages.get` and saves
206
+ through `update_oozes → update_ooze → apiv2.pages.update`; a captured `submit!`/`sign_off!` rides
207
+ along on the single `updatePage` via `Input::Page::Update.from_model` (reads the `_compat_*` flags).
208
+ - **`dirty?`** also treats a pending `submit!`/`sign_off!` as dirty (so a submit-only page still saves).
209
+ - **`OozeBaseCase#dry_run_feedback`** prints an affirmative `[dry-run] would create/update <ref>` line.
210
+ - **`RegisterUpdateCase#enqueue`** accepts any duck-typed entry (`#dirty?` + `#as_update`), not just
211
+ v2 `Page`/`PageStage` — GraphQL compat pages queue correctly under OozeRedirect.
212
+
6
213
  ## [3.2.15] - 2026-05-xx
7
214
 
8
215
  ### Added
@@ -0,0 +1,78 @@
1
+ # usecases
2
+
3
+ The use-case registry and all built-in case base classes for scripting against EcoPortal.
4
+
5
+ ---
6
+
7
+ ## What a use case is
8
+
9
+ A use case is a self-contained, named, runnable unit of work. It registers itself with
10
+ the CLI framework, receives `session`, `options`, and `usecase` from the runner, and
11
+ executes its `process` (or `process_ooze` / `process_page`) method.
12
+
13
+ ```
14
+ CLI invokes rake → rake finds registered case → UseCase#launch → main() → process()
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Directory structure
20
+
21
+ | Path | What lives there |
22
+ |------|-----------------|
23
+ | `graphql/` | GraphQL-native base cases + samples (see `graphql/CLAUDE.md`) |
24
+ | `ooze_samples/` | APIv2/REST base cases: `OozeBaseCase`, `RegisterUpdateCase` |
25
+ | `ooze_cases/` | Concrete built-in ooze cases (export register, etc.) |
26
+ | `default/` | Built-in people, location, and utility cases |
27
+ | `default_cases/` | Loader and samples for default cases |
28
+ | `samples/` | Misc driver samples |
29
+ | `graphql.rb` | GraphQL use case loader |
30
+ | `ooze_samples.rb` | Ooze/REST use case loader |
31
+ | `default.rb` | Default use case loader |
32
+
33
+ ---
34
+
35
+ ## Adding a new use case
36
+
37
+ 1. Subclass the appropriate base:
38
+
39
+ | Your use case | Inherit from |
40
+ |---|---|
41
+ | Process pages in a register (update workflow) | `Eco::API::UseCases::GraphQL::PageCase` |
42
+ | Process pages org-wide (cross-register, audit) | `Eco::API::UseCases::GraphQL::OrgPageCase` |
43
+ | Custom GraphQL script (export, report, one-off) | `Eco::API::UseCases::GraphQL::Base` |
44
+ | Legacy APIv2 register update | `Eco::API::UseCases::OozeSamples::RegisterUpdateCase` |
45
+
46
+ 2. Set `name` and `type`:
47
+ ```ruby
48
+ name 'my-case-name' # CLI identifier: called with -my-case-name
49
+ type :other # :people | :contractors | :other
50
+ ```
51
+
52
+ 3. Override the entry point (`process_page`, `process`, or `process_ooze`).
53
+
54
+ 4. Register in the org's `config/cli.rb`:
55
+ ```ruby
56
+ cases.add('-my-case-name', :other, 'Description')
57
+ ```
58
+
59
+ ---
60
+
61
+ ## How cases are launched
62
+
63
+ `Eco::API::UseCases::UseCase#launch` calls `callback.call(*uio.params)` where the
64
+ callback is bound to `method(:main)`. Before launch, `@session` and `@options` are
65
+ injected into the instance — subclasses access them via the `attr_reader` in `CaseEnv`.
66
+
67
+ The `:other` type passes `(session, options, usecase)` positionally to `main`.
68
+ For `GraphQL::Base` subclasses the signature is `main(*_args)` — `session` and
69
+ `options` are already available via the helpers module before `main` is called.
70
+
71
+ ---
72
+
73
+ ## Related
74
+
75
+ - `graphql/CLAUDE.md` — GraphQL case hierarchy, PageCase/OrgPageCase
76
+ - `ooze_samples/` — legacy v2 cases (RegisterUpdateCase, OozeBaseCase)
77
+ - `eco-helpers/CLAUDE.md` — top-level gem context
78
+ - `ecoportal-api-graphql` — upstream gem providing `SearchConf`, `Compat::Pages`, etc.
@@ -0,0 +1,30 @@
1
+ module Eco
2
+ module API
3
+ class UseCases
4
+ class Default
5
+ # Namespace for CLI-integrated page use cases.
6
+ # These are concrete, fully functional cases exposed to all org environments.
7
+ #
8
+ # == Convention
9
+ # Cases here should:
10
+ # - Inherit from Eco::API::UseCases::GraphQL::Samples::Pages::Page::Base
11
+ # (or OrgPage::Base for org-wide operations)
12
+ # - Be registered in the org's config/cli.rb
13
+ # - Be as org-agnostic as possible (accept register_id, filters via options)
14
+ #
15
+ # == Adding a new default page case
16
+ # 1. Create the file here: default/pages/my_case.rb
17
+ # 2. Inherit from the appropriate pages sample base class
18
+ # 3. Require it below
19
+ # 4. Wire CLI registration in usecases/default_cases/samples.rb or the
20
+ # org's config/cli.rb
21
+ #
22
+ # == Currently
23
+ # No default page cases yet — add them here as common patterns emerge
24
+ # across org implementations.
25
+ module Pages
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,120 @@
1
+ # usecases/graphql
2
+
3
+ GraphQL-native use case base classes and helpers. All cases here work directly with
4
+ `ecoportal-api-graphql` — no v2 REST layer, no ooze objects.
5
+
6
+ ---
7
+
8
+ ## Class hierarchy
9
+
10
+ ```
11
+ Eco::API::Common::Loaders::UseCase (registration + launch)
12
+
13
+ Eco::API::UseCases::GraphQL::Base ← universal GraphQL env
14
+ ├── GraphQL::Samples::Pages::Page::Base ← register-scoped pages
15
+ │ ├── GraphQL::Samples::Pages::OrgPage::Base ← org-wide pages
16
+ │ └── your subclass (process_page, search_conf)
17
+ └── your subclass directly (custom scripts: exports, reports, one-offs)
18
+ ```
19
+
20
+ Samples live under `samples/pages/` — NOT in the `graphql/` root. The root only
21
+ has `base.rb`, `helpers.rb`, `utils.rb`, and `samples.rb`.
22
+
23
+ ---
24
+
25
+ ## Base — `graphql/base.rb`
26
+
27
+ Universal GraphQL environment. Provides `graphql`, `session`, `options`, `config`,
28
+ `simulate?`, `log`, `backup` via `Helpers::Base` (see `helpers/CLAUDE.md`).
29
+
30
+ Override `process` to write your script:
31
+ ```ruby
32
+ class MyCase < Eco::API::UseCases::GraphQL::Base
33
+ name 'my-case'
34
+ def process
35
+ graphql.currentOrganization.contractorEntities.each { |c| puts c.name }
36
+ end
37
+ end
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Pages — `samples/pages/`
43
+
44
+ Page processing base cases. Follow the hierarchy: `page/base` → `org_page/base`.
45
+
46
+ ### `samples/pages/page/base.rb` — `Samples::Pages::Page::Base`
47
+
48
+ For **register-scoped** page update workflows.
49
+
50
+ **Class methods:** `register_id 'REG_ID'`, `batch_size 50` (default)
51
+
52
+ **Override points:**
53
+ - `process_page(page)` — **required** — transformation for one page
54
+ - `search_conf` — optional — call `super` to keep register scope, then add filters
55
+
56
+ **Protected helpers:** `update_page`, `skip(reason)`, `each_page`
57
+
58
+ **KPI readers:** `total_pages`, `processed_pages`, `updated_pages`, `skipped_pages`, `failed_pages`
59
+
60
+ **DSL (via `samples/pages/page/dsl.rb`):** `sc`, `in_register`, `state_is`, `external_id_eq`, `updated_since`
61
+
62
+ ```ruby
63
+ class Custom::UseCase::UpdateStatus < Eco::API::UseCases::GraphQL::Samples::Pages::Page::Base
64
+ name 'update-status'
65
+ register_id 'REG_ABC'
66
+
67
+ def search_conf
68
+ super.filter(state_is(:active))
69
+ end
70
+
71
+ def process_page(page)
72
+ page.name = page.name.upcase
73
+ update_page(page)
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### `samples/pages/org_page/base.rb` — `Samples::Pages::OrgPage::Base`
79
+
80
+ Inherits `Page::Base`. `search_conf` starts empty (org-wide, no register scope).
81
+ Use for: archive sweeps, cross-register audits, bulk org operations.
82
+
83
+ ---
84
+
85
+ ## Samples — `graphql/samples/`
86
+
87
+ Built-in ready-to-use case implementations:
88
+ - `samples/location.rb` — location structure management cases
89
+ - `samples/contractors.rb` — contractor entity cases
90
+
91
+ See `samples/CLAUDE.md` for details.
92
+
93
+ ---
94
+
95
+ ## Helpers — `graphql/helpers/`
96
+
97
+ Mixins providing domain-specific access patterns. See `helpers/CLAUDE.md`.
98
+
99
+ ---
100
+
101
+ ## Loader order in `graphql.rb`
102
+
103
+ ```ruby
104
+ require 'graphql/helpers' # environment mixins (graphql, session, simulate? etc.)
105
+ require 'graphql/utils' # utility modules (SFTP etc.)
106
+ require 'graphql/base' # GraphQL::Base — universal foundation
107
+ require 'graphql/samples' # sample cases: location, contractors, pages, ...
108
+ # └─ graphql/samples/pages.rb
109
+ # └─ pages/page.rb → page/dsl.rb, page/base.rb
110
+ # └─ pages/org_page.rb → org_page/dsl.rb, org_page/base.rb
111
+ ```
112
+
113
+ Page base cases are in `samples/pages/` — NOT in the `graphql/` root.
114
+ Custom org cases are NOT loaded here — they live in the implementation repo.
115
+
116
+ ## default/pages/
117
+
118
+ CLI-integrated page use cases go in `default/pages/` (mirroring `default/locations/`
119
+ and `default/people/`). Currently empty — add cases there when a pattern is common
120
+ enough to expose to all org environments. See `default/pages.rb` for the convention.
@@ -0,0 +1,22 @@
1
+ module Eco::API::UseCases::GraphQL::Compat::OozeRedirect
2
+ # Array subclass that calls the field's setter when elements are appended,
3
+ # ensuring GraphQL dirty-tracking fires on `fld.people_ids << value`.
4
+ class DirtyArray < Array
5
+ def initialize(field, data)
6
+ @field = field
7
+ super(Array(data))
8
+ end
9
+
10
+ def <<(value)
11
+ super
12
+ @field.people_ids = to_a
13
+ self
14
+ end
15
+
16
+ def push(*values)
17
+ super
18
+ @field.people_ids = to_a
19
+ self
20
+ end
21
+ end
22
+ end