eco-helpers 3.2.16 → 3.2.17

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -0
  3. data/eco-helpers.gemspec +2 -2
  4. data/lib/eco/api/usecases/graphql/compat/parity/comparison.rb +70 -0
  5. data/lib/eco/api/usecases/graphql/compat/parity/harness.rb +102 -0
  6. data/lib/eco/api/usecases/graphql/compat/parity/run_result.rb +96 -0
  7. data/lib/eco/api/usecases/graphql/compat.rb +5 -0
  8. data/lib/eco/api/usecases/graphql/helpers/location/command/end_points/optimizations.rb +4 -4
  9. data/lib/eco/api/usecases/graphql/helpers/pages/copying.rb +71 -0
  10. data/lib/eco/api/usecases/graphql/helpers/pages/creatable.rb +78 -0
  11. data/lib/eco/api/usecases/graphql/helpers/pages/filters.rb +114 -0
  12. data/lib/eco/api/usecases/graphql/helpers/pages/ooze_handlers.rb +112 -0
  13. data/lib/eco/api/usecases/graphql/helpers/pages/rescuable.rb +52 -0
  14. data/lib/eco/api/usecases/graphql/helpers/pages/shortcuts.rb +186 -0
  15. data/lib/eco/api/usecases/graphql/helpers/pages/typed_fields_pairing.rb +303 -0
  16. data/lib/eco/api/usecases/graphql/helpers/pages.rb +21 -0
  17. data/lib/eco/api/usecases/graphql/helpers.rb +1 -0
  18. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +1 -1
  19. data/lib/eco/api/usecases/graphql/samples/pages/register/base.rb +181 -0
  20. data/lib/eco/api/usecases/graphql/samples/pages/register/migration_case.rb +132 -0
  21. data/lib/eco/api/usecases/graphql/samples/pages/register/target_oozes_update_case.rb +163 -0
  22. data/lib/eco/api/usecases/graphql/samples/pages/register.rb +8 -0
  23. data/lib/eco/api/usecases/graphql/samples/pages/template/base.rb +70 -0
  24. data/lib/eco/api/usecases/graphql/samples/pages/template/command_emitter.rb +139 -0
  25. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/builder.rb +126 -0
  26. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/format_map.rb +108 -0
  27. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/parser.rb +98 -0
  28. data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build.rb +17 -0
  29. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/applier.rb +141 -0
  30. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/drift_report.rb +104 -0
  31. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/loop.rb +155 -0
  32. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/recording_executor.rb +58 -0
  33. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/sync_readiness.rb +178 -0
  34. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/verifier.rb +141 -0
  35. data/lib/eco/api/usecases/graphql/samples/pages/template/deploy.rb +21 -0
  36. data/lib/eco/api/usecases/graphql/samples/pages/template.rb +11 -0
  37. data/lib/eco/api/usecases/graphql/samples/pages.rb +2 -0
  38. data/lib/eco/version.rb +1 -1
  39. metadata +34 -5
@@ -0,0 +1,178 @@
1
+ module Eco::API::UseCases::GraphQL::Samples::Pages::Template
2
+ module Deploy
3
+ # Sync-readiness monitoring.
4
+ #
5
+ # Given a register subset (its entries) and the register's ACTIVE template, report which entries
6
+ # CAN sync vs which cannot — where "can sync" means: every field the template marks REQUIRED is
7
+ # present on the entry with a non-empty value AND typed correctly (the entry field's type matches
8
+ # the template field's type). This is the pre-flight an integration runs before pushing entries
9
+ # downstream, and the drift-detection the customer-facing integrations mandate needs.
10
+ #
11
+ # Operates on raw docs (template doc + entry docs) so it is session-less and offline-runnable:
12
+ # * template doc: the JSON the gem returns for a template (stages/sections/dataFields).
13
+ # * entry docs: each register entry as { 'id' =>, 'fields' => [ {id/label/type/value}, ... ] }
14
+ # (or anything exposing the same shape). A field is matched to a template field by id first,
15
+ # then by label — mirroring how the pairing engine falls back.
16
+ #
17
+ # monitor = SyncReadiness.new(template_doc: tpl, entries: [e1, e2, ...])
18
+ # monitor.ready # => [EntryReadiness(ready: true), ...]
19
+ # monitor.not_ready # => [EntryReadiness(ready: false, ...), ...]
20
+ # monitor.summary # => { total:, ready:, not_ready: }
21
+ # monitor.report # => human summary
22
+ class SyncReadiness
23
+ # Per-entry readiness verdict.
24
+ EntryReadiness = Struct.new(:entry_id, :ready, :missing_required, :type_mismatches,
25
+ keyword_init: true) do
26
+ def ready?
27
+ ready ? true : false
28
+ end
29
+
30
+ def reasons
31
+ out = Array(missing_required).map { |m| "missing required '#{m}'" }
32
+ Array(type_mismatches).each do |m|
33
+ out << "type mismatch '#{m[:label]}' (want #{m[:expected]}, got #{m[:actual]})"
34
+ end
35
+ out
36
+ end
37
+
38
+ def to_h
39
+ { entry_id: entry_id, ready: ready?, missing_required: Array(missing_required),
40
+ type_mismatches: Array(type_mismatches) }
41
+ end
42
+ end
43
+
44
+ attr_reader :entries
45
+
46
+ # @param template_doc [Hash] the active template doc.
47
+ # @param entries [Array<Hash,#id>] register entry docs to assess.
48
+ def initialize(template_doc:, entries:)
49
+ @template_doc = template_doc || {}
50
+ @entries = Array(entries)
51
+ end
52
+
53
+ def verdicts
54
+ @verdicts ||= @entries.map { |entry| assess(entry) }
55
+ end
56
+
57
+ def ready
58
+ verdicts.select(&:ready?)
59
+ end
60
+
61
+ def not_ready
62
+ verdicts.reject(&:ready?)
63
+ end
64
+
65
+ def summary
66
+ { total: verdicts.size, ready: ready.size, not_ready: not_ready.size }
67
+ end
68
+
69
+ def to_h
70
+ { summary: summary, entries: verdicts.map(&:to_h) }
71
+ end
72
+
73
+ def report
74
+ lines = ["SYNC READINESS: #{summary[:ready]}/#{summary[:total]} entries ready"]
75
+ not_ready.each do |v|
76
+ lines << " * entry #{v.entry_id}: NOT READY"
77
+ v.reasons.each { |r| lines << " - #{r}" }
78
+ end
79
+ lines.join("\n")
80
+ end
81
+
82
+ private
83
+
84
+ # The template fields, indexed by id and by (downcased) label, carrying type + required.
85
+ def template_fields
86
+ @template_fields ||= begin
87
+ out = { by_id: {}, by_label: {} }
88
+ each_template_field do |f|
89
+ spec = { id: f['id'], label: f['label'], type: field_type(f), required: !!f['required'] }
90
+ out[:by_id][f['id']] = spec if f['id']
91
+ out[:by_label][label_key(f['label'])] = spec if f['label']
92
+ end
93
+ out
94
+ end
95
+ end
96
+
97
+ def required_fields
98
+ @required_fields ||= template_fields[:by_id].values.select { |s| s[:required] }.
99
+ +(template_fields[:by_label].values.select { |s| s[:required] }).
100
+ uniq { |s| s[:id] || s[:label] }
101
+ end
102
+
103
+ def assess(entry)
104
+ entry_fields = index_entry_fields(entry)
105
+ missing = []
106
+ mismatches = []
107
+
108
+ required_fields.each do |spec|
109
+ efield = entry_fields[:by_id][spec[:id]] || entry_fields[:by_label][label_key(spec[:label])]
110
+ if efield.nil? || value_empty?(efield[:value])
111
+ missing << spec[:label]
112
+ elsif spec[:type] && efield[:type] && spec[:type] != efield[:type]
113
+ mismatches << { label: spec[:label], expected: spec[:type], actual: efield[:type] }
114
+ end
115
+ end
116
+
117
+ EntryReadiness.new(
118
+ entry_id: entry_id(entry),
119
+ ready: missing.empty? && mismatches.empty?,
120
+ missing_required: missing,
121
+ type_mismatches: mismatches
122
+ )
123
+ end
124
+
125
+ def index_entry_fields(entry)
126
+ out = { by_id: {}, by_label: {} }
127
+ entry_field_docs(entry).each do |f|
128
+ spec = { id: f['id'] || f[:id], label: f['label'] || f[:label],
129
+ type: field_type(f), value: f['value'] || f[:value] }
130
+ out[:by_id][spec[:id]] = spec if spec[:id]
131
+ out[:by_label][label_key(spec[:label])] = spec if spec[:label]
132
+ end
133
+ out
134
+ end
135
+
136
+ def entry_field_docs(entry)
137
+ return Array(entry['fields'] || entry[:fields]) if entry.is_a?(Hash)
138
+ return entry.components.to_a if entry.respond_to?(:components)
139
+
140
+ []
141
+ end
142
+
143
+ def entry_id(entry)
144
+ return entry['id'] || entry[:id] if entry.is_a?(Hash)
145
+
146
+ entry.respond_to?(:id) ? entry.id : nil
147
+ end
148
+
149
+ def each_template_field(&block)
150
+ Array(@template_doc['stages']).each do |st|
151
+ Array(st['sections']).each do |sec|
152
+ fields_of(sec).each(&block)
153
+ end
154
+ end
155
+ end
156
+
157
+ def fields_of(section)
158
+ Array(section['dataFields']) + Array(section['leftDataFields']) + Array(section['rightDataFields'])
159
+ end
160
+
161
+ def field_type(field)
162
+ field['__typename'] || field['type'] || field[:type]
163
+ end
164
+
165
+ def label_key(label)
166
+ label.to_s.strip.downcase
167
+ end
168
+
169
+ def value_empty?(value)
170
+ return true if value.nil?
171
+ return value.strip.empty? if value.is_a?(String)
172
+ return value.empty? if value.respond_to?(:empty?)
173
+
174
+ false
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,141 @@
1
+ module Eco::API::UseCases::GraphQL::Samples::Pages::Template
2
+ module Deploy
3
+ # Pluggable post-deploy verification interface.
4
+ #
5
+ # The deploy loop wants to run the freshly-deployed template through a check framework (ideally the
6
+ # `ecoportal-qa` gem's check runner). But `ecoportal-qa` is NOT a hard dependency of eco-helpers,
7
+ # so we do not `require` it here. Instead the loop calls a small `Verifier` interface; a concrete
8
+ # verifier is injected. When `ecoportal-qa` IS on the load path, `QaVerifier` wires it in; when it
9
+ # isn't, `NullVerifier` (always-pass, records that qa was unavailable) keeps the loop runnable
10
+ # offline.
11
+ #
12
+ # A verifier receives the post-deploy template doc and returns a `Verdict`.
13
+ #
14
+ # verdict = Verifier.default.verify(post_deploy_doc)
15
+ # verdict.pass? # => true/false
16
+ # verdict.findings # => [ {severity:, message:}, ... ]
17
+ class Verifier
18
+ # A verification verdict. `pass?` is the gate; `findings` carries per-check detail.
19
+ Verdict = Struct.new(:pass, :findings, :verifier, keyword_init: true) do
20
+ def pass?
21
+ pass ? true : false
22
+ end
23
+
24
+ def failures
25
+ Array(findings).select { |f| %i[error fail].include?((f[:severity] || f['severity'])&.to_sym) }
26
+ end
27
+
28
+ def to_h
29
+ { pass: pass?, verifier: verifier, findings: Array(findings) }
30
+ end
31
+
32
+ def report
33
+ head = "VERIFY (#{verifier}): #{pass? ? 'PASS' : 'FAIL'}"
34
+ return head if Array(findings).empty?
35
+
36
+ ([head] + Array(findings).map do |f|
37
+ " - [#{f[:severity] || f['severity']}] #{f[:message] || f['message']}"
38
+ end).join("\n")
39
+ end
40
+ end
41
+
42
+ # The verifier the loop uses when none is injected: qa if available, else the null verifier.
43
+ def self.default
44
+ qa_available? ? QaVerifier.new : NullVerifier.new
45
+ end
46
+
47
+ # Is the ecoportal-qa check framework loadable?
48
+ def self.qa_available?
49
+ return @qa_available unless @qa_available.nil?
50
+
51
+ @qa_available = begin
52
+ require 'ecoportal/qa'
53
+ defined?(::Ecoportal::QA) ? true : false
54
+ rescue LoadError
55
+ false
56
+ end
57
+ end
58
+
59
+ # Reset the memoised availability probe (used by specs that stub the load path).
60
+ def self.reset_qa_probe!
61
+ @qa_available = nil
62
+ end
63
+
64
+ # Subclass / duck-type contract: return a Verdict for the given template doc.
65
+ def verify(_template_doc)
66
+ raise NotImplementedError, "#{self.class} must implement #verify(template_doc)"
67
+ end
68
+
69
+ # Always-pass verifier used when no real check framework is wired in. It is honest about it:
70
+ # the verdict records that qa was unavailable so a report never falsely claims "qa verified".
71
+ class NullVerifier < Verifier
72
+ def verify(_template_doc)
73
+ Verdict.new(
74
+ pass: true,
75
+ verifier: 'null',
76
+ findings: [{ severity: :info, message: 'ecoportal-qa not available; no checks run' }]
77
+ )
78
+ end
79
+ end
80
+
81
+ # Adapter onto the ecoportal-qa check runner. Kept dependency-free at load time — it only touches
82
+ # `Ecoportal::QA` inside #verify, so requiring this file never forces the qa gem. The exact qa
83
+ # runner API is intentionally accessed through a single seam (`run_qa`) so it is easy to correct
84
+ # once the qa public surface stabilises (see TODO).
85
+ #
86
+ # TODO(qa-integration): the qa check-runner public API is still moving (see the qa repo's
87
+ # PHASE3-SCOPE.md). This adapter assumes a `check_set.run(doc)`-style entry returning objects
88
+ # responding to `#pass?` and `#findings`. Confirm and adjust `run_qa` when qa is pinned as a dep.
89
+ class QaVerifier < Verifier
90
+ def initialize(check_set: nil)
91
+ super()
92
+ @check_set = check_set
93
+ end
94
+
95
+ def verify(template_doc)
96
+ result = run_qa(template_doc)
97
+ Verdict.new(pass: result_pass?(result), verifier: 'ecoportal-qa', findings: result_findings(result))
98
+ rescue StandardError => e
99
+ Verdict.new(pass: false, verifier: 'ecoportal-qa',
100
+ findings: [{ severity: :error, message: "qa verification raised: #{e.message}" }])
101
+ end
102
+
103
+ private
104
+
105
+ # SINGLE integration seam — the one place that knows the qa runner shape.
106
+ def run_qa(template_doc)
107
+ set = @check_set || default_check_set
108
+ set.run(template_doc)
109
+ end
110
+
111
+ def default_check_set
112
+ raise NotImplementedError, 'no qa check_set injected and no default wired' unless defined?(::Ecoportal::QA)
113
+
114
+ # Placeholder: the concrete constructor is confirmed once qa is a pinned dependency.
115
+ if ::Ecoportal::QA.respond_to?(:default_check_set)
116
+ ::Ecoportal::QA.default_check_set
117
+ else
118
+ (raise NotImplementedError,
119
+ 'qa default check set not available')
120
+ end
121
+ end
122
+
123
+ def result_pass?(result)
124
+ return result.pass? if result.respond_to?(:pass?)
125
+
126
+ Array(result).all? { |r| r.respond_to?(:pass?) ? r.pass? : true }
127
+ end
128
+
129
+ def result_findings(result)
130
+ source = result.respond_to?(:findings) ? result.findings : result
131
+ Array(source).map do |f|
132
+ next f if f.is_a?(Hash)
133
+
134
+ { severity: (f.respond_to?(:severity) ? f.severity : :info),
135
+ message: (f.respond_to?(:message) ? f.message : f.to_s) }
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,21 @@
1
+ module Eco::API::UseCases::GraphQL::Samples::Pages::Template
2
+ # Phase 5: deploy → verify → monitor loop for template (workflow) changes.
3
+ #
4
+ # Consumes a gem `Ecoportal::API::GraphQL::Diff::Deploy` command batch (from
5
+ # `Diff::Deploy.from_versions(...)`), dry-run applies it (live behind an explicit `commit:` flag),
6
+ # confirms the applied delta matches the intended delta via a pre/post self-version diff
7
+ # (`Diff::VersionDiff`), verifies the result through a pluggable `Verifier` (ecoportal-qa when
8
+ # present), and reports register sync-readiness against the deployed template.
9
+ #
10
+ # All components are session-less and offline-runnable against fixtures — the live seams (apply /
11
+ # re-read) are injected. See each file's header. Dry-run is the DEFAULT.
12
+ module Deploy
13
+ end
14
+ end
15
+
16
+ require_relative 'deploy/applier'
17
+ require_relative 'deploy/recording_executor'
18
+ require_relative 'deploy/drift_report'
19
+ require_relative 'deploy/verifier'
20
+ require_relative 'deploy/sync_readiness'
21
+ require_relative 'deploy/loop'
@@ -0,0 +1,11 @@
1
+ module Eco::API::UseCases::GraphQL::Samples::Pages
2
+ # Template (workflow) build-from-scratch + (later) diff-and-update samples.
3
+ # See ecoportal-api-graphql/.ai-assistance/projects/template-maintenance/.
4
+ module Template
5
+ end
6
+ end
7
+
8
+ require_relative 'template/command_emitter'
9
+ require_relative 'template/base'
10
+ require_relative 'template/csv_build'
11
+ require_relative 'template/deploy'
@@ -4,4 +4,6 @@ module Eco::API::UseCases::GraphQL::Samples
4
4
  end
5
5
 
6
6
  require_relative 'pages/page'
7
+ require_relative 'pages/register'
7
8
  require_relative 'pages/org_page'
9
+ require_relative 'pages/template'
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = '3.2.16'.freeze
2
+ VERSION = '3.2.17'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.16
4
+ version: 3.2.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
@@ -262,7 +262,7 @@ dependencies:
262
262
  version: '1.3'
263
263
  - - ">="
264
264
  - !ruby/object:Gem::Version
265
- version: 1.3.9
265
+ version: 1.3.11
266
266
  type: :runtime
267
267
  prerelease: false
268
268
  version_requirements: !ruby/object:Gem::Requirement
@@ -272,7 +272,7 @@ dependencies:
272
272
  version: '1.3'
273
273
  - - ">="
274
274
  - !ruby/object:Gem::Version
275
- version: 1.3.9
275
+ version: 1.3.11
276
276
  - !ruby/object:Gem::Dependency
277
277
  name: ecoportal-api-v2
278
278
  requirement: !ruby/object:Gem::Requirement
@@ -282,7 +282,7 @@ dependencies:
282
282
  version: '3.3'
283
283
  - - ">="
284
284
  - !ruby/object:Gem::Version
285
- version: 3.3.1
285
+ version: 3.3.3
286
286
  type: :runtime
287
287
  prerelease: false
288
288
  version_requirements: !ruby/object:Gem::Requirement
@@ -292,7 +292,7 @@ dependencies:
292
292
  version: '3.3'
293
293
  - - ">="
294
294
  - !ruby/object:Gem::Version
295
- version: 3.3.1
295
+ version: 3.3.3
296
296
  - !ruby/object:Gem::Dependency
297
297
  name: ed25519
298
298
  requirement: !ruby/object:Gem::Requirement
@@ -844,6 +844,9 @@ files:
844
844
  - lib/eco/api/usecases/graphql/compat/ooze_redirect/dirty_array.rb
845
845
  - lib/eco/api/usecases/graphql/compat/ooze_redirect/field_patches.rb
846
846
  - lib/eco/api/usecases/graphql/compat/ooze_redirect/force_compat.rb
847
+ - lib/eco/api/usecases/graphql/compat/parity/comparison.rb
848
+ - lib/eco/api/usecases/graphql/compat/parity/harness.rb
849
+ - lib/eco/api/usecases/graphql/compat/parity/run_result.rb
847
850
  - lib/eco/api/usecases/graphql/helpers.rb
848
851
  - lib/eco/api/usecases/graphql/helpers/CLAUDE.md
849
852
  - lib/eco/api/usecases/graphql/helpers/base.rb
@@ -877,6 +880,14 @@ files:
877
880
  - lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb
878
881
  - lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb
879
882
  - lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb
883
+ - lib/eco/api/usecases/graphql/helpers/pages.rb
884
+ - lib/eco/api/usecases/graphql/helpers/pages/copying.rb
885
+ - lib/eco/api/usecases/graphql/helpers/pages/creatable.rb
886
+ - lib/eco/api/usecases/graphql/helpers/pages/filters.rb
887
+ - lib/eco/api/usecases/graphql/helpers/pages/ooze_handlers.rb
888
+ - lib/eco/api/usecases/graphql/helpers/pages/rescuable.rb
889
+ - lib/eco/api/usecases/graphql/helpers/pages/shortcuts.rb
890
+ - lib/eco/api/usecases/graphql/helpers/pages/typed_fields_pairing.rb
880
891
  - lib/eco/api/usecases/graphql/samples.rb
881
892
  - lib/eco/api/usecases/graphql/samples/CLAUDE.md
882
893
  - lib/eco/api/usecases/graphql/samples/contractors.rb
@@ -913,6 +924,24 @@ files:
913
924
  - lib/eco/api/usecases/graphql/samples/pages/page.rb
914
925
  - lib/eco/api/usecases/graphql/samples/pages/page/base.rb
915
926
  - lib/eco/api/usecases/graphql/samples/pages/page/dsl.rb
927
+ - lib/eco/api/usecases/graphql/samples/pages/register.rb
928
+ - lib/eco/api/usecases/graphql/samples/pages/register/base.rb
929
+ - lib/eco/api/usecases/graphql/samples/pages/register/migration_case.rb
930
+ - lib/eco/api/usecases/graphql/samples/pages/register/target_oozes_update_case.rb
931
+ - lib/eco/api/usecases/graphql/samples/pages/template.rb
932
+ - lib/eco/api/usecases/graphql/samples/pages/template/base.rb
933
+ - lib/eco/api/usecases/graphql/samples/pages/template/command_emitter.rb
934
+ - lib/eco/api/usecases/graphql/samples/pages/template/csv_build.rb
935
+ - lib/eco/api/usecases/graphql/samples/pages/template/csv_build/builder.rb
936
+ - lib/eco/api/usecases/graphql/samples/pages/template/csv_build/format_map.rb
937
+ - lib/eco/api/usecases/graphql/samples/pages/template/csv_build/parser.rb
938
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy.rb
939
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/applier.rb
940
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/drift_report.rb
941
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/loop.rb
942
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/recording_executor.rb
943
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/sync_readiness.rb
944
+ - lib/eco/api/usecases/graphql/samples/pages/template/deploy/verifier.rb
916
945
  - lib/eco/api/usecases/graphql/utils.rb
917
946
  - lib/eco/api/usecases/graphql/utils/sftp.rb
918
947
  - lib/eco/api/usecases/lib.rb