Class: CollavreLinear::InboundApplier

Inherits:
Object
  • Object
show all
Defined in:
app/services/collavre_linear/inbound_applier.rb

Overview

Task 10 — applies a verified inbound Linear webhook payload to local Collavre state (Creatives, IssueLinks, CommentLinks).

payload is a PARSED HASH with string keys, exactly as InboundApplyJob hands it over (never a JSON string). Shape (Linear webhook envelope):

{
"action"      => "create" | "update" | "remove",
"type"        => "Issue" | "Comment" | ...,
"data"        => { ...entity fields... },
"updatedFrom" => { ...changed fields (update only)... },
"webhookTimestamp" => <ms epoch>
}

Loop guard: every Creative mutated here gets skip_linear_sync = true set BEFORE save, so the CreativeSyncObserver's after_commit does NOT bounce the change straight back out to Linear (echo prevention, Task 8).

Sequence writes: FieldMapper only COMPUTES the sequence integer. We assign it through the closure_tree-managed model (creative.sequence = value; save!), never update_column/update_all, so the model's ordering callbacks run. Core Collavre::Creative has no dedicated reorder helper (it relies on has_closure_tree order: :sequence with a plain sequence column), so the attribute-assign-then-save path is the correct model-level write.

Conflict rule: when the local link is dirty (has un-synced local edits) the inbound payload is dispositioned against our last-seen remote timestamp (remote_updated_at):

* remote strictly NEWER than baseline => genuine concurrent remote edit =>
flip to `conflict`, post a Main-topic comment, skip the field apply.
* remote present but NOT newer (<=) => a stale echo. A genuinely new remote
edit always carries updatedAt > baseline, so a not-newer payload on a
dirty link can only be our own outbound update webhooking back (or a
replayed delivery). Applying it would clobber the newer pending local
edit, so we no-op. (Especially reachable today: issue echo dropping is
disabled while `app_actor_id` is nil.)
* remote or baseline nil => no basis to compare => apply.

"Newer" = remote timestamp > link.remote_updated_at, where the remote timestamp is taken from data["updatedAt"] (parsed) and falls back to webhookTimestamp (ms epoch) when updatedAt is absent.

Instance Method Summary collapse

Constructor Details

#initialize(payload) ⇒ InboundApplier

Returns a new instance of InboundApplier.



45
46
47
48
49
50
# File 'app/services/collavre_linear/inbound_applier.rb', line 45

def initialize(payload)
  @payload = payload || {}
  @action  = @payload["action"]
  @type    = @payload["type"]
  @data    = @payload["data"] || {}
end

Instance Method Details

#apply!Object



52
53
54
55
56
57
58
59
60
61
62
# File 'app/services/collavre_linear/inbound_applier.rb', line 52

def apply!
  case @type
  when "Comment"
    apply_comment!
  when "Issue"
    apply_issue!
  end
  # Other types (e.g. Project — the webhook subscribes to it) are ignored:
  # routing them through apply_issue! would use the project UUID as a
  # linear_issue_id and create a blank Creative in a one-link install.
end