Class: Ecoportal::API::GraphQL::Diff::Deploy
- Inherits:
-
Object
- Object
- Ecoportal::API::GraphQL::Diff::Deploy
- Defined in:
- lib/ecoportal/api/graphql/diff/deploy.rb
Overview
Deploy orchestration: turns a diff into the replayable, ordered WorkflowCommand batch that
executeWorkflowCommands consumes — plus the honest list of changes that could NOT be
synthesised (needs-human), so a review step can gate the apply.
Two entry points:
# self-version replay (same object, real ids) — the "commit" replayed onto itself/a clone plan = Deploy.from_versions(before_doc, after_doc)
# cross-object deploy (UAT delta -> a real PROD target): the diff gives the delta, but the # move targets (section/stage) must resolve against the PROD doc's ids. plan = Deploy.from_versions(uat_v1, uat_v2, target_doc: prod_doc)
plan.commands # => ordered Array of built command hashes, ready to send plan.unsupported # => [Change, ...] needing human handling (never guessed) plan.changelog # => human one-liners for a ticket / review checklist plan.execute!(page) # => sends via the given executor (page.execute_workflow_commands)
SAFETY — Deploy is inert until execute! is called with an explicit executor. It never
applies anything on its own, and it surfaces unsupported so a human gates the deploy.
Instance Attribute Summary collapse
-
#diff ⇒ Object
readonly
Returns the value of attribute diff.
-
#synthesizer ⇒ Object
readonly
Returns the value of attribute synthesizer.
Class Method Summary collapse
-
.from_cross_object(source_doc, target_doc, engine: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.new(pairing: :assisted, scope: :data_migration, intent: :deploy)) ⇒ Object
Build a plan for a CROSS-OBJECT deploy (UAT<->PROD, page<->template): the two docs do not share ids, so fields are PAIRED (equivalence) before the delta is emitted.
-
.from_versions(before_doc, after_doc, target_doc: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.default) ⇒ Object
Build a plan from two snapshots.
Instance Method Summary collapse
- #changelog ⇒ Object
-
#commands ⇒ Object
Ordered, dependency-safe Array of built command hashes ready for executeWorkflowCommands.
-
#execute!(executor, allow_partial: false) ⇒ Object
Apply the batch.
-
#fully_supported? ⇒ Boolean
True when every detected change was synthesised into a command (nothing needs a human).
-
#initialize(diff, resolver: nil, thread_placeholders: true) ⇒ Deploy
constructor
A new instance of Deploy.
-
#pairing ⇒ Object
The pairing result when this plan is cross-object (nil for a self-version plan).
- #summary ⇒ Object
- #to_h ⇒ Object
-
#unsupported ⇒ Object
Changes with no faithful command — surfaced for human review, never guessed.
Constructor Details
#initialize(diff, resolver: nil, thread_placeholders: true) ⇒ Deploy
Returns a new instance of Deploy.
34 35 36 37 38 39 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 34 def initialize(diff, resolver: nil, thread_placeholders: true) @diff = diff @synthesizer = CommandSynthesizer.new( diff.changes, resolver: resolver, thread_placeholders: thread_placeholders ) end |
Instance Attribute Details
#diff ⇒ Object (readonly)
Returns the value of attribute diff.
26 27 28 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 26 def diff @diff end |
#synthesizer ⇒ Object (readonly)
Returns the value of attribute synthesizer.
26 27 28 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 26 def synthesizer @synthesizer end |
Class Method Details
.from_cross_object(source_doc, target_doc, engine: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.new(pairing: :assisted, scope: :data_migration, intent: :deploy)) ⇒ Object
Build a plan for a CROSS-OBJECT deploy (UAT<->PROD, page<->template): the two docs do not
share ids, so fields are PAIRED (equivalence) before the delta is emitted. The pairing
(accepted/ambiguous/unmatched) is exposed via #pairing so a human can adjudicate the
unresolved set before applying. engine: lets the caller pass a Pairing::Engine wired
to a Ledger (learns over time); strategy: defaults to the assisted/data-migration mode.
plan = Deploy.from_cross_object(uat_doc, prod_doc, engine: engine) plan.pairing.ambiguous # review before deploy plan.commands # the paired delta as WorkflowCommands
64 65 66 67 68 69 70 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 64 def self.from_cross_object(source_doc, target_doc, engine: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.new(pairing: :assisted, scope: :data_migration, intent: :deploy)) resolver ||= (IdResolver.from_doc(target_doc) if target_doc) diff = CrossObjectDiff.new(source_doc, target_doc, engine: engine, strategy: strategy) new(diff, resolver: resolver, thread_placeholders: thread_placeholders) end |
.from_versions(before_doc, after_doc, target_doc: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.default) ⇒ Object
Build a plan from two snapshots. When target_doc is given (cross-object deploy), a
resolver is derived from it so field/section moves can address the target's real ids.
An explicit resolver: overrides the derived one.
strategy: selects the diff modality (scope / move-sensitivity / intent). It defaults to
the self-version structural changelog strategy — the historical behaviour. When the
strategy's intent is :deploy (or unspecified) placeholder threading stays ON.
48 49 50 51 52 53 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 48 def self.from_versions(before_doc, after_doc, target_doc: nil, resolver: nil, thread_placeholders: true, strategy: Strategy.default) resolver ||= (IdResolver.from_doc(target_doc) if target_doc) diff = VersionDiff.new(before_doc, after_doc, strategy: strategy) new(diff, resolver: resolver, thread_placeholders: thread_placeholders) end |
Instance Method Details
#changelog ⇒ Object
88 89 90 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 88 def changelog @diff.changelog end |
#commands ⇒ Object
Ordered, dependency-safe Array of built command hashes ready for executeWorkflowCommands.
79 80 81 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 79 def commands @synthesizer.commands end |
#execute!(executor, allow_partial: false) ⇒ Object
Apply the batch. executor must respond to execute_workflow_commands(commands) (the
gem's page/template mutation facade). Raises unless the plan is fully supported — the
caller must review/clear unsupported first — unless allow_partial: true.
112 113 114 115 116 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 112 def execute!(executor, allow_partial: false) raise ArgumentError, "#{unsupported.size} unsupported change(s); review before deploy" unless fully_supported? || allow_partial executor.execute_workflow_commands(commands) end |
#fully_supported? ⇒ Boolean
True when every detected change was synthesised into a command (nothing needs a human).
101 102 103 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 101 def fully_supported? unsupported.empty? end |
#pairing ⇒ Object
The pairing result when this plan is cross-object (nil for a self-version plan). Lets a
caller inspect/adjudicate ambiguous + unmatched pairings before execute!.
74 75 76 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 74 def pairing @diff.respond_to?(:pairing) ? @diff.pairing : nil end |
#summary ⇒ Object
92 93 94 95 96 97 98 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 92 def summary { changes: @diff.summary, commands: commands.size, unsupported: unsupported.size } end |
#to_h ⇒ Object
105 106 107 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 105 def to_h { summary: summary, commands: commands, unsupported: unsupported.map(&:to_h) } end |
#unsupported ⇒ Object
Changes with no faithful command — surfaced for human review, never guessed.
84 85 86 |
# File 'lib/ecoportal/api/graphql/diff/deploy.rb', line 84 def unsupported @synthesizer.unsupported end |