Module: Rubino::Boot::ConfigGuard

Defined in:
lib/rubino/boot/config_guard.rb

Overview

Loads configuration at process startup, turning a malformed/corrupt config.yml into a clean, actionable boot abort instead of a raw Ruby + Psych double backtrace (CFG-1).

The entrypoint (‘exe/rubino`) calls Config::Loader#load for EVERY command, before Thor dispatch. Any Config::ConfigError (or a Psych::SyntaxError that escapes the loader) used to propagate all the way out of `exe/rubino:16`, so a single typo in config.yml killed the process with a stack trace — even `rubino doctor`, whose graceful corruption handler (#259) was never reached because boot died first.

ConfigGuard.load! runs the load behind a rescue that writes a single-line diagnostic (what’s wrong + the config path + how to fix it) to $stderr and exits non-zero — boot abort, not exception, mirroring EncryptionKey.validate!. doctor’s own handling still works: doctor re-loads via the Loader and reports corruption itself, so a clean boot here does not mask it.

Class Method Summary collapse

Class Method Details

.load!(loader: Config::Loader.new, stderr: $stderr, argv: []) ⇒ Object

The Loader normalizes every malformed config shape into a Config::ConfigError at the source. The remaining classes here are a defensive backstop: should any raw Psych/IO failure ever slip past the loader (a new shape, a refactor), it still becomes a clean boot abort rather than a double backtrace on every command (CFG-R2).



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rubino/boot/config_guard.rb', line 28

def self.load!(loader: Config::Loader.new, stderr: $stderr, argv: [])
  loader.load
  # LOAD-time schema validation (F8): a HAND-EDITED config.yml with an
  # unknown key or a wrong-typed value used to load SILENTLY (the validator
  # only ran at `config set` time) and only blow up later. Surface those as
  # a clear, NON-FATAL warning here — the boot chokepoint every command
  # already passes through — so the user is told at startup instead of
  # discovering it as a runtime crash / provider 4xx. Never fatal: a
  # warning must not block a usable config, and a probe hiccup is ignored.
  # Pure-meta commands (version/help) never need a configured model, so the
  # config-issue warning is noise on `rubino --version`/`--help` — skip it.
  warn_config_issues(loader, stderr) unless meta_command?(argv)
  nil
rescue Config::ConfigError, Psych::Exception, SystemCallError, IOError => e
  stderr.puts "rubino: config error — #{e.message}"
  stderr.puts "rubino: fix #{loader.config_path}, restore a backup, or re-run 'rubino setup'."
  exit 1
end

.meta_command?(argv) ⇒ Boolean

‘–version`/`-v`/`version` and `–help`/`-h`/`help` are pure-meta: they print static text and exit, so a config-issue warning on them is pure noise. True when the invocation is one of those (the meta flag/word is the FIRST token, matching how Commands.start dispatches them).

Returns:

  • (Boolean)


51
52
53
54
# File 'lib/rubino/boot/config_guard.rb', line 51

def self.meta_command?(argv)
  first = Array(argv).first.to_s
  %w[--version -v version --help -h help].include?(first)
end

.warn_config_issues(loader, stderr) ⇒ Object

Emits a one-line-per-issue config WARNING to stderr (F8), or nothing when the config is clean. Best-effort — any failure here is swallowed so a validation hiccup can never break boot.



59
60
61
62
63
64
65
66
67
68
# File 'lib/rubino/boot/config_guard.rb', line 59

def self.warn_config_issues(loader, stderr)
  issues = Config::Validator.warnings(loader.raw_config)
  return if issues.empty?

  stderr.puts "rubino: warning: #{loader.config_path} has #{issues.size} " \
              "config issue#{"s" if issues.size != 1} (run `rubino doctor` for details):"
  issues.first(5).each { |msg| stderr.puts "rubino:   - #{msg}" }
rescue StandardError
  nil
end