gitlab-glaz 1.2.0 → 2.0.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 +4 -4
- data/Cargo.lock +678 -30
- data/Cargo.toml +4 -4
- data/README.md +25 -1
- data/ext/glaz/Cargo.toml +1 -0
- data/ext/glaz/src/lib.rs +227 -49
- data/lib/gitlab/glaz/govern_policy_engine.rb +279 -105
- data/lib/gitlab/glaz/version.rb +1 -1
- data/lib/gitlab/glaz.rb +5 -5
- data/lib/proto/govern_pb.rb +10 -3
- metadata +3 -3
data/Cargo.toml
CHANGED
|
@@ -4,7 +4,7 @@ members = ["ext/*"]
|
|
|
4
4
|
resolver = "2"
|
|
5
5
|
|
|
6
6
|
[workspace.package]
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
edition = "2024"
|
|
9
9
|
|
|
10
10
|
# Shared dependencies for all crates
|
|
@@ -12,6 +12,6 @@ edition = "2024"
|
|
|
12
12
|
magnus = { version = "0.8.2", features = ["rb-sys"] }
|
|
13
13
|
rb-sys = "0.9.128"
|
|
14
14
|
prost = "0.14"
|
|
15
|
-
glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "
|
|
16
|
-
glaz-roles = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "
|
|
17
|
-
glaz-proto = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "
|
|
15
|
+
glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "0c90ffcd6e6f74b7a1998e196e7d324ad7d54b56" }
|
|
16
|
+
glaz-roles = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "0c90ffcd6e6f74b7a1998e196e7d324ad7d54b56" }
|
|
17
|
+
glaz-proto = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "0c90ffcd6e6f74b7a1998e196e7d324ad7d54b56" }
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> [!WARNING]
|
|
4
4
|
> This gem is currently designed entirely for internal use at GitLab.
|
|
5
5
|
|
|
6
|
-
Ruby bindings for the [Glaz](https://gitlab.com/gitlab-org/auth/glaz) authorization
|
|
6
|
+
Ruby bindings for the [Glaz](https://gitlab.com/gitlab-org/auth/glaz) authorization and governance engines. A thin [Magnus](https://github.com/matsadler/magnus) FFI extension exposes the Rust `glaz-module` engines to Ruby: Cedar permission checks run in-process, and Rego governance policies are fetched from the Policy Store and evaluated in-process.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -37,6 +37,30 @@ The schema is loaded once at construction and cannot change; build a new engine
|
|
|
37
37
|
|
|
38
38
|
Schemas must declare `User` and `Resource` entities (checks build `User::"<subject_uuid>"` and `Resource::"<object_uuid>"` UIDs), and every action's context type must include `permissions: Set<Action>`, which the engine injects with the checked action.
|
|
39
39
|
|
|
40
|
+
### Governance policies
|
|
41
|
+
|
|
42
|
+
`Gitlab::Glaz::GovernPolicyEngine` evaluates the Rego governance policies that apply to an event trigger. The engine fetches them itself from the Policy Store REST API, so name the store first:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Gitlab::Glaz::GovernPolicyEngine.policy_store_url = 'https://gitlab.com/api/v4'
|
|
46
|
+
# or export GLAZ_POLICY_STORE_URL; a bare http(s) URL, no credentials
|
|
47
|
+
|
|
48
|
+
result = Gitlab::Glaz.govern_policy_engine.evaluate(
|
|
49
|
+
trigger: 'deployment_requested',
|
|
50
|
+
context: { organization: { id: 'organizations/1' }, environment: { tier: 'production' }, approvals: [] },
|
|
51
|
+
principal: 'users/42', # who the decision is about
|
|
52
|
+
resource: 'organizations/1/deployments/9', # what the decision is about
|
|
53
|
+
store_authorization: "Bearer #{token}" # per request; nil sends no Authorization header
|
|
54
|
+
)
|
|
55
|
+
# => { decisions: [{ policy_id: 1, matched: true, actions: [{ action_type: 'block', gating: true, ... }], ... }],
|
|
56
|
+
# undecided_policy_ids: [],
|
|
57
|
+
# identifier: 'ed9cb624-7d8f-51eb-ad5a-ef394efcee74' }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each call makes one `GET {store_url}/organizations/{id}/security/policy_store?trigger_type={trigger}` (500 ms connect / 2 s total timeout by default, no redirects, TLS against the platform trust store, `*_PROXY`/`NO_PROXY` honoured), so egress rules must allow it. Deny when any returned action is `gating` or `undecided_policy_ids` is non-empty; allow otherwise. Caller mistakes raise `ArgumentError`, store faults raise `RuntimeError`, and a policy that could not be decided (broken, or over the per-evaluation time budget) is reported in-band on its decision's `error` and listed in `undecided_policy_ids`. `data` must not define a policy's own `violation`/`deny`/`allow` rules. `principal` and `resource` name the decision subject (each trimmed, and rejected if blank, newline-carrying, or over 64 KiB); the response's `identifier` is a stable UUIDv5 over `(principal, trigger, resource)`, so re-evaluating the same subject yields the same identifier for journaling, action deduplication, and approval correlation.
|
|
61
|
+
|
|
62
|
+
Without a store URL the shared engine still serves `#validate` (save-time parse check) and `#debug_evaluate` (raw single-policy evaluation); only trigger-keyed evaluation needs the store. See the class documentation for the full contract.
|
|
63
|
+
|
|
40
64
|
## Development
|
|
41
65
|
|
|
42
66
|
```shell
|
data/ext/glaz/Cargo.toml
CHANGED
data/ext/glaz/src/lib.rs
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
use std::ffi::c_void;
|
|
2
|
+
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
use std::time::Duration;
|
|
5
|
+
|
|
6
|
+
use magnus::rb_sys::AsRawValue;
|
|
1
7
|
use magnus::{Error, Ruby, prelude::*};
|
|
2
8
|
use prost::Message;
|
|
3
9
|
|
|
@@ -53,70 +59,106 @@ impl PermissionCheckEngine {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/// Thin 1:1 wrapper around `glaz_module::GovernPolicyCheckEngine`, the Rego
|
|
56
|
-
/// governance evaluator.
|
|
57
|
-
///
|
|
58
|
-
///
|
|
59
|
-
///
|
|
60
|
-
/// `
|
|
61
|
-
///
|
|
62
|
-
/// `validate`
|
|
63
|
-
///
|
|
64
|
-
/// actionable feedback without treating a user mistake as an exceptional
|
|
65
|
-
/// condition; a malformed protobuf payload raises `ArgumentError`, while
|
|
66
|
-
/// internal engine failures (`NotInitialized`, `Runtime`) raise
|
|
67
|
-
/// `RuntimeError` via `map_error`.
|
|
62
|
+
/// governance evaluator. The policy lookup fetches from the Policy Store
|
|
63
|
+
/// REST API named at construction; authentication is per request, on
|
|
64
|
+
/// `EvaluateGovernPolicyRequest.store_authorization`. Stateless between
|
|
65
|
+
/// calls. `evaluate_policies` raises on a whole-call failure (matching
|
|
66
|
+
/// `PermissionCheckEngine`'s convention): `ArgumentError` for caller
|
|
67
|
+
/// faults, `RuntimeError` for backend faults, see `map_error`.
|
|
68
|
+
/// `debug_evaluate` and `validate` are infallible for policy errors, which
|
|
69
|
+
/// they return in-band; only a malformed protobuf payload raises.
|
|
68
70
|
///
|
|
69
|
-
/// Registered as `Glaz::Native::GovernPolicyEngine`
|
|
70
|
-
/// `Glaz::GovernPolicyEngine`
|
|
71
|
-
///
|
|
72
|
-
/// class is raw FFI plumbing; prefer the wrapper unless you specifically
|
|
73
|
-
/// need `debug_evaluate`.
|
|
71
|
+
/// Registered as `Glaz::Native::GovernPolicyEngine` to avoid colliding in
|
|
72
|
+
/// name with the `Gitlab::Glaz::GovernPolicyEngine` wrapper — this class is
|
|
73
|
+
/// raw FFI plumbing; prefer the wrapper.
|
|
74
74
|
#[magnus::wrap(class = "Glaz::Native::GovernPolicyEngine")]
|
|
75
75
|
struct GovernPolicyEngine {
|
|
76
|
-
|
|
76
|
+
// Shared with the per-call lookup thread, see `on_own_thread`.
|
|
77
|
+
engine: Arc<glaz_module::GovernPolicyCheckEngine>,
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
impl GovernPolicyEngine {
|
|
80
|
-
|
|
81
|
+
/// `base_url` is the Policy Store REST API root (e.g.
|
|
82
|
+
/// `"https://gitlab.com/api/v4"`); `nil` timeouts keep
|
|
83
|
+
/// `glaz-govern`'s defaults. Without `base_url` the engine has no policy
|
|
84
|
+
/// source: `validate` and `debug_evaluate` work, `evaluate_policies`
|
|
85
|
+
/// fails closed (see `NoStore`).
|
|
86
|
+
fn initialize(
|
|
87
|
+
base_url: Option<String>,
|
|
88
|
+
connect_timeout_ms: Option<u64>,
|
|
89
|
+
timeout_ms: Option<u64>,
|
|
90
|
+
) -> Self {
|
|
91
|
+
let Some(base_url) = base_url else {
|
|
92
|
+
return Self {
|
|
93
|
+
engine: Arc::new(glaz_module::GovernPolicyCheckEngine::with_lookup(NoStore)),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
let mut config = glaz_module::StoreConfig::new(base_url);
|
|
97
|
+
if let Some(ms) = connect_timeout_ms {
|
|
98
|
+
config = config.with_connect_timeout(Duration::from_millis(ms));
|
|
99
|
+
}
|
|
100
|
+
if let Some(ms) = timeout_ms {
|
|
101
|
+
config = config.with_total_timeout(Duration::from_millis(ms));
|
|
102
|
+
}
|
|
81
103
|
Self {
|
|
82
|
-
engine: glaz_module::GovernPolicyCheckEngine::
|
|
104
|
+
engine: Arc::new(glaz_module::GovernPolicyCheckEngine::with_store(config)),
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
/// Evaluate
|
|
87
|
-
///
|
|
88
|
-
///
|
|
108
|
+
/// Evaluate the request's `query` directly, with no
|
|
109
|
+
/// `violation`/`deny`/`allow` interpretation and no policy lookup. A
|
|
110
|
+
/// blank query is reported in-band.
|
|
111
|
+
///
|
|
112
|
+
/// Runs with the GVL released (`without_gvl`), on its own thread where a
|
|
113
|
+
/// forked child needs one (`on_own_thread`): the parse it performs
|
|
114
|
+
/// parks the calling thread the same way `evaluate_policies`'s lookup
|
|
115
|
+
/// does. Not interruptible from Ruby until it returns.
|
|
89
116
|
fn debug_evaluate(
|
|
90
117
|
ruby: &Ruby,
|
|
91
118
|
rb_self: &Self,
|
|
92
119
|
proto_bytes: magnus::RString,
|
|
93
120
|
) -> Result<magnus::RString, Error> {
|
|
94
|
-
|
|
95
|
-
|
|
121
|
+
// Copy the request out of the Ruby string first: once the GVL is
|
|
122
|
+
// released the GC may move or free the buffer behind `as_slice`.
|
|
123
|
+
let request = unsafe { proto_bytes.as_slice() }.to_vec();
|
|
124
|
+
let engine = Arc::clone(&rb_self.engine);
|
|
125
|
+
let response = without_gvl(ruby, move || {
|
|
126
|
+
on_own_thread(move || Ok(engine.debug_evaluate(&request)))
|
|
127
|
+
})?
|
|
128
|
+
.map_err(|e| map_error(ruby, e))?;
|
|
96
129
|
Ok(ruby.str_from_slice(&response))
|
|
97
130
|
}
|
|
98
131
|
|
|
99
|
-
///
|
|
100
|
-
///
|
|
132
|
+
/// Trigger-keyed batch evaluation: takes an encoded
|
|
133
|
+
/// `EvaluateGovernPolicyRequest`, resolves the applicable policies from
|
|
134
|
+
/// the Policy Store (keyed on `trigger` and the context's
|
|
135
|
+
/// `organization.id`) and returns the encoded
|
|
136
|
+
/// `EvaluateGovernPolicyResponse`. Raises `ArgumentError` for caller
|
|
137
|
+
/// faults and `RuntimeError` for backend faults — never an empty batch,
|
|
138
|
+
/// which would read fail-open. A broken individual policy is reported
|
|
139
|
+
/// in-band on its decision's `error`.
|
|
101
140
|
///
|
|
102
|
-
///
|
|
103
|
-
///
|
|
104
|
-
///
|
|
105
|
-
|
|
106
|
-
/// result shape - all caller-fixable - and `RuntimeError` for an
|
|
107
|
-
/// engine-side fault (e.g. the Rego evaluation time budget was
|
|
108
|
-
/// exceeded), which is not.
|
|
109
|
-
fn evaluate(
|
|
141
|
+
/// Runs with the GVL released (`without_gvl`), on its own thread where
|
|
142
|
+
/// a forked child needs one (`on_own_thread`). Not interruptible from
|
|
143
|
+
/// Ruby until it returns.
|
|
144
|
+
fn evaluate_policies(
|
|
110
145
|
ruby: &Ruby,
|
|
111
146
|
rb_self: &Self,
|
|
112
147
|
proto_bytes: magnus::RString,
|
|
113
148
|
) -> Result<magnus::RString, Error> {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
// Copy the request out of the Ruby string first: once the GVL is
|
|
150
|
+
// released the GC may move or free the buffer behind `as_slice`.
|
|
151
|
+
let request = unsafe { proto_bytes.as_slice() }.to_vec();
|
|
152
|
+
let engine = Arc::clone(&rb_self.engine);
|
|
153
|
+
let response = without_gvl(ruby, move || {
|
|
154
|
+
on_own_thread(move || {
|
|
155
|
+
engine
|
|
156
|
+
.evaluate_policies(&request)
|
|
157
|
+
.map(|response| response.encode_to_vec())
|
|
158
|
+
})
|
|
159
|
+
})?
|
|
160
|
+
.map_err(|e| map_error(ruby, e))?;
|
|
161
|
+
Ok(ruby.str_from_slice(&response))
|
|
120
162
|
}
|
|
121
163
|
|
|
122
164
|
/// Parse and compile a Rego policy without evaluating it against input.
|
|
@@ -125,17 +167,47 @@ impl GovernPolicyEngine {
|
|
|
125
167
|
/// returned in-band as `{ valid: false, errors: [...] }` rather than
|
|
126
168
|
/// raised. Only a malformed protobuf payload raises `ArgumentError`; an
|
|
127
169
|
/// internal engine fault raises `RuntimeError` via `map_error`.
|
|
170
|
+
///
|
|
171
|
+
/// Runs with the GVL released (`without_gvl`), on its own thread where a
|
|
172
|
+
/// forked child needs one (`on_own_thread`): the parse it performs
|
|
173
|
+
/// parks the calling thread the same way `evaluate_policies`'s lookup
|
|
174
|
+
/// does. Not interruptible from Ruby until it returns.
|
|
128
175
|
fn validate(
|
|
129
176
|
ruby: &Ruby,
|
|
130
177
|
rb_self: &Self,
|
|
131
178
|
proto_bytes: magnus::RString,
|
|
132
179
|
) -> Result<magnus::RString, Error> {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
180
|
+
// Copy the request out of the Ruby string first: once the GVL is
|
|
181
|
+
// released the GC may move or free the buffer behind `as_slice`.
|
|
182
|
+
let request = unsafe { proto_bytes.as_slice() }.to_vec();
|
|
183
|
+
let engine = Arc::clone(&rb_self.engine);
|
|
184
|
+
let response = without_gvl(ruby, move || {
|
|
185
|
+
on_own_thread(move || {
|
|
186
|
+
engine
|
|
187
|
+
.validate(&request)
|
|
188
|
+
.map(|response| response.encode_to_vec())
|
|
189
|
+
})
|
|
190
|
+
})?
|
|
191
|
+
.map_err(|e| map_error(ruby, e))?;
|
|
192
|
+
Ok(ruby.str_from_slice(&response))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// The lookup of an engine constructed without a Policy Store: every
|
|
197
|
+
/// lookup fails closed as `LookupError::Unavailable` (a `RuntimeError`), so
|
|
198
|
+
/// such an engine can never report "nothing to enforce". The Ruby wrapper
|
|
199
|
+
/// rejects the call earlier with configuration guidance.
|
|
200
|
+
struct NoStore;
|
|
201
|
+
|
|
202
|
+
impl glaz_module::PolicyLookup for NoStore {
|
|
203
|
+
fn lookup(
|
|
204
|
+
&self,
|
|
205
|
+
_key: &glaz_module::LookupKey,
|
|
206
|
+
_store_authorization: &str,
|
|
207
|
+
) -> Result<Vec<glaz_module::StoredPolicy>, glaz_module::LookupError> {
|
|
208
|
+
Err(glaz_module::LookupError::Unavailable(
|
|
209
|
+
"no Policy Store configured for this engine".to_string(),
|
|
210
|
+
))
|
|
139
211
|
}
|
|
140
212
|
}
|
|
141
213
|
|
|
@@ -158,6 +230,109 @@ fn roles(ruby: &Ruby) -> Result<magnus::RArray, Error> {
|
|
|
158
230
|
Ok(array)
|
|
159
231
|
}
|
|
160
232
|
|
|
233
|
+
/// Run `func` with the GVL released and hand its result back once the GVL
|
|
234
|
+
/// is held again. `func` must not touch the Ruby VM, so callers copy their
|
|
235
|
+
/// input out of Ruby objects first. A panic in `func` is caught at the
|
|
236
|
+
/// `extern "C"` boundary and resumed afterwards.
|
|
237
|
+
///
|
|
238
|
+
/// No unblock function is registered, so `func` always runs to completion;
|
|
239
|
+
/// a Ruby interrupt aimed at this thread takes effect once it returns.
|
|
240
|
+
///
|
|
241
|
+
/// Catching the panic requires `panic = "unwind"`, the default profile
|
|
242
|
+
/// setting; magnus's method wrappers already rely on it to turn panics into
|
|
243
|
+
/// Ruby exceptions, so an `abort` profile would break the extension as a
|
|
244
|
+
/// whole, not just this helper.
|
|
245
|
+
fn without_gvl<F, T>(ruby: &Ruby, func: F) -> Result<T, Error>
|
|
246
|
+
where
|
|
247
|
+
F: FnOnce() -> T + Send,
|
|
248
|
+
T: Send,
|
|
249
|
+
{
|
|
250
|
+
enum Call<F, T> {
|
|
251
|
+
Pending(F),
|
|
252
|
+
Done(std::thread::Result<T>),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
unsafe extern "C" fn trampoline<F: FnOnce() -> T, T>(data: *mut c_void) -> *mut c_void {
|
|
256
|
+
// SAFETY: `data` is the `Call` below, which outlives this call.
|
|
257
|
+
let slot = unsafe { &mut *data.cast::<Option<Call<F, T>>>() };
|
|
258
|
+
let Some(Call::Pending(func)) = slot.take() else {
|
|
259
|
+
unreachable!("the trampoline runs exactly once, on a pending call");
|
|
260
|
+
};
|
|
261
|
+
*slot = Some(Call::Done(catch_unwind(AssertUnwindSafe(func))));
|
|
262
|
+
std::ptr::null_mut()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let mut call = Some(Call::Pending(func));
|
|
266
|
+
// `protect` catches Ruby unwinding out of the interrupt checks
|
|
267
|
+
// `rb_thread_call_without_gvl` performs before and after `func`.
|
|
268
|
+
let protected = magnus::rb_sys::protect(|| {
|
|
269
|
+
unsafe {
|
|
270
|
+
rb_sys::rb_thread_call_without_gvl(
|
|
271
|
+
Some(trampoline::<F, T>),
|
|
272
|
+
(&mut call as *mut Option<Call<F, T>>).cast::<c_void>(),
|
|
273
|
+
None,
|
|
274
|
+
std::ptr::null_mut(),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
ruby.qnil().as_raw()
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// A panic outranks an interrupt that arrived after it.
|
|
281
|
+
match call {
|
|
282
|
+
Some(Call::Done(Err(panic))) => resume_unwind(panic),
|
|
283
|
+
Some(Call::Done(Ok(value))) => protected.map(|_| value),
|
|
284
|
+
Some(Call::Pending(_)) | None => {
|
|
285
|
+
protected?;
|
|
286
|
+
unreachable!("rb_thread_call_without_gvl returned without running the call")
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// On Apple targets, run `func` on a native thread of its own; elsewhere,
|
|
292
|
+
/// inline.
|
|
293
|
+
///
|
|
294
|
+
/// While resolving a host name under a timeout the HTTP client parks its
|
|
295
|
+
/// thread, and on Apple targets Rust's `std` parks with a per-thread
|
|
296
|
+
/// libdispatch semaphore. A forked child inherits the forking thread's
|
|
297
|
+
/// semaphore and libdispatch aborts the process (`SIGTRAP`) on its first
|
|
298
|
+
/// use, so a Unicorn worker evaluating on the thread its master evaluated
|
|
299
|
+
/// on would crash. A fresh thread gets a fresh semaphore. Linux parks on a
|
|
300
|
+
/// futex, which survives a fork.
|
|
301
|
+
fn on_own_thread<F, T>(func: F) -> Result<T, glaz_module::GlazError>
|
|
302
|
+
where
|
|
303
|
+
F: FnOnce() -> Result<T, glaz_module::GlazError> + Send + 'static,
|
|
304
|
+
T: Send + 'static,
|
|
305
|
+
{
|
|
306
|
+
#[cfg(not(target_vendor = "apple"))]
|
|
307
|
+
{
|
|
308
|
+
func()
|
|
309
|
+
}
|
|
310
|
+
#[cfg(target_vendor = "apple")]
|
|
311
|
+
{
|
|
312
|
+
let handle = std::thread::Builder::new()
|
|
313
|
+
.name("glaz-policy-lookup".to_string())
|
|
314
|
+
// The main thread's 8 MiB rather than the 2 MiB spawn default.
|
|
315
|
+
.stack_size(8 << 20)
|
|
316
|
+
.spawn(func)
|
|
317
|
+
.map_err(|e| {
|
|
318
|
+
glaz_module::GlazError::Runtime(format!(
|
|
319
|
+
"could not start the policy lookup thread: {e}"
|
|
320
|
+
))
|
|
321
|
+
})?;
|
|
322
|
+
match handle.join() {
|
|
323
|
+
Ok(result) => result,
|
|
324
|
+
Err(panic) => resume_unwind(panic),
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// The engine is called concurrently from any thread: other Ruby threads
|
|
330
|
+
// while one waits outside the GVL, and the lookup thread on Apple targets.
|
|
331
|
+
const _: () = {
|
|
332
|
+
const fn assert_sync_send<T: Sync + Send>() {}
|
|
333
|
+
assert_sync_send::<glaz_module::GovernPolicyCheckEngine>();
|
|
334
|
+
};
|
|
335
|
+
|
|
161
336
|
fn map_error(ruby: &Ruby, e: glaz_module::GlazError) -> Error {
|
|
162
337
|
use glaz_module::GlazError::*;
|
|
163
338
|
match e {
|
|
@@ -190,12 +365,15 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
190
365
|
)?;
|
|
191
366
|
let native = glaz.define_module("Native")?;
|
|
192
367
|
let govern = native.define_class("GovernPolicyEngine", ruby.class_object())?;
|
|
193
|
-
govern.define_singleton_method("new", magnus::function!(GovernPolicyEngine::initialize,
|
|
368
|
+
govern.define_singleton_method("new", magnus::function!(GovernPolicyEngine::initialize, 3))?;
|
|
194
369
|
govern.define_method(
|
|
195
370
|
"debug_evaluate",
|
|
196
371
|
magnus::method!(GovernPolicyEngine::debug_evaluate, 1),
|
|
197
372
|
)?;
|
|
198
|
-
govern.define_method(
|
|
373
|
+
govern.define_method(
|
|
374
|
+
"evaluate_policies",
|
|
375
|
+
magnus::method!(GovernPolicyEngine::evaluate_policies, 1),
|
|
376
|
+
)?;
|
|
199
377
|
govern.define_method("validate", magnus::method!(GovernPolicyEngine::validate, 1))?;
|
|
200
378
|
Ok(())
|
|
201
379
|
}
|