Class: Insika::Soak::Envelope

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/soak/envelope.rb

Overview

The frozen pre-declared envelope for a soak run. Parses the fenced yaml block inside a markdown file, validates it completely or not at all, and exposes the SHA-256 of the WHOLE file — every hourly snapshot is stamped with it, so editing the envelope mid-run turns the run invalid instead of producing a verdict nobody can defend.

No defaults, no tolerance: an envelope with a hole is not a pre-declaration. Contrast with EnvSchema (tolerant, warn-and-boot) — that resilience protects a live service; this strictness protects a claim.

Constant Summary collapse

REQUIRED =

Required keys. A missing one is a ConfigError, not a default.

%i[
  version target duration_hours warmup_hours
  arrival turns_per_hour session_turns concurrency_cap web_concurrency
  rss_growth_ratio prep_p95_drift_ratio
  restarts_max error_rate_ceiling no_usage_rate_ceiling
  coverage_min_ratio gap_seconds_max hourly_turn_floor
].freeze
CALIBRATED =

Written after E1, before E2 (techspec D3). Absent -> calibrated? is false and the 72h run refuses to start; the 4h dry run does not require them.

%i[rss_ceiling_mb prep_p95_ceiling_ms total_p95_ceiling_ms].freeze
RATIO_KEYS =

Keys validated as ratios: must be strictly greater than 1 (the gate is "no more than 1.15x", so a ratio of 1 or below is a nonsense gate).

%i[rss_growth_ratio prep_p95_drift_ratio].freeze
POSITIVE_COUNT_KEYS =

Keys validated as positive integers.

%i[
  duration_hours warmup_hours turns_per_hour session_turns
  concurrency_cap web_concurrency gap_seconds_max hourly_turn_floor
].freeze
RATE_KEYS =

Keys validated as rates in [0, 1].

%i[error_rate_ceiling no_usage_rate_ceiling coverage_min_ratio].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(values:, sha:) ⇒ Envelope

Returns a new instance of Envelope.



125
126
127
128
# File 'lib/insika/soak/envelope.rb', line 125

def initialize(values:, sha:)
  @values = values
  @sha = sha
end

Instance Attribute Details

#shaObject (readonly)

Returns the value of attribute sha.



123
124
125
# File 'lib/insika/soak/envelope.rb', line 123

def sha
  @sha
end

#valuesObject (readonly)

Returns the value of attribute values.



123
124
125
# File 'lib/insika/soak/envelope.rb', line 123

def values
  @values
end

Class Method Details

.load(path) ⇒ Object

Raises Insika::ConfigError: file missing, no fenced yaml block, unparseable, a REQUIRED key absent, or a value out of range.



46
47
48
49
50
51
# File 'lib/insika/soak/envelope.rb', line 46

def self.load(path)
  bytes = File.binread(path)
  parse(bytes, sha: "sha256:#{Digest::SHA256.hexdigest(bytes)}")
rescue Errno::ENOENT
  raise ConfigError, "soak envelope not found: #{path}"
end

.parse(source, sha:) ⇒ Object

Pure parse (specs, fixtures). sha is stamped verbatim.



54
55
56
57
58
# File 'lib/insika/soak/envelope.rb', line 54

def self.parse(source, sha:)
  new(values: validate(YAML.safe_load(fenced_yaml(source)) || {}), sha: sha)
rescue Psych::SyntaxError => e
  raise ConfigError, "soak envelope yaml is unparseable: #{e.message.lines.first.to_s.strip}"
end

Instance Method Details

#[](key) ⇒ Object



130
# File 'lib/insika/soak/envelope.rb', line 130

def [](key) = values[key]

#calibrated?Boolean

Returns:

  • (Boolean)


132
# File 'lib/insika/soak/envelope.rb', line 132

def calibrated? = CALIBRATED.all? { |key| !values[key].nil? }

#dry_run?Boolean

E1 shape (<= 8h): the dry run skips the calibrated ceilings.

Returns:

  • (Boolean)


135
# File 'lib/insika/soak/envelope.rb', line 135

def dry_run? = values[:duration_hours] <= 8

#to_hObject



137
# File 'lib/insika/soak/envelope.rb', line 137

def to_h = values.merge(sha: sha)