Class: CollavreLinear::InboundApplier
- Inherits:
-
Object
- Object
- CollavreLinear::InboundApplier
- 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
- #apply! ⇒ Object
-
#initialize(payload) ⇒ InboundApplier
constructor
A new instance of InboundApplier.
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 |