Loading
feat(govern): Policy Store REST client behind the PolicyLookup seam
What
The behavioral half of the split requested in the review (comment 3736928080): stacked on !177 (closed) (glaz-policy-store-client crate + lookup error taxonomy, inert). This MR switches everything onto the client, as one story — no implicit policy source anywhere:
glaz-moduleconstructs explicitly: zero-confignew()/Defaultdeleted (an implicit source would evaluate fixture policies against a real tenant, or fail open as an empty "nothing to enforce" batch);with_lookup/with_limits(lookup, …)/with_store(config: StoreConfig)with the client types re-exported, and the client as a hard link-time dependency.- The canned fixture retires from every shipped surface:
glaz-governexposes thePolicyLookupseam and no implementation — the list lookup is a#[cfg(test)] ListLookup, and every test states its own policies (including the scope-filtered one from the scope-evaluation work). - ABI v3, one constructor:
glaz_govern_engine_new/_with_limitsdeleted;glaz_govern_engine_new_with_store(…, max_time_ms, check_interval)is the only way to build an engine (name kept so a stale host resolving v2's zero-argumentengine_newfails at symbol lookup instead of calling a wrong signature). - Self-contained conformance:
cases.jsonis a{policies, cases}document (Go-client re-sync required); every harness serves the entities from a local stub speaking the store's list contract and drives the shipped constructor.smoke.cfails closed without a store URL (GLAZ_SMOKE_NO_STOREopts out for the packaging link proofs);ffi-linkruns the ephemeral-port stub plus thenmexport gate.
The client's own contract, hardening, and stub-server suite are reviewed in !177 (closed) — this MR contains no HTTP code.
Decisions
- HTTP client: blocking
ureq3 with platform trust roots (self-managed behind an internal CA works), chosen for simplicity — one call per lookup needs no async runtime. 500 ms connect / 2 s total defaults, pinned by test — including behaviorally, against a server that accepts and never responds.deny.tomlcarries per-crateCDLA-Permissive-2.0exceptions for the webpki root-store data crates only. - No caching, no retries in this milestone — the hot-path strategy (cache / GVL release / in-process fetch for the Rails host) is a tracked decision: #17, to be recorded on gitlab-org/gitlab#604407.
- CI:
ffi-linkruns the C smoke test against a local stub on an ephemeral port (fail-closed when the stub URL is missing) and asserts the released staticlib exportsglaz_govern_engine_new_with_store. No--all-featuresanywhere — no workspace crate declares features anymore.
Testing
-
cargo test --workspace— 249 tests, everything in the default build: 19 stub-server contract/hardening tests (single-request localTcpListener, no new dev-dependencies), module + FFI end-to-end backend-fault tests, explicit-construction coverage -
cargo clippy --workspace --all-targets -- -D warnings,cargo fmt --all --check, C smoke test against the real staticlib (three states: stub URL set, missing = failure, explicit opt-out)
Out of scope / draft until
Deliberately not here: caching/invalidation (#17, #604407), enforcement_mode evaluation semantics (#607657 — the client already carries the field's gate), gem configuration plumbing (gem-repo follow-up: ext/glaz must move to explicit construction since zero-config new() no longer exists).
Draft until:
- The store serves executable policy content (transpiled
regoon the entity, or the GOVERN-006 evaluation/bundle read) — the DTO/mapping will likely reshape with it - A bounded, engine-facing read route exists (
Govern::Policy.evaluation_candidatesis only a model method today; the current route is the unbounded CRUD list behind admin-scopedread_govern_policy) with a lesser-scoped token provisioned for GLAZ - One smoke test against a real GDK store (experiment enabled, Ultimate)
- The hot-path decision in #17
Author checklist
- Title follows Conventional Commits and breaking changes are flagged (pre-release surface; the gem binding is adapted in its own repo)
- Docs / comments updated where behaviour changed
- No unrelated changes swept in
Edited by Artur Fedorov