Skip to content

feat: in-process embedded permissions and per-schema-version caching#3166

Open
josephschorr wants to merge 4 commits into
authzed:mainfrom
josephschorr:embedded-permissions
Open

feat: in-process embedded permissions and per-schema-version caching#3166
josephschorr wants to merge 4 commits into
authzed:mainfrom
josephschorr:embedded-permissions

Conversation

@josephschorr

Copy link
Copy Markdown
Member

Summary

Adds the ability to embed SpiceDB's permission engine in-process (no gRPC server) and makes per-schema-version caching of derived artifacts possible, eliminating repeated work on the check hot path.

Changes

  • datalayer: cache schema-derived artifacts on the stored-schema cache. The stored-schema cache already shares one instance per schema version (keyed by schema hash). Bundle lazily-built, schema-derived caches (compiled caveats today; type systems / reachability later) into that instance via a new CachedSchema type, so they share the schema's lifetime and are discarded, together, when the schema changes. Register a kind with RegisterDerivedCache; retrieve the per-schema instance with GetDerivedCache. The stored-schema cache now holds *datalayer.CachedSchema; pkg/datastore is unchanged.

  • datastore: honor schema mode when writing bootstrap data. NewDatastore's bootstrap always wrote schema in legacy per-definition form regardless of the configured schema mode, so a server reading from the unified schema would find none. Adds a BootstrapSchemaMode option; the zero value preserves prior behavior.

  • caveats: cache compiled caveats per schema version. ComputeCheck constructs a fresh CaveatRunner per call, so its per-runner deserialized-caveat cache never persists across checks. Deserializing a caveat rebuilds its CEL environment, which is expensive and — with a rich caveat type set — dominated check cost. This caches compiled caveats on the schema-derived cache, keyed by caveat name, so the CEL environment is built once per schema version rather than once per check. Falls back to per-runner caching when the reader is not backed by a unified stored schema.

  • embedded: add in-process permissions library. Adds pkg/embedded, a focused library for running permission checks in-process against a datastore via the dispatch engine — no gRPC, caveat context passed as native Go values. Includes a README and tests.

Testing

  • mage lint:all passes; unit suite passes.
  • New tests: pkg/datalayer (derived-cache registry: lazy build, per-schema isolation, duplicate/unregistered handling), internal/caveats (compiled-caveat cache), pkg/embedded (checks across both legacy and unified schema modes, incl. caveated/conditional results).

@josephschorr josephschorr requested a review from a team as a code owner June 9, 2026 02:22
@github-actions github-actions Bot added area/cli Affects the command line area/api v1 Affects the v1 API area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools) labels Jun 9, 2026
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

@josephschorr josephschorr force-pushed the embedded-permissions branch from e01a253 to 96c22be Compare June 9, 2026 17:25
The datalayer's stored-schema cache shares a single instance per schema
version (keyed by schema hash). Bundle lazily-built, schema-derived caches
(e.g. compiled caveats, type systems) into that instance via a new
CachedSchema type, so they share the schema's lifetime and are discarded,
together, when the schema changes.

Register a cache kind once with RegisterDerivedCache and retrieve the
per-schema instance with GetDerivedCache. The stored-schema cache now holds
*CachedSchema instead of *datastore.ReadOnlyStoredSchema; pkg/datastore is
unchanged.
NewDatastore's bootstrap always wrote schema in legacy per-definition form
regardless of the configured schema mode, so a server reading from the
unified schema would find none. Add a BootstrapSchemaMode option so the
bootstrap datalayer writes schema in the requested mode. The zero value
preserves the prior (legacy) behavior.
ComputeCheck constructs a fresh CaveatRunner per call, so its per-runner
deserialized-caveat cache never persists across checks. Deserializing a
caveat rebuilds its CEL environment, which is expensive and, with a rich
caveat type set, dominated check cost.

Cache compiled caveats on the schema-derived cache (CachedSchema), keyed by
caveat name, so the CEL environment is built once per schema version rather
than once per check. Falls back to per-runner caching when the reader is not
backed by a unified stored schema.
Add pkg/embedded, a focused library for running permission checks in-process
against a datastore via the dispatch engine, without standing up a gRPC
server. Caveat context is passed as native Go values. Includes a README and
tests covering both legacy and unified schema modes.
@josephschorr josephschorr force-pushed the embedded-permissions branch from 96c22be to 7fc3209 Compare June 9, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api v1 Affects the v1 API area/cli Affects the command line area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant