Class: Vangrail::Policy

Inherits:
Struct
  • Object
show all
Defined in:
lib/vangrail/judgement.rb,
lib/vangrail/judgement.rb

Overview

Where the two lines are drawn between allowing, reviewing, and blocking.

Three actions rather than two, because the middle one is what a posterior makes possible and a yes-or-no rail cannot express. Most of the interesting traffic on a documentation desk lands there: one rail fired, the base rate is low, and the honest answer is that this page is a hundred times more suspicious than average and still probably fine. Blocking it costs a reader their answer; ignoring it wastes the detection. Queueing it costs somebody a minute.

The defaults are stated as what they are: a starting policy, not a finding. What they should be depends on what a false block costs against what a missed injection costs, and that is a deployment's judgement rather than a library's.

Constant Summary collapse

DEFAULT =

Assigned outside the struct body, because a constant written inside a Struct.new block lands in the enclosing module rather than in the struct.

Policy.new(block_at: 0.5, review_at: 0.05)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#block_atObject

Returns the value of attribute block_at

Returns:

  • (Object)

    the current value of block_at



91
92
93
# File 'lib/vangrail/judgement.rb', line 91

def block_at
  @block_at
end

#review_atObject

Returns the value of attribute review_at

Returns:

  • (Object)

    the current value of review_at



91
92
93
# File 'lib/vangrail/judgement.rb', line 91

def review_at
  @review_at
end

Class Method Details

.from_costs(missed_attack:, false_block:, review: nil) ⇒ Object

The two lines, derived from what the three outcomes cost instead of chosen.

A posterior is only half an answer: acting on it needs to know what being wrong is worth in each direction, and that is a fact about the deployment rather than about the text. Written out, the decision rule is the ordinary one from decision theory. Allowing a page costs the chance it was an attack times what a missed attack costs. Blocking costs the chance it was fine times what a wrong block costs a reader. Sending it to a person costs what a minute of their time costs, whatever the page turns out to be.

Choosing the cheapest of the three gives both thresholds directly: reviewing beats allowing above review / missed_attack, and blocking beats reviewing above 1 - review / false_block.

Policy.from_costs(missed_attack: 1000, false_block: 10, review: 1)
# => block above 0.9, review above 0.001

The units cancel, so they can be euros, minutes, or anything else applied consistently. What they cannot be is unstated: a threshold with no cost behind it is a preference, and this is the arithmetic that turns the preference into a claim somebody can argue with.

Raises:

  • (ArgumentError)


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/vangrail/judgement.rb', line 123

def self.from_costs(missed_attack:, false_block:, review: nil)
  raise ArgumentError, 'costs must be positive' unless [missed_attack, false_block].all?(&:positive?)

  # With no human in the loop there is one line, and it is the classic
  # threshold: block when the expected cost of allowing exceeds the
  # expected cost of blocking.
  return two_way(missed_attack, false_block) if review.nil?

  raise ArgumentError, 'review cost must be positive' unless review.positive?

  review_at = review.fdiv(missed_attack)
  block_at = 1 - review.fdiv(false_block)
  # Reviewing everything costs more than being wrong: there is no band, and
  # saying so beats silently inverting the thresholds.
  return two_way(missed_attack, false_block) if review_at >= block_at

  new(block_at: block_at, review_at: review_at)
end

.two_way(missed_attack, false_block) ⇒ Object



142
143
144
145
# File 'lib/vangrail/judgement.rb', line 142

def self.two_way(missed_attack, false_block)
  threshold = false_block.fdiv(false_block + missed_attack)
  new(block_at: threshold, review_at: threshold)
end

Instance Method Details

#action_for(posterior) ⇒ Object



92
93
94
95
96
97
# File 'lib/vangrail/judgement.rb', line 92

def action_for(posterior)
  return :block if posterior >= block_at
  return :review if posterior >= review_at

  :allow
end