Class: Vangrail::Rail

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

Overview

The whole rail protocol: a name, the sides it applies to, and call.

class ShoutRail < Vangrail::Rail
def decide(text, _context)
  return pass if text == text.downcase

  modify(text.downcase, reason: 'lowered')
end
end

Deliberately not a DSL. A rail is an object with one method, so a Ruby application can write one in five lines, test it without a network, and put it in the same ordered list as the model-backed ones. Subclasses implement #decide; #call scrubs the bytes first so every rail sees readable text.

Constant Summary collapse

SIDES =

Three sides, not two. :context is text the application retrieved and is about to put in a prompt: a wiki page, a search result, a file. It is the side an attacker usually reaches without touching the application at all, and a stack that checks only what the user typed and what the model answered never looks at it.

%i[input context output].freeze
DEFAULT_SIDES =

What a rail gets by default. Context is opt-in per rail, because a rail written to judge a question is rarely the right one to judge a document.

%i[input output].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name: nil, sides: DEFAULT_SIDES) ⇒ Rail

Returns a new instance of Rail.

Raises:

  • (ArgumentError)


34
35
36
37
38
39
# File 'lib/vangrail/rail.rb', line 34

def initialize(name: nil, sides: DEFAULT_SIDES)
  @name = (name || default_name).to_s
  @sides = Array(sides).map(&:to_sym)
  unknown = @sides - SIDES
  raise ArgumentError, "unknown side(s): #{unknown.join(', ')}" unless unknown.empty?
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



32
33
34
# File 'lib/vangrail/rail.rb', line 32

def name
  @name
end

#sidesObject (readonly)

Returns the value of attribute sides.



32
33
34
# File 'lib/vangrail/rail.rb', line 32

def sides
  @sides
end

Class Method Details

.usable(text) ⇒ Object

UTF-8 or something that can be read as it. A body off a socket arrives tagged ASCII-8BIT whatever is in it, so the tag is corrected before the bytes are judged; only genuinely broken sequences are scrubbed, and the scrub leaves a replacement character where the byte was, which Rails::Obfuscation then treats as the evasion it is.



59
60
61
62
63
# File 'lib/vangrail/rail.rb', line 59

def self.usable(text)
  body = text.to_s
  body = body.dup.force_encoding(Encoding::UTF_8) unless body.encoding == Encoding::UTF_8
  body.valid_encoding? ? body : body.scrub
end

Instance Method Details

#applies_to?(side) ⇒ Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/vangrail/rail.rb', line 41

def applies_to?(side)
  sides.include?(side.to_sym)
end

#cache_key(_text, _context) ⇒ Object



70
# File 'lib/vangrail/rail.rb', line 70

def cache_key(_text, _context) = nil

#call(text, context = {}) ⇒ Object

Returns a Result. context is a hash the engine threads through: :side, :user_input, :passages, :history, plus anything a caller adds.

Scrubs here so rail.call(page, side: :context) (the shape the README and the tutorial teach) never hands a subclass bytes it cannot read.



50
51
52
# File 'lib/vangrail/rail.rb', line 50

def call(text, context = {})
  decide(Rail.usable(text), context)
end

#decide(_text, _context) ⇒ Object

Subclasses implement this. text is already readable UTF-8.

Raises:

  • (NotImplementedError)


66
67
68
# File 'lib/vangrail/rail.rb', line 66

def decide(_text, _context)
  raise NotImplementedError, "#{self.class} must implement #decide"
end

#language_agnostic?Boolean

True when a hit or a silence is meaningful even if the page is not in a language the lexicons cover. Markup, budgets, and secrets do not care what language the words are in. Pattern and concept rails do.

Returns:

  • (Boolean)


98
99
100
# File 'lib/vangrail/rail.rb', line 98

def language_agnostic?
  false
end

#offline?Boolean

Does this rail need the network. Used to report a posture and to let a caller build a model-free engine on purpose. The rare case is networked.

Returns:

  • (Boolean)


76
77
78
# File 'lib/vangrail/rail.rb', line 76

def offline?
  true
end

#placeholder?Boolean

A rail that stands in for one that could not be built, rather than a rail that ran. The engine prefers the reason from something that actually ran when both are uncertain, because "the endpoint refused the connection" is more actionable than "no endpoint was resolved".

Returns:

  • (Boolean)


84
85
86
# File 'lib/vangrail/rail.rb', line 84

def placeholder?
  false
end

#posture?Boolean

True when this rail's job is to say whether the other rails apply, not to contribute a hit. Language is the case: it never blocks, and a miss means the lexicon rails did not read the page.

Returns:

  • (Boolean)


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

def posture?
  false
end

#quantifies?Boolean

Returns:

  • (Boolean)


72
# File 'lib/vangrail/rail.rb', line 72

def quantifies? = false

#to_sObject



102
103
104
# File 'lib/vangrail/rail.rb', line 102

def to_s
  name
end