Class: Vangrail::Result

Inherits:
Object
  • Object
show all
Defined in:
lib/vangrail/result.rb

Overview

What a rail decided about one piece of text.

Three statuses, matching the contract the upstream toolkit settled on for standalone rail checks:

:passed    the text is cleared and unchanged
:modified  a rail rewrote the text; `content` carries the rewrite
:blocked   a rail stopped the turn; `content` carries the refusal, if any

Two states would be one too few. A rail that redacts a token from an answer has neither passed the text nor blocked the turn, and folding that into either one loses the fact that the reader is looking at edited output.

certain is orthogonal to status. A rail that is off, not enabled, or unreachable returns :passed with certain false, which is the difference between "checked and clean" and "not checked". Callers that report a safety posture read it; callers that only route on the decision can ignore it.

Constant Summary collapse

STATUSES =
%i[passed modified blocked].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(status:, rail:, content: nil, reason: nil, categories: [], model: nil, latency_ms: nil, raw: nil, certain: true, rewritten_by: []) ⇒ Result

rewritten_by is every rail that changed the text, in the order they ran. rail stays the last one, because that is what a caller routing on a single name already reads, and a pass where a redaction was followed by a disclosure mark reported only the mark: the audit record whose purpose is to explain what the desk did lost the redaction entirely.

Raises:

  • (ArgumentError)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/vangrail/result.rb', line 31

def initialize(status:, rail:, content: nil, reason: nil, categories: [], model: nil,
               latency_ms: nil, raw: nil, certain: true, rewritten_by: [])
  status = status.to_sym
  raise ArgumentError, "status must be one of #{STATUSES.join(', ')}" unless STATUSES.include?(status)

  @status = status
  @rail = rail
  @content = content
  @reason = reason
  @categories = Array(categories)
  @model = model
  @latency_ms = latency_ms
  @raw = raw
  @certain = certain
  @rewritten_by = Array(rewritten_by).map(&:to_s)
end

Instance Attribute Details

#categoriesObject (readonly)

Returns the value of attribute categories.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def categories
  @categories
end

#contentObject (readonly)

Returns the value of attribute content.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def content
  @content
end

#latency_msObject (readonly)

Returns the value of attribute latency_ms.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def latency_ms
  @latency_ms
end

#modelObject (readonly)

Returns the value of attribute model.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def model
  @model
end

#railObject (readonly)

Returns the value of attribute rail.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def rail
  @rail
end

#rawObject (readonly)

Returns the value of attribute raw.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def raw
  @raw
end

#reasonObject (readonly)

Returns the value of attribute reason.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def reason
  @reason
end

#rewritten_byObject (readonly)

Returns the value of attribute rewritten_by.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def rewritten_by
  @rewritten_by
end

#statusObject (readonly)

Returns the value of attribute status.



24
25
26
# File 'lib/vangrail/result.rb', line 24

def status
  @status
end

Class Method Details

.blocked(rail:, **kwargs) ⇒ Object



56
57
58
# File 'lib/vangrail/result.rb', line 56

def self.blocked(rail:, **kwargs)
  new(status: :blocked, rail: rail, **kwargs)
end

.modified(rail:, content:, **kwargs) ⇒ Object



52
53
54
# File 'lib/vangrail/result.rb', line 52

def self.modified(rail:, content:, **kwargs)
  new(status: :modified, rail: rail, content: content, **kwargs)
end

.passed(rail:, **kwargs) ⇒ Object



48
49
50
# File 'lib/vangrail/result.rb', line 48

def self.passed(rail:, **kwargs)
  new(status: :passed, rail: rail, **kwargs)
end

.unchecked(rail:, reason:, **kwargs) ⇒ Object

No rail ran. Allowed, and explicitly not vouched for.



61
62
63
# File 'lib/vangrail/result.rb', line 61

def self.unchecked(rail:, reason:, **kwargs)
  new(status: :passed, rail: rail, certain: false, reason: reason, **kwargs)
end

Instance Method Details

#allowed?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/vangrail/result.rb', line 77

def allowed?
  !blocked?
end

#blocked?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/vangrail/result.rb', line 73

def blocked?
  status == :blocked
end

#certain?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/vangrail/result.rb', line 81

def certain?
  @certain
end

#content_or(original) ⇒ Object

The text to carry forward: a rewrite or a blocked body when the rail left one, otherwise what the caller passed in.



87
88
89
# File 'lib/vangrail/result.rb', line 87

def content_or(original)
  content.nil? ? original : content
end

#modified?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/vangrail/result.rb', line 69

def modified?
  status == :modified
end

#passed?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/vangrail/result.rb', line 65

def passed?
  status == :passed
end

#to_hObject



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/vangrail/result.rb', line 113

def to_h
  {
    'status' => status.to_s,
    'certain' => certain?,
    'rail' => rail&.to_s,
    'reason' => reason,
    'categories' => (categories unless categories.empty?),
    'rewritten_by' => (rewritten_by unless rewritten_by.empty?),
    'model' => model,
    'latency_ms' => latency_ms,
  }.compact
end

#to_sObject



126
127
128
129
130
131
132
# File 'lib/vangrail/result.rb', line 126

def to_s
  parts = ["#{rail}=#{status}"]
  parts << 'unchecked' unless certain?
  parts << categories.join(',') unless categories.empty?
  parts << reason if reason
  parts.join(' ')
end

#with_rail(name, content: self.content, certain: certain?) ) ⇒ Object



105
106
107
108
109
110
111
# File 'lib/vangrail/result.rb', line 105

def with_rail(name, content: self.content, certain: certain?)
  self.class.new(
    status: status, rail: name, content: content, reason: reason, categories: categories,
    model: model, latency_ms: latency_ms, raw: raw, certain: certain,
    rewritten_by: rewritten_by
  )
end

#with_rewrites(names, categories: self.categories) ⇒ Object

A copy with a different rail name, for an engine reporting which of its rails produced a decision. Optional content and certain override the fields a later rail must not be allowed to drop. The same result with the rewrite chain replaced, and its categories with it, for a caller that ran several rails and knows which of them changed the text.



97
98
99
100
101
102
103
# File 'lib/vangrail/result.rb', line 97

def with_rewrites(names, categories: self.categories)
  self.class.new(
    status: status, rail: rail, content: content, reason: reason, categories: categories,
    model: model, latency_ms: latency_ms, raw: raw, certain: certain?,
    rewritten_by: names
  )
end