Module: Insika::Coercion

Overview

Shared coercions at the input/persistence boundary. They used to live copied in each store/handler; a single home keeps them from drifting.

Constant Summary collapse

TRUTHY =

The operator-facing truthy: what a form checkbox ("1"), a JSON round-trip ("true") or the DSL (true) produce for an opt-in. One list, one reading — AgentProfile#stream_public?, SkillCatalog#blanket? and the Studio all consult it, so a value that opts in on one surface cannot opt out on another.

[true, "true", "1", "yes", "on"].freeze

Class Method Summary collapse

Class Method Details

.blank?(value) ⇒ Boolean

nil or blank (incl. whitespace-only) -> true.

Returns:

  • (Boolean)


17
# File 'lib/insika/coercion.rb', line 17

def blank?(value) = value.nil? || value.to_s.strip.empty?

.deep_stringify(obj) ⇒ Object

Normalizes keys and Symbols to String recursively (the stores' JSON model has no Symbol). Hash -> keys and values; Array -> elements.



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/insika/coercion.rb', line 45

def deep_stringify(obj)
  case obj
  when Hash
    obj.each_with_object({}) { |(k, v), acc| acc[k.to_s] = deep_stringify(v) }
  when Array
    obj.map { |v| deep_stringify(v) }
  when Symbol
    obj.to_s
  else
    obj
  end
end

.presence(str) ⇒ Object

Present string or nil: nil and blank (incl. whitespace-only) become nil; everything else becomes a stripped String.



11
12
13
14
# File 'lib/insika/coercion.rb', line 11

def presence(str)
  s = str.to_s.strip
  s.empty? ? nil : s
end

.present?(value) ⇒ Boolean

Inverse of blank? — a usable, non-empty value.

Returns:

  • (Boolean)


20
# File 'lib/insika/coercion.rb', line 20

def present?(value) = !blank?(value)

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


28
# File 'lib/insika/coercion.rb', line 28

def truthy?(value) = TRUTHY.include?(value)

.utf8(str) ⇒ Object

Bytes that come from outside the engine (sockets, pipes, subprocesses) arrive tagged BINARY, or as UTF-8 carrying invalid sequences. Both break JSON.generate the moment the text has an accent or an emoji — today a warning ("UTF-8 string passed as BINARY"), an exception from json 3.0 — and that text ends up in a transcript, an event and an SSE frame. Reinterprets the bytes as UTF-8 (the wire encoding of every source we read) and scrubs what is not valid, so what crosses the boundary is always serializable.



37
38
39
40
41
# File 'lib/insika/coercion.rb', line 37

def utf8(str)
  s = str.to_s
  s = s.dup.force_encoding(Encoding::UTF_8) unless s.encoding == Encoding::UTF_8
  s.valid_encoding? ? s : s.scrub
end