From f31c0f78a7346b98e70c9ff68f0ce78ea6807020 Mon Sep 17 00:00:00 2001 From: Catherine Chambers Date: Mon, 20 Jul 2026 11:03:57 -0400 Subject: [PATCH 1/7] Added badges for Accelerated Queries and Event Streams to appropriate content --- app/materialize/api/client-sdks/page.mdx | 2 + .../api/lookup-permission-sets/page.mdx | 2 + .../api/watch-permission-sets/page.mdx | 2 + app/materialize/concepts/_meta.ts | 2 +- .../concepts/managing-client-state/page.mdx | 2 + .../permission-set-lifecycle/page.mdx | 2 + .../concepts/permission-sets/page.mdx | 2 + app/materialize/concepts/snapshots/page.mdx | 5 ++- .../getting-started/overview/page.mdx | 26 ++++++++---- .../guides/recommended-architecture/page.mdx | 2 + .../guides/relational-database/page.mdx | 2 + components/feature-badge.css | 42 +++++++++++++++++++ components/feature-badge.tsx | 29 +++++++++++++ lib/changed-pages.json | 21 ++++++++++ mdx-components.ts | 2 + 15 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 components/feature-badge.css create mode 100644 components/feature-badge.tsx diff --git a/app/materialize/api/client-sdks/page.mdx b/app/materialize/api/client-sdks/page.mdx index 0af9a011..d8541972 100644 --- a/app/materialize/api/client-sdks/page.mdx +++ b/app/materialize/api/client-sdks/page.mdx @@ -5,6 +5,8 @@ description: "SDK versions with generated gRPC and protobuf support for the Auth # Client SDKs + + All SpiceDB SDKs have the generated gRPC and protobuf code - [authzed-go v0.15.0](https://github.com/authzed/authzed-go/releases/tag/v0.15.0) diff --git a/app/materialize/api/lookup-permission-sets/page.mdx b/app/materialize/api/lookup-permission-sets/page.mdx index 59d4506b..8b4a4ba6 100644 --- a/app/materialize/api/lookup-permission-sets/page.mdx +++ b/app/materialize/api/lookup-permission-sets/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # LookupPermissionSets + + This API complements [WatchPermissionSets]. When you first bring on a system that needs permissions data, [LookupPermissionSets] lets you create an initial snapshot of the permissions data, and then you can use the [WatchPermissionSets] API to keep the snapshot updated. diff --git a/app/materialize/api/watch-permission-sets/page.mdx b/app/materialize/api/watch-permission-sets/page.mdx index e8cd739e..4bdfd0a2 100644 --- a/app/materialize/api/watch-permission-sets/page.mdx +++ b/app/materialize/api/watch-permission-sets/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # WatchPermissionSets + + This is an update stream of all the permissions Materialize is configured to watch. You can use this to store all permissions tracked in the system closer to your application database to be used in database-native ACL filtering. Permissions can also be stored in secondary indexes like Elasticsearch. diff --git a/app/materialize/concepts/_meta.ts b/app/materialize/concepts/_meta.ts index eeb3adaa..46b3f58f 100644 --- a/app/materialize/concepts/_meta.ts +++ b/app/materialize/concepts/_meta.ts @@ -1,6 +1,6 @@ export default { + snapshots: "Snapshots", "permission-sets": "Permission Sets", "permission-set-lifecycle": "Permission Set Lifecycle", - snapshots: "Snapshots", "managing-client-state": "Managing Client State", }; diff --git a/app/materialize/concepts/managing-client-state/page.mdx b/app/materialize/concepts/managing-client-state/page.mdx index 0f6764c2..39b65bf9 100644 --- a/app/materialize/concepts/managing-client-state/page.mdx +++ b/app/materialize/concepts/managing-client-state/page.mdx @@ -5,6 +5,8 @@ description: "State diagram of the transitions a client application moves throug # Managing Client State + + This diagram shows the various states your client application will need to transition through when calling the [LookupPermissionSets] and the [WatchPermissionSets] APIs. ![authzed-materialize](/images/materialize-client-state-diagram.png) diff --git a/app/materialize/concepts/permission-set-lifecycle/page.mdx b/app/materialize/concepts/permission-set-lifecycle/page.mdx index 7d8b1407..b9dd16a7 100644 --- a/app/materialize/concepts/permission-set-lifecycle/page.mdx +++ b/app/materialize/concepts/permission-set-lifecycle/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # The permission set lifecycle + + The **permission set lifecycle** describes how your application's local copy of permission data is populated, kept current, and rebuilt over time — from an empty index to a complete, queryable [Permission Sets](./permission-sets) store, and back to a fresh index when your schema changes. When you first bring a system online, you have no permission data locally. diff --git a/app/materialize/concepts/permission-sets/page.mdx b/app/materialize/concepts/permission-sets/page.mdx index b7b0f88b..613266c8 100644 --- a/app/materialize/concepts/permission-sets/page.mdx +++ b/app/materialize/concepts/permission-sets/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # Permission Sets + + A **permission set** is the unit of precomputed authorization data that Materialize produces. Where SpiceDB answers a permission question on demand by walking the relationship graph, Materialize continuously **precomputes** the membership of the permissions you configure it to watch and exposes that denormalized data to your application. diff --git a/app/materialize/concepts/snapshots/page.mdx b/app/materialize/concepts/snapshots/page.mdx index 255a5140..3ac5034c 100644 --- a/app/materialize/concepts/snapshots/page.mdx +++ b/app/materialize/concepts/snapshots/page.mdx @@ -7,11 +7,14 @@ import { Callout } from "nextra/components"; # Snapshots + + A **snapshot** is a point-in-time, internally consistent view of every [Permission Set](./permission-sets) Materialize is tracking, computed at a specific SpiceDB revision. Every piece of permission data Materialize hands you is anchored to the revision (`ZedToken`) it was computed at — that revision _is_ the snapshot's identity. -Snapshots are what make the two Materialize APIs compose safely: +Snapshots underpin both Materialize features: they're what Materialize hydrates in order to answer Accelerated Queries, and they're what make the Event Streams APIs compose safely: +- Materialize reads the current snapshot directly to answer Accelerated Queries like `CheckPermission`, `CheckBulkPermissions`, `LookupResources`, and `LookupSubjects`. - [The initial backfill](./permission-set-lifecycle) reads a snapshot in full. - [WatchPermissionSets](../api/watch-permission-sets) advances that snapshot forward, delivering the deltas that move it from one revision to the next. diff --git a/app/materialize/getting-started/overview/page.mdx b/app/materialize/getting-started/overview/page.mdx index f8ff5cd1..d53747d3 100644 --- a/app/materialize/getting-started/overview/page.mdx +++ b/app/materialize/getting-started/overview/page.mdx @@ -7,21 +7,31 @@ import { Callout } from "nextra/components"; # What is Materialize? - - AuthZed Materialize is available to users of AuthZed [Dedicated] as part of an early access - program. Don't hesitate to get in touch with your AuthZed account team if you would like to - participate. - - AuthZed Materialize takes inspiration from the Leopard index component described in the [Zanzibar paper](https://zanzibar.tech/2IoYDUFMAE:0:T). Much like the concept of a materialized view in relational databases, AuthZed Materialize is a service that you configure with a list of permissions that you want it to precompute, and it will calculate how those permissions change after relationships are written (specifically, when those relationships affect a subject's membership in a permission set or a set's permission on a specific resource), or when a new schema is written. -These precomputed permissions can then be used either to provide faster checks and lookups through Accelerated Queries, or streamed into your own application database and **colocated** alongside the data they protect — a pattern known as **colocated permissions** — so you can search, sort, filter, and paginate over authorized objects natively and much more efficiently. -In summary, AuthZed Materialize allows you to: +Materialize puts that precomputed data to work in two ways: **Accelerated Queries**, which is generally available, and **Event Streams**, which is in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which also shows its release status. + +## Accelerated Queries + +Accelerated Queries is generally available. + +Accelerated Queries use Materialize's precomputed permissions cache to answer the SpiceDB queries your application already makes, without changing how you call SpiceDB: - Speed up `CheckPermission` and `CheckBulkPermissions`. - Speed up `LookupResources` and `LookupSubjects`, especially when there is a large number of resources. + +## Event Streams + + + Event Streams is available to users of AuthZed [Dedicated] as part of an early access program. + Don't hesitate to get in touch with your AuthZed account team if you would like to participate. + + +Event Streams let you take Materialize's precomputed permission data out of SpiceDB and keep your own copy of it — either **colocated** alongside the data it protects in your application database, or in a secondary index like a search engine, a pattern known as **colocated permissions**. + +- Download the current state of every [permission set](../../concepts/permission-sets) with `LookupPermissionSets`, then keep it current in real time with `WatchPermissionSets`. See [The Permission Set Lifecycle](../../concepts/permission-set-lifecycle). - Build authorization-aware UIs — sort, filter, and paginate over many thousands of authorized objects natively in your own database by colocating computed permissions next to your application data. - Perform ACL filtering in secondary indexes too, like a search index (e.g. Elasticsearch) — authorization-aware UIs and search-index filtering are the same idea (ACL filtering) applied to different stores. diff --git a/app/materialize/guides/recommended-architecture/page.mdx b/app/materialize/guides/recommended-architecture/page.mdx index 9a792552..6493514e 100644 --- a/app/materialize/guides/recommended-architecture/page.mdx +++ b/app/materialize/guides/recommended-architecture/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # Recommended Architecture + + ## Consuming Client ![authzed-materialize](/images/authzed-materialize.png) diff --git a/app/materialize/guides/relational-database/page.mdx b/app/materialize/guides/relational-database/page.mdx index 1b093d83..b562b5e0 100644 --- a/app/materialize/guides/relational-database/page.mdx +++ b/app/materialize/guides/relational-database/page.mdx @@ -7,6 +7,8 @@ import { Callout } from "nextra/components"; # Syncing to a Relational Database + + Just as with relational database materialized views, you need to provide Materialize with the "queries" you'd like it to pre-compute. The configuration is described as a list of `resource#permission@subject` tuples. Example: diff --git a/components/feature-badge.css b/components/feature-badge.css new file mode 100644 index 00000000..cef07624 --- /dev/null +++ b/components/feature-badge.css @@ -0,0 +1,42 @@ +/* Materialize feature badges — clickable pill linking back to the feature's + definition on the Overview page. Text casing, font weight, border opacity, + and padding are adopted from Sandworm's status-badge.tsx + (github.com/authzed/design/tree/main/sandworm/components/ui) for family + resemblance, even though that component's semantics (content lifecycle: + ready/draft/deprecated/under-review) differ from ours (feature identity + + release maturity). Colors are teal (Accelerated Queries) and violet + (Event Streams), light/dark-adjusted the same way docs-home.css already + tunes those tokens for this site's two themes. */ + +.feature-badge { + display: inline-block; + margin: 1.5rem 0 1.75rem; + padding: 0.25rem 0.5rem; + border: 1px solid; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 500; + text-decoration: none; +} + +.feature-badge-accelerated-queries { + color: hsl(178 35% 33%); + background: hsl(178 35% 33% / 0.1); + border-color: hsl(178 35% 33% / 0.5); +} +html.dark .feature-badge-accelerated-queries { + color: hsl(177 28.2% 45.9%); + background: hsl(177 28.2% 45.9% / 0.1); + border-color: hsl(177 28.2% 45.9% / 0.5); +} + +.feature-badge-event-streams { + color: hsl(253 62% 50%); + background: hsl(253 62% 50% / 0.1); + border-color: hsl(253 62% 50% / 0.5); +} +html.dark .feature-badge-event-streams { + color: hsl(253 73.4% 63.1%); + background: hsl(253 73.4% 63.1% / 0.1); + border-color: hsl(253 73.4% 63.1% / 0.5); +} diff --git a/components/feature-badge.tsx b/components/feature-badge.tsx new file mode 100644 index 00000000..44db7475 --- /dev/null +++ b/components/feature-badge.tsx @@ -0,0 +1,29 @@ +import "./feature-badge.css"; +import Link from "next/link"; + +const FEATURES = { + "accelerated-queries": { + label: "Accelerated Queries", + status: "GA", + href: "/materialize/getting-started/overview#accelerated-queries", + }, + "event-streams": { + label: "Event Streams", + status: "Early Access", + href: "/materialize/getting-started/overview#event-streams", + }, +} as const; + +/* Marks which Materialize feature a page belongs to, and that feature's + release status (an intrinsic property of the feature, not of the page). + Used in MDX as right under the + H1 (mdx-components.ts). Links back to that feature's definition on the + Overview page. */ +export function FeatureBadge({ feature }: { feature: keyof typeof FEATURES }) { + const { label, status, href } = FEATURES[feature]; + return ( + + {label} · {status} + + ); +} diff --git a/lib/changed-pages.json b/lib/changed-pages.json index 88183dbf..9e53a9ea 100644 --- a/lib/changed-pages.json +++ b/lib/changed-pages.json @@ -1,4 +1,16 @@ { + "/materialize/api/client-sdks": { + "status": "updated" + }, + "/materialize/api/lookup-permission-sets": { + "status": "updated" + }, + "/materialize/api/watch-permission-sets": { + "status": "updated" + }, + "/materialize/concepts/managing-client-state": { + "status": "updated" + }, "/materialize/concepts/permission-set-lifecycle": { "status": "updated" }, @@ -7,5 +19,14 @@ }, "/materialize/concepts/snapshots": { "status": "updated" + }, + "/materialize/getting-started/overview": { + "status": "updated" + }, + "/materialize/guides/recommended-architecture": { + "status": "updated" + }, + "/materialize/guides/relational-database": { + "status": "updated" } } diff --git a/mdx-components.ts b/mdx-components.ts index 9533ba64..699f653a 100644 --- a/mdx-components.ts +++ b/mdx-components.ts @@ -1,6 +1,7 @@ import { useMDXComponents as getDocsMDXComponents } from "nextra-theme-docs"; import type { Component } from "react"; import { Yes, No } from "@/components/feature-icon"; +import { FeatureBadge } from "@/components/feature-badge"; const docsComponents = getDocsMDXComponents(); @@ -8,5 +9,6 @@ export const useMDXComponents = (components?: Component) => ({ ...docsComponents, Yes, No, + FeatureBadge, ...components, }); From b861e212309e3b156c7fb43e5ae9d855360c46ae Mon Sep 17 00:00:00 2001 From: Catherine Chambers Date: Mon, 20 Jul 2026 11:31:39 -0400 Subject: [PATCH 2/7] correcting that AQ is still in Early Access --- app/materialize/getting-started/overview/page.mdx | 8 ++++++-- components/feature-badge.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/materialize/getting-started/overview/page.mdx b/app/materialize/getting-started/overview/page.mdx index d53747d3..19e53d0a 100644 --- a/app/materialize/getting-started/overview/page.mdx +++ b/app/materialize/getting-started/overview/page.mdx @@ -11,11 +11,15 @@ AuthZed Materialize takes inspiration from the Leopard index component described Much like the concept of a materialized view in relational databases, AuthZed Materialize is a service that you configure with a list of permissions that you want it to precompute, and it will calculate how those permissions change after relationships are written (specifically, when those relationships affect a subject's membership in a permission set or a set's permission on a specific resource), or when a new schema is written. -Materialize puts that precomputed data to work in two ways: **Accelerated Queries**, which is generally available, and **Event Streams**, which is in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which also shows its release status. +Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which also shows its release status. ## Accelerated Queries -Accelerated Queries is generally available. + + Accelerated Queries is available to users of AuthZed [Dedicated] as part of an early access + program. Don't hesitate to get in touch with your AuthZed account team if you would like to + participate. + Accelerated Queries use Materialize's precomputed permissions cache to answer the SpiceDB queries your application already makes, without changing how you call SpiceDB: diff --git a/components/feature-badge.tsx b/components/feature-badge.tsx index 44db7475..b27767e1 100644 --- a/components/feature-badge.tsx +++ b/components/feature-badge.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; const FEATURES = { "accelerated-queries": { label: "Accelerated Queries", - status: "GA", + status: "Early Access", href: "/materialize/getting-started/overview#accelerated-queries", }, "event-streams": { From b2df00a808780d6d5b415cca0d79235b54194f08 Mon Sep 17 00:00:00 2001 From: Corey Thomas <125082303+Corey-T1000@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:57:00 -0400 Subject: [PATCH 3/7] feat(materialize): split feature badges into pill + early-access note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The badge was carrying two jobs in one control: which Materialize feature a page documents, and how finished that feature is. A pill is the right form for the first and a poor one for the second — it has no room to say what "Early Access" means or who to contact — and since both features share the same status today, it read as ten repetitions of a word that couldn't explain itself. Split them. The pill carries identity plus an asterisk; a colour-matched note carries what the asterisk means, docked in the TOC rail beside the pill it belongs to. Same accent token on both, so they read as one marker across two columns rather than two unrelated notices. Nextra hides the TOC below 80rem, which would leave the asterisk pointing at nothing on a laptop, so the note also renders inline under the pills and CSS shows exactly one. That 80rem is duplicated from Nextra's own `x:max-xl:hidden` — it isn't exposed as a variable, so a future Nextra bump needs to re-check it. Also lifts the Sandworm accent palette out of `.docs-home` onto :root and html.dark. It was scoped to the landing page, so feature-badge.css had to hardcode the teal and violet HSL values with a comment as the only link back to the source of truth. The badge now reads them via var(), and its two html.dark override blocks go away since the tokens flip themselves. The --stone-* ramp stays scoped: it's inverted for that page, not general-purpose. Page-to-feature mapping moves into lib/materialize-features.ts. Nextra's toc.extraContent is a global slot with no access to page props, so the note can only learn its feature from the route; keeping a prop on the pill as well would have been a second source of truth. MDX now uses a bare and Snapshots renders both pills from it. Early access is a separate flag from the feature list, so a feature going GA is a one-line change that drops its asterisk and note while keeping pill and colour. Colour values are unchanged throughout: --teal-500 resolves to 178 35% 33% light / 177 28.2% 45.9% dark and --violet-500 to 253 62% 50% / 253 73.4% 63.1%, matching what shipped. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/globals.css | 48 ++++++++++ app/materialize/api/client-sdks/page.mdx | 2 +- .../api/lookup-permission-sets/page.mdx | 2 +- .../api/watch-permission-sets/page.mdx | 2 +- .../concepts/managing-client-state/page.mdx | 2 +- .../permission-set-lifecycle/page.mdx | 2 +- .../concepts/permission-sets/page.mdx | 2 +- app/materialize/concepts/snapshots/page.mdx | 2 +- .../getting-started/overview/page.mdx | 2 +- .../guides/recommended-architecture/page.mdx | 2 +- .../guides/relational-database/page.mdx | 2 +- components/docs-home.css | 35 +------- components/feature-badge.css | 60 ++++++++----- components/feature-badge.tsx | 61 ++++++++----- components/feature-notes.css | 87 +++++++++++++++++++ components/feature-notes.tsx | 49 +++++++++++ components/toc-extra-content.tsx | 4 + lib/materialize-features.ts | 48 ++++++++++ 18 files changed, 329 insertions(+), 83 deletions(-) create mode 100644 components/feature-notes.css create mode 100644 components/feature-notes.tsx create mode 100644 lib/materialize-features.ts diff --git a/app/globals.css b/app/globals.css index b1c19af9..a130fd32 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,6 +2,54 @@ @custom-variant dark (&:is(html[class~="dark"] *)); +/* =========================================================================== + Sandworm accent tokens — site-wide. + Raw HSL triplets (consumed as `hsl(var(--teal-500) / 0.5)`), so any + component can tint without redeclaring a color. LIGHT is the base; DARK + overrides under `html.dark` (next-themes, which also resolves "system"). + The light shades are deliberately DARKER than Sandworm's native register + so they stay legible on a light ground — don't "correct" them upward + without re-checking contrast. + Previously scoped under `.docs-home` in components/docs-home.css, which + put them out of reach of everything else on the site. The `--stone-*` ramp + stays scoped there: it's inverted for that page and isn't general-purpose. + ======================================================================== */ +:root { + --magenta-300: 316 50% 45%; + --magenta-400: 316 48% 47%; + --magenta-500: 316 50% 44%; + --magenta-600: 316 52% 40%; + --magenta-950: 315 33.3% 7.1%; + --red-400: 352 70% 48%; + --teal-300: 178 40% 32%; + --teal-400: 178 38% 35%; + --teal-500: 178 35% 33%; + --teal-700: 178 30% 26%; + --violet-400: 253 60% 52%; + --violet-500: 253 62% 50%; + --violet-700: 255 50% 46%; + --sand-300: 30 75% 42%; + --blue-500: 200 75% 40%; +} + +html.dark { + --magenta-300: 318 41.2% 70%; + --magenta-400: 317 40.2% 60%; + --magenta-500: 316 39.2% 51%; + --magenta-600: 316 42% 45%; + --magenta-950: 315 33.3% 7.1%; + --red-400: 355 91% 73.9%; + --teal-300: 175 27.5% 72.9%; + --teal-400: 176 28% 56%; + --teal-500: 177 28.2% 45.9%; + --teal-700: 178 30% 30%; + --violet-400: 253 76.5% 70%; + --violet-500: 253 73.4% 63.1%; + --violet-700: 255 58% 43.9%; + --sand-300: 28 100% 72%; + --blue-500: 195 73.4% 63.1%; +} + @theme { --breakpoint-*: initial; --breakpoint-sm: 640px; diff --git a/app/materialize/api/client-sdks/page.mdx b/app/materialize/api/client-sdks/page.mdx index d8541972..5f591be5 100644 --- a/app/materialize/api/client-sdks/page.mdx +++ b/app/materialize/api/client-sdks/page.mdx @@ -5,7 +5,7 @@ description: "SDK versions with generated gRPC and protobuf support for the Auth # Client SDKs - + All SpiceDB SDKs have the generated gRPC and protobuf code diff --git a/app/materialize/api/lookup-permission-sets/page.mdx b/app/materialize/api/lookup-permission-sets/page.mdx index 8b4a4ba6..aab23e5c 100644 --- a/app/materialize/api/lookup-permission-sets/page.mdx +++ b/app/materialize/api/lookup-permission-sets/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # LookupPermissionSets - + This API complements [WatchPermissionSets]. When you first bring on a system that needs permissions data, [LookupPermissionSets] lets you create an initial snapshot of the permissions data, and then you can use the [WatchPermissionSets] API to keep the snapshot updated. diff --git a/app/materialize/api/watch-permission-sets/page.mdx b/app/materialize/api/watch-permission-sets/page.mdx index 4bdfd0a2..3542fa39 100644 --- a/app/materialize/api/watch-permission-sets/page.mdx +++ b/app/materialize/api/watch-permission-sets/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # WatchPermissionSets - + This is an update stream of all the permissions Materialize is configured to watch. You can use this to store all permissions tracked in the system closer to your application database to be used in database-native ACL filtering. diff --git a/app/materialize/concepts/managing-client-state/page.mdx b/app/materialize/concepts/managing-client-state/page.mdx index 39b65bf9..41a7d41f 100644 --- a/app/materialize/concepts/managing-client-state/page.mdx +++ b/app/materialize/concepts/managing-client-state/page.mdx @@ -5,7 +5,7 @@ description: "State diagram of the transitions a client application moves throug # Managing Client State - + This diagram shows the various states your client application will need to transition through when calling the [LookupPermissionSets] and the [WatchPermissionSets] APIs. diff --git a/app/materialize/concepts/permission-set-lifecycle/page.mdx b/app/materialize/concepts/permission-set-lifecycle/page.mdx index b9dd16a7..3da59617 100644 --- a/app/materialize/concepts/permission-set-lifecycle/page.mdx +++ b/app/materialize/concepts/permission-set-lifecycle/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # The permission set lifecycle - + The **permission set lifecycle** describes how your application's local copy of permission data is populated, kept current, and rebuilt over time — from an empty index to a complete, queryable [Permission Sets](./permission-sets) store, and back to a fresh index when your schema changes. diff --git a/app/materialize/concepts/permission-sets/page.mdx b/app/materialize/concepts/permission-sets/page.mdx index 613266c8..cbfe1603 100644 --- a/app/materialize/concepts/permission-sets/page.mdx +++ b/app/materialize/concepts/permission-sets/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # Permission Sets - + A **permission set** is the unit of precomputed authorization data that Materialize produces. Where SpiceDB answers a permission question on demand by walking the relationship graph, Materialize continuously **precomputes** the membership of the permissions you configure it to watch and exposes that denormalized data to your application. diff --git a/app/materialize/concepts/snapshots/page.mdx b/app/materialize/concepts/snapshots/page.mdx index 3ac5034c..fa915947 100644 --- a/app/materialize/concepts/snapshots/page.mdx +++ b/app/materialize/concepts/snapshots/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # Snapshots - + A **snapshot** is a point-in-time, internally consistent view of every [Permission Set](./permission-sets) Materialize is tracking, computed at a specific SpiceDB revision. Every piece of permission data Materialize hands you is anchored to the revision (`ZedToken`) it was computed at — that revision _is_ the snapshot's identity. diff --git a/app/materialize/getting-started/overview/page.mdx b/app/materialize/getting-started/overview/page.mdx index 19e53d0a..1ac7bf76 100644 --- a/app/materialize/getting-started/overview/page.mdx +++ b/app/materialize/getting-started/overview/page.mdx @@ -11,7 +11,7 @@ AuthZed Materialize takes inspiration from the Leopard index component described Much like the concept of a materialized view in relational databases, AuthZed Materialize is a service that you configure with a list of permissions that you want it to precompute, and it will calculate how those permissions change after relationships are written (specifically, when those relationships affect a subject's membership in a permission set or a set's permission on a specific resource), or when a new schema is written. -Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which also shows its release status. +Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which links back to the feature it belongs to. ## Accelerated Queries diff --git a/app/materialize/guides/recommended-architecture/page.mdx b/app/materialize/guides/recommended-architecture/page.mdx index 6493514e..8729e8fe 100644 --- a/app/materialize/guides/recommended-architecture/page.mdx +++ b/app/materialize/guides/recommended-architecture/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # Recommended Architecture - + ## Consuming Client diff --git a/app/materialize/guides/relational-database/page.mdx b/app/materialize/guides/relational-database/page.mdx index b562b5e0..4d7e53ec 100644 --- a/app/materialize/guides/relational-database/page.mdx +++ b/app/materialize/guides/relational-database/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # Syncing to a Relational Database - + Just as with relational database materialized views, you need to provide Materialize with the "queries" you'd like it to pre-compute. The configuration is described as a list of `resource#permission@subject` tuples. diff --git a/components/docs-home.css b/components/docs-home.css index 4ddb1c97..ed9e891d 100644 --- a/components/docs-home.css +++ b/components/docs-home.css @@ -25,22 +25,9 @@ --stone-900: 300 24% 94%; --stone-950: 300 30% 96%; --stone-975: 300 40% 98%; - /* ---- Accents — LIGHT (darker shades so they read on a light ground) ---- */ - --magenta-300: 316 50% 45%; - --magenta-400: 316 48% 47%; - --magenta-500: 316 50% 44%; - --magenta-600: 316 52% 40%; - --magenta-950: 315 33.3% 7.1%; - --red-400: 352 70% 48%; - --teal-300: 178 40% 32%; - --teal-400: 178 38% 35%; - --teal-500: 178 35% 33%; - --teal-700: 178 30% 26%; - --violet-400: 253 60% 52%; - --violet-500: 253 62% 50%; - --violet-700: 255 50% 46%; - --sand-300: 30 75% 42%; - --blue-500: 200 75% 40%; + /* ---- Accents (magenta/red/teal/violet/sand/blue) now live site-wide on + `:root` + `html.dark` in app/globals.css, so components outside this page + can use them too. Both light and dark values moved together. ---- */ /* ---- Shared structural tokens ---- */ --gradient-brand-warm: linear-gradient( to right, @@ -81,21 +68,7 @@ html.dark .docs-home { --stone-900: 278 28.6% 11%; --stone-950: 280 36.6% 8%; --stone-975: 282 50% 3.9%; - --magenta-300: 318 41.2% 70%; - --magenta-400: 317 40.2% 60%; - --magenta-500: 316 39.2% 51%; - --magenta-600: 316 42% 45%; - --magenta-950: 315 33.3% 7.1%; - --red-400: 355 91% 73.9%; - --teal-300: 175 27.5% 72.9%; - --teal-400: 176 28% 56%; - --teal-500: 177 28.2% 45.9%; - --teal-700: 178 30% 30%; - --violet-400: 253 76.5% 70%; - --violet-500: 253 73.4% 63.1%; - --violet-700: 255 58% 43.9%; - --sand-300: 28 100% 72%; - --blue-500: 195 73.4% 63.1%; + /* Dark accents also moved to app/globals.css (`html.dark`). */ background: radial-gradient(120% 80% at 88% -10%, hsl(var(--magenta-950) / 0.55), transparent 60%), diff --git a/components/feature-badge.css b/components/feature-badge.css index cef07624..486016c5 100644 --- a/components/feature-badge.css +++ b/components/feature-badge.css @@ -5,13 +5,21 @@ resemblance, even though that component's semantics (content lifecycle: ready/draft/deprecated/under-review) differ from ours (feature identity + release maturity). Colors are teal (Accelerated Queries) and violet - (Event Streams), light/dark-adjusted the same way docs-home.css already - tunes those tokens for this site's two themes. */ + (Event Streams), read from the shared Sandworm accent tokens in + app/globals.css so they can't drift from the rest of the site. */ + +/* The row owns the vertical rhythm so two pills (Snapshots) sit on one line + without doubling the margin between them. */ +.feature-badge-row { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 1.5rem 0 1.75rem; +} .feature-badge { display: inline-block; - margin: 1.5rem 0 1.75rem; - padding: 0.25rem 0.5rem; + padding: 0.25rem 0.75rem; border: 1px solid; border-radius: 9999px; font-size: 0.75rem; @@ -19,24 +27,36 @@ text-decoration: none; } -.feature-badge-accelerated-queries { - color: hsl(178 35% 33%); - background: hsl(178 35% 33% / 0.1); - border-color: hsl(178 35% 33% / 0.5); +/* Points at the early-access note in the TOC rail. Nudged up rather than set + as so it doesn't stretch the pill's line box. */ +.feature-badge-mark { + display: inline-block; + margin-left: 0.15em; + font-size: 0.9em; + line-height: 1; + vertical-align: 0.28em; } -html.dark .feature-badge-accelerated-queries { - color: hsl(177 28.2% 45.9%); - background: hsl(177 28.2% 45.9% / 0.1); - border-color: hsl(177 28.2% 45.9% / 0.5); + +.feature-badge-sr { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; } -.feature-badge-event-streams { - color: hsl(253 62% 50%); - background: hsl(253 62% 50% / 0.1); - border-color: hsl(253 62% 50% / 0.5); +/* No light/dark variants needed — --teal-500 and --violet-500 flip themselves + under html.dark (app/globals.css). */ +.feature-badge-accelerated-queries { + color: hsl(var(--teal-500)); + background: hsl(var(--teal-500) / 0.1); + border-color: hsl(var(--teal-500) / 0.5); } -html.dark .feature-badge-event-streams { - color: hsl(253 73.4% 63.1%); - background: hsl(253 73.4% 63.1% / 0.1); - border-color: hsl(253 73.4% 63.1% / 0.5); + +.feature-badge-event-streams { + color: hsl(var(--violet-500)); + background: hsl(var(--violet-500) / 0.1); + border-color: hsl(var(--violet-500) / 0.5); } diff --git a/components/feature-badge.tsx b/components/feature-badge.tsx index b27767e1..98fbfc5e 100644 --- a/components/feature-badge.tsx +++ b/components/feature-badge.tsx @@ -1,29 +1,46 @@ +"use client"; + import "./feature-badge.css"; import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { FEATURES, EARLY_ACCESS, featuresForPath } from "@/lib/materialize-features"; +import { FeatureNotes } from "./feature-notes"; + +/* Marks which Materialize feature a page belongs to. Used in MDX as a bare + right under the H1 (mdx-components.ts) — the feature is + read from the route via lib/materialize-features.ts, so a page that spans + both features (Snapshots) renders both pills without repeating itself. -const FEATURES = { - "accelerated-queries": { - label: "Accelerated Queries", - status: "Early Access", - href: "/materialize/getting-started/overview#accelerated-queries", - }, - "event-streams": { - label: "Event Streams", - status: "Early Access", - href: "/materialize/getting-started/overview#event-streams", - }, -} as const; + Identity, not status: a pill has no room to say what "Early Access" means + or who to contact, so it carries an asterisk instead, and the note in the + TOC rail (feature-notes.tsx) tells that story in the matching colour. */ +export function FeatureBadge() { + const features = featuresForPath(usePathname()); + if (features.length === 0) return null; -/* Marks which Materialize feature a page belongs to, and that feature's - release status (an intrinsic property of the feature, not of the page). - Used in MDX as right under the - H1 (mdx-components.ts). Links back to that feature's definition on the - Overview page. */ -export function FeatureBadge({ feature }: { feature: keyof typeof FEATURES }) { - const { label, status, href } = FEATURES[feature]; return ( - - {label} · {status} - + <> + + {features.map((key) => { + const { label, href } = FEATURES[key]; + return ( + + {label} + {EARLY_ACCESS[key] && ( + <> + + (in early access) + + )} + + ); + })} + + {/* Shown only below Nextra's TOC breakpoint, where the rail copy is + hidden and the asterisk would otherwise point at nothing. */} + + ); } diff --git a/components/feature-notes.css b/components/feature-notes.css new file mode 100644 index 00000000..be566688 --- /dev/null +++ b/components/feature-notes.css @@ -0,0 +1,87 @@ +/* Early-access note, tinted with the same Sandworm accent token as the pill it + belongs to (app/globals.css), so the pill's asterisk and this note read as + one marker. Deliberately quieter than a Nextra : a hairline rule + rather than a filled box. */ + +.feature-notes { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +/* Exactly one placement is ever visible. Nextra hides the TOC column below + 80rem (`x:max-xl:hidden` in nextra-theme-docs/style.css) — matching that + number here is what keeps the asterisk from pointing at nothing on a + laptop. If Nextra ever moves its breakpoint, this has to move with it. */ +.feature-notes-rail { + margin-top: 1.5rem; +} +@media (width < 80rem) { + .feature-notes-rail { + display: none; + } +} + +.feature-notes-inline { + margin: -0.75rem 0 1.75rem; + max-width: 46rem; +} +@media (width >= 80rem) { + .feature-notes-inline { + display: none; + } +} + +.feature-note { + border-left: 2px solid; + padding: 0.125rem 0 0.125rem 0.625rem; +} + +.feature-note-head { + display: flex; + align-items: baseline; + gap: 0.2em; + margin: 0 0 0.15rem; + font-size: 0.75rem; + font-weight: 500; + line-height: 1.35; +} + +.feature-note-mark { + font-size: 0.9em; + line-height: 1; +} + +.feature-note-body { + margin: 0; + font-size: 0.6875rem; + line-height: 1.5; + color: #6b7280; +} +html.dark .feature-note-body { + color: #9ca3af; +} + +.feature-note-body a { + color: inherit; + text-decoration: underline; + text-underline-offset: 2px; +} +.feature-note-body a:hover { + text-decoration-thickness: 2px; +} + +/* Colour identity — matches .feature-badge-* one for one. */ +.feature-note-accelerated-queries { + border-color: hsl(var(--teal-500) / 0.5); +} +.feature-note-accelerated-queries .feature-note-head { + color: hsl(var(--teal-500)); +} + +.feature-note-event-streams { + border-color: hsl(var(--violet-500) / 0.5); +} +.feature-note-event-streams .feature-note-head { + color: hsl(var(--violet-500)); +} diff --git a/components/feature-notes.tsx b/components/feature-notes.tsx new file mode 100644 index 00000000..21b5d5c4 --- /dev/null +++ b/components/feature-notes.tsx @@ -0,0 +1,49 @@ +"use client"; + +import "./feature-notes.css"; +import { usePathname } from "next/navigation"; +import { FEATURES, EARLY_ACCESS, featuresForPath } from "@/lib/materialize-features"; + +const DEDICATED = "https://authzed.com/docs/authzed/guides/picking-a-product#dedicated"; + +/* The early-access story for whichever feature(s) the current page documents — + what the asterisk on the pill means. Renders nothing outside Materialize. + + Rendered TWICE, in two placements, with CSS showing exactly one: + + rail — docked in the TOC column, the preferred home + inline — directly under the pills, for narrow viewports + + Nextra hides the TOC below 80rem (`x:max-xl:hidden`), so on a laptop or + phone the rail copy is gone and the asterisk would point at nothing. The + inline copy takes over at exactly that breakpoint. Both are in the DOM; + `display: none` keeps the hidden one out of the accessibility tree too. + + Tinted with the same feature token as its pill (app/globals.css) so the + pill and the note read as one marker, not two unrelated notices. */ +export function FeatureNotes({ placement }: { placement: "rail" | "inline" }) { + const features = featuresForPath(usePathname()).filter((key) => EARLY_ACCESS[key]); + if (features.length === 0) return null; + + return ( +
+ {features.map((key) => ( + + ))} +
+ ); +} diff --git a/components/toc-extra-content.tsx b/components/toc-extra-content.tsx index 5128bd01..f243717d 100644 --- a/components/toc-extra-content.tsx +++ b/components/toc-extra-content.tsx @@ -1,9 +1,13 @@ import { Feedback } from "@/components/feedback"; import { TocCTA } from "@/components/cta"; +import { FeatureNotes } from "@/components/feature-notes"; export function TocExtraContent() { return ( <> + {/* Page-specific, so it sits above the generic Cloud prompt. Renders + nothing outside the Materialize section. */} + diff --git a/lib/materialize-features.ts b/lib/materialize-features.ts new file mode 100644 index 00000000..cd7b63d6 --- /dev/null +++ b/lib/materialize-features.ts @@ -0,0 +1,48 @@ +/* Which Materialize feature each page documents. + + Single source of truth for both markers: the inline pill under the H1 + (components/feature-badge.tsx) and the early-access note in the TOC rail + (components/feature-notes.tsx). The rail lives in a global Nextra slot + that can't see page props, so both read the route instead — that way the + pill and the note can never disagree about what a page belongs to. + + Paths are basePath-relative: usePathname() strips NEXT_PUBLIC_BASE_DIR. */ + +export const FEATURES = { + "accelerated-queries": { + label: "Accelerated Queries", + href: "/materialize/getting-started/overview#accelerated-queries", + }, + "event-streams": { + label: "Event Streams", + href: "/materialize/getting-started/overview#event-streams", + }, +} as const; + +export type FeatureKey = keyof typeof FEATURES; + +/* Every feature is in early access today, so the note's text doesn't vary. + It's per-feature anyway because the note is colour-matched to its pill — + and because these will go GA on different dates. */ +export const EARLY_ACCESS: Record = { + "accelerated-queries": true, + "event-streams": true, +}; + +const PAGE_FEATURES: Record = { + "/materialize/api/client-sdks": ["event-streams"], + "/materialize/api/lookup-permission-sets": ["event-streams"], + "/materialize/api/watch-permission-sets": ["event-streams"], + "/materialize/concepts/managing-client-state": ["event-streams"], + "/materialize/concepts/permission-set-lifecycle": ["event-streams"], + "/materialize/concepts/permission-sets": ["event-streams"], + "/materialize/concepts/snapshots": ["accelerated-queries", "event-streams"], + "/materialize/guides/recommended-architecture": ["event-streams"], + "/materialize/guides/relational-database": ["event-streams"], +}; + +export function featuresForPath(pathname: string | null): readonly FeatureKey[] { + if (!pathname) return []; + const clean = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname; + return PAGE_FEATURES[clean] ?? []; +} From 74b7d042d4d19de4c03b2527ca0f413ebfbf533d Mon Sep 17 00:00:00 2001 From: Catherine Chambers Date: Mon, 20 Jul 2026 15:45:56 -0400 Subject: [PATCH 4/7] Add Hydration and Watched Permissions concept pages, architecture diagram - New concept pages: Hydration and Watched Permissions, grounded in the Tiger source (../internal/tiger) - Overview page: adds a system architecture diagram showing the services behind Materialize (Snapshotter, Hydrator, Datastore, SpiceDB, Cache Server, Event Log) and how they connect to a customer's own systems - Renamed "Consuming Client" to "Event Processor" (guide + diagram) to avoid confusion with a SpiceDB client - Recolored the Accelerated Queries badge/diagram accent from teal to blue for better legibility, still sourced from the authzed.com palette - Flagged the new/changed pages as "Needs Review" in the sidebar nav Co-Authored-By: Claude Sonnet 5 --- app/materialize/concepts/_meta.ts | 2 + app/materialize/concepts/hydration/page.mdx | 26 ++++ .../concepts/permission-sets/page.mdx | 2 +- app/materialize/concepts/snapshots/page.mdx | 2 +- .../concepts/watched-permissions/page.mdx | 53 +++++++ app/materialize/getting-started/_meta.ts | 2 +- .../guides/recommended-architecture/page.mdx | 4 +- .../guides/relational-database/page.mdx | 11 +- public/images/materialize-architecture.svg | 130 ++++++++++++++++++ 9 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 app/materialize/concepts/hydration/page.mdx create mode 100644 app/materialize/concepts/watched-permissions/page.mdx create mode 100644 public/images/materialize-architecture.svg diff --git a/app/materialize/concepts/_meta.ts b/app/materialize/concepts/_meta.ts index 46b3f58f..b6872266 100644 --- a/app/materialize/concepts/_meta.ts +++ b/app/materialize/concepts/_meta.ts @@ -1,5 +1,7 @@ export default { + "watched-permissions": "Watched Permissions (Needs Review)", snapshots: "Snapshots", + hydration: "Hydration (Needs Review)", "permission-sets": "Permission Sets", "permission-set-lifecycle": "Permission Set Lifecycle", "managing-client-state": "Managing Client State", diff --git a/app/materialize/concepts/hydration/page.mdx b/app/materialize/concepts/hydration/page.mdx new file mode 100644 index 00000000..f47221a6 --- /dev/null +++ b/app/materialize/concepts/hydration/page.mdx @@ -0,0 +1,26 @@ +--- +title: "Hydration" +description: "Hydration is how Materialize turns your SpiceDB schema and relationships into precomputed permission sets, computing only what's reachable from the permissions you've configured it to watch." +--- + +# Hydration + + + +**Hydration** is the process Materialize uses to turn your SpiceDB schema and relationship data into the precomputed [permission sets](./permission-sets) that both Materialize features depend on: [Accelerated Queries](../getting-started/overview#accelerated-queries) reads hydrated data directly to answer checks and lookups, and [Event Streams](../getting-started/overview#event-streams) exposes that same hydrated data through `LookupPermissionSets` and `WatchPermissionSets`. + +## Only what's reachable + +Hydration doesn't compute every permission your schema could produce, only the [permissions you've configured Materialize to watch](./watched-permissions), plus everything those permissions structurally depend on through unions, intersections, exclusions, and arrows. This **reachability** is a pure function of your schema and your watched permissions, so Materialize computes it once per hydration run and reuses it throughout. + +This is also why the cost of hydration tracks the size of the reachable graph, not the size of your whole schema: watching a permission with a much wider reach, or adding a new watched permission, can noticeably change how much work hydration has to do. + +## Offline hydration vs. online hydration + +- **Offline hydration** reads a full [snapshot](./snapshots) of your relationship data and computes every reachable permission set from scratch. This is what backs the initial [backfill](./permission-set-lifecycle). +- **Online hydration** applies as new relationship and schema changes arrive after that initial pass, updating only the permission sets a given change actually affects, in the order the changes occurred. + +## When rehydration is required + +A [breaking schema change](./snapshots#snapshot-lifecycle-events) invalidates previously hydrated permission sets, since the reachability computed from the old schema no longer holds. +Materialize signals this so you can re-run the [backfill](./permission-set-lifecycle) at the new revision — see [The Permission Set Lifecycle](./permission-set-lifecycle) for the full rebuild flow. diff --git a/app/materialize/concepts/permission-sets/page.mdx b/app/materialize/concepts/permission-sets/page.mdx index cbfe1603..83baa1e2 100644 --- a/app/materialize/concepts/permission-sets/page.mdx +++ b/app/materialize/concepts/permission-sets/page.mdx @@ -10,7 +10,7 @@ import { Callout } from "nextra/components"; A **permission set** is the unit of precomputed authorization data that Materialize produces. -Where SpiceDB answers a permission question on demand by walking the relationship graph, Materialize continuously **precomputes** the membership of the permissions you configure it to watch and exposes that denormalized data to your application. +Where SpiceDB answers a permission question on demand by walking the relationship graph, Materialize continuously [**hydrates**](./hydration) the membership of the [permissions you configure it to watch](./watched-permissions) and exposes that denormalized data to your application. Conceptually, a permission set captures two kinds of edges: diff --git a/app/materialize/concepts/snapshots/page.mdx b/app/materialize/concepts/snapshots/page.mdx index fa915947..bc13d7cd 100644 --- a/app/materialize/concepts/snapshots/page.mdx +++ b/app/materialize/concepts/snapshots/page.mdx @@ -12,7 +12,7 @@ import { Callout } from "nextra/components"; A **snapshot** is a point-in-time, internally consistent view of every [Permission Set](./permission-sets) Materialize is tracking, computed at a specific SpiceDB revision. Every piece of permission data Materialize hands you is anchored to the revision (`ZedToken`) it was computed at — that revision _is_ the snapshot's identity. -Snapshots underpin both Materialize features: they're what Materialize hydrates in order to answer Accelerated Queries, and they're what make the Event Streams APIs compose safely: +Snapshots underpin both Materialize features: they're what Materialize [hydrates](./hydration) in order to answer Accelerated Queries, and they're what make the Event Streams APIs compose safely: - Materialize reads the current snapshot directly to answer Accelerated Queries like `CheckPermission`, `CheckBulkPermissions`, `LookupResources`, and `LookupSubjects`. - [The initial backfill](./permission-set-lifecycle) reads a snapshot in full. diff --git a/app/materialize/concepts/watched-permissions/page.mdx b/app/materialize/concepts/watched-permissions/page.mdx new file mode 100644 index 00000000..ec08d0c3 --- /dev/null +++ b/app/materialize/concepts/watched-permissions/page.mdx @@ -0,0 +1,53 @@ +--- +title: "Watched Permissions" +description: "A watched permission is a resource#permission@subject tuple that tells Materialize which permission to precompute — the configuration that drives hydration and everything built on top of it." +--- + +import { Callout } from "nextra/components"; + +# Watched Permissions + + + +A **watched permission** is the unit of configuration for Materialize: a `resource#permission@subject` tuple that tells Materialize which permission, for which subject type, to precompute. +Everything Materialize does starts from the list of watched permissions you configure. + +## Format + +A watched permission is written the same way you'd write a relationship or a check, minus the specific object IDs: + +```zed +resource#view@user +resource#edit@user +``` + +Each tuple names: + +- The **resource type** holding the permission (`resource`) +- The **permission or relation** to watch (`view`, `edit`) +- The **subject type** Materialize should compute membership for (`user`) + +If you only care about a specific relation on the subject — for example, `group#member` rather than the group itself — you can specify that too: `resource#view@group#member`. + +## What watching a permission actually does + +Materialize doesn't precompute your whole schema — only the watched permissions and whatever they structurally reach through unions, intersections, exclusions, and arrows. That reachable set is exactly what [hydration](./hydration) computes and keeps up to date. + +Because reachability is a pure function of your schema and your watched permissions, adding a new watched permission — or watching a permission with a much wider reach through your schema — extends that graph, and with it, the amount of work hydration has to do. + +## Constraints + +A watched permission must reference real schema elements: an existing resource type, an existing permission or relation on it, and an existing subject type. Materialize validates this when you configure it and will reject a permission that doesn't resolve. + +The permission's path also can't cross any of the schema features Materialize doesn't support. See [Limitations](../getting-started/limitations) for the full list. + +## Configuring watched permissions + + + During early access, work with your AuthZed account team ro configure you list of watched + permissions. + + +[Caveats]: https://authzed.com/docs/spicedb/concepts/caveats +[Wildcard]: https://authzed.com/docs/spicedb/concepts/schema#wildcards +[.all intersections]: https://authzed.com/docs/spicedb/concepts/schema#all-intersection-arrow diff --git a/app/materialize/getting-started/_meta.ts b/app/materialize/getting-started/_meta.ts index 3a8ce84f..4ddfd690 100644 --- a/app/materialize/getting-started/_meta.ts +++ b/app/materialize/getting-started/_meta.ts @@ -1,4 +1,4 @@ export default { - overview: "Overview", + overview: "Overview (Needs Review)", limitations: "Limitations", }; diff --git a/app/materialize/guides/recommended-architecture/page.mdx b/app/materialize/guides/recommended-architecture/page.mdx index 8729e8fe..c43fb54f 100644 --- a/app/materialize/guides/recommended-architecture/page.mdx +++ b/app/materialize/guides/recommended-architecture/page.mdx @@ -9,11 +9,11 @@ import { Callout } from "nextra/components"; -## Consuming Client +## Event Processor ![authzed-materialize](/images/authzed-materialize.png) -Customers will need to build a client to act as an "event processor" that consumes permission updates and writes those updates to a datastore like Postgres. +Customers will need to build a client to act as an event processor that consumes permission updates and writes those updates to a datastore like Postgres. The consumer should be designed with resumability in mind by keeping track of the last revision consumed, just as any other stream processor. ## Durability diff --git a/app/materialize/guides/relational-database/page.mdx b/app/materialize/guides/relational-database/page.mdx index 4d7e53ec..2ede8918 100644 --- a/app/materialize/guides/relational-database/page.mdx +++ b/app/materialize/guides/relational-database/page.mdx @@ -3,26 +3,17 @@ title: "Syncing to a Relational Database" description: "Store Materialize permission sets in member_to_set and set_to_set tables and use SQL joins for authorization-aware filtering, with worked examples." --- -import { Callout } from "nextra/components"; - # Syncing to a Relational Database -Just as with relational database materialized views, you need to provide Materialize with the "queries" you'd like it to pre-compute. -The configuration is described as a list of `resource#permission@subject` tuples. -Example: +Materialize is configured with a list of [watched permissions](../concepts/watched-permissions) — the `resource#permission@subject` tuples it pre-computes. This guide assumes you've already configured watched permissions like: ```zed resource#view@user resource#edit@user ``` - - During early access provisioning, Materialize instances are not self-service, so you'll need to - provide the permissions to be computed by Materialize directly to your AuthZed account team. - - ## Relational Database You can find a runnable version of these examples [here](https://dbfiddle.uk/dX10Cu3Z). diff --git a/public/images/materialize-architecture.svg b/public/images/materialize-architecture.svg new file mode 100644 index 00000000..36073bdf --- /dev/null +++ b/public/images/materialize-architecture.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + AuthZed Materialize + + + Your Systems + + + + + Snapshotter + periodic snapshots + of schema + relationships + + + + + Hydrator + computes permission sets + for watched permissions + + + + + Datastore + underlying storage + + + + + SpiceDB + the service + + + + + Cache Server + serves hydrated + permission sets + + + + + Event Log + persists permission set changes + + + + + + Your Application + calls SpiceDB exactly as + it always has + + + + + Event Processor + backfills + watches + permission sets + + + + + Your Database + or search index + (colocated permissions) + + + + + + schema + + relationships + + + + snapshot + + + + reads / writes + + + + Accelerated + Queries + (dispatch) + + + + hydrated permission sets + + + + watches datastore directly (real-time updates) + + + + permission set + changes + + + + checks & lookups (e.g. CheckPermission) + + + + Event Streams + + + + stores permission + sets + From dfdbde83e46ba969c1ae75b59803c6a05d4a09be Mon Sep 17 00:00:00 2001 From: Catherine Chambers Date: Tue, 21 Jul 2026 13:56:28 -0400 Subject: [PATCH 5/7] Simplified badges per Corey's work. Added a new architecture diagram to the overview and some improvements to the concept pages --- .gitignore | 3 + app/materialize/concepts/hydration/page.mdx | 4 +- .../concepts/watched-permissions/page.mdx | 10 +- .../getting-started/overview/page.mdx | 9 +- lib/changed-pages.json | 6 + lib/materialize-features.ts | 2 + public/images/materialize-architecture.svg | 172 +++++++++--------- 7 files changed, 114 insertions(+), 92 deletions(-) diff --git a/.gitignore b/.gitignore index ebae1a3d..693c8837 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ public/robots.txt public/sitemap-0.xml public/sitemap.xml public/feed.json + +# macOS +.DS_Store diff --git a/app/materialize/concepts/hydration/page.mdx b/app/materialize/concepts/hydration/page.mdx index f47221a6..d570fb57 100644 --- a/app/materialize/concepts/hydration/page.mdx +++ b/app/materialize/concepts/hydration/page.mdx @@ -5,7 +5,7 @@ description: "Hydration is how Materialize turns your SpiceDB schema and relatio # Hydration - + **Hydration** is the process Materialize uses to turn your SpiceDB schema and relationship data into the precomputed [permission sets](./permission-sets) that both Materialize features depend on: [Accelerated Queries](../getting-started/overview#accelerated-queries) reads hydrated data directly to answer checks and lookups, and [Event Streams](../getting-started/overview#event-streams) exposes that same hydrated data through `LookupPermissionSets` and `WatchPermissionSets`. @@ -23,4 +23,4 @@ This is also why the cost of hydration tracks the size of the reachable graph, n ## When rehydration is required A [breaking schema change](./snapshots#snapshot-lifecycle-events) invalidates previously hydrated permission sets, since the reachability computed from the old schema no longer holds. -Materialize signals this so you can re-run the [backfill](./permission-set-lifecycle) at the new revision — see [The Permission Set Lifecycle](./permission-set-lifecycle) for the full rebuild flow. +Materialize signals this so you can re-run the [backfill](./permission-set-lifecycle) at the new revision. See [The Permission Set Lifecycle](./permission-set-lifecycle) for the full rebuild flow. diff --git a/app/materialize/concepts/watched-permissions/page.mdx b/app/materialize/concepts/watched-permissions/page.mdx index ec08d0c3..b22ca832 100644 --- a/app/materialize/concepts/watched-permissions/page.mdx +++ b/app/materialize/concepts/watched-permissions/page.mdx @@ -7,7 +7,7 @@ import { Callout } from "nextra/components"; # Watched Permissions - + A **watched permission** is the unit of configuration for Materialize: a `resource#permission@subject` tuple that tells Materialize which permission, for which subject type, to precompute. Everything Materialize does starts from the list of watched permissions you configure. @@ -27,13 +27,13 @@ Each tuple names: - The **permission or relation** to watch (`view`, `edit`) - The **subject type** Materialize should compute membership for (`user`) -If you only care about a specific relation on the subject — for example, `group#member` rather than the group itself — you can specify that too: `resource#view@group#member`. +If you only care about a specific relation on the subject (for example, `group#member` rather than the group itself) you can specify that too: `resource#view@group#member`. ## What watching a permission actually does -Materialize doesn't precompute your whole schema — only the watched permissions and whatever they structurally reach through unions, intersections, exclusions, and arrows. That reachable set is exactly what [hydration](./hydration) computes and keeps up to date. +Materialize doesn't precompute your whole schema. It only tracks your watched permissions and whatever they structurally reach through unions, intersections, exclusions, and arrows. That reachable set is exactly what [hydration](./hydration) computes and keeps up to date. -Because reachability is a pure function of your schema and your watched permissions, adding a new watched permission — or watching a permission with a much wider reach through your schema — extends that graph, and with it, the amount of work hydration has to do. +Because reachability is a pure function of your schema and your watched permissions, adding a new watched permission, or watching a permission with a much wider reach through your schema, extends that graph, and with it, the amount of work hydration has to do. ## Constraints @@ -44,7 +44,7 @@ The permission's path also can't cross any of the schema features Materialize do ## Configuring watched permissions - During early access, work with your AuthZed account team ro configure you list of watched + During early access, work with your AuthZed account team to configure your list of watched permissions. diff --git a/app/materialize/getting-started/overview/page.mdx b/app/materialize/getting-started/overview/page.mdx index 1ac7bf76..8792651d 100644 --- a/app/materialize/getting-started/overview/page.mdx +++ b/app/materialize/getting-started/overview/page.mdx @@ -4,6 +4,7 @@ description: "AuthZed Materialize precomputes the permissions you configure it t --- import { Callout } from "nextra/components"; +import MaterializeArchitecture from "@/public/images/materialize-architecture.svg"; # What is Materialize? @@ -11,7 +12,13 @@ AuthZed Materialize takes inspiration from the Leopard index component described Much like the concept of a materialized view in relational databases, AuthZed Materialize is a service that you configure with a list of permissions that you want it to precompute, and it will calculate how those permissions change after relationships are written (specifically, when those relationships affect a subject's membership in a permission set or a set's permission on a specific resource), or when a new schema is written. -Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features — look for the badge at the top of the page, which links back to the feature it belongs to. +Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features. Look for badges at the top of the page, which link back to the feature it belongs to. + + ## Accelerated Queries diff --git a/lib/changed-pages.json b/lib/changed-pages.json index 9e53a9ea..7bd5d40c 100644 --- a/lib/changed-pages.json +++ b/lib/changed-pages.json @@ -8,6 +8,9 @@ "/materialize/api/watch-permission-sets": { "status": "updated" }, + "/materialize/concepts/hydration": { + "status": "new" + }, "/materialize/concepts/managing-client-state": { "status": "updated" }, @@ -20,6 +23,9 @@ "/materialize/concepts/snapshots": { "status": "updated" }, + "/materialize/concepts/watched-permissions": { + "status": "new" + }, "/materialize/getting-started/overview": { "status": "updated" }, diff --git a/lib/materialize-features.ts b/lib/materialize-features.ts index cd7b63d6..fa29b5b9 100644 --- a/lib/materialize-features.ts +++ b/lib/materialize-features.ts @@ -37,6 +37,8 @@ const PAGE_FEATURES: Record = { "/materialize/concepts/permission-set-lifecycle": ["event-streams"], "/materialize/concepts/permission-sets": ["event-streams"], "/materialize/concepts/snapshots": ["accelerated-queries", "event-streams"], + "/materialize/concepts/hydration": ["accelerated-queries", "event-streams"], + "/materialize/concepts/watched-permissions": ["accelerated-queries", "event-streams"], "/materialize/guides/recommended-architecture": ["event-streams"], "/materialize/guides/relational-database": ["event-streams"], }; diff --git a/public/images/materialize-architecture.svg b/public/images/materialize-architecture.svg index 36073bdf..40f17d71 100644 --- a/public/images/materialize-architecture.svg +++ b/public/images/materialize-architecture.svg @@ -1,130 +1,134 @@ - + - - + + - + - - AuthZed Materialize + + AuthZed Materialize - - Your Systems + + SpiceDB - + + Your Systems + + + - - Snapshotter - periodic snapshots - of schema + relationships + + Hydrator + computes permission sets + for watched permissions - - Hydrator - computes permission sets - for watched permissions + + Cache Server + serves hydrated + permission sets - - Datastore - underlying storage + + Snapshotter + periodic snapshots + of schema + relationships - - SpiceDB - the service + + Event Log + persists permission set changes + - - Cache Server - serves hydrated - permission sets + + SpiceDB + the service - - Event Log - persists permission set changes + + Datastore + underlying storage - - Your Application - calls SpiceDB exactly as - it always has + + Your Application + calls SpiceDB exactly as + it always has - - Event Processor - backfills + watches - permission sets + + Event Processor + backfills + watches + permission sets - - Your Database - or search index - (colocated permissions) + + Your Database + or search index + (colocated permissions) - - - schema + - relationships - - - - snapshot - - - - reads / writes - - - - Accelerated - Queries - (dispatch) - - - - hydrated permission sets - - - - watches datastore directly (real-time updates) - - - - permission set - changes - - - - checks & lookups (e.g. CheckPermission) - - - - Event Streams + + + snapshot + + + + hydrated + permission sets + + + + permission set + changes + + + + reads / writes + + + + schema + relationships + + + + Accelerated + Queries + + + + watches datastore + directly (real-time) + + + + checks & lookups + + + + Event Streams - - stores permission - sets + + stores permission sets From 79b212af7bdfccbf418843a192ee7b5346ef7ec6 Mon Sep 17 00:00:00 2001 From: Catherine Chambers Date: Tue, 21 Jul 2026 14:21:34 -0400 Subject: [PATCH 6/7] fixed whitespace to may lint happy --- app/materialize/getting-started/overview/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/materialize/getting-started/overview/page.mdx b/app/materialize/getting-started/overview/page.mdx index 8792651d..6ea52376 100644 --- a/app/materialize/getting-started/overview/page.mdx +++ b/app/materialize/getting-started/overview/page.mdx @@ -12,7 +12,7 @@ AuthZed Materialize takes inspiration from the Leopard index component described Much like the concept of a materialized view in relational databases, AuthZed Materialize is a service that you configure with a list of permissions that you want it to precompute, and it will calculate how those permissions change after relationships are written (specifically, when those relationships affect a subject's membership in a permission set or a set's permission on a specific resource), or when a new schema is written. -Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features. Look for badges at the top of the page, which link back to the feature it belongs to. +Materialize puts that precomputed data to work in two ways: **Accelerated Queries** and **Event Streams**, both currently in early access. Every other piece of Materialize content belongs to one of these two features. Look for badges at the top of the page, which link back to the feature it belongs to. Date: Tue, 21 Jul 2026 16:01:39 -0400 Subject: [PATCH 7/7] Redraw the Materialize architecture diagram against Sandworm Reworks the diagram to follow AuthZed's Sandworm design system (design/diagrams.md): dashed 7-3 scope containers vs. solid node borders, mono-caps container labels riding the dashed edge, orthogonal connectors with rounded corners, and stone/teal color tokens instead of black/grey. Also fixes several label-overlap and text-wrapping issues found along the way. Co-Authored-By: Claude Sonnet 5 --- public/images/materialize-architecture.svg | 163 +++++++++++---------- 1 file changed, 88 insertions(+), 75 deletions(-) diff --git a/public/images/materialize-architecture.svg b/public/images/materialize-architecture.svg index 40f17d71..61fe3e7f 100644 --- a/public/images/materialize-architecture.svg +++ b/public/images/materialize-architecture.svg @@ -1,134 +1,147 @@ - + + - + - + - + - + - - - AuthZed Materialize + + + + AUTHZED MATERIALIZE - - SpiceDB + + + SPICEDB - - Your Systems + + + YOUR SYSTEMS - - - Hydrator - computes permission sets - for watched permissions + + Hydrator + computes permission sets + for watched permissions - - Cache Server - serves hydrated - permission sets + + Cache Server + serves hydrated + permission sets - - Snapshotter - periodic snapshots - of schema + relationships + + Snapshotter + periodic snapshots of + schema + relationships - - Event Log - persists permission set changes + + Event Log + persists permission + set changes - - SpiceDB - the service + + SpiceDB + the service - - Datastore - underlying storage + + Datastore + underlying storage - - Your Application - calls SpiceDB exactly as - it always has + + Your Application + calls SpiceDB exactly as + it always has - - Event Processor - backfills + watches - permission sets + + Event Processor + backfills + watches + permission sets - - Your Database - or search index - (colocated permissions) + + Your Database + or search index + (colocated permissions) - + - - snapshot + + snapshot - - hydrated - permission sets + + hydrated + permission sets - - permission set - changes + + permission set + changes - - reads / writes + + reads / writes - - - schema + relationships + + + schema + relationships - - - Accelerated - Queries + + + Accelerated + Queries - - - watches datastore - directly (real-time) + + + watches datastore + directly (real-time) - - checks & lookups + + checks + lookups - - - Event Streams + + + Event Streams - - stores permission sets + + stores permission sets