secretspec 0.17.0-x64-mingw-ucrt

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: 1d1d9e1742ef9b9e710391ac60972a3986f01a4b3a9b36e795cb0d4ee81dcc7b
4
+ data.tar.gz: 0f0669f542ca1fe1e9d1b6207e4f876f3308cf5e3cf704e1160986da81f0bcaf
5
+ SHA512:
6
+ metadata.gz: 503e164dfafed57750aa6432e5ef7fc30e14c2a6e2be71f7bae12ee2871b657bd54ee0a01e2b338ada1a6fb472b4920958eae1ec323afbbf78fae6367894dbc1
7
+ data.tar.gz: 1a56a09c37f6f7dfa82b7aa7c7245faca95894295da55ac4977fbfc2cb7b88710c6e28a15c222f3ebf9936cac45329a5d172d305e4a2031c88fc30219ef7dc57
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # secretspec (Ruby SDK)
2
+
3
+ Ruby bindings for [SecretSpec](https://secretspec.dev/), a declarative secrets
4
+ manager. A thin client over the `secretspec-ffi` C ABI, statically linked into a
5
+ native C extension at build time (no runtime library to locate). Resolution
6
+ happens in the Rust core, so the SDK inherits every provider with no Ruby-side
7
+ logic.
8
+
9
+ ```ruby
10
+ require "secretspec"
11
+
12
+ resolved = Secretspec::SecretSpec.builder
13
+ .with_provider("keyring://")
14
+ .with_profile("production")
15
+ .with_reason("boot web app")
16
+ .load
17
+
18
+ puts resolved.provider, resolved.profile
19
+ db = resolved.secrets["DATABASE_URL"]
20
+ puts db.get # the value, or the file path for as_path secrets
21
+ resolved.set_as_env! # export everything into ENV
22
+ ```
23
+
24
+ A missing required secret raises `Secretspec::MissingRequiredError`; any other
25
+ failure raises `Secretspec::Error` (with a stable `#kind`).
26
+
27
+ ## Scopes (0.17+)
28
+
29
+ Use `.with_scope("api")` to resolve only a named `[scopes.api]` subset. Both
30
+ `resolved.scope` and `report.scope` return the selected scope:
31
+
32
+ ```ruby
33
+ resolved = Secretspec::SecretSpec.builder.with_scope("api").load
34
+ ```
35
+
36
+ ## Cleanup
37
+
38
+ `as_path` secrets are materialized to temp files that outlive the call. Pass a
39
+ block to `load` (which closes automatically) or call `resolved.close` when done
40
+ so the secret files do not accumulate in the temp dir.
41
+
42
+ ## Value-free report
43
+
44
+ `report` returns the inventory/preflight view: per-secret status and provenance,
45
+ never a value. Unlike `load`, it does not raise when a required secret is missing
46
+ — it appears as a `SecretReport` with status `"missing_required"`.
47
+
48
+ ```ruby
49
+ report = Secretspec::SecretSpec.builder.with_profile("production").report
50
+ report.secrets.each { |s| puts [s.name, s.status, s.required].join(" ") }
51
+ ```
52
+
53
+ ## Library discovery
54
+
55
+ The native library is found via the `SECRETSPEC_FFI_LIB` environment variable,
56
+ or a Cargo `target` directory found by searching up from the working directory.
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Builds the secretspec native extension, statically linking the secretspec-ffi
4
+ # archive (libsecretspec_ffi.a) into the extension object. A Rust staticlib does
5
+ # not carry its own native dependency closure, so the archive's transitive system
6
+ # libs (captured from `rustc --print native-static-libs`, never hardcoded) are
7
+ # appended to the link line after it.
8
+
9
+ require "mkmf"
10
+
11
+ ext_dir = __dir__
12
+ pkg_dir = File.expand_path("../..", ext_dir) # secretspec-rb
13
+ repo_root = File.expand_path("..", pkg_dir) # workspace root (dev checkout)
14
+ vendor = File.join(pkg_dir, "vendor")
15
+
16
+ # The staticlib: explicit contract, the bundled platform-gem copy, or a Cargo
17
+ # target dir (dev checkout, newest of release/debug).
18
+ def find_staticlib(vendor, repo_root)
19
+ env = ENV["SECRETSPEC_FFI_STATICLIB"]
20
+ return env if env && !env.empty? && File.exist?(env)
21
+
22
+ bundled = File.join(vendor, "libsecretspec_ffi.a")
23
+ return bundled if File.exist?(bundled)
24
+
25
+ %w[release debug]
26
+ .map { |p| File.join(repo_root, "target", p, "libsecretspec_ffi.a") }
27
+ .select { |c| File.exist?(c) }
28
+ .max_by { |c| File.mtime(c) }
29
+ end
30
+
31
+ # The archive's transitive native deps: explicit contract, the bundled manifest,
32
+ # or captured live from rustc (dev checkout).
33
+ def find_native_libs(vendor, repo_root)
34
+ env = ENV["SECRETSPEC_FFI_NATIVE_LIBS"]
35
+ return env if env && !env.empty?
36
+
37
+ manifest = File.join(vendor, "native-static-libs.txt")
38
+ return File.read(manifest).strip if File.exist?(manifest)
39
+
40
+ note = `cd #{repo_root} && cargo rustc -q -p secretspec-ffi --crate-type staticlib -- --print native-static-libs 2>&1`
41
+ note[/native-static-libs:\s*(.*)/, 1].to_s.strip
42
+ end
43
+
44
+ staticlib = find_staticlib(vendor, repo_root)
45
+ abort("secretspec: could not locate libsecretspec_ffi.a; set SECRETSPEC_FFI_STATICLIB") unless staticlib
46
+
47
+ # Header: the bundled vendor copy (platform gem) or the ffi crate's include dir.
48
+ include_dir =
49
+ if File.exist?(File.join(vendor, "secretspec.h"))
50
+ vendor
51
+ else
52
+ File.join(repo_root, "secretspec-ffi", "include")
53
+ end
54
+
55
+ $INCFLAGS << " -I#{include_dir}"
56
+ # $LOCAL_LIBS is emitted before $libs on the link line, so the archive (pulled
57
+ # for the referenced symbols) precedes the system libs it depends on.
58
+ $LOCAL_LIBS << " #{staticlib}"
59
+ $libs = "#{$libs} #{find_native_libs(vendor, repo_root)}"
60
+ # The Windows gem bundles MinGW import libraries next to the staticlib
61
+ # (libwindows.*.a / libwinapi_*.a ship inside cargo registry crates, so an
62
+ # installing machine has them nowhere else); let the linker search vendor/.
63
+ $LIBPATH << vendor if File.directory?(vendor)
64
+
65
+ create_makefile("secretspec/secretspec_ext")
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Native glue for the secretspec Ruby SDK.
3
+ *
4
+ * A thin C extension that statically links the secretspec-ffi archive
5
+ * (libsecretspec_ffi.a) and exposes its three C ABI functions to Ruby as
6
+ * Secretspec::Native.c_resolve / c_abi_version. The Rust resolver is embedded in
7
+ * this extension object, so there is no separate cdylib to ship or dlopen.
8
+ */
9
+ #include <ruby.h>
10
+ #include <ruby/thread.h>
11
+ #include <stdlib.h>
12
+ #include "secretspec.h"
13
+
14
+ static void *
15
+ resolve_nogvl(void *arg)
16
+ {
17
+ return secretspec_resolve((const char *)arg);
18
+ }
19
+
20
+ /*
21
+ * Secretspec::Native.c_resolve(request_json) -> String or nil
22
+ *
23
+ * Marshals the JSON request to the Rust resolver and copies the owned response
24
+ * into a Ruby String before freeing it. Returns nil if the resolver returns NULL
25
+ * (catastrophic allocation failure); the Ruby wrapper turns that into an Error.
26
+ *
27
+ * The resolver may block on network-backed providers (1Password, LastPass,
28
+ * Vault), so it runs with the GVL released — otherwise the round-trip would
29
+ * freeze every other Ruby thread. The request bytes are copied into a C-owned
30
+ * buffer first: the Ruby string may move once the GVL is released.
31
+ */
32
+ static VALUE
33
+ native_resolve(VALUE self, VALUE request_json)
34
+ {
35
+ char *request = strdup(StringValueCStr(request_json));
36
+ if (request == NULL) {
37
+ return Qnil;
38
+ }
39
+ char *result = rb_thread_call_without_gvl(
40
+ resolve_nogvl, request, RUBY_UBF_IO, NULL);
41
+ free(request);
42
+ if (result == NULL) {
43
+ return Qnil;
44
+ }
45
+ VALUE out = rb_str_new_cstr(result);
46
+ secretspec_free(result);
47
+ return out;
48
+ }
49
+
50
+ /* Secretspec::Native.c_abi_version -> String (static, not freed). */
51
+ static VALUE
52
+ native_abi_version(VALUE self)
53
+ {
54
+ return rb_str_new_cstr(secretspec_abi_version());
55
+ }
56
+
57
+ void
58
+ Init_secretspec_ext(void)
59
+ {
60
+ VALUE mod = rb_define_module("Secretspec");
61
+ VALUE native = rb_define_module_under(mod, "Native");
62
+ rb_define_singleton_method(native, "c_resolve", native_resolve, 1);
63
+ rb_define_singleton_method(native, "c_abi_version", native_abi_version, 0);
64
+ }
data/lib/secretspec.rb ADDED
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby SDK for SecretSpec, a declarative secrets manager.
4
+ #
5
+ # A thin client over the secretspec-ffi C ABI. The Rust resolver is statically
6
+ # linked into a native extension (secretspec_ext), so the SDK inherits every
7
+ # provider with no Ruby-side logic and there is nothing to locate at runtime.
8
+ # Mirrors the Rust derive crate's vocabulary.
9
+
10
+ require "json"
11
+
12
+ # The compiled extension lives next to this file in a source/dev checkout, but in
13
+ # an installed gem RubyGems places it in a separate extensions dir already on
14
+ # $LOAD_PATH. Put this file's dir on the path so the absolute require resolves in
15
+ # both layouts.
16
+ $LOAD_PATH.unshift(__dir__) unless $LOAD_PATH.include?(__dir__)
17
+ require "secretspec/secretspec_ext"
18
+
19
+ module Secretspec
20
+ # Response wire-format version this SDK understands. Tracks secretspec-ffi's
21
+ # RESOLVE_SCHEMA_VERSION; a mismatch means the loaded library is incompatible.
22
+ RESOLVE_SCHEMA_VERSION = 2
23
+
24
+ # Wire-format version of the value-free report. Tracks secretspec's
25
+ # RESOLUTION_REPORT_SCHEMA_VERSION.
26
+ REPORT_SCHEMA_VERSION = 1
27
+
28
+ # A resolution failure (bad manifest, provider error, reason policy).
29
+ class Error < StandardError
30
+ attr_reader :kind
31
+
32
+ def initialize(kind, message)
33
+ @kind = kind
34
+ super("#{message} (kind: #{kind})")
35
+ end
36
+ end
37
+
38
+ # One or more required secrets were not found anywhere.
39
+ class MissingRequiredError < Error
40
+ attr_reader :missing
41
+
42
+ def initialize(missing)
43
+ @missing = missing
44
+ super("missing_required", "missing required secret(s): #{missing.join(', ')}")
45
+ end
46
+ end
47
+
48
+ # One resolved secret. Exactly one of +value+ / +path+ is set.
49
+ ResolvedSecret = Struct.new(:value, :path, :as_path, :source, :source_provider) do
50
+ # The usable string: the file path for as_path secrets, else the value.
51
+ def get
52
+ as_path ? path : value
53
+ end
54
+ end
55
+
56
+ # A successful resolution, mirroring the Rust Resolved wrapper.
57
+ Resolved = Struct.new(:provider, :profile, :secrets, :missing_optional, :scope) do
58
+ # Export each resolved secret into ENV by its declared name. Secrets with no
59
+ # usable value (e.g. under no_values) are skipped rather than deleted from
60
+ # ENV (assigning nil would remove the variable).
61
+ def set_as_env!
62
+ secrets.each do |name, secret|
63
+ value = secret.get
64
+ ENV[name] = value unless value.nil?
65
+ end
66
+ end
67
+
68
+ # Flat { "SECRET_NAME" => value } hash (the file path for as_path). A secret
69
+ # with no usable value (e.g. under no_values) maps to nil, matching the null
70
+ # the other SDKs emit. Feed this to a quicktype-generated deserializer (e.g.
71
+ # from_dynamic!). See `secretspec schema`.
72
+ def fields
73
+ secrets.transform_values(&:get)
74
+ end
75
+
76
+ # Remove the temp files backing any as_path secrets in this result. The
77
+ # resolver persists those files (mode 0400) so their paths stay valid after
78
+ # resolve returns; the caller owns their lifetime. Call #close (or pass a
79
+ # block to Builder#load, which closes automatically) when done so secret
80
+ # files do not accumulate in the temp dir. A file already gone is not an
81
+ # error.
82
+ def close
83
+ secrets.each_value do |secret|
84
+ next unless secret.as_path && secret.path
85
+
86
+ File.delete(secret.path) if File.exist?(secret.path)
87
+ end
88
+ nil
89
+ end
90
+ end
91
+
92
+ # Value-free resolution outcome for one declared secret: how it would resolve
93
+ # and from where, never the value itself.
94
+ SecretReport = Struct.new(:name, :status, :required, :source_provider,
95
+ :default_applied, :generated, :as_path)
96
+
97
+ # A value-free resolution snapshot. Unlike Resolved, a missing required secret
98
+ # is a "missing_required" status here, not an error, so a report describes a
99
+ # profile even when its secrets are not all available.
100
+ Report = Struct.new(:provider, :profile, :secrets, :scope)
101
+
102
+ # The narrow C ABI, statically linked into the secretspec_ext extension. The
103
+ # Native.c_resolve / c_abi_version C functions are defined in
104
+ # ext/secretspec/secretspec_ext.c; these wrappers add the Ruby-side error type.
105
+ module Native
106
+ class << self
107
+ def resolve(request_json)
108
+ result = c_resolve(request_json)
109
+ raise Error.new("ffi", "secretspec_resolve returned null") if result.nil?
110
+
111
+ result
112
+ end
113
+
114
+ def abi_version
115
+ c_abi_version
116
+ end
117
+ end
118
+ end
119
+
120
+ # Entry point mirroring the derive crate's SecretSpec::builder().
121
+ class SecretSpec
122
+ def self.builder
123
+ Builder.new
124
+ end
125
+ end
126
+
127
+ # Fluent builder for a resolution.
128
+ class Builder
129
+ def initialize
130
+ @request = {}
131
+ end
132
+
133
+ def with_path(path)
134
+ @request["path"] = path if path
135
+ self
136
+ end
137
+
138
+ def with_provider(provider)
139
+ @request["provider"] = provider if provider
140
+ self
141
+ end
142
+
143
+ def with_profile(profile)
144
+ @request["profile"] = profile if profile
145
+ self
146
+ end
147
+
148
+ # Limit resolution to a named manifest scope (SecretSpec 0.17+).
149
+ def with_scope(scope)
150
+ @request["scope"] = scope if scope
151
+ self
152
+ end
153
+
154
+ def with_reason(reason)
155
+ @request["reason"] = reason if reason
156
+ self
157
+ end
158
+
159
+ # Omit secret values, returning only structure and provenance.
160
+ def with_no_values(no_values = true)
161
+ @request["no_values"] = no_values
162
+ self
163
+ end
164
+
165
+ # Resolve the secrets. Raises MissingRequiredError if a required secret is
166
+ # missing, and Error for any other failure.
167
+ #
168
+ # Without a block, returns the Resolved (the caller should #close it when
169
+ # done to clean up any as_path temp files). With a block, yields the Resolved
170
+ # and closes it afterwards, returning the block's value.
171
+ def load
172
+ response = parse_response(JSON.generate(@request), "resolve", RESOLVE_SCHEMA_VERSION)
173
+
174
+ missing = response["missing_required"] || []
175
+ raise MissingRequiredError.new(missing) unless missing.empty?
176
+
177
+ secrets = {}
178
+ (response["secrets"] || {}).each do |name, entry|
179
+ secrets[name] = ResolvedSecret.new(
180
+ entry["value"], entry["path"], entry["as_path"] || false,
181
+ entry["source"], entry["source_provider"]
182
+ )
183
+ end
184
+
185
+ resolved = Resolved.new(
186
+ response["provider"], response["profile"], secrets,
187
+ response["missing_optional"] || [], response["scope"]
188
+ )
189
+ return resolved unless block_given?
190
+
191
+ begin
192
+ yield resolved
193
+ ensure
194
+ resolved.close
195
+ end
196
+ end
197
+
198
+ # Resolve a value-free Report (the inventory/preflight view, the same one the
199
+ # CLI exposes as `check --json`). Unlike #load, never raises
200
+ # MissingRequiredError: a missing required secret appears as a SecretReport
201
+ # with status "missing_required".
202
+ def report
203
+ request = @request.merge("mode" => "report")
204
+ response = parse_response(JSON.generate(request), "report", REPORT_SCHEMA_VERSION)
205
+
206
+ secrets = (response["secrets"] || []).map do |s|
207
+ SecretReport.new(s["name"], s["status"], s["required"],
208
+ s["source_provider"], s["default_applied"],
209
+ s["generated"], s["as_path"])
210
+ end
211
+ Report.new(response["provider"], response["profile"], secrets, response["scope"])
212
+ end
213
+
214
+ private
215
+
216
+ # Resolve a JSON request payload and return the validated "response" hash, or
217
+ # raise. +kind+ is "resolve" or "report"; it selects the schema version to
218
+ # enforce and labels the version-mismatch message.
219
+ def parse_response(payload, kind, expected_version)
220
+ envelope = JSON.parse(Native.resolve(payload))
221
+
222
+ unless envelope["ok"]
223
+ err = envelope["error"] || {}
224
+ raise Error.new(err["kind"] || "unknown", err["message"] || "")
225
+ end
226
+
227
+ response = envelope["response"]
228
+ raise Error.new("ffi", "secretspec_resolve reported ok with no response") if response.nil?
229
+
230
+ version = response["schema_version"]
231
+ unless version == expected_version
232
+ raise Error.new("version",
233
+ "unsupported #{kind} schema version #{version} " \
234
+ "(expected #{expected_version}); the secretspec-ffi " \
235
+ "library and this SDK are out of sync")
236
+ end
237
+
238
+ response
239
+ end
240
+ end
241
+
242
+ def self.abi_version
243
+ Native.abi_version
244
+ end
245
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ -lsecur32 -ladvapi32 -lkernel32 -lwinapi_advapi32 -lwinapi_cfgmgr32 -lwinapi_gdi32 -lwinapi_kernel32 -lwinapi_msimg32 -lwinapi_opengl32 -lwinapi_synchronization -lwinapi_user32 -lwinapi_winspool -lwindows.0.53.0 -lwindows.0.52.0 -lbcrypt -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -ldbghelp
@@ -0,0 +1,76 @@
1
+ /*
2
+ * SecretSpec C ABI.
3
+ *
4
+ * A deliberately narrow, JSON-in / JSON-out boundary. The entire native surface
5
+ * is the three functions below; all richness lives in the versioned JSON
6
+ * contract so language bindings stay thin.
7
+ *
8
+ * Request JSON (all fields optional):
9
+ * { "path": ".../secretspec.toml", "provider": "keyring://",
10
+ * "profile": "production", "scope": "api", "reason": "boot",
11
+ * "no_values": false, "mode": "resolve" }
12
+ *
13
+ * "scope" selects a named [scopes] subset of the active profile (0.17+).
14
+ *
15
+ * "mode" selects the response shape and defaults to "resolve":
16
+ *
17
+ * "resolve" the value-carrying resolve response. Set "no_values" to strip
18
+ * the values from it.
19
+ * "report" the value-free resolution report: the inventory/preflight view
20
+ * the CLI exposes as `check --json`.
21
+ *
22
+ * Any other value is rejected with an "invalid_request" error.
23
+ *
24
+ * "no_values" is NOT the same as "mode": "report". A "no_values" resolve blanks
25
+ * the values but keeps the resolve shape: its "secrets" is an object keyed by
26
+ * name, it never says whether a secret is *declared* required, and when a
27
+ * required secret is missing that object is empty. A report's "secrets" is an
28
+ * ARRAY of per-secret entries carrying "name", "required", "status"
29
+ * ("resolved" / "missing_required" / "missing_optional") and provenance, and
30
+ * lists every declared secret whether or not it resolved. "required" is
31
+ * reachable only via "report".
32
+ *
33
+ * Response envelope:
34
+ * { "ok": true, "response": { ...resolve response | resolution report... } }
35
+ * { "ok": false, "error": { "kind": "io", "message": "..." } }
36
+ *
37
+ * "ok": false means the call itself failed (bad manifest, provider error,
38
+ * unknown "mode"); a missing required secret is a domain result reported inside
39
+ * an "ok": true response.
40
+ *
41
+ * A resolve response carries secret values unless "no_values" was set; a report
42
+ * never does. Treat returned strings as sensitive and free them promptly.
43
+ */
44
+ #ifndef SECRETSPEC_H
45
+ #define SECRETSPEC_H
46
+
47
+ #ifdef __cplusplus
48
+ extern "C" {
49
+ #endif
50
+
51
+ /*
52
+ * Resolve secrets described by `request_json` (a NUL-terminated UTF-8 JSON
53
+ * string). Returns a newly allocated, NUL-terminated JSON response envelope
54
+ * that the caller OWNS and must release with secretspec_free().
55
+ *
56
+ * Returns NULL only on catastrophic allocation failure.
57
+ */
58
+ char *secretspec_resolve(const char *request_json);
59
+
60
+ /*
61
+ * Free a string previously returned by secretspec_resolve(). NULL is ignored.
62
+ * Must not be called twice on the same pointer.
63
+ */
64
+ void secretspec_free(char *ptr);
65
+
66
+ /*
67
+ * Return the ABI version as a static NUL-terminated string. Do NOT free; the
68
+ * pointer is valid for the lifetime of the loaded library.
69
+ */
70
+ const char *secretspec_abi_version(void);
71
+
72
+ #ifdef __cplusplus
73
+ } /* extern "C" */
74
+ #endif
75
+
76
+ #endif /* SECRETSPEC_H */
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secretspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.17.0
5
+ platform: x64-mingw-ucrt
6
+ authors:
7
+ - Cachix
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'Ruby bindings for SecretSpec: a native extension that statically links
14
+ the secretspec-ffi C ABI.'
15
+ email:
16
+ executables: []
17
+ extensions:
18
+ - ext/secretspec/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - ext/secretspec/extconf.rb
23
+ - ext/secretspec/secretspec_ext.c
24
+ - lib/secretspec.rb
25
+ - vendor/libsecretspec_ffi.a
26
+ - vendor/libwinapi_advapi32.a
27
+ - vendor/libwinapi_cfgmgr32.a
28
+ - vendor/libwinapi_gdi32.a
29
+ - vendor/libwinapi_kernel32.a
30
+ - vendor/libwinapi_msimg32.a
31
+ - vendor/libwinapi_opengl32.a
32
+ - vendor/libwinapi_synchronization.a
33
+ - vendor/libwinapi_user32.a
34
+ - vendor/libwinapi_winspool.a
35
+ - vendor/libwindows.0.52.0.a
36
+ - vendor/libwindows.0.53.0.a
37
+ - vendor/native-static-libs.txt
38
+ - vendor/secretspec.h
39
+ homepage: https://secretspec.dev/
40
+ licenses:
41
+ - Apache-2.0
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.5.22
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Declarative secrets, every environment, any provider (Ruby SDK)
62
+ test_files: []