Class: Insika::Commands::ResolveProposal

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/commands/resolve_proposal.rb

Overview

the human's answer : approve (→ the store, CAS, provenance distilled:<session_ref>), reject (with an optional reason), dismiss (latches the tuple). Approval never silently overwrites an operator edit (D5/E3): the CAS baseline captured at distill time guards the write, and a lost race flips the proposal to :stale carrying the fact's CURRENT value — both values visible on the wiki. Synchronous control command — the Studio dispatches it; no task, no actor.

Constant Summary collapse

DECISIONS =
%w[approved rejected dismissed].freeze

Instance Method Summary collapse

Constructor Details

#initialize(proposal_store:, memory_store:, event_stream:) ⇒ ResolveProposal

Returns a new instance of ResolveProposal.



15
16
17
18
19
# File 'lib/insika/commands/resolve_proposal.rb', line 15

def initialize(proposal_store:, memory_store:, event_stream:)
  @proposal_store = proposal_store
  @memory_store = memory_store
  @event_stream = event_stream
end

Instance Method Details

#call(command) ⇒ Object

payload: { proposal_id:, decision: "approved"|"rejected"|"dismissed", operator?, note? } operator from payload || command.meta || "operator" -> Proposal (the resolved record; a :stale one carries current_value)

Raises:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/insika/commands/resolve_proposal.rb', line 25

def call(command)
  proposal_id = Coercion.presence(command.payload[:proposal_id] || command.payload["proposal_id"])
  raise ValidationError, "proposal_id is required" if proposal_id.nil?

  decision = Coercion.presence(command.payload[:decision] || command.payload["decision"])
  raise ValidationError, "decision is required" unless DECISIONS.include?(decision)

  proposal = @proposal_store.find(proposal_id)
  raise Insika::NotFoundError, "proposal not found: #{proposal_id}" if proposal.nil?

  operator = operator_for(command)
  note = Coercion.presence(command.payload[:note] || command.payload["note"])

  case decision
  when "approved" then approve(command, proposal, operator, note)
  when "rejected"
    resolved = @proposal_store.reject(id: proposal.id, operator: operator, note: note)
    emit(:proposal_rejected, resolved)
    resolved
  else # "dismissed"
    resolved = @proposal_store.dismiss(id: proposal.id, operator: operator, note: note)
    emit(:proposal_dismissed, resolved)
    resolved
  end
end