diff --git a/app/_meta.ts b/app/_meta.ts
index 8734c6d1..32c9f01c 100644
--- a/app/_meta.ts
+++ b/app/_meta.ts
@@ -33,4 +33,7 @@ export default {
changes: {
display: "hidden",
},
+ review: {
+ display: "hidden",
+ },
} satisfies MetaRecord;
diff --git a/app/authzed/concepts/audit-logging/page.mdx b/app/authzed/concepts/audit-logging/page.mdx
index 2ad53d6f..694d2ae6 100644
--- a/app/authzed/concepts/audit-logging/page.mdx
+++ b/app/authzed/concepts/audit-logging/page.mdx
@@ -7,7 +7,8 @@ import { Callout, Tabs } from "nextra/components";
# Audit Logging
-Audit Logging is functionality exclusive to AuthZed products that publishes logs of SpiceDB API operations to a log sink.
+Audit Logging publishes logs of SpiceDB API operations to a log sink.
+It ships with AuthZed Cloud, Dedicated, and Enterprise; SpiceDB open source does not include it.
## Log Format
@@ -171,25 +172,31 @@ The process for setting up audit logging varies depending on the AuthZed product
Using the web dashboard, navigate to the Permission System's settings page to find the Audit Log settings.
-### Self-Hosted
+### SpiceDB Enterprise (self-hosted) [#self-hosted]
+
+
+ **AuthZed Enterprise only.** The `--extender-audit-*` flags below are flags on `spicedb serve` in
+ the AuthZed-licensed SpiceDB Enterprise binary. They are not present in [SpiceDB open
+ source](/authzed/guides/picking-a-product#open-source), which has no audit logging.
+
Use the following command-line flags:
-| Flag | Description | Default |
-| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
-| --extender-enabled | must be set to`authzed-audit` | |
-| --extender-audit-batch-size-limit | defines the maximum number of audit events to be processed as a unit | `10000` |
-| --extender-audit-buffer-size | defines the size of the audit log buffer that holds events to be processed by workers | `1000000` |
-| --extender-audit-buffer-window | defines maximum amount of time events are buffered before being pushed | `1s` |
-| --extender-audit-disabled-on-methods strings | list of comma-separated, fully-qualified API methods to disable events for. Watch API is always excluded (e.g. `/authzed.api.v1.PermissionsService/CheckPermission`) | |
-| --extender-audit-initial-retry-interval duration | sets the first retry backoff in case of a failure to push audit events to the backend | `1s` |
-| --extender-audit-max-retry-interval duration | sets the maximum backoff duration in case of failure to push events | `30s` |
-| --extender-audit-retry-randomizer-factor | sets the randomization factor for the backoff duration - this helps prevent thundering herds on event push errors | `0.5` |
-| --extender-audit-stream-name | defines the name of the target stream/topic (e.g. Kafka Topic, Kinesis Stream...) | `spicedb` |
-| --extender-audit-target-configuration | target-type specific configuration | `[]` |
-| --extender-audit-target-endpoint-url string | defines the URL of target endpoint to ingest audit events. If left unspecified, some types will try to determine automatically (e.g. AWS SDK) | |
-| --extender-audit-target-type | defines the type of target to ingest audit events | `noop` |
-| --extender-audit-worker-count | defines the number of worker goroutines to process audit events (default 5) | |
+| Flag | Description | Default |
+| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
+| `--extender-enabled` | must be set to `authzed-audit` | |
+| `--extender-audit-batch-size-limit` | defines the maximum number of audit events to be processed as a unit | `10000` |
+| `--extender-audit-buffer-size` | defines the size of the audit log buffer that holds events to be processed by workers | `1000000` |
+| `--extender-audit-buffer-window` | defines maximum amount of time events are buffered before being pushed | `1s` |
+| `--extender-audit-disabled-on-methods` strings | list of comma-separated, fully-qualified API methods to disable events for. Watch API is always excluded (e.g. `/authzed.api.v1.PermissionsService/CheckPermission`) | |
+| `--extender-audit-initial-retry-interval` duration | sets the first retry backoff in case of a failure to push audit events to the backend | `1s` |
+| `--extender-audit-max-retry-interval` duration | sets the maximum backoff duration in case of failure to push events | `30s` |
+| `--extender-audit-retry-randomizer-factor` | sets the randomization factor for the backoff duration - this helps prevent thundering herds on event push errors | `0.5` |
+| `--extender-audit-stream-name` | defines the name of the target stream/topic (e.g. Kafka Topic, Kinesis Stream...) | `spicedb` |
+| `--extender-audit-target-configuration` | target-type specific configuration | `[]` |
+| `--extender-audit-target-endpoint-url` string | defines the URL of target endpoint to ingest audit events. If left unspecified, some types will try to determine automatically (e.g. AWS SDK) | |
+| `--extender-audit-target-type` | defines the type of target to ingest audit events | `noop` |
+| `--extender-audit-worker-count` | defines the number of worker goroutines to process audit events (default 5) | |
### Tokens in Audit Logs
diff --git a/app/authzed/concepts/private-networking/page.mdx b/app/authzed/concepts/private-networking/page.mdx
index dc8e7d69..796e78f6 100644
--- a/app/authzed/concepts/private-networking/page.mdx
+++ b/app/authzed/concepts/private-networking/page.mdx
@@ -7,7 +7,7 @@ import { Callout } from "nextra/components";
# Private Networking
-Private Networking is functionality exclusive to AuthZed Dedicated that restricts network access to internal networks.
+Private Networking restricts network access to internal networks.
When enabled, connections that are not configured are rejected.
By adding this additional layer of security, entire classes of security risk are eliminated because only trusted networks have access to the software powering your authorization systems.
diff --git a/app/authzed/concepts/restricted-api-access/page.mdx b/app/authzed/concepts/restricted-api-access/page.mdx
index 150b0037..d24dd05c 100644
--- a/app/authzed/concepts/restricted-api-access/page.mdx
+++ b/app/authzed/concepts/restricted-api-access/page.mdx
@@ -7,7 +7,8 @@ import { Callout, Tabs } from "nextra/components";
# Restricted API Access
-Restricted API Access is functionality exclusive to AuthZed products that restricts access to SpiceDB for API Tokens.
+Restricted API Access restricts access to SpiceDB for API Tokens.
+It ships with AuthZed Cloud, Dedicated, and Enterprise; SpiceDB open source does not include it.
This functionality enables organizations to apply the principle of least-privilege to services accessing SpiceDB.
For example, read-only tokens can be created for services that should never need to write to SpiceDB.
@@ -209,15 +210,31 @@ The process for setting up this feature varies depending on the AuthZed product
Using the web dashboard, navigate to the Permission System's "Access" tab.
-### Self-Hosted
+### SpiceDB Enterprise (self-hosted) [#self-hosted]
+
+
+ **AuthZed Enterprise only.** The `--extender-*` flags below are flags on `spicedb serve` in the
+ AuthZed-licensed SpiceDB Enterprise binary. They are not present in [SpiceDB open
+ source](/authzed/guides/picking-a-product#open-source), so passing them to the open source
+ `spicedb` fails with an unknown-flag error.
+
+
+For example:
+
+```sh
+spicedb serve \
+ --grpc-preshared-key "" \
+ --extender-enabled authzed-fgam \
+ --extender-authzed-fgam-endpoint file:///etc/spicedb/fgam.yaml
+```
Use the following command-line flags:
-| Flag | Description | Default |
-| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
-| --extender-authzed-fgam-endpoint | defines the external SpiceDB endpoint used to authorize operations for the authzed-fgam extender. If a file:// endpoint is provided, server is run embedded with static configuration | |
-| --extender-authzed-fgam-preshared-key | defines the external SpiceDB preshared key used to authorize operations for the authzed-fgam extender. Ignored if endpoint is local (file://) | |
-| --extender-enabled | must be set to `authzed-fgam` | |
+| Flag | Description | Default |
+| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
+| `--extender-authzed-fgam-endpoint` | defines the external SpiceDB endpoint used to authorize operations for the authzed-fgam extender. If a file:// endpoint is provided, server is run embedded with static configuration | |
+| `--extender-authzed-fgam-preshared-key` | defines the external SpiceDB preshared key used to authorize operations for the authzed-fgam extender. Ignored if endpoint is local (file://) | |
+| `--extender-enabled` | must be set to `authzed-fgam` | |
If you set `--extender-authzed-fgam-endpoint` to a file, it must be a YAML configuration file.
diff --git a/app/authzed/concepts/workload-isolation/page.mdx b/app/authzed/concepts/workload-isolation/page.mdx
index 718846a9..f5c1bc92 100644
--- a/app/authzed/concepts/workload-isolation/page.mdx
+++ b/app/authzed/concepts/workload-isolation/page.mdx
@@ -7,7 +7,7 @@ import YouTube from "@/components/youtube-wrapper";
# Workload Isolation
-Workload Isolation is functionality exclusive to AuthZed products by which we give your SpiceDB deployments access to hardware that is only used by you, to guarantee performance and prevent "noisy neighbor" problems.
+Workload Isolation gives your SpiceDB deployments access to hardware that is only used by you, to guarantee performance and prevent "noisy neighbor" problems.
## Control Plane
diff --git a/app/globals.css b/app/globals.css
index a130fd32..7b1236d1 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -289,3 +289,150 @@ html.dark-mode {
display: inline-flex;
}
}
+
+/* ── Tables ─────────────────────────────────────────────────────────────────
+ Nextra's default MDX table is a full grid: 1px borders on every cell, zebra
+ rows, 16px body text, generous padding. On reference tables (flag lists,
+ feature matrices) that reads as a spreadsheet and fights the page. This
+ restyles every markdown table to horizontal rules only, a quiet mono
+ header row, and slightly smaller type, in the same register as the
+ Feature Matrix and the web comparison tables. Unlayered so it wins over
+ Nextra's `x:` utilities without !important. */
+
+.nextra-content table,
+article table {
+ --tbl-rule: hsl(280 5% 84%);
+ --tbl-head-rule: hsl(280 6% 60%);
+ --tbl-hover: hsl(300 8% 96%);
+ --tbl-head: hsl(280 4% 44%);
+ font-size: 0.875rem;
+ line-height: 1.5;
+ border-collapse: collapse;
+}
+html.dark .nextra-content table,
+html.dark article table {
+ --tbl-rule: hsl(279 9% 22%);
+ --tbl-head-rule: hsl(279 8% 40%);
+ --tbl-hover: hsl(279 12% 11%);
+ --tbl-head: hsl(280 4% 58%);
+}
+
+.nextra-content table tr,
+article table tr {
+ border: 0;
+ border-bottom: 1px solid var(--tbl-rule);
+ background: transparent;
+}
+.nextra-content table thead tr,
+article table thead tr {
+ border-bottom: 1px solid var(--tbl-head-rule);
+}
+.nextra-content table tbody tr:last-child,
+article table tbody tr:last-child {
+ border-bottom: 1px solid var(--tbl-head-rule);
+}
+.nextra-content table tbody tr:hover,
+article table tbody tr:hover {
+ background: var(--tbl-hover);
+}
+
+.nextra-content table th,
+.nextra-content table td,
+article table th,
+article table td {
+ border: 0;
+ padding: 0.55rem 0.75rem;
+ vertical-align: top;
+ text-align: left;
+}
+.nextra-content table th:first-child,
+.nextra-content table td:first-child,
+article table th:first-child,
+article table td:first-child {
+ padding-left: 0.25rem;
+}
+.nextra-content table th:last-child,
+.nextra-content table td:last-child,
+article table th:last-child,
+article table td:last-child {
+ padding-right: 0.25rem;
+}
+
+/* Header row: mono, small, tracked, muted. A label, not a bold sentence. */
+.nextra-content table th,
+article table th {
+ padding-top: 0.25rem;
+ padding-bottom: 0.5rem;
+ font-family: var(--font-mono, ui-monospace, monospace);
+ font-size: 0.7rem;
+ font-weight: 500;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--tbl-head);
+}
+
+/* Centered columns (Feature Matrix) keep their alignment from the markdown. */
+.nextra-content table th[style*="text-align: center"],
+.nextra-content table td[style*="text-align: center"],
+article table th[style*="text-align: center"],
+article table td[style*="text-align: center"] {
+ text-align: center;
+}
+
+/* First column is the row's identifier: keep it from collapsing, break it only
+ at a hyphen or space. Long code values (connection URIs) may break anywhere
+ rather than push the table sideways. */
+.nextra-content table td:first-child,
+article table td:first-child {
+ min-width: 11rem;
+ overflow-wrap: normal;
+}
+.nextra-content table td code,
+.nextra-content table td code.nextra-code,
+article table td code,
+article table td code.nextra-code {
+ font-size: 0.8em;
+ white-space: normal;
+ /* word-break, not overflow-wrap: Nextra's own `code.nextra-code` rule pins
+ overflow-wrap to break-word, which does not shrink a cell's min-content,
+ so a long URI would still push the table sideways. */
+ word-break: break-word;
+}
+/* Trailing column is usually a short value (default, type). A floor keeps
+ `1000000` on one line while a long URI in the same slot still wraps. */
+.nextra-content table td:last-child,
+article table td:last-child {
+ min-width: 6rem;
+}
+
+/* ── Callouts ───────────────────────────────────────────────────────────────
+ Nextra colours its with stock Tailwind hues (yellow-700 text on
+ yellow-50, blue-700 on blue-100, …), none of which are Sandworm. Re-key each
+ type to the site's own tokens, in the same tint/border/ink recipe the
+ product and feature pills use: 10% tint, 50% border, full-strength ink.
+ Types are matched on Nextra's own background class since the component
+ exposes no type attribute. */
+
+.nextra-callout {
+ --co: var(--blue-500);
+ background: hsl(var(--co) / 0.1);
+ border-color: hsl(var(--co) / 0.5);
+ color: hsl(var(--co));
+}
+.nextra-callout[class*="x:bg-yellow-"] {
+ --co: var(--sand-300);
+}
+.nextra-callout[class*="x:bg-red-"] {
+ --co: var(--red-400);
+}
+.nextra-callout[class*="x:bg-green-"] {
+ --co: var(--teal-500);
+}
+.nextra-callout[class*="x:bg-purple-"] {
+ --co: var(--violet-500);
+}
+/* Light mode: the tokens are mid-lightness, so ink needs to be darker than
+ the border to keep body text readable on the tint. */
+html:not(.dark) .nextra-callout {
+ color: hsl(from hsl(var(--co)) h s calc(l - 10));
+}
diff --git a/app/materialize/api/client-sdks/page.mdx b/app/materialize/api/client-sdks/page.mdx
index 5f591be5..0af9a011 100644
--- a/app/materialize/api/client-sdks/page.mdx
+++ b/app/materialize/api/client-sdks/page.mdx
@@ -5,8 +5,6 @@ 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/download-permission-sets/page.mdx b/app/materialize/api/download-permission-sets/page.mdx
index 212ef933..9a448dbb 100644
--- a/app/materialize/api/download-permission-sets/page.mdx
+++ b/app/materialize/api/download-permission-sets/page.mdx
@@ -7,8 +7,6 @@ import { Callout } from "nextra/components";
# DownloadPermissionSets
-
-
For large datasets, `DownloadPermissionSets` is a faster alternative to [LookupPermissionSets] for the initial [backfill](../concepts/permission-set-lifecycle): instead of streaming individual events over a single connection, it hands you a manifest of files you can fetch directly from blob storage, in parallel.
## Request
diff --git a/app/materialize/api/lookup-permission-sets/page.mdx b/app/materialize/api/lookup-permission-sets/page.mdx
index aab23e5c..59d4506b 100644
--- a/app/materialize/api/lookup-permission-sets/page.mdx
+++ b/app/materialize/api/lookup-permission-sets/page.mdx
@@ -7,8 +7,6 @@ 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 3542fa39..e8cd739e 100644
--- a/app/materialize/api/watch-permission-sets/page.mdx
+++ b/app/materialize/api/watch-permission-sets/page.mdx
@@ -7,8 +7,6 @@ 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/hydration/page.mdx b/app/materialize/concepts/hydration/page.mdx
index c8407158..8d2fe1f9 100644
--- a/app/materialize/concepts/hydration/page.mdx
+++ b/app/materialize/concepts/hydration/page.mdx
@@ -5,8 +5,6 @@ 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`.
## Only what's reachable
diff --git a/app/materialize/concepts/managing-client-state/page.mdx b/app/materialize/concepts/managing-client-state/page.mdx
index 41a7d41f..0f6764c2 100644
--- a/app/materialize/concepts/managing-client-state/page.mdx
+++ b/app/materialize/concepts/managing-client-state/page.mdx
@@ -5,8 +5,6 @@ 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 34f4c4cc..f297d804 100644
--- a/app/materialize/concepts/permission-set-lifecycle/page.mdx
+++ b/app/materialize/concepts/permission-set-lifecycle/page.mdx
@@ -7,8 +7,6 @@ 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 211a59c4..227c6a95 100644
--- a/app/materialize/concepts/permission-sets/page.mdx
+++ b/app/materialize/concepts/permission-sets/page.mdx
@@ -7,8 +7,6 @@ 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 [**hydrates**](./hydration) the membership of the [permissions you configure it to watch](./watched-permissions) 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 fba99a11..262115ce 100644
--- a/app/materialize/concepts/snapshots/page.mdx
+++ b/app/materialize/concepts/snapshots/page.mdx
@@ -7,7 +7,6 @@ import { Callout } from "nextra/components";
# Snapshots
-
A **permission set 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 permission set snapshot's identity.
diff --git a/app/materialize/concepts/watched-permissions/page.mdx b/app/materialize/concepts/watched-permissions/page.mdx
index 4221dff3..0b538184 100644
--- a/app/materialize/concepts/watched-permissions/page.mdx
+++ b/app/materialize/concepts/watched-permissions/page.mdx
@@ -7,8 +7,6 @@ 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.
diff --git a/app/materialize/getting-started/limitations/page.mdx b/app/materialize/getting-started/limitations/page.mdx
index f1d60df4..375eb20a 100644
--- a/app/materialize/getting-started/limitations/page.mdx
+++ b/app/materialize/getting-started/limitations/page.mdx
@@ -5,8 +5,6 @@ description: "Current limitations of AuthZed Materialize, including caveats, wil
# Limitations
-
-
- Your schema can contain any of the following, but they cannot be on the path of your configured Materialize permissions or it will throw an error:
- [Caveats]
- [Wildcard] subject types
diff --git a/app/materialize/guides/recommended-architecture/page.mdx b/app/materialize/guides/recommended-architecture/page.mdx
index c43fb54f..f669e6fb 100644
--- a/app/materialize/guides/recommended-architecture/page.mdx
+++ b/app/materialize/guides/recommended-architecture/page.mdx
@@ -7,8 +7,6 @@ import { Callout } from "nextra/components";
# Recommended Architecture
-
-
## Event Processor

diff --git a/app/materialize/guides/relational-database/page.mdx b/app/materialize/guides/relational-database/page.mdx
index 45e92372..4f3c8708 100644
--- a/app/materialize/guides/relational-database/page.mdx
+++ b/app/materialize/guides/relational-database/page.mdx
@@ -5,8 +5,6 @@ description: "Store Materialize permission sets in member_to_set and set_to_set
# Syncing to a Relational Database
-
-
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
diff --git a/app/review/page.tsx b/app/review/page.tsx
new file mode 100644
index 00000000..49c6c1e5
--- /dev/null
+++ b/app/review/page.tsx
@@ -0,0 +1,158 @@
+import fs from "node:fs";
+import path from "node:path";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { Yes, No } from "@/components/feature-icon";
+import { PRODUCTS, PRODUCT_ORDER, availabilityForPath } from "@/lib/products";
+import "./review.css";
+
+export const metadata = { title: "Product availability review" };
+
+/* REVIEW AID — one table of every docs page and the product pill it renders,
+ so product can confirm the map in lib/products.ts in one sitting. Preview
+ deploys and local dev only, hidden from the nav. Delete this route once
+ the map is signed off. */
+const SHOW = process.env.NODE_ENV !== "production" || process.env.VERCEL_ENV === "preview";
+
+/* Rows whose tiers come straight from a Feature Matrix row on
+ authzed/guides/picking-a-product. */
+const MATRIX = new Set([
+ "/authzed/concepts/audit-logging",
+ "/authzed/concepts/restricted-api-access",
+ "/authzed/concepts/workload-isolation",
+ "/authzed/concepts/private-networking",
+ "/authzed/concepts/management-dashboard",
+ "/authzed/concepts/multi-region",
+ "/authzed/concepts/update-channels",
+ "/authzed/concepts/rate-limiting",
+ "/authzed/concepts/security-embargo",
+ "/authzed/guides/setting-up-private-networking",
+]);
+
+/* Rows set by judgment with no matrix row or section rule to lean on. */
+const CONFIRM: Record = {
+ "/authzed/concepts/deployments": "Assumed Cloud + Dedicated (managed deployments).",
+ "/authzed/concepts/feature-maturity": "Assumed all managed tiers.",
+ "/authzed/api/http-api": "Assumed all managed tiers; page duplicates the SpiceDB HTTP API doc.",
+ "/mcp": "Assumed all four; unclear which products the MCP servers target.",
+ "/mcp/authzed/authzed-mcp-server": "Assumed all four.",
+ "/mcp/authzed/spicedb-dev-mcp-server": "Assumed all four; reads as an OSS dev tool.",
+ "/spicedb/getting-started/install":
+ "Install pages read “Not in Cloud, Dedicated”. True, but product may prefer no pill here.",
+};
+
+type Row = { route: string; title: string };
+
+function collectPages(): Row[] {
+ const root = path.join(process.cwd(), "app");
+ const out: Row[] = [];
+ const walk = (dir: string) => {
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, ent.name);
+ if (ent.isDirectory()) walk(full);
+ else if (ent.name === "page.mdx") {
+ const rel = path.relative(root, path.dirname(full));
+ const route = "/" + rel.split(path.sep).join("/");
+ const title =
+ fs
+ .readFileSync(full, "utf8")
+ .match(/^title:\s*"?([^"\n]*)"?/m)?.[1]
+ ?.trim() ?? "";
+ out.push({ route: route === "/." ? "/" : route, title });
+ }
+ }
+ };
+ walk(root);
+ return out.sort((a, b) => a.route.localeCompare(b.route));
+}
+
+function sourceOf(route: string): { kind: "matrix" | "section" | "confirm"; note?: string } {
+ if (MATRIX.has(route)) return { kind: "matrix" };
+ for (const [prefix, note] of Object.entries(CONFIRM)) {
+ if (route === prefix || route.startsWith(prefix + "/")) return { kind: "confirm", note };
+ }
+ return { kind: "section" };
+}
+
+const SOURCE_LABEL = {
+ matrix: "Feature Matrix",
+ section: "Section default",
+ confirm: "Please confirm",
+} as const;
+
+export default function ReviewPage() {
+ if (!SHOW) notFound();
+ const rows = collectPages().map((r) => ({
+ ...r,
+ a: availabilityForPath(r.route),
+ src: sourceOf(r.route),
+ }));
+ const needs = rows.filter((r) => r.src.kind === "confirm").length;
+
+ return (
+
+
Product availability review
+
+ Every docs page and the Available on pill it renders, from one map in{" "}
+ lib/products.ts. Click a page to see the pill in place. Rows marked{" "}
+ Please confirm ({needs}) were set by judgment; the rest come from a Feature
+ Matrix row or from the section the page lives in. To change a row, comment on the PR with
+ the page and the tiers.
+
+
+
+ available
+
+
+ DIY build it yourself
+
+
+ not in
+
+
+ · no pill on this page
+
+