disposita 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2fb4585036294fbe8e0879b28e7a2dcba238402e3719cdd4fabacf016af8f1ee
4
+ data.tar.gz: 6761b686729f6fb6042fb8e204c990bad8f7839197b2a9251bd2eb4b07b4c785
5
+ SHA512:
6
+ metadata.gz: 99b4d980ac2044614b82a938f44edb69f63128ffa4e0edb86b2c3179e7905941d8de0daa5631c57e36a3a0dee8b6a9af2655e1d550c997503e86c266bb17cff3
7
+ data.tar.gz: 3c6c118173f448fc1b830c584f90d5dab96a641c6bfd3ab1b6ad28364ff3eb6ae28b85875b5c87055989d5a6fbcf07b7d73ce97cfb1810f9696f48953bdb6103
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to Disposita will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows Semantic Versioning.
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+
11
+ - Load YARD correctly and keep Markdown out of Ruby source parsing; validate documentation in CI.
12
+ - Redact secret defaults in schema metadata and preserve caller-owned path arrays.
13
+
14
+ ### Added
15
+
16
+ - Complete namespace/helper documentation and regression coverage for source contracts, safe YAML and failed atomic writes.
17
+
18
+ ## [0.1.0] - 2026-09-04
19
+
20
+ ### Added
21
+
22
+ - Consumer-owned schema DSL with nested namespaces.
23
+ - Typed settings with conservative coercion and custom validation.
24
+ - Defaults, required/optional settings and strict unknown-setting detection.
25
+ - Boolean, enum and typed-array helpers for configuration-oriented types.
26
+ - Immutable resolved configuration with dot access and detached hash export.
27
+ - Explicit layered resolution and source provenance.
28
+ - Environment source with explicit names or generated prefixes.
29
+ - In-memory source for runtime overrides and tests.
30
+ - Safe YAML loading and serialization.
31
+ - Atomic YAML file persistence with explicit writable targets.
32
+ - Secret metadata, diagnostic redaction and opt-in secret file persistence.
33
+ - Cross-platform user configuration path helpers.
34
+ - Schema introspection and schema version checks.
35
+ - Structured Disposita error hierarchy.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rubcraft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,322 @@
1
+ # Disposita
2
+
3
+ Declarative, typed and layered configuration infrastructure for Ruby applications, gems and CLIs.
4
+
5
+ Disposita provides the mechanics of configuration while leaving ownership and meaning with the consumer. It does not know what `:ssh`, `production` or a timeout mean to your application; it only knows how those values are declared, loaded, coerced, validated, layered and persisted.
6
+
7
+ ## Why Disposita?
8
+
9
+ Configuration tends to grow into repeated infrastructure: parsers, defaults, environment variables, per-user paths, project overrides, validation, persistence and diagnostics. Disposita centralizes that infrastructure without making a toolkit or framework the owner of every consumer's configuration.
10
+
11
+ ## Installation
12
+
13
+ Add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem "disposita"
17
+ ```
18
+
19
+ Then run `bundle install`.
20
+
21
+ ## Define a schema
22
+
23
+ ```ruby
24
+ require "disposita"
25
+
26
+ SCMConfig = Disposita.define(:scm, version: 1) do
27
+ namespace :git do
28
+ setting :default_remote,
29
+ type: String,
30
+ default: "origin",
31
+ description: "Default Git remote"
32
+
33
+ setting :transport,
34
+ type: Disposita::Types.enum(:ssh, :https),
35
+ default: :ssh,
36
+ env: "SCM_GIT_TRANSPORT"
37
+
38
+ setting :timeout,
39
+ type: Integer,
40
+ default: 30
41
+
42
+ setting :token,
43
+ type: String,
44
+ secret: true,
45
+ optional: true
46
+ end
47
+ end
48
+ ```
49
+
50
+ `Disposita.define` returns a schema object. It does not register global mutable state and it performs no filesystem or environment reads by itself.
51
+
52
+ ### Setting options
53
+
54
+ Within `Disposita.define`, `namespace(name) { ... }` groups settings; namespaces can nest.
55
+ `setting(name, type:, ...) { |value| ... }` declares a leaf. Its optional validator runs after coercion and must return a truthy value.
56
+
57
+ | Option | Default | Meaning |
58
+ | --- | --- | --- |
59
+ | `type:` | Required | Ruby class, a built-in type helper, or an object implementing `valid?` and optionally `coerce`. Otherwise validation uses `===`. |
60
+ | `default:` | Absent | Value used when no source provides one. An explicit `nil` still counts as a default and must satisfy the declared type. |
61
+ | `required:` | `false` | Reject resolution if the setting is absent. Cannot be combined with a default or `optional: true`. |
62
+ | `optional:` | `false` | Explicitly documents that absence is allowed; settings are already optional unless required. |
63
+ | `env:` | `nil` | Explicit variable name, taking precedence over generated names. |
64
+ | `secret:` | `false` | Redact diagnostics and deny ordinary file persistence. |
65
+ | `description:` | `nil` | Consumer-facing text returned by schema introspection. |
66
+ | `coerce:` | `true` | Convert raw input before checking semantic validation. Set false for strict values. |
67
+
68
+ `Schema#load` accepts no arguments for defaults-only resolution. `Schema#resolve` requires an explicit source or array; use `resolve([])` for defaults alone.
69
+
70
+ ## Resolve layers
71
+
72
+ ```ruby
73
+ global = Disposita::Sources::File.new(
74
+ File.join(Disposita::Paths.user_config("scm"), "config.yml"),
75
+ name: :global
76
+ )
77
+
78
+ project = Disposita::Sources::File.new(
79
+ ".scm.yml",
80
+ name: :project
81
+ )
82
+
83
+ config = SCMConfig.load(
84
+ sources: [global, project],
85
+ env: ENV,
86
+ env_prefix: "SCM",
87
+ overrides: { git: { timeout: 10 } }
88
+ )
89
+ ```
90
+
91
+ Precedence is explicit and follows source order. Schema defaults are always the lowest layer; runtime overrides supplied to `load` are the highest layer.
92
+
93
+ ```text
94
+ defaults < global < project < environment < runtime
95
+ ```
96
+
97
+ ## Typed access
98
+
99
+ ```ruby
100
+ config.git.default_remote # => "origin"
101
+ config.git.transport # => :ssh
102
+ config.git.timeout # => 10
103
+ ```
104
+
105
+ Resolved configuration is immutable. `to_h` returns a detached copy for interoperability.
106
+
107
+ ## Coercion
108
+
109
+ Disposita performs conservative coercion when a setting allows it (the default):
110
+
111
+ ```text
112
+ "5432" -> Integer
113
+ "1.5" -> Float
114
+ "ssh" -> Symbol / enum value
115
+ "false" -> Boolean
116
+ ```
117
+
118
+ Use `coerce: false` to require an already-typed value.
119
+
120
+ ```ruby
121
+ setting :strict_port, type: Integer, coerce: false
122
+ ```
123
+
124
+ ## Validation
125
+
126
+ A setting can add consumer-owned semantic validation:
127
+
128
+ ```ruby
129
+ setting :timeout, type: Integer, default: 30 do |value|
130
+ value.positive?
131
+ end
132
+ ```
133
+
134
+ Disposita runs the rule; the consumer defines what the rule means.
135
+
136
+ ## Environment variables
137
+
138
+ A setting may name its environment variable explicitly:
139
+
140
+ ```ruby
141
+ setting :transport, type: Symbol, env: "SCM_GIT_TRANSPORT"
142
+ ```
143
+
144
+ Or a source may generate names from a prefix and setting path:
145
+
146
+ ```ruby
147
+ Disposita::Sources::Environment.new(prefix: "SCM")
148
+ # git.transport -> SCM_GIT_TRANSPORT
149
+ ```
150
+
151
+ ## Safe YAML
152
+
153
+ The bundled file source uses `Psych.safe_load` and disables Ruby-object deserialization and YAML aliases. Symbols are serialized as strings and coerced back according to the schema.
154
+
155
+ ```yaml
156
+ version: 1
157
+ git:
158
+ transport: ssh
159
+ ```
160
+
161
+ Disposita intentionally ships YAML only in 0.1.0. The format boundary is isolated so JSON or TOML can be added without changing schema ownership or resolution semantics.
162
+
163
+ ## Explicit writes
164
+
165
+ Reading may combine many layers. Writing always targets one explicit writable source.
166
+
167
+ ```ruby
168
+ project = Disposita::Sources::File.new(".scm.yml", name: :project)
169
+
170
+ SCMConfig.write(project, git: { transport: :https })
171
+ ```
172
+
173
+ Writes are validated and performed atomically through a temporary file followed by rename.
174
+
175
+ Defaults are not written automatically: consumers persist only the overrides they choose.
176
+
177
+ ## Secrets
178
+
179
+ A setting can be marked as sensitive:
180
+
181
+ ```ruby
182
+ setting :token, type: String, secret: true
183
+ ```
184
+
185
+ The actual value remains available to application code:
186
+
187
+ ```ruby
188
+ config.git.token
189
+ ```
190
+
191
+ But diagnostics redact it:
192
+
193
+ ```ruby
194
+ config.inspect
195
+ # => #<Disposita::Configuration git=#<... token=[REDACTED]>>
196
+ ```
197
+
198
+ Normal file sources reject secret persistence by default. A consumer must opt in explicitly:
199
+
200
+ ```ruby
201
+ private_store = Disposita::Sources::File.new(
202
+ "~/.config/scm/private.yml",
203
+ name: :private,
204
+ allow_secrets: true
205
+ )
206
+ ```
207
+
208
+ Secret-aware behavior is not encryption. Disposita 0.1.0 deliberately does not implement cryptographic storage, key management, Vault, KMS or OS keychains.
209
+
210
+ ## Provenance
211
+
212
+ Disposita tracks which layer supplied the winning value:
213
+
214
+ ```ruby
215
+ config.source_of("git.transport")
216
+ # => :project
217
+
218
+ config.explain("git.transport")
219
+ # => { path: "git.transport", value: :https, source: :project }
220
+ ```
221
+
222
+ Secret values are redacted from `explain`.
223
+
224
+ ## Paths
225
+
226
+ User-level configuration paths follow platform conventions:
227
+
228
+ - Linux: `$XDG_CONFIG_HOME/<app>` or `~/.config/<app>`
229
+ - macOS: `~/Library/Application Support/<app>`
230
+ - Windows: `%APPDATA%\<app>` (falling back to `%LOCALAPPDATA%`)
231
+
232
+ Project paths remain consumer-owned:
233
+
234
+ ```ruby
235
+ Disposita::Paths.project(Dir.pwd, ".rubcraft/scm.yml")
236
+ ```
237
+
238
+ Disposita never imposes `.rubcraft`, `.disposita`, or another project directory.
239
+
240
+ ## Schema introspection
241
+
242
+ ```ruby
243
+ SCMConfig.describe("git.transport")
244
+ # => {
245
+ # path: "git.transport",
246
+ # type: "enum(:ssh, :https)",
247
+ # default: :ssh,
248
+ # has_default: true,
249
+ # required: false,
250
+ # secret: false,
251
+ # env: "SCM_GIT_TRANSPORT",
252
+ # description: nil
253
+ # }
254
+ ```
255
+
256
+ This metadata is intended to support future CLI help, documentation and UI tooling without coupling Disposita to a particular CLI framework. Secret defaults appear as `[REDACTED]`, while `has_default` still indicates whether a default exists. Direct configuration access and `to_h` return actual values.
257
+
258
+ Prefer `describe` for diagnostic tooling. The lower-level `setting` and `settings` methods expose internal definition objects, including actual defaults; they are not safe logging representations.
259
+
260
+ ## Schema versions
261
+
262
+ Each schema has a version and file sources may persist it. Disposita rejects configuration produced by a newer schema version. Automatic migrations are intentionally deferred beyond 0.1.0 so the migration contract can be designed without freezing a premature API.
263
+
264
+ ## Ownership model
265
+
266
+ Disposita owns:
267
+
268
+ - schema infrastructure
269
+ - type checking and configuration-oriented coercion
270
+ - loading and persistence primitives
271
+ - layering and provenance
272
+ - conventional user paths
273
+ - validation mechanics
274
+ - secret-aware diagnostics
275
+
276
+ Consumers own:
277
+
278
+ - the schema itself
279
+ - project file locations
280
+ - layer policy and precedence
281
+ - what each setting means
282
+ - semantic validation rules
283
+ - whether and where secrets may be persisted
284
+
285
+ A library such as SCM can remain configuration-agnostic while `SCM CLI`, Rubcraft Toolkit, or another application defines its own Disposita schema around SCM.
286
+
287
+ ## Non-goals for 0.1.0
288
+
289
+ Disposita does not aim to be a general-purpose type system, secret manager, encryption framework, Rails settings singleton, command-line parser or business-rule engine.
290
+
291
+ ## Development
292
+
293
+ ```bash
294
+ bundle install
295
+ bundle exec rspec
296
+ bundle exec rubocop
297
+ bundle exec rake
298
+ COVERAGE=true bundle exec rspec
299
+ bundle exec rake yard
300
+ ```
301
+
302
+ The test suite is organized by public behavior and subsystem, with integration-style schema specs separated from source/format/path specs.
303
+
304
+ ## License
305
+
306
+ MIT.
307
+
308
+ ## API documentation
309
+
310
+ Disposita's public API is documented with YARD comments that explain not only
311
+ method signatures, but also ownership, precedence, persistence safety and common
312
+ usage patterns. Generate the local documentation with:
313
+
314
+ ```sh
315
+ bundle exec rake yard
316
+ ```
317
+
318
+ The generated site is written to `doc/`. CI generates it with warnings treated as failures. Internal implementation objects are
319
+ marked with `@api private`; applications should build against the documented
320
+ public objects such as `Disposita`, `Disposita::Schema`,
321
+ `Disposita::Configuration`, `Disposita::Source`, `Disposita::Sources::*`,
322
+ `Disposita::Types` and `Disposita::Paths`.
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Disposita
4
+ # Immutable, typed configuration produced after all sources are resolved.
5
+ #
6
+ # Configuration is the object an application normally consumes at runtime.
7
+ # Values can be accessed through dot notation or brackets, but mutation is
8
+ # intentionally unsupported. {#to_h} returns a detached copy when a mutable
9
+ # representation is needed for interoperability.
10
+ #
11
+ # Every selected value retains provenance, allowing diagnostic tooling to
12
+ # explain whether it came from a default, global file, project file, ENV or a
13
+ # runtime override. Settings marked +secret: true+ are redacted from
14
+ # diagnostic representations such as {#inspect} and {#explain}.
15
+ #
16
+ # @example
17
+ # config.server.port # => 3000
18
+ # config[:server][:port] # => 3000
19
+ # config.source_of("server.port") # => :default
20
+ #
21
+ # @see Disposita::Schema#resolve
22
+ class Configuration
23
+ # Read-only namespace node used to provide ergonomic nested access.
24
+ #
25
+ # Node deliberately wraps hashes instead of exposing them directly so the
26
+ # resolved configuration remains immutable and secret-aware diagnostics can
27
+ # be delegated back to the owning Configuration.
28
+ class Node
29
+ # @param data [Hash] subtree represented by this node.
30
+ # @param root [Disposita::Configuration] owning root configuration.
31
+ # @param path [Array<Symbol>] absolute path represented by this node.
32
+ def initialize(data, root:, path: [])
33
+ @data = data
34
+ @root = root
35
+ @path = path
36
+ end
37
+
38
+ # Reads a child setting or namespace by key.
39
+ #
40
+ # @param key [String, Symbol] immediate child name.
41
+ # @return [Object, Disposita::Configuration::Node] scalar value or nested
42
+ # read-only node.
43
+ # @raise [KeyError] when the child is not present in the resolved data.
44
+ def [](key)
45
+ value_for(key)
46
+ end
47
+
48
+ # Returns a detached mutable Hash representation of this subtree.
49
+ #
50
+ # Changing the returned object never mutates the resolved configuration.
51
+ # This method returns actual secret values and should therefore not be
52
+ # treated as a safe logging representation.
53
+ #
54
+ # @return [Hash]
55
+ def to_h
56
+ Internal::HashTools.deep_dup(@data)
57
+ end
58
+
59
+ # Returns a secret-aware diagnostic representation.
60
+ #
61
+ # @return [String] representation with secret settings replaced by
62
+ # +[REDACTED]+.
63
+ def inspect
64
+ @root.__send__(:inspect_node, @data, @path)
65
+ end
66
+
67
+ # Resolves dot access to a setting or namespace; unknown calls use Ruby lookup.
68
+ # @param name [Symbol] requested setting name.
69
+ # @param arguments [Array<Object>] arguments supplied by the caller.
70
+ # @return [Object] resolved setting or namespace.
71
+ # @raise [NoMethodError] for unknown names or calls with arguments.
72
+ def method_missing(name, *arguments)
73
+ return super unless arguments.empty? && @data.key?(name)
74
+
75
+ value_for(name)
76
+ end
77
+
78
+ def respond_to_missing?(name, include_private = false)
79
+ @data.key?(name) || super
80
+ end
81
+
82
+ private
83
+
84
+ def value_for(key)
85
+ key = key.to_sym
86
+ raise KeyError, "unknown configuration key: #{(@path + [key]).join('.')}" unless @data.key?(key)
87
+
88
+ value = @data[key]
89
+ value.is_a?(Hash) ? self.class.new(value, root: @root, path: @path + [key]) : value
90
+ end
91
+ end
92
+
93
+ # @return [Disposita::Schema] schema that produced this configuration.
94
+ attr_reader :schema
95
+
96
+ # Builds an immutable resolved configuration.
97
+ #
98
+ # Applications normally receive instances from {Disposita::Schema#resolve}
99
+ # or {Disposita::Schema#load} rather than constructing them directly.
100
+ #
101
+ # @param schema [Disposita::Schema] owning schema.
102
+ # @param data [Hash] fully coerced and validated values.
103
+ # @param provenance [Hash{Array<Symbol> => Symbol}] selected source for each
104
+ # resolved leaf setting.
105
+ def initialize(schema:, data:, provenance:)
106
+ @schema = schema
107
+ @data = Internal::HashTools.deep_freeze(data)
108
+ @provenance = provenance.freeze
109
+ @root = Node.new(@data, root: self)
110
+ freeze
111
+ end
112
+
113
+ # Reads a top-level setting or namespace.
114
+ #
115
+ # @param key [String, Symbol] top-level key.
116
+ # @return [Object, Disposita::Configuration::Node]
117
+ # @raise [KeyError] when the key is absent.
118
+ def [](key) = @root[key]
119
+
120
+ # Reports which source supplied the effective value for a setting.
121
+ #
122
+ # @param path [String, Array<String, Symbol>, Symbol] setting path.
123
+ # @return [Symbol, nil] source name, +:default+ for schema defaults, or +nil+
124
+ # when no provenance entry exists.
125
+ # @example
126
+ # config.source_of("git.transport") # => :project
127
+ def source_of(path)
128
+ @provenance[normalize_path(path)]
129
+ end
130
+
131
+ # Explains one effective setting without exposing secret values.
132
+ #
133
+ # This small stable structure is intended for diagnostics and CLI commands
134
+ # such as +config explain+. A secret setting reports +[REDACTED]+ even though
135
+ # direct application access still returns its real value.
136
+ #
137
+ # @param path [String, Array<String, Symbol>, Symbol] setting path.
138
+ # @return [Hash] frozen Hash containing +:path+, +:value+ and +:source+.
139
+ # @raise [KeyError] if the path is not declared by the schema.
140
+ def explain(path)
141
+ normalized = normalize_path(path)
142
+ setting = schema.setting(normalized)
143
+ raise KeyError, "unknown configuration key: #{normalized.join('.')}" unless setting
144
+
145
+ {
146
+ path: normalized.join("."),
147
+ value: setting.secret? ? "[REDACTED]" : Internal::HashTools.get(@data, normalized),
148
+ source: @provenance[normalized]
149
+ }.freeze
150
+ end
151
+
152
+ # Returns a detached mutable Hash containing resolved values.
153
+ #
154
+ # Unlike {#inspect}, this is a data export API and therefore includes actual
155
+ # secret values. Callers are responsible for avoiding accidental logging or
156
+ # persistence of the returned Hash.
157
+ #
158
+ # @return [Hash]
159
+ def to_h
160
+ Internal::HashTools.deep_dup(@data)
161
+ end
162
+
163
+ # Returns a secret-aware diagnostic representation of the configuration.
164
+ #
165
+ # @return [String]
166
+ def inspect = inspect_node(@data, [])
167
+
168
+ # Resolves dot access to a setting or namespace; unknown calls use Ruby lookup.
169
+ # @param name [Symbol] requested setting name.
170
+ # @param arguments [Array<Object>] arguments supplied by the caller.
171
+ # @return [Object] resolved setting or namespace.
172
+ # @raise [NoMethodError] for unknown names or calls with arguments.
173
+ def method_missing(name, *arguments)
174
+ return super unless arguments.empty? && @root.respond_to?(name)
175
+
176
+ @root.public_send(name)
177
+ end
178
+
179
+ def respond_to_missing?(name, include_private = false)
180
+ @root.respond_to?(name) || super
181
+ end
182
+
183
+ private
184
+
185
+ def normalize_path(path)
186
+ case path
187
+ when String then path.split(".").map!(&:to_sym)
188
+ when Array then path.map(&:to_sym)
189
+ else Array(path).map!(&:to_sym)
190
+ end
191
+ end
192
+
193
+ def inspect_node(data, prefix)
194
+ body = data.map do |key, value|
195
+ path = prefix + [key]
196
+ setting = schema.setting(path)
197
+ rendered = if setting&.secret?
198
+ "[REDACTED]"
199
+ elsif value.is_a?(Hash)
200
+ inspect_node(value, path)
201
+ else
202
+ value.inspect
203
+ end
204
+ "#{key}=#{rendered}"
205
+ end.join(" ")
206
+ "#<#{self.class.name} #{body}>"
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Disposita
4
+ # Base class for errors raised by Disposita.
5
+ #
6
+ # Consumers may rescue this class when they want one boundary around all
7
+ # configuration failures, or rescue a narrower subclass to render specific
8
+ # diagnostics in a CLI or application.
9
+ class Error < StandardError; end
10
+
11
+ # Raised when the consumer declares an invalid schema.
12
+ class SchemaError < Error; end
13
+
14
+ # Base class for failures discovered while validating configuration values.
15
+ class ValidationError < Error; end
16
+
17
+ # Raised when a raw value cannot be converted to its declared schema type.
18
+ class CoercionError < ValidationError; end
19
+
20
+ # Raised when a required setting remains absent after all layers are resolved.
21
+ class MissingSettingError < ValidationError; end
22
+
23
+ # Raised when a source contains a setting not declared by the schema.
24
+ class UnknownSettingError < ValidationError; end
25
+
26
+ # Raised when serialized configuration cannot be decoded safely.
27
+ class ParseError < Error; end
28
+
29
+ # Raised when a source cannot provide its configuration data.
30
+ class LoadError < Error; end
31
+
32
+ # Raised when configuration cannot be persisted to the requested source.
33
+ class SaveError < Error; end
34
+
35
+ # Raised when persisted configuration is newer than the runtime schema.
36
+ class VersionError < Error; end
37
+
38
+ # Raised when a conventional or project path cannot be resolved safely.
39
+ class PathError < Error; end
40
+
41
+ # Raised when secret data would be written to a source that forbids secrets.
42
+ #
43
+ # This protects against accidental persistence only. It does not imply that a
44
+ # source allowing secrets provides encryption or a secret-management system.
45
+ class UnsafeSecretPersistenceError < SaveError; end
46
+ end