Class: ApprovalEngine::ApprovalBuilder

Inherits:
Object
  • Object
show all
Defined in:
app/services/approval_engine/approval_builder.rb

Overview

Stamps a concrete, actionable ledger (Approval → Track → Steps) out of one or more abstract TrackTemplates.

A single template builds a single-track approval. Several templates build a scatter-gather approval: one parallel track per template, all active at once, gathering per the approval’s ‘approvals_required` (`:all` by default).

Each template step is expanded into one Step per resolved actor — so “any one of five senior devs” becomes five sibling steps sharing one consensus policy. Only each track’s first layer starts ‘pending`; later layers wait until the layer before them resolves.

Defined Under Namespace

Classes: BuilderError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(templates:, target:, event_name: nil, trigger_rule: nil, approvals_required: "all") ⇒ ApprovalBuilder

Returns a new instance of ApprovalBuilder.



32
33
34
35
36
37
38
# File 'app/services/approval_engine/approval_builder.rb', line 32

def initialize(templates:, target:, event_name: nil, trigger_rule: nil, approvals_required: "all")
  @templates           = templates
  @target              = target
  @event_name          = event_name
  @trigger_rule        = trigger_rule
  @approvals_required  = approvals_required.to_s
end

Class Method Details

.build!(template:, target:, event_name: nil, trigger_rule: nil) ⇒ Object

Build a single-track approval from one template. ‘event_name` is the event that triggered this run (nil when started manually) — recorded on the Approval for audit/display. `trigger_rule` is the rule that matched, when auto-routed, so the approval remembers its provenance.



20
21
22
# File 'app/services/approval_engine/approval_builder.rb', line 20

def self.build!(template:, target:, event_name: nil, trigger_rule: nil)
  new(templates: [ template ], target: target, event_name: event_name, trigger_rule: trigger_rule).build!
end

.build_parallel!(templates:, target:, event_name: nil, approvals_required: "all") ⇒ Object

Build a scatter-gather approval, one parallel track per template. ‘approvals_required` is the gather consensus (default `:all`).

Raises:



26
27
28
29
30
# File 'app/services/approval_engine/approval_builder.rb', line 26

def self.build_parallel!(templates:, target:, event_name: nil, approvals_required: "all")
  raise BuilderError, "build_parallel! needs at least one template" if templates.blank?

  new(templates: templates, target: target, event_name: event_name, approvals_required: approvals_required).build!
end

.build_quarantine_approval!(target:, tenant_id:, reason:) ⇒ Object

The fail-closed quarantine state. Built when a dynamic rule blows up, so ops can see exactly why a track never started instead of hitting a 500.



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

def self.build_quarantine_approval!(target:, tenant_id:, reason:)
  Approval.create!(tenant_id: tenant_id, target: target, status: "quarantined").tap do |approval|
    OutboxEvent.create!(
      tenant_id: approval.tenant_id,
      event_name: "approval.quarantined",
      record: approval,
      error_payload: reason
    )
  end
end

Instance Method Details

#build!Object



40
41
42
43
44
45
46
47
48
# File 'app/services/approval_engine/approval_builder.rb', line 40

def build!
  guard_single_tenant!
  guard_gather_consensus!
  ActiveRecord::Base.transaction do
    approval = build_approval
    templates.each { |template| build_track!(approval, template) }
    approval
  end
end