From 49d714b9ca61feca7526e8d1fd1e656a7761b8a5 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Mon, 7 Sep 2026 17:10:35 +0300 Subject: [PATCH 01/11] Add New Edge PDP documentation Documents the new single-binary PDP (permitio/pdp-v3) as an additional deployment option alongside the existing Edge PDP (permitio/pdp-v2), which remains fully supported. Covers the architecture (embedded SurrealDB/RocksDB, supervised OPA and NATS leaf children, loopback query/bundle servers), how policy and data stay in sync over a durable per-PDP event stream, the consistency model, cold start vs warm resume, resource footprint and tuning, deployment requirements, the security model, and the feature-support envelope. Every endpoint, environment variable, port, and default in this page was verified against the cloud-pdp source rather than the repo's README or runbook, both of which are stale in several places. Three points are deliberately stated as gaps rather than features, because the implementation contradicts them today: - OpenTelemetry is not wired up in edge-pdp. It never calls init_observability, has no opentelemetry dependency, reads no OTEL_* vars, and serves no /metrics endpoint. - Absolute resource footprint is not reduced. CI pins a 4GiB limit and notes that 500 MiB OOMKills. The documented win is architectural: the fact graph moves from OPA's in-memory document to on-disk RocksDB, so memory stops scaling with data set size. - No propagation-latency or throughput figures exist in the repo, so the page makes the structural argument and explicitly says numbers are not yet published. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp.mdx | 436 +++++++++++++++++++++++++++++ sidebars.js | 1 + 2 files changed, 437 insertions(+) create mode 100644 docs/concepts/pdp/new-edge-pdp.mdx diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx new file mode 100644 index 00000000..b682e62c --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -0,0 +1,436 @@ +--- +title: New Edge PDP +sidebar_label: New Edge PDP +description: A single-binary PDP with an embedded database that keeps a local copy of your policy and authorization data in sync over a durable event stream — no network in the decision path. +sidebar_position: 4 +--- + +# New Edge PDP + +**A single-binary Policy Decision Point with an embedded database, kept in sync over a durable event stream.** + +The **New Edge PDP** is a ground-up rewrite of the PDP runtime, shipped as `permitio/pdp-v3`. It runs one container per Permit environment, holds a **local, on-disk copy** of that environment's policy and authorization data, and answers authorization queries with **no network call in the decision path**. + +:::note Availability +The New Edge PDP is a **new deployment option**, not a replacement. The existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) remains fully supported and continues to be the right choice for the capabilities listed under [Feature support](#feature-support) below. + +It is currently in early access and enabled per account. [Contact us](mailto:support@permit.io) to have it turned on. +::: + +--- + +## Why a new runtime + +Today's Edge PDP keeps your authorization data inside OPA's in-memory document, and refreshes it by reacting to a notification and then **fetching the changed data back from Permit's API**. That design is proven, but it has three structural ceilings: + +- **Memory scales with your data.** Because the fact graph lives in memory, a large environment needs a large PDP — which is why sharding exists for the biggest data sets. +- **Every update costs a round trip.** A notification tells the PDP *that* something changed; a second call is needed to find out *what*. +- **Catching up after a disconnect is a re-fetch.** There is no server-side record of precisely what a given PDP has and hasn't seen. + +The New Edge PDP changes all three. Data lives in an **embedded on-disk database**, updates **arrive already containing the change**, and the control plane **durably retains** each PDP's unacknowledged changes until that specific PDP confirms it applied them. + +--- + +## Architecture + +Everything runs inside one container. The only externally exposed listeners are the authorization API and the health endpoint. + +```mermaid +flowchart LR + subgraph VPC["Your VPC / network"] + apps["Your app / service
(Permit SDK)"] + probes["Your orchestrator
(liveness and readiness probes)"] + + subgraph EDGE["New Edge PDP — one container per Permit environment"] + public["Authorization API
:7000 — opens once ready
bearer: PDP_API_KEY"] + opa["OPA policy engine
supervised child"] + health["Health and readiness
:7001"] + queryloop["Query loopback
127.0.0.1:7002 — graph data"] + bundleloop["Bundle loopback
127.0.0.1:7003 — Rego bundle"] + edgedb[("Embedded database
SurrealDB + RocksDB
persistent volume")] + ingest["Change ingest and repair
applies changes; self-heals"] + leaf["NATS leaf
supervised child
durable JetStream"] + end + end + + permit["Permit.io
app.permit.io · api.permit.io"] + git["Policy Store
your own Git service"] + + apps -->|"Authorize (REST)"| public + public -->|"Evaluate"| opa + probes -->|"Probes"| health + opa -->|"Graph data"| queryloop + opa -->|"Policy bundle"| bundleloop + queryloop -->|"Reads"| edgedb + leaf -->|"Policy KV"| bundleloop + leaf -->|"Change stream"| ingest + ingest -->|"Applies"| edgedb + permit -->|"Event channel (WSS :443)"| leaf + opa -->|"Decision logs (HTTPS)"| permit + permit -->|"Policy as code (SSH)"| git +``` + +### What runs where + +| Component | Kind | Role | +| --- | --- | --- | +| **Authorization API** | In-process | The public REST surface on port `7000`. Opens **only once the PDP is ready**. | +| **OPA policy engine** | Supervised child process | Evaluates Rego. Bound to loopback only — never reachable from outside the container. | +| **Embedded database** | In-process (SurrealDB over RocksDB) | Your environment's fact graph — users, tenants, resource instances, relationships — **on disk**. | +| **NATS leaf node** | Supervised child process | Durable, on-disk event store. Holds the link to Permit's control plane. | +| **Change ingest and repair** | In-process task | Applies incoming changes to the embedded database; detects and repairs gaps. | +| **Query / bundle loopbacks** | In-process | Serve OPA its graph data and its policy bundle over `127.0.0.1`. | +| **Health and readiness** | In-process | Port `7001`. Answers throughout startup, including a long cold start. | + +### The decision path + +``` +client → :7000 authorization API + → 127.0.0.1 OPA + → 127.0.0.1 query loopback + → embedded database (local disk) +``` + +Every hop is loopback or local disk. **No step in answering an authorization query contacts Permit's API**, by construction — the New Edge PDP has no control plane other than its event stream, and cannot reach `api.permit.io` for a decision even if it wanted to. + +--- + +## How policy and data stay in sync + +The container's NATS leaf node holds a persistent link to Permit's control plane and **sources** four independent planes onto local, on-disk storage: + +| Plane | Carries | +| --- | --- | +| **Change stream** | Transactions of authorization facts — users, tenants, resource instances, relationship tuples | +| **Policy files** | The compiled Rego bundle OPA evaluates | +| **Policy schema** | Role and permission definitions from your policy | +| **Snapshot** | A one-shot bulk transfer, used only during cold start | + +Each change arrives as a **transaction** — an ordered set of operations with a timestamp and a transaction ID — rather than as a "something changed, come and fetch it" signal. The data is in the message. + +### Retention is per-PDP, and server-side + +Each PDP has its own durable position in the change stream. The control plane **retains a change until that specific PDP has acknowledged applying it** — so a PDP that is restarting, disconnected, or slow does not lose updates, and does not need to re-fetch its whole data set to catch up. Interest is held by the subscription's *existence*, not by an open connection; a PDP that is disconnected for minutes resumes exactly where it left off. + +### Self-healing + +Two background tasks keep the local copy honest: + +- **A gap detector** compares the PDP's position in the change stream against the oldest change the control plane still holds. If the PDP has fallen so far behind that the stream can no longer bridge the difference, it signals for a rebuild. +- **A rebuild orchestrator** then builds a fresh copy of the database **in a side directory** and swaps it in atomically. + +Crucially, **the PDP keeps answering from the existing database while the rebuild runs**. The public port is never re-bound, no request is refused, and no decision waits on the rebuild. Staleness is surfaced on the health endpoint rather than turned into errors. + +:::info Design principle +The New Edge PDP **serves stale before it serves nothing**. A control-plane outage costs you freshness, never availability — decisions keep flowing from the local copy indefinitely. +::: + +--- + +## Consistency + +Consistency is a property of the design rather than something you configure. + +**Three mechanisms combine:** + +1. **A durably ordered log.** Changes are carried on an ordered, persisted stream. Nothing is dropped, and nothing is trimmed before every PDP that needs it has confirmed it. + +2. **Order-independent application.** Each fact carries `(timestamp, transaction id)`, and a write is applied **only if it is strictly newer than what is already stored**. Because the merge rule is convergent, the final state does not depend on the order in which changes were applied — which is what makes it safe to apply several transactions concurrently without risking a stale write landing on top of a fresh one. + +3. **Per-transaction atomicity.** All operations within a transaction are applied together, so a decision never observes half of a multi-part change. + +Together these give **convergence by construction**: two PDPs in the same environment that have seen the same set of changes hold the same state, regardless of the order or timing in which they received them, and re-delivering a change that was already applied is a no-op. + +:::note What this does and does not guarantee +The consistency model is **eventual, with a convergent merge**. It guarantees that PDPs converge, that no update is lost, and that replays are safe. + +It does **not** provide read-your-own-writes. There is no local write API — the New Edge PDP is read-only with respect to your data, and a fact written through Permit's API becomes visible once it propagates. If you need write-then-immediately-read semantics, use the existing Edge PDP's [local facts uploader](/how-to/manage-data/local-facts-uploader). + +A PDP that restarts with a backlog becomes ready and begins serving while it is still catching up. Whether it is serving stale data, and by how long, is reported on the health endpoint. +::: + +--- + +## Propagation latency + +The sync path is structurally shorter than the existing Edge PDP's: + +| | Edge PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | +| --- | --- | --- | +| Change notification | WebSocket notification | Push delivery on a durable per-PDP subscription | +| Data fetch | **A second call back to Permit's API** | None — the change travels *in* the message | +| Round trips per change | 2 or more | 1 | +| Missed while offline | Reconnect, then re-fetch | Retained server-side; resumes at its exact position | +| Calls to `api.permit.io` | Per update | **Never** | + +Removing the fetch leg removes both a round trip and a dependency: propagation no longer depends on the PDP being able to reach Permit's API, only on the event stream. + +:::caution Numbers not yet published +Permit has not yet published measured propagation-latency figures for the New Edge PDP. The improvement described above is architectural. If you have a latency target to validate against, [talk to us](mailto:support@permit.io) before designing around a specific number. +::: + +--- + +## Cold start and warm resume + +The PDP decides which path to take from the state of its persistent volume. + +### Warm resume — a restart with an intact volume + +The embedded database, the event store, and every subscription position are already on disk. The PDP reopens the database and resumes applying changes **from its last acknowledged position**. There is no snapshot transfer and no re-fetch. + +This is the normal case for a pod restart, a rolling update, or a short disconnection — and it is the path that benefits most from the design, because a disconnected PDP's changes were retained for it rather than discarded. + +### Cold start — first boot, or an unbridgeable gap + +On a genuinely fresh PDP — or when the gap detector determines the change stream can no longer bridge the difference — the PDP requests a **snapshot** of its environment. + +The snapshot is not a stream of individual records. It is a set of **pre-built database files**, transferred in chunks, checksum-verified, and then **bulk-loaded directly into the storage engine** — bypassing the normal per-record write path entirely. The snapshot also carries the Rego bundle and policy schema, so a cold-started PDP comes up with policy and data together. + +The boundary between the two is exact: the snapshot records the precise stream position it was cut at, and the PDP resumes the change stream from the very next change. Nothing is applied twice in a way that matters, and nothing is skipped. + +A checksum mismatch, a missing chunk, or an empty snapshot is **fail-closed** — the PDP refuses to come up on a partial data set rather than serving decisions against one. + +:::caution Cold start is not fast — plan probes accordingly +A cold start transfers and loads the environment's entire data set. Permit's own CI observes **84 seconds to roughly 4 minutes** for this on test environments, and larger environments will take longer. + +The health endpoint answers throughout, so **set a generous startup probe** and let readiness — not liveness — gate traffic. The authorization port does not open until the PDP is ready, so there is no window in which it accepts a request it cannot answer correctly. +::: + +--- + +## Running at high volume + +What makes the New Edge PDP suitable for high request rates is mostly what it *doesn't* do per request: + +- **No network in the decision path** — every hop is loopback or local disk. +- **No control-plane call to authenticate** — the API key is verified against the PDP's own key in constant time. Permit's API is never contacted to authorize a request. +- **No dependency on Permit's availability** — a control-plane outage affects freshness, not throughput or availability. +- **Reads do not contend with syncing** — incoming changes are applied to the embedded database concurrently and independently of the request path. +- **The same evaluation core as the managed Cloud PDP**, which is already tuned for large-scale checks. + +:::caution Numbers not yet published +As with propagation latency, Permit has not published measured throughput figures for the New Edge PDP. The [Cloud PDP benchmarks](/concepts/pdp/cloud-pdp-benchmarks) measure a different deployment and should not be read as New Edge PDP numbers. +::: + +--- + +## Resource footprint + +The New Edge PDP changes **where** your authorization data lives, and that is the change that matters for sizing. + +| | Edge PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | +| --- | --- | --- | +| Fact graph | In OPA's **in-memory** document | **On disk**, in an embedded database | +| Memory vs. data size | Scales with your data set | Bounded by a configurable cache | +| Processes | Python API server + OPAL client + OPA | Native binary + NATS leaf + OPA | +| Language runtime | CPython | None — compiled binary | +| Persistent storage | Not required | **Required** | + +The architectural consequence is that **memory stops scaling linearly with your data set**. On the existing Edge PDP, a large environment needs a large PDP, and very large data sets need sharding. On the New Edge PDP, the data set lives on disk and memory is governed by the database's cache size, which you control. + +:::caution Size generously to start, then tune +The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. + +If you need a smaller footprint, tune the storage engine directly: + +| Variable | Default | Effect | +| --- | --- | --- | +| `SURREAL_ROCKSDB_BLOCK_CACHE_SIZE` | `536870912` (512 MiB) | Read cache. The single largest lever on resident memory. | +| `SURREAL_ROCKSDB_WRITE_BUFFER_SIZE` | `268435456` (256 MiB) | In-memory write buffer before flushing to disk. | +| `SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER` | `32` | How many write buffers may exist at once. | +| `SURREAL_ROCKSDB_BACKGROUND_THREADS` | `4` | Background compaction threads. | + +Benchmark against your own data set before committing to a size. +::: + +### Disk + +Two paths must be on persistent storage — the embedded database and the event store. Size for **roughly twice your data set**, plus headroom: during a rebuild, two generations of the database exist side by side until the new one is swapped in. + +--- + +## Feature support + +The New Edge PDP is **wire-compatible on the endpoints it implements** — same paths, same request and response shapes, so existing SDK integrations that use those endpoints work unchanged. But it implements a **subset** of the existing Edge PDP's surface. + +Its capability envelope is that of the **managed Cloud PDP, deployed in your own network** — not a port of the `pdp-v2` container. + +### Supported + +| Capability | Endpoint | +| --- | --- | +| **RBAC** | — | +| **ReBAC**, including role derivation | — | +| **Multi-tenancy** | — | +| [Check](/how-to/enforce-permissions/check) | `POST /allowed` | +| [Bulk check](/how-to/enforce-permissions/bulk-check) | `POST /allowed/bulk` | +| [User permissions](/how-to/enforce-permissions/user-permissions) | `POST /user-permissions` | +| [Authorized users](/how-to/enforce-permissions/authorized-users) | `POST /authorized_users` | +| AuthZen access evaluation | `POST /access/v1/evaluation` | +| AuthZen bulk evaluations | `POST /access/v1/evaluations` | +| AuthZen subject / resource / action search | `POST /access/v1/search/{subject,resource,action}` | +| AuthZen discovery | `GET /.well-known/authzen-configuration` | +| Decision logs to Permit | — | +| Health and readiness | `GET /health`, `GET /healthy` | + +### Not supported + +Use the existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) if you need any of these: + +| Not supported | Notes | +| --- | --- | +| **ABAC** — attribute-based access control | Condition sets, user sets, and resource sets are not evaluated. A check against an ABAC policy returns a deny. | +| **Policy-as-Code / custom OPA bundles** | Per-environment Rego bundle publishing is not available yet. | +| [**All-tenants check**](/how-to/enforce-permissions/all-tenants-check) | `POST /allowed/all-tenants` is not served. | +| [**Data filtering**](/how-to/enforce-permissions/data-filtering) via partial evaluation | Graph-based filtering through `/user-permissions` and `/authorized_users` **is** available. | +| [**URL-based enforcement**](/how-to/enforce-permissions/url-mapping/url-mapping-check) | `POST /allowed_url` is not served. | +| **Local facts / read-your-own-writes** | There is no local write API. | +| [**Local enforcement APIs**](/how-to/enforce-permissions/list-role-assignments) | `/local/*` endpoints are not served. | +| **Custom data sources** | The change stream is the only data plane. | +| **Kong and nginx integration endpoints** | — | +| **Envoy ext-authz / proxy mode** | — | +| **`/metrics` endpoint** | See [Observability](#observability). | +| **Offline / air-gapped first boot** | A PDP that cannot reach the control plane at first boot cannot start. Once started, it survives outages indefinitely. | + +--- + +## Observability + +### Health and readiness + +Three endpoints on the management port (`7001`): + +| Endpoint | Auth | Behavior | +| --- | --- | --- | +| `GET /health` | None | Always `200`. Reports per-component status. Answers throughout startup. | +| `GET /health/ready` | None | `200` only when every gating component is up, otherwise `503`. | +| `GET /health/detail` | `PDP_API_KEY` bearer | Full diagnostic detail per component. | + +Wire **liveness** to `/health` and **readiness** to `/health/ready`. Nine components gate readiness — the control-plane link, each of the four sync planes, the embedded database, OPA, and both loopbacks. + +`/health/detail` is where you diagnose a lagging or stale PDP: it reports child-process restart counts and exit codes, the PDP's position in the change stream against the control plane's oldest retained change, whether the PDP is currently **serving stale** data and for how long, and whether decision-log shipping is healthy. + +:::note Staleness never blocks decisions +Neither staleness nor decision-log failures gate readiness. A PDP that cannot ship its audit logs keeps answering authorization queries — the design deliberately refuses to trade the primary function for the secondary one. +::: + +### Logs + +Structured logs go to stdout at `INFO` by default; `RUST_LOG` overrides (`info,edge_pdp=debug` is a good verbose setting). Both child processes' output is captured and re-emitted on the main log stream with attribution and correct log levels, so container logs are the single place to look. + +:::warning Child debug levels expose data +`EDGE_NATS_LOG_LEVEL=trace` logs raw message payloads — which means **your authorization data** — and `EDGE_OPA_LOG_LEVEL=debug` logs authorization request and response bodies. Use these only for short, deliberate debugging sessions. +::: + +### Decision logs + +Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that `EDGE_DECISION_LOG_OPT_OUT` is not set — the local setting can only turn logging off, never on. + +:::caution OpenTelemetry is not available yet +The New Edge PDP **does not currently export OpenTelemetry traces, metrics, or logs**, and does not read `OTEL_*` environment variables. It also does not serve a `/metrics` endpoint. + +Observability today is structured stdout logs plus the health endpoints described above. If OTLP export is a requirement for your deployment, [let us know](mailto:support@permit.io) — it is a known gap rather than a design decision. +::: + +--- + +## Configuration + +### Required + +| Variable | Description | +| --- | --- | +| `PDP_API_KEY` | Your environment-level Permit API key. This single value binds the container to one environment, authenticates it to the control plane, and is the bearer token the authorization API accepts. | +| `PDP_CONTROL_PLANE` | The control-plane endpoint. **See the warning below — this is not the same kind of value as on `pdp-v2`.** | + +:::warning `PDP_CONTROL_PLANE` means something different here +On the existing Edge PDP, `PDP_CONTROL_PLANE` is an **HTTPS API base URL** (`https://api.permit.io`). + +On the New Edge PDP it is the **event-stream endpoint**, and it must be a bare host and port with **no path and no trailing slash** — for example `wss://control-plane.permit.io:443`. Copying the `pdp-v2` value across will not work. + +Permit will give you the correct value for your account when the New Edge PDP is enabled. +::: + +### Storage and ports + +| Variable | Default | Description | +| --- | --- | --- | +| `EDGE_DB_PATH` | `/var/lib/edge-pdp/db` | Embedded database directory. **Must be on a persistent volume.** | +| `EDGE_DATA_DIR` | `/var/lib/edge-pdp` | Event store and generated credentials. **Must be on a persistent volume.** | +| `EDGE_PUBLIC_PORT` | `7000` | Authorization API. The only port your services need. | +| `EDGE_HEALTH_PORT` | `7001` | Health and readiness. | + +Ports `7002`, `7003`, `8181`, `4222`, and `8222` are used internally and bind to loopback only. Expose **`7000` and `7001`** and nothing else. + +### Behavior and tuning + +| Variable | Default | Description | +| --- | --- | --- | +| `RUST_LOG` | `info` | Log level. Try `info,edge_pdp=debug` when debugging. | +| `PDP_DEBUG` | `false` | Include debug detail in authorization responses. | +| `OPA_TIMEOUT_MS` | `800` | Per-request evaluation timeout. | +| `EDGE_PARALLELISM` | `4` | How many change transactions are applied concurrently. | +| `EDGE_DECISION_LOG_OPT_OUT` | `false` | Set to `true` to stop shipping decision logs. | +| `EDGE_NATS_LOG_LEVEL` | follows `RUST_LOG` | Control-plane client log level. See the warning above. | +| `EDGE_OPA_LOG_LEVEL` | follows `RUST_LOG` | OPA log level. See the warning above. | +| `EDGE_DRAIN_TIMEOUT_SECS` | `10` | Shutdown drain budget. | +| `EDGE_CHILD_TERMINATION_TIMEOUT_SECS` | `30` | Child-process shutdown budget. | + +--- + +## Deployment requirements + +The New Edge PDP has real operational requirements that the existing Edge PDP does not. Read these before deploying. + +**Persistent storage is mandatory.** Both `EDGE_DB_PATH` and `EDGE_DATA_DIR` must survive restarts. On ephemeral storage, every restart becomes a full cold start — minutes of startup, and a full snapshot transfer each time. + +**Set `terminationGracePeriodSeconds` to at least 40.** It must be at least `EDGE_DRAIN_TIMEOUT_SECS + EDGE_CHILD_TERMINATION_TIMEOUT_SECS`, which is 40 seconds at defaults. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. + +**Set a generous startup probe.** Cold start can take minutes. Point liveness at `/health` and readiness at `/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. + +**Run one container per Permit environment.** The API key binds the container to exactly one environment; there is no multi-environment mode. + +**Memory: start at 4 GiB.** See [Resource footprint](#resource-footprint) for tuning. + +**Runs as non-root** (uid/gid `10001`). Ensure your mounted volumes are writable by that user. + +:::tip Running locally on macOS +Port `7000` collides with AirPlay Receiver on macOS. Map it to a different host port, for example `-p 7766:7000`, and point your SDK at `http://localhost:7766`. +::: + +--- + +## Security model + +**Requests are authenticated against the container's own key.** The bearer token on an authorization request is compared, in constant time, against the container's `PDP_API_KEY`. Permit's API is **never** contacted to authenticate or authorize a request — the code that could do so is not compiled into this binary at all. + +**One container serves exactly one environment.** The environment is fixed at startup from the API key. A valid key for a *different* environment does not authenticate, and there is no code path by which a caller can steer a request at data outside the container's own environment. + +**Credentials never reach the child processes.** Both OPA and the NATS leaf are started with a cleared environment; only the specific variables each one needs are re-added. Credential files are written `0600` with protections against symlink attacks from other containers sharing the volume, and are referenced by path rather than inlined into any generated config. + +**The blast radius of a compromised PDP is one environment's data.** It holds no credential that grants access to any other environment, and no ability to reach Permit's management API. + +--- + +## Choosing between the two + +| Choose the **existing Edge PDP** when you need | Choose the **New Edge PDP** when you need | +| --- | --- | +| ABAC, condition sets, user or resource sets | The largest data sets, without sharding | +| Policy-as-Code / custom Rego bundles | Bounded, tunable memory rather than memory that scales with data | +| Read-your-own-writes / local facts | Decisions that never depend on reaching Permit's API | +| Data filtering via partial evaluation | Sync that survives long disconnections without a re-fetch | +| URL-based enforcement, Kong, or ext-authz | Fewer moving parts — one binary, no Python runtime | +| An OpenTelemetry or `/metrics` pipeline, or deployment without persistent storage | Observability through structured logs and detailed health endpoints | + +Both connect to the same Permit environment and enforce the same policies you author in Permit. You can run them side by side while you evaluate. + +--- + +## Related documentation + +- [PDP Overview](/concepts/pdp/overview) — deployment models for the existing Edge PDP +- [Cloud PDP Capabilities](/concepts/pdp/cloud-pdp-capabilities) — the managed PDP's capability envelope, which the New Edge PDP mirrors +- [PDP Configuration](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP +- [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/sidebars.js b/sidebars.js index ccbb886e..04995884 100644 --- a/sidebars.js +++ b/sidebars.js @@ -379,6 +379,7 @@ const sidebars = { "concepts/pdp/configuration", "concepts/pdp/cloud-pdp-capabilities", "concepts/pdp/cloud-pdp-benchmarks", + "concepts/pdp/new-edge-pdp", "how-to/manage-data/local-facts-uploader", ], }, From e4ec81e2b1398a78096d9b0aa644dc9ebcc9d039 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 11:27:16 +0300 Subject: [PATCH 02/11] Make the architecture diagram larger and zoomable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Mermaid diagram rendered small and could not be clicked. Docusaurus renders Mermaid as inline SVG, so it is capped at the ~640px doc content column and the image-zoom plugin never applied to it — that plugin is wired to `.markdown :not(em) > img`, which only matches real elements. Replaces it with the architecture SVG extracted from the source diagram, served as a static asset. It now renders as an , so click-to-zoom applies and the diagram opens near full viewport width where every port, label and edge is legible. Verified in a browser: medium-zoom attaches and the zoomed state renders correctly. The SVG relied on CSS custom properties defined by its original host page, so the extracted file inlines the palette, paints its own background, and carries a prefers-color-scheme dark variant — otherwise it would render colourless, or as dark text on a dark page. Fonts fall back to a system monospace stack rather than the webfont the original pulled from Google, which cannot load inside an . Also renames the container frame label to "New Edge PDP" and widens its backing mask to match, so the dashed region border stays hidden behind the longer string. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp.mdx | 39 +-- .../images/pdp/new-edge-pdp-architecture.svg | 310 ++++++++++++++++++ 2 files changed, 315 insertions(+), 34 deletions(-) create mode 100644 static/images/pdp/new-edge-pdp-architecture.svg diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index b682e62c..9fab4a6d 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -35,40 +35,11 @@ The New Edge PDP changes all three. Data lives in an **embedded on-disk database Everything runs inside one container. The only externally exposed listeners are the authorization API and the health endpoint. -```mermaid -flowchart LR - subgraph VPC["Your VPC / network"] - apps["Your app / service
(Permit SDK)"] - probes["Your orchestrator
(liveness and readiness probes)"] - - subgraph EDGE["New Edge PDP — one container per Permit environment"] - public["Authorization API
:7000 — opens once ready
bearer: PDP_API_KEY"] - opa["OPA policy engine
supervised child"] - health["Health and readiness
:7001"] - queryloop["Query loopback
127.0.0.1:7002 — graph data"] - bundleloop["Bundle loopback
127.0.0.1:7003 — Rego bundle"] - edgedb[("Embedded database
SurrealDB + RocksDB
persistent volume")] - ingest["Change ingest and repair
applies changes; self-heals"] - leaf["NATS leaf
supervised child
durable JetStream"] - end - end - - permit["Permit.io
app.permit.io · api.permit.io"] - git["Policy Store
your own Git service"] - - apps -->|"Authorize (REST)"| public - public -->|"Evaluate"| opa - probes -->|"Probes"| health - opa -->|"Graph data"| queryloop - opa -->|"Policy bundle"| bundleloop - queryloop -->|"Reads"| edgedb - leaf -->|"Policy KV"| bundleloop - leaf -->|"Change stream"| ingest - ingest -->|"Applies"| edgedb - permit -->|"Event channel (WSS :443)"| leaf - opa -->|"Decision logs (HTTPS)"| permit - permit -->|"Policy as code (SSH)"| git -``` +![Architecture of the New Edge PDP: your app and orchestrator on the left, the single Edge PDP container holding the authorization API, OPA, the health server, the query and bundle loopbacks, the embedded database, the change-ingest worker and the NATS leaf, with Permit.io and your policy store on the right](/images/pdp/new-edge-pdp-architecture.svg) + +

+ Click the diagram to enlarge it. +

### What runs where diff --git a/static/images/pdp/new-edge-pdp-architecture.svg b/static/images/pdp/new-edge-pdp-architecture.svg new file mode 100644 index 00000000..09f72026 --- /dev/null +++ b/static/images/pdp/new-edge-pdp-architecture.svg @@ -0,0 +1,310 @@ + + + + + Edge PDP Architecture + An architecture diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Your app / service · Permit SDK · Your VPC / network + + + + Your app / service + Permit SDK + + + + Your orchestrator · liveness & readiness probes · Your VPC / network + + + + Your orchestrator + liveness & readiness probes + + + + Authorization API · :7000 — opens once ready · Your VPC / network › New Edge PDP — one container per Permit environment · bearer: PDP_API_KEY + + + + Authorization API + :7000 — opens once ready + bearer: PDP_API_KEY + + + + OPA policy engine · supervised child · Your VPC / network › New Edge PDP — one container per Permit environment + + + + OPA policy engine + supervised child + + + + Health & readiness · :7001 — 9 gating components · Your VPC / network › New Edge PDP — one container per Permit environment + + + + Health & readiness + :7001 — 9 gating components + + + + Query loopback · 127.0.0.1:7002 — graph data · Your VPC / network › New Edge PDP — one container per Permit environment + + + + Query loopback + 127.0.0.1:7002 — graph data + + + + Bundle loopback · 127.0.0.1:7003 — rego bundle · Your VPC / network › New Edge PDP — one container per Permit environment + + + + Bundle loopback + 127.0.0.1:7003 — rego bundle + + + + Embedded database · SurrealDB + RocksDB, on disk · Your VPC / network › New Edge PDP — one container per Permit environment · persistent volume + + + + Embedded database + SurrealDB + RocksDB, on disk + persistent volume + + + + WAL ingest & repair · applies changes; self-heals · Your VPC / network › New Edge PDP — one container per Permit environment + + + + WAL ingest & repair + applies changes; self-heals + + + + NATS leaf · supervised child · Your VPC / network › New Edge PDP — one container per Permit environment · durable JetStream + + + + NATS leaf + supervised child + durable JetStream + + + + Policy Store · your own Git service · Architecture component + + + + Policy Store + your own Git service + + + + Permit.io · app.permit.io · api.permit.io · Architecture component · multi-tenant + + + + Permit.io + app.permit.io · api.permit.io + multi-tenant + + + + + + Authorize (REST) + + + + Evaluate + + + + Probes + + + + Graph data + + + + Policy bundle + + + + Reads + + + + Policy KV + + + + LOCAL_WAL + + + + Applies + + + + Event channel (WSS :443) + + + + Decision logs (HTTPS) + + + + Policy as code (SSH) + + + + + + Your VPC / network + + + + + New Edge PDP — one container per Permit environment + + + + + Legend + + + Backend + + + + Database + + + + Cloud + + + + Message bus + + + + External + + + \ No newline at end of file From 958a620c97c3d326c65ffecebeb6b2796e57d617 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 11:40:40 +0300 Subject: [PATCH 03/11] Address Copilot review: probe port hazard, control-plane format, alt text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings from the Copilot review on #649. Health endpoints (valid concern, different cause than reported). Copilot read `GET /healthy` and `GET /health/ready` as an internal contradiction. Both are real, on different listeners: the authorization port serves /health and its /healthy alias, while /health/ready and /health/detail exist only on the management port. Rather than drop either, the page now says so explicitly and warns about the two ways this bites — a readiness probe on :7000/health/ready 404s forever, and :7000 does not open until the PDP is ready, so any probe there fails for the whole cold start and a liveness probe would kill the container before it finishes booting. PDP_CONTROL_PLANE. The text said "bare host and port" while the example carried a wss:// scheme. Now states scheme + host + explicit port, no path, no trailing slash. Diagram alt text said "Edge PDP container"; now "New Edge PDP". Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp.mdx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 9fab4a6d..d7417656 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -35,7 +35,7 @@ The New Edge PDP changes all three. Data lives in an **embedded on-disk database Everything runs inside one container. The only externally exposed listeners are the authorization API and the health endpoint. -![Architecture of the New Edge PDP: your app and orchestrator on the left, the single Edge PDP container holding the authorization API, OPA, the health server, the query and bundle loopbacks, the embedded database, the change-ingest worker and the NATS leaf, with Permit.io and your policy store on the right](/images/pdp/new-edge-pdp-architecture.svg) +![Architecture of the New Edge PDP: your app and orchestrator on the left, the single New Edge PDP container holding the authorization API, OPA, the health server, the query and bundle loopbacks, the embedded database, the change-ingest worker and the NATS leaf, with Permit.io and your policy store on the right](/images/pdp/new-edge-pdp-architecture.svg)

Click the diagram to enlarge it. @@ -243,7 +243,7 @@ Its capability envelope is that of the **managed Cloud PDP, deployed in your own | AuthZen subject / resource / action search | `POST /access/v1/search/{subject,resource,action}` | | AuthZen discovery | `GET /.well-known/authzen-configuration` | | Decision logs to Permit | — | -| Health and readiness | `GET /health`, `GET /healthy` | +| Health check on the authorization port | `GET /health`, `GET /healthy` (aliases for the same handler) | ### Not supported @@ -270,7 +270,7 @@ Use the existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) ### Health and readiness -Three endpoints on the management port (`7001`): +Health is served on the **management port (`7001`)**, which is where all probes belong: | Endpoint | Auth | Behavior | | --- | --- | --- | @@ -278,7 +278,16 @@ Three endpoints on the management port (`7001`): | `GET /health/ready` | None | `200` only when every gating component is up, otherwise `503`. | | `GET /health/detail` | `PDP_API_KEY` bearer | Full diagnostic detail per component. | -Wire **liveness** to `/health` and **readiness** to `/health/ready`. Nine components gate readiness — the control-plane link, each of the four sync planes, the embedded database, OPA, and both loopbacks. +Wire **liveness** to `:7001/health` and **readiness** to `:7001/health/ready`. Nine components gate readiness — the control-plane link, each of the four sync planes, the embedded database, OPA, and both loopbacks. + +:::warning Probe `7001`, never `7000` +The authorization port (`7000`) also answers `GET /health` — and `GET /healthy`, an alias kept so tooling written for the existing Edge PDP keeps working. Neither is a substitute for the management port: + +- There is **no `/health/ready` on port `7000`**. A readiness probe pointed at `:7000/health/ready` gets a `404` and the pod never becomes ready. +- Port `7000` does not open at all until the PDP is ready, so any probe against it **fails for the entire cold start** — which can be minutes. A liveness probe there will kill the container before it ever finishes starting. + +Port `7001` answers from the moment the process starts, which is exactly what makes it the safe probe target. +::: `/health/detail` is where you diagnose a lagging or stale PDP: it reports child-process restart counts and exit codes, the PDP's position in the change stream against the control plane's oldest retained change, whether the PDP is currently **serving stale** data and for how long, and whether decision-log shipping is healthy. @@ -318,7 +327,7 @@ Observability today is structured stdout logs plus the health endpoints describe :::warning `PDP_CONTROL_PLANE` means something different here On the existing Edge PDP, `PDP_CONTROL_PLANE` is an **HTTPS API base URL** (`https://api.permit.io`). -On the New Edge PDP it is the **event-stream endpoint**, and it must be a bare host and port with **no path and no trailing slash** — for example `wss://control-plane.permit.io:443`. Copying the `pdp-v2` value across will not work. +On the New Edge PDP it is the **event-stream endpoint**. Give it a **scheme, host, and explicit port**, with **no path and no trailing slash** — for example `wss://control-plane.permit.io:443`. The port is not optional, and a trailing `/` or any path segment will prevent the PDP from connecting. Copying the `pdp-v2` value across will not work. Permit will give you the correct value for your account when the New Edge PDP is enabled. ::: From e1123f7b40db2baf49f67d29a2c91c49ab4b0fde Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 12:06:59 +0300 Subject: [PATCH 04/11] Address Omer's review: compatibility matrix, config page, drop control-plane var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures per review feedback on #649. Feature compatibility and roadmap matrix replaces the split supported/not-supported lists. RBAC, ReBAC, multi-tenancy and ABAC are now nested under Check rather than floating as peers of it, and every row is scored against both runtimes with a supported / roadmap / not-supported key. PDP_CONTROL_PLANE is gone from the docs. The API key carries the routing and sends the PDP to the correct plane on its own; edge-pdp/src/config.rs:543 calls the variable an "operator ESCAPE HATCH, not the normal path", so documenting it as required was wrong and would have had customers setting a value they do not need. Cold start no longer leads with "is not fast" or quotes the CI figure. It now says what is actually true and useful — bulk-loading pre-built database files beats the old PDP's cold start, which applies its data set through the normal write path — while keeping the startup-probe guidance, which is what readers act on. Configuration moves to its own page, mirroring the existing PDP's configuration page, and carries an early-access note that variables and defaults may still change. The main page keeps a short pointer. OpenTelemetry moves into the matrix as roadmap. Removes forward-looking phrasing elsewhere ("known gap rather than a design decision", "not available yet") so nothing reads as a commitment or a timeline. Also corrects two rows while building the matrix: the old PDP has no Envoy ext-authz either, so listing it as a New Edge PDP gap was misleading and it is dropped; Prometheus /metrics and OTLP export are now separate rows, since the old PDP has the former (horizon/config.py:250) but not the latter. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- .../pdp/new-edge-pdp-configuration.mdx | 99 +++++++++++ docs/concepts/pdp/new-edge-pdp.mdx | 160 ++++++++---------- sidebars.js | 1 + 3 files changed, 170 insertions(+), 90 deletions(-) create mode 100644 docs/concepts/pdp/new-edge-pdp-configuration.mdx diff --git a/docs/concepts/pdp/new-edge-pdp-configuration.mdx b/docs/concepts/pdp/new-edge-pdp-configuration.mdx new file mode 100644 index 00000000..8f621b06 --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-configuration.mdx @@ -0,0 +1,99 @@ +--- +title: New Edge PDP Configuration +sidebar_label: New Edge PDP Configuration +description: Environment variable reference for the New Edge PDP (permitio/pdp-v3) — credentials, storage, ports, logging, and storage-engine tuning. +sidebar_position: 5 +--- + +# New Edge PDP Configuration + +Environment variable reference for the [New Edge PDP](/concepts/pdp/new-edge-pdp) (`permitio/pdp-v3`). + +For the existing Edge PDP (`permitio/pdp-v2`), see [Configurations](/concepts/pdp/configuration) instead — the two runtimes do not share a configuration surface, and a variable of the same name may mean something different. + +:::note Early access +The New Edge PDP is in early access, and its configuration surface is still settling. **Some variables and defaults on this page may change** before general availability. Pin an image tag and re-check this page when you upgrade. +::: + +--- + +## Credentials + +| Variable | Default | Description | +| --- | --- | --- | +| `PDP_API_KEY` | **required** | Your environment-level Permit API key. | + +`PDP_API_KEY` is the entire credential. A single value: + +- binds the container to exactly one Permit environment, +- authenticates it to the control plane, +- carries the control-plane routing, so the PDP finds the right plane on its own, +- and is the bearer token the authorization API accepts from your services. + +:::tip No control-plane URL to set +Unlike the existing Edge PDP, there is **no `PDP_CONTROL_PLANE` value to configure**. The API key routes the PDP to the correct control plane automatically. +::: + +--- + +## Storage and ports + +| Variable | Default | Description | +| --- | --- | --- | +| `EDGE_DB_PATH` | `/var/lib/edge-pdp/db` | Embedded database directory. **Must be on a persistent volume.** | +| `EDGE_DATA_DIR` | `/var/lib/edge-pdp` | Event store and generated credentials. **Must be on a persistent volume.** | +| `EDGE_PUBLIC_PORT` | `7000` | Authorization API — the only port your services need. | +| `EDGE_HEALTH_PORT` | `7001` | Health and readiness. Probe this port, [never `7000`](/concepts/pdp/new-edge-pdp#health-and-readiness). | + +Ports `7002`, `7003`, `8181`, `4222`, and `8222` are used internally and bind to loopback only. Expose **`7000` and `7001`** and nothing else. + +Both storage paths must be on persistent storage that survives restarts. On ephemeral storage every restart becomes a full cold start. See [Deployment requirements](/concepts/pdp/new-edge-pdp#deployment-requirements). + +--- + +## Behavior and tuning + +| Variable | Default | Description | +| --- | --- | --- | +| `RUST_LOG` | `info` | Log level. Try `info,edge_pdp=debug` when debugging. | +| `PDP_DEBUG` | `false` | Include debug detail in authorization responses. | +| `OPA_TIMEOUT_MS` | `800` | Per-request policy evaluation timeout. | +| `EDGE_PARALLELISM` | `4` | How many change transactions are applied concurrently. | +| `EDGE_DECISION_LOG_OPT_OUT` | `false` | Set to `true` to stop shipping decision logs. | +| `EDGE_DRAIN_TIMEOUT_SECS` | `10` | Shutdown drain budget. | +| `EDGE_CHILD_TERMINATION_TIMEOUT_SECS` | `30` | Child-process shutdown budget. | + +Your `terminationGracePeriodSeconds` must be at least the sum of the last two — **40 seconds at defaults**. See [Deployment requirements](/concepts/pdp/new-edge-pdp#deployment-requirements). + +### Child process logging + +| Variable | Default | Description | +| --- | --- | --- | +| `EDGE_NATS_LOG_LEVEL` | follows `RUST_LOG` | Control-plane client log level. | +| `EDGE_OPA_LOG_LEVEL` | follows `RUST_LOG` | Policy engine log level. | + +:::warning Debug levels expose your data +`EDGE_NATS_LOG_LEVEL=trace` logs raw message payloads — which means **your authorization data** — and `EDGE_OPA_LOG_LEVEL=debug` logs authorization request and response bodies. Use these only for short, deliberate debugging sessions, and never leave them on in production. +::: + +--- + +## Storage engine + +The embedded database inherits storage-engine defaults tuned for cloud-scale workloads, not for a small container. These are the levers that matter for memory footprint: + +| Variable | Default | Effect | +| --- | --- | --- | +| `SURREAL_ROCKSDB_BLOCK_CACHE_SIZE` | `536870912` (512 MiB) | Read cache. The single largest lever on resident memory. | +| `SURREAL_ROCKSDB_WRITE_BUFFER_SIZE` | `268435456` (256 MiB) | In-memory write buffer before flushing to disk. | +| `SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER` | `32` | How many write buffers may exist at once. | +| `SURREAL_ROCKSDB_BACKGROUND_THREADS` | `4` | Background compaction threads. | + +At these defaults a container given only a few hundred MiB will be OOM-killed on startup. Start at **4 GiB** and tune down against your own data set. See [Resource footprint](/concepts/pdp/new-edge-pdp#resource-footprint). + +--- + +## Related documentation + +- [New Edge PDP](/concepts/pdp/new-edge-pdp) — architecture, feature compatibility, and deployment requirements +- [Configurations](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index d7417656..1edb1db7 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -12,7 +12,7 @@ sidebar_position: 4 The **New Edge PDP** is a ground-up rewrite of the PDP runtime, shipped as `permitio/pdp-v3`. It runs one container per Permit environment, holds a **local, on-disk copy** of that environment's policy and authorization data, and answers authorization queries with **no network call in the decision path**. :::note Availability -The New Edge PDP is a **new deployment option**, not a replacement. The existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) remains fully supported and continues to be the right choice for the capabilities listed under [Feature support](#feature-support) below. +The New Edge PDP is a **new deployment option**, not a replacement. The existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) remains fully supported and continues to be the right choice for the capabilities listed under [Feature compatibility](#feature-compatibility-and-roadmap) below. It is currently in early access and enabled per account. [Contact us](mailto:support@permit.io) to have it turned on. ::: @@ -115,7 +115,7 @@ Together these give **convergence by construction**: two PDPs in the same enviro :::note What this does and does not guarantee The consistency model is **eventual, with a convergent merge**. It guarantees that PDPs converge, that no update is lost, and that replays are safe. -It does **not** provide read-your-own-writes. There is no local write API — the New Edge PDP is read-only with respect to your data, and a fact written through Permit's API becomes visible once it propagates. If you need write-then-immediately-read semantics, use the existing Edge PDP's [local facts uploader](/how-to/manage-data/local-facts-uploader). +It does **not** provide read-your-own-writes. The New Edge PDP is read-only with respect to your data: a fact written through Permit's API becomes visible once it propagates. If you need write-then-immediately-read semantics today, use the existing Edge PDP's [local facts uploader](/how-to/manage-data/local-facts-uploader). A PDP that restarts with a backlog becomes ready and begins serving while it is still catching up. Whether it is serving stale data, and by how long, is reported on the health endpoint. ::: @@ -162,10 +162,12 @@ The boundary between the two is exact: the snapshot records the precise stream p A checksum mismatch, a missing chunk, or an empty snapshot is **fail-closed** — the PDP refuses to come up on a partial data set rather than serving decisions against one. -:::caution Cold start is not fast — plan probes accordingly -A cold start transfers and loads the environment's entire data set. Permit's own CI observes **84 seconds to roughly 4 minutes** for this on test environments, and larger environments will take longer. +Bulk-loading pre-built database files is substantially faster than the existing Edge PDP's cold start, which fetches and applies its data set through the normal write path. -The health endpoint answers throughout, so **set a generous startup probe** and let readiness — not liveness — gate traffic. The authorization port does not open until the PDP is ready, so there is no window in which it accepts a request it cannot answer correctly. +:::tip Still give startup room +A cold start transfers and loads your environment's entire data set, so it is not instant — how long depends on how much data your environment holds. + +Set a **generous startup probe** and let readiness, not liveness, gate traffic. The health endpoint answers throughout, and the authorization port does not open until the PDP is ready, so there is no window in which it accepts a request it cannot answer correctly. ::: --- @@ -203,16 +205,7 @@ The architectural consequence is that **memory stops scaling linearly with your :::caution Size generously to start, then tune The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. -If you need a smaller footprint, tune the storage engine directly: - -| Variable | Default | Effect | -| --- | --- | --- | -| `SURREAL_ROCKSDB_BLOCK_CACHE_SIZE` | `536870912` (512 MiB) | Read cache. The single largest lever on resident memory. | -| `SURREAL_ROCKSDB_WRITE_BUFFER_SIZE` | `268435456` (256 MiB) | In-memory write buffer before flushing to disk. | -| `SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER` | `32` | How many write buffers may exist at once. | -| `SURREAL_ROCKSDB_BACKGROUND_THREADS` | `4` | Background compaction threads. | - -Benchmark against your own data set before committing to a size. +If you need a smaller footprint, tune the storage engine directly — see [Storage engine](/concepts/pdp/new-edge-pdp-configuration#storage-engine) for the variables that matter. Benchmark against your own data set before committing to a size. ::: ### Disk @@ -221,48 +214,64 @@ Two paths must be on persistent storage — the embedded database and the event --- -## Feature support +## Feature compatibility and roadmap The New Edge PDP is **wire-compatible on the endpoints it implements** — same paths, same request and response shapes, so existing SDK integrations that use those endpoints work unchanged. But it implements a **subset** of the existing Edge PDP's surface. Its capability envelope is that of the **managed Cloud PDP, deployed in your own network** — not a port of the `pdp-v2` container. -### Supported +**✅ Supported · 🚧 On the roadmap · ❌ Not supported** + +### Permission checks + +| Capability | Endpoint | Edge PDP | New Edge PDP | +| --- | --- | :-: | :-: | +| [**Check**](/how-to/enforce-permissions/check) | `POST /allowed` | ✅ | ✅ | +|   └ RBAC | | ✅ | ✅ | +|   └ ReBAC, including role derivation | | ✅ | ✅ | +|   └ Multi-tenancy | | ✅ | ✅ | +|   └ ABAC — condition, user and resource sets | | ✅ | ❌ | +| [**Bulk check**](/how-to/enforce-permissions/bulk-check) | `POST /allowed/bulk` | ✅ | ✅ | +| [**User permissions**](/how-to/enforce-permissions/user-permissions) | `POST /user-permissions` | ✅ | ✅ | +| [**Authorized users**](/how-to/enforce-permissions/authorized-users) | `POST /authorized_users` | ✅ | ✅ | +| [**All-tenants check**](/how-to/enforce-permissions/all-tenants-check) | `POST /allowed/all-tenants` | ✅ | ❌ | +| [**Data filtering**](/how-to/enforce-permissions/data-filtering) via partial evaluation | — | ✅ | ❌ | +| [**URL-based enforcement**](/how-to/enforce-permissions/url-mapping/url-mapping-check) | `POST /allowed_url` | ✅ | ❌ | + +:::note Filtering without partial evaluation +Partial evaluation is not available, but graph-based filtering through `/user-permissions` and `/authorized_users` is — for many "which resources can this user see?" cases that is the same answer by a different route. +::: -| Capability | Endpoint | -| --- | --- | -| **RBAC** | — | -| **ReBAC**, including role derivation | — | -| **Multi-tenancy** | — | -| [Check](/how-to/enforce-permissions/check) | `POST /allowed` | -| [Bulk check](/how-to/enforce-permissions/bulk-check) | `POST /allowed/bulk` | -| [User permissions](/how-to/enforce-permissions/user-permissions) | `POST /user-permissions` | -| [Authorized users](/how-to/enforce-permissions/authorized-users) | `POST /authorized_users` | -| AuthZen access evaluation | `POST /access/v1/evaluation` | -| AuthZen bulk evaluations | `POST /access/v1/evaluations` | -| AuthZen subject / resource / action search | `POST /access/v1/search/{subject,resource,action}` | -| AuthZen discovery | `GET /.well-known/authzen-configuration` | -| Decision logs to Permit | — | -| Health check on the authorization port | `GET /health`, `GET /healthy` (aliases for the same handler) | - -### Not supported - -Use the existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) if you need any of these: - -| Not supported | Notes | -| --- | --- | -| **ABAC** — attribute-based access control | Condition sets, user sets, and resource sets are not evaluated. A check against an ABAC policy returns a deny. | -| **Policy-as-Code / custom OPA bundles** | Per-environment Rego bundle publishing is not available yet. | -| [**All-tenants check**](/how-to/enforce-permissions/all-tenants-check) | `POST /allowed/all-tenants` is not served. | -| [**Data filtering**](/how-to/enforce-permissions/data-filtering) via partial evaluation | Graph-based filtering through `/user-permissions` and `/authorized_users` **is** available. | -| [**URL-based enforcement**](/how-to/enforce-permissions/url-mapping/url-mapping-check) | `POST /allowed_url` is not served. | -| **Local facts / read-your-own-writes** | There is no local write API. | -| [**Local enforcement APIs**](/how-to/enforce-permissions/list-role-assignments) | `/local/*` endpoints are not served. | -| **Custom data sources** | The change stream is the only data plane. | -| **Kong and nginx integration endpoints** | — | -| **Envoy ext-authz / proxy mode** | — | -| **`/metrics` endpoint** | See [Observability](#observability). | -| **Offline / air-gapped first boot** | A PDP that cannot reach the control plane at first boot cannot start. Once started, it survives outages indefinitely. | +### Standards + +| Capability | Endpoint | Edge PDP | New Edge PDP | +| --- | --- | :-: | :-: | +| AuthZen access evaluation | `POST /access/v1/evaluation` | ✅ | ✅ | +| AuthZen bulk evaluations | `POST /access/v1/evaluations` | ✅ | ✅ | +| AuthZen subject / resource / action search | `POST /access/v1/search/{subject,resource,action}` | ✅ | ✅ | +| AuthZen discovery | `GET /.well-known/authzen-configuration` | ✅ | ✅ | + +### Policy and data + +| Capability | Edge PDP | New Edge PDP | +| --- | :-: | :-: | +| Policy-as-Code / custom Rego bundles | ✅ | ❌ | +| Local facts / read-your-own-writes | ✅ | ❌ | +| [Local enforcement APIs](/how-to/enforce-permissions/list-role-assignments) (`/local/*`) | ✅ | ❌ | +| Custom data sources | ✅ | ❌ | +| Kong and nginx integration endpoints | ✅ | ❌ | +| [Offline / air-gapped first boot](/how-to/deploy/offline-mode) | ✅ | ❌ | + +A New Edge PDP that cannot reach the control plane on its **first** boot cannot start. Once it has started, it survives control-plane outages indefinitely. + +### Operations + +| Capability | Edge PDP | New Edge PDP | +| --- | :-: | :-: | +| Decision logs to Permit | ✅ | ✅ | +| Health and readiness endpoints | ✅ | ✅ | +| Prometheus `/metrics` endpoint | ✅ | ❌ | +| OpenTelemetry (OTLP traces, metrics, logs) | ❌ | 🚧 | --- @@ -307,56 +316,26 @@ Structured logs go to stdout at `INFO` by default; `RUST_LOG` overrides (`info,e Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that `EDGE_DECISION_LOG_OPT_OUT` is not set — the local setting can only turn logging off, never on. -:::caution OpenTelemetry is not available yet -The New Edge PDP **does not currently export OpenTelemetry traces, metrics, or logs**, and does not read `OTEL_*` environment variables. It also does not serve a `/metrics` endpoint. +:::note OpenTelemetry and `/metrics` +The New Edge PDP does not currently export OpenTelemetry traces, metrics, or logs, does not read `OTEL_*` environment variables, and does not serve a `/metrics` endpoint. OTLP export is on the roadmap — see [Feature compatibility](#feature-compatibility-and-roadmap). -Observability today is structured stdout logs plus the health endpoints described above. If OTLP export is a requirement for your deployment, [let us know](mailto:support@permit.io) — it is a known gap rather than a design decision. +Observability today is structured stdout logs plus the health endpoints described above. If OTLP export matters for your deployment, [let us know](mailto:support@permit.io). ::: --- ## Configuration -### Required - -| Variable | Description | -| --- | --- | -| `PDP_API_KEY` | Your environment-level Permit API key. This single value binds the container to one environment, authenticates it to the control plane, and is the bearer token the authorization API accepts. | -| `PDP_CONTROL_PLANE` | The control-plane endpoint. **See the warning below — this is not the same kind of value as on `pdp-v2`.** | +`PDP_API_KEY` is the only variable you must set. It binds the container to one Permit environment, authenticates it to the control plane, **carries the control-plane routing so there is no endpoint URL to configure**, and is the bearer token your services present to the authorization API. -:::warning `PDP_CONTROL_PLANE` means something different here -On the existing Edge PDP, `PDP_CONTROL_PLANE` is an **HTTPS API base URL** (`https://api.permit.io`). +Everything else — storage paths, ports, log levels, and storage-engine tuning — has a working default. -On the New Edge PDP it is the **event-stream endpoint**. Give it a **scheme, host, and explicit port**, with **no path and no trailing slash** — for example `wss://control-plane.permit.io:443`. The port is not optional, and a trailing `/` or any path segment will prevent the PDP from connecting. Copying the `pdp-v2` value across will not work. +**See [New Edge PDP Configuration](/concepts/pdp/new-edge-pdp-configuration) for the full environment variable reference.** -Permit will give you the correct value for your account when the New Edge PDP is enabled. +:::note Early access +Configuration is still settling while the New Edge PDP is in early access. Some variables and defaults may change before general availability. ::: -### Storage and ports - -| Variable | Default | Description | -| --- | --- | --- | -| `EDGE_DB_PATH` | `/var/lib/edge-pdp/db` | Embedded database directory. **Must be on a persistent volume.** | -| `EDGE_DATA_DIR` | `/var/lib/edge-pdp` | Event store and generated credentials. **Must be on a persistent volume.** | -| `EDGE_PUBLIC_PORT` | `7000` | Authorization API. The only port your services need. | -| `EDGE_HEALTH_PORT` | `7001` | Health and readiness. | - -Ports `7002`, `7003`, `8181`, `4222`, and `8222` are used internally and bind to loopback only. Expose **`7000` and `7001`** and nothing else. - -### Behavior and tuning - -| Variable | Default | Description | -| --- | --- | --- | -| `RUST_LOG` | `info` | Log level. Try `info,edge_pdp=debug` when debugging. | -| `PDP_DEBUG` | `false` | Include debug detail in authorization responses. | -| `OPA_TIMEOUT_MS` | `800` | Per-request evaluation timeout. | -| `EDGE_PARALLELISM` | `4` | How many change transactions are applied concurrently. | -| `EDGE_DECISION_LOG_OPT_OUT` | `false` | Set to `true` to stop shipping decision logs. | -| `EDGE_NATS_LOG_LEVEL` | follows `RUST_LOG` | Control-plane client log level. See the warning above. | -| `EDGE_OPA_LOG_LEVEL` | follows `RUST_LOG` | OPA log level. See the warning above. | -| `EDGE_DRAIN_TIMEOUT_SECS` | `10` | Shutdown drain budget. | -| `EDGE_CHILD_TERMINATION_TIMEOUT_SECS` | `30` | Child-process shutdown budget. | - --- ## Deployment requirements @@ -410,7 +389,8 @@ Both connect to the same Permit environment and enforce the same policies you au ## Related documentation +- [New Edge PDP Configuration](/concepts/pdp/new-edge-pdp-configuration) — full environment variable reference - [PDP Overview](/concepts/pdp/overview) — deployment models for the existing Edge PDP - [Cloud PDP Capabilities](/concepts/pdp/cloud-pdp-capabilities) — the managed PDP's capability envelope, which the New Edge PDP mirrors -- [PDP Configuration](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP +- [Configurations](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP - [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/sidebars.js b/sidebars.js index 04995884..96266d40 100644 --- a/sidebars.js +++ b/sidebars.js @@ -380,6 +380,7 @@ const sidebars = { "concepts/pdp/cloud-pdp-capabilities", "concepts/pdp/cloud-pdp-benchmarks", "concepts/pdp/new-edge-pdp", + "concepts/pdp/new-edge-pdp-configuration", "how-to/manage-data/local-facts-uploader", ], }, From 054aaec881728fc216aa2eff84eca109d9bdbebb Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 12:50:43 +0300 Subject: [PATCH 05/11] Drop the environment variable reference while it is still settling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Omer flagged that some env vars are likely to change. Rather than publish a reference that goes stale, this removes it — it can come back once the surface settles. Deletes the configuration page added in the previous commit and its sidebar entry. The Configuration section on the main page keeps only PDP_API_KEY, which is the credential and is not going to change, and points readers at support for anything else. Also strips the volatile EDGE_* and SURREAL_ROCKSDB_* names from the prose that mentioned them incidentally, keeping the guidance itself intact: persistent storage is still mandatory, the 40s grace period is still stated as a combined shutdown budget, the child-process log-level warning still warns that turning them up exposes authorization data, and the storage-engine tuning advice still says which knobs matter without naming them. RUST_LOG stays, being a Rust ecosystem convention rather than something this PDP defines. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- .../pdp/new-edge-pdp-configuration.mdx | 99 ------------------- docs/concepts/pdp/new-edge-pdp.mdx | 21 ++-- sidebars.js | 1 - 3 files changed, 9 insertions(+), 112 deletions(-) delete mode 100644 docs/concepts/pdp/new-edge-pdp-configuration.mdx diff --git a/docs/concepts/pdp/new-edge-pdp-configuration.mdx b/docs/concepts/pdp/new-edge-pdp-configuration.mdx deleted file mode 100644 index 8f621b06..00000000 --- a/docs/concepts/pdp/new-edge-pdp-configuration.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: New Edge PDP Configuration -sidebar_label: New Edge PDP Configuration -description: Environment variable reference for the New Edge PDP (permitio/pdp-v3) — credentials, storage, ports, logging, and storage-engine tuning. -sidebar_position: 5 ---- - -# New Edge PDP Configuration - -Environment variable reference for the [New Edge PDP](/concepts/pdp/new-edge-pdp) (`permitio/pdp-v3`). - -For the existing Edge PDP (`permitio/pdp-v2`), see [Configurations](/concepts/pdp/configuration) instead — the two runtimes do not share a configuration surface, and a variable of the same name may mean something different. - -:::note Early access -The New Edge PDP is in early access, and its configuration surface is still settling. **Some variables and defaults on this page may change** before general availability. Pin an image tag and re-check this page when you upgrade. -::: - ---- - -## Credentials - -| Variable | Default | Description | -| --- | --- | --- | -| `PDP_API_KEY` | **required** | Your environment-level Permit API key. | - -`PDP_API_KEY` is the entire credential. A single value: - -- binds the container to exactly one Permit environment, -- authenticates it to the control plane, -- carries the control-plane routing, so the PDP finds the right plane on its own, -- and is the bearer token the authorization API accepts from your services. - -:::tip No control-plane URL to set -Unlike the existing Edge PDP, there is **no `PDP_CONTROL_PLANE` value to configure**. The API key routes the PDP to the correct control plane automatically. -::: - ---- - -## Storage and ports - -| Variable | Default | Description | -| --- | --- | --- | -| `EDGE_DB_PATH` | `/var/lib/edge-pdp/db` | Embedded database directory. **Must be on a persistent volume.** | -| `EDGE_DATA_DIR` | `/var/lib/edge-pdp` | Event store and generated credentials. **Must be on a persistent volume.** | -| `EDGE_PUBLIC_PORT` | `7000` | Authorization API — the only port your services need. | -| `EDGE_HEALTH_PORT` | `7001` | Health and readiness. Probe this port, [never `7000`](/concepts/pdp/new-edge-pdp#health-and-readiness). | - -Ports `7002`, `7003`, `8181`, `4222`, and `8222` are used internally and bind to loopback only. Expose **`7000` and `7001`** and nothing else. - -Both storage paths must be on persistent storage that survives restarts. On ephemeral storage every restart becomes a full cold start. See [Deployment requirements](/concepts/pdp/new-edge-pdp#deployment-requirements). - ---- - -## Behavior and tuning - -| Variable | Default | Description | -| --- | --- | --- | -| `RUST_LOG` | `info` | Log level. Try `info,edge_pdp=debug` when debugging. | -| `PDP_DEBUG` | `false` | Include debug detail in authorization responses. | -| `OPA_TIMEOUT_MS` | `800` | Per-request policy evaluation timeout. | -| `EDGE_PARALLELISM` | `4` | How many change transactions are applied concurrently. | -| `EDGE_DECISION_LOG_OPT_OUT` | `false` | Set to `true` to stop shipping decision logs. | -| `EDGE_DRAIN_TIMEOUT_SECS` | `10` | Shutdown drain budget. | -| `EDGE_CHILD_TERMINATION_TIMEOUT_SECS` | `30` | Child-process shutdown budget. | - -Your `terminationGracePeriodSeconds` must be at least the sum of the last two — **40 seconds at defaults**. See [Deployment requirements](/concepts/pdp/new-edge-pdp#deployment-requirements). - -### Child process logging - -| Variable | Default | Description | -| --- | --- | --- | -| `EDGE_NATS_LOG_LEVEL` | follows `RUST_LOG` | Control-plane client log level. | -| `EDGE_OPA_LOG_LEVEL` | follows `RUST_LOG` | Policy engine log level. | - -:::warning Debug levels expose your data -`EDGE_NATS_LOG_LEVEL=trace` logs raw message payloads — which means **your authorization data** — and `EDGE_OPA_LOG_LEVEL=debug` logs authorization request and response bodies. Use these only for short, deliberate debugging sessions, and never leave them on in production. -::: - ---- - -## Storage engine - -The embedded database inherits storage-engine defaults tuned for cloud-scale workloads, not for a small container. These are the levers that matter for memory footprint: - -| Variable | Default | Effect | -| --- | --- | --- | -| `SURREAL_ROCKSDB_BLOCK_CACHE_SIZE` | `536870912` (512 MiB) | Read cache. The single largest lever on resident memory. | -| `SURREAL_ROCKSDB_WRITE_BUFFER_SIZE` | `268435456` (256 MiB) | In-memory write buffer before flushing to disk. | -| `SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER` | `32` | How many write buffers may exist at once. | -| `SURREAL_ROCKSDB_BACKGROUND_THREADS` | `4` | Background compaction threads. | - -At these defaults a container given only a few hundred MiB will be OOM-killed on startup. Start at **4 GiB** and tune down against your own data set. See [Resource footprint](/concepts/pdp/new-edge-pdp#resource-footprint). - ---- - -## Related documentation - -- [New Edge PDP](/concepts/pdp/new-edge-pdp) — architecture, feature compatibility, and deployment requirements -- [Configurations](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 1edb1db7..149545b4 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -205,7 +205,7 @@ The architectural consequence is that **memory stops scaling linearly with your :::caution Size generously to start, then tune The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. -If you need a smaller footprint, tune the storage engine directly — see [Storage engine](/concepts/pdp/new-edge-pdp-configuration#storage-engine) for the variables that matter. Benchmark against your own data set before committing to a size. +A smaller footprint is possible by tuning the storage engine's cache and write-buffer sizes — [contact us](mailto:support@permit.io) for the current settings. Benchmark against your own data set before committing to a size. ::: ### Disk @@ -309,12 +309,12 @@ Neither staleness nor decision-log failures gate readiness. A PDP that cannot sh Structured logs go to stdout at `INFO` by default; `RUST_LOG` overrides (`info,edge_pdp=debug` is a good verbose setting). Both child processes' output is captured and re-emitted on the main log stream with attribution and correct log levels, so container logs are the single place to look. :::warning Child debug levels expose data -`EDGE_NATS_LOG_LEVEL=trace` logs raw message payloads — which means **your authorization data** — and `EDGE_OPA_LOG_LEVEL=debug` logs authorization request and response bodies. Use these only for short, deliberate debugging sessions. +The child processes have their own log levels, and turning them all the way up exposes data: the control-plane client logs raw message payloads at `trace` — which means **your authorization data** — and the policy engine logs authorization request and response bodies at `debug`. Use these only for short, deliberate debugging sessions, and never leave them on in production. ::: ### Decision logs -Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that `EDGE_DECISION_LOG_OPT_OUT` is not set — the local setting can only turn logging off, never on. +Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that the PDP's local opt-out is not set — the local setting can only turn logging off, never on. :::note OpenTelemetry and `/metrics` The New Edge PDP does not currently export OpenTelemetry traces, metrics, or logs, does not read `OTEL_*` environment variables, and does not serve a `/metrics` endpoint. OTLP export is on the roadmap — see [Feature compatibility](#feature-compatibility-and-roadmap). @@ -326,14 +326,12 @@ Observability today is structured stdout logs plus the health endpoints describe ## Configuration -`PDP_API_KEY` is the only variable you must set. It binds the container to one Permit environment, authenticates it to the control plane, **carries the control-plane routing so there is no endpoint URL to configure**, and is the bearer token your services present to the authorization API. +`PDP_API_KEY` is the only value you must set. It binds the container to one Permit environment, authenticates it to the control plane, **carries the control-plane routing so there is no endpoint URL to configure**, and is the bearer token your services present to the authorization API. -Everything else — storage paths, ports, log levels, and storage-engine tuning — has a working default. +Everything else — storage paths, ports, log levels, and storage-engine tuning — has a working default, so a container with a valid API key and a persistent volume will start. -**See [New Edge PDP Configuration](/concepts/pdp/new-edge-pdp-configuration) for the full environment variable reference.** - -:::note Early access -Configuration is still settling while the New Edge PDP is in early access. Some variables and defaults may change before general availability. +:::note Configuration reference +The New Edge PDP's remaining environment variables are still settling while it is in early access, so they are not documented here yet. [Contact us](mailto:support@permit.io) if you need to tune storage, logging, or the storage engine for your deployment. ::: --- @@ -342,9 +340,9 @@ Configuration is still settling while the New Edge PDP is in early access. Some The New Edge PDP has real operational requirements that the existing Edge PDP does not. Read these before deploying. -**Persistent storage is mandatory.** Both `EDGE_DB_PATH` and `EDGE_DATA_DIR` must survive restarts. On ephemeral storage, every restart becomes a full cold start — minutes of startup, and a full snapshot transfer each time. +**Persistent storage is mandatory.** Both the embedded database directory and the event store must survive restarts. On ephemeral storage, every restart becomes a full cold start — minutes of startup, and a full snapshot transfer each time. -**Set `terminationGracePeriodSeconds` to at least 40.** It must be at least `EDGE_DRAIN_TIMEOUT_SECS + EDGE_CHILD_TERMINATION_TIMEOUT_SECS`, which is 40 seconds at defaults. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. +**Set `terminationGracePeriodSeconds` to at least 40.** That is the combined shutdown budget for draining and for stopping the child processes, at default settings. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. **Set a generous startup probe.** Cold start can take minutes. Point liveness at `/health` and readiness at `/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. @@ -389,7 +387,6 @@ Both connect to the same Permit environment and enforce the same policies you au ## Related documentation -- [New Edge PDP Configuration](/concepts/pdp/new-edge-pdp-configuration) — full environment variable reference - [PDP Overview](/concepts/pdp/overview) — deployment models for the existing Edge PDP - [Cloud PDP Capabilities](/concepts/pdp/cloud-pdp-capabilities) — the managed PDP's capability envelope, which the New Edge PDP mirrors - [Configurations](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP diff --git a/sidebars.js b/sidebars.js index 96266d40..04995884 100644 --- a/sidebars.js +++ b/sidebars.js @@ -380,7 +380,6 @@ const sidebars = { "concepts/pdp/cloud-pdp-capabilities", "concepts/pdp/cloud-pdp-benchmarks", "concepts/pdp/new-edge-pdp", - "concepts/pdp/new-edge-pdp-configuration", "how-to/manage-data/local-facts-uploader", ], }, From 2a831b6bfa6984cf49660e3a02bb72622f9edba2 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 13:11:12 +0300 Subject: [PATCH 06/11] Remove the Configuration section entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the whole section rather than keeping a stub. Configuration is settling and onboarding for the New Edge PDP goes through Permit anyway, so a partial section was carrying little and risked going stale. Nothing else depended on it: no inbound links or anchors pointed at it, and the operational requirements readers actually need — persistent storage, the 40s grace period, probe targets, memory sizing, which ports to expose — all live in Deployment requirements. PDP_API_KEY still appears where it is describing behaviour rather than instructing configuration: the bearer for /health/detail, and the constant-time comparison in the security model. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp.mdx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 149545b4..98c56ee8 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -324,18 +324,6 @@ Observability today is structured stdout logs plus the health endpoints describe --- -## Configuration - -`PDP_API_KEY` is the only value you must set. It binds the container to one Permit environment, authenticates it to the control plane, **carries the control-plane routing so there is no endpoint URL to configure**, and is the bearer token your services present to the authorization API. - -Everything else — storage paths, ports, log levels, and storage-engine tuning — has a working default, so a container with a valid API key and a persistent volume will start. - -:::note Configuration reference -The New Edge PDP's remaining environment variables are still settling while it is in early access, so they are not documented here yet. [Contact us](mailto:support@permit.io) if you need to tune storage, logging, or the storage engine for your deployment. -::: - ---- - ## Deployment requirements The New Edge PDP has real operational requirements that the existing Edge PDP does not. Read these before deploying. From dd7275608c9e5921889726e6f0e3f6715d8abc34 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 14:14:53 +0300 Subject: [PATCH 07/11] Split into four pages, unify the parity table, drop "Edge PDP" for v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second round of review feedback from Omer. Terminology. The old PDP is no longer called "Edge PDP" anywhere — that term was confusing and largely unused. It is now the "container PDP" (permitio/pdp-v2), matching the wording already used in cloud-pdp-capabilities.mdx. "New Edge PDP" stays as the name for v3. Splitting. One long page becomes four, wired into the sidebar as a collapsible "New Edge PDP" sub-category with the overview as its landing page: Overview - what it is, why, architecture, choosing between the two How It Works - sync, consistency, latency, cold start, scale, footprint Feature Parity - the comparison table Deployment - requirements, observability, security model Parity table is now a single table rather than four split by theme, with the check sub-capabilities nested under Check. Removes the data-filtering row. Partial evaluation is not a feature of the container PDP either (no compile endpoint in horizon/ or pdp-server/), so scoring it as a v3 gap was wrong in both columns. Graph-based filtering, which both support, is covered by the user-permissions and authorized-users rows and called out in a note below the table. Same reasoning retires the ext-authz mention left in "Choosing between the two". Drops the standalone OpenTelemetry note from the observability section; the parity table is where that belongs and it was duplicated. One rendering fix found while checking the result: the merged table was 732px in a 640px column with overflow-x visible, so the New Edge PDP column was clipped rather than scrollable. Folding the endpoint into the capability cell drops it to three columns. Verified every table on all four pages now fits. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp-deployment.mdx | 98 ++++++ .../pdp/new-edge-pdp-feature-parity.mdx | 61 ++++ .../pdp/new-edge-pdp-how-it-works.mdx | 166 +++++++++ docs/concepts/pdp/new-edge-pdp.mdx | 316 +----------------- sidebars.js | 14 +- 5 files changed, 353 insertions(+), 302 deletions(-) create mode 100644 docs/concepts/pdp/new-edge-pdp-deployment.mdx create mode 100644 docs/concepts/pdp/new-edge-pdp-feature-parity.mdx create mode 100644 docs/concepts/pdp/new-edge-pdp-how-it-works.mdx diff --git a/docs/concepts/pdp/new-edge-pdp-deployment.mdx b/docs/concepts/pdp/new-edge-pdp-deployment.mdx new file mode 100644 index 00000000..5a27e430 --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-deployment.mdx @@ -0,0 +1,98 @@ +--- +title: New Edge PDP — Deployment +sidebar_label: Deployment +description: Deployment requirements, health and readiness probes, logging, and the security model for the New Edge PDP. +sidebar_position: 4 +--- + +# Deploying the New Edge PDP + +Operational requirements, observability surfaces, and the security model for the [New Edge PDP](./new-edge-pdp). + +--- + +## Deployment requirements + +The New Edge PDP has real operational requirements that the container PDP does not. Read these before deploying. + +**Persistent storage is mandatory.** Both the embedded database directory and the event store must survive restarts. On ephemeral storage, every restart becomes a full cold start — minutes of startup, and a full snapshot transfer each time. + +**Set `terminationGracePeriodSeconds` to at least 40.** That is the combined shutdown budget for draining and for stopping the child processes, at default settings. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. + +**Set a generous startup probe.** Cold start can take minutes. Point liveness at `/health` and readiness at `/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. + +**Run one container per Permit environment.** The API key binds the container to exactly one environment; there is no multi-environment mode. + +**Memory: start at 4 GiB.** See [Resource footprint](./new-edge-pdp-how-it-works#resource-footprint) for tuning. + +**Expose ports `7000` and `7001` only.** Everything else binds to loopback inside the container. + +**Runs as non-root** (uid/gid `10001`). Ensure your mounted volumes are writable by that user. + +:::tip Running locally on macOS +Port `7000` collides with AirPlay Receiver on macOS. Map it to a different host port, for example `-p 7766:7000`, and point your SDK at `http://localhost:7766`. +::: + +--- + +## Health and readiness + +Health is served on the **management port (`7001`)**, which is where all probes belong: + +| Endpoint | Auth | Behavior | +| --- | --- | --- | +| `GET /health` | None | Always `200`. Reports per-component status. Answers throughout startup. | +| `GET /health/ready` | None | `200` only when every gating component is up, otherwise `503`. | +| `GET /health/detail` | `PDP_API_KEY` bearer | Full diagnostic detail per component. | + +Wire **liveness** to `:7001/health` and **readiness** to `:7001/health/ready`. Nine components gate readiness — the control-plane link, each of the four sync planes, the embedded database, OPA, and both loopbacks. + +:::warning Probe `7001`, never `7000` +The authorization port (`7000`) also answers `GET /health` — and `GET /healthy`, an alias kept so tooling written for the container PDP keeps working. Neither is a substitute for the management port: + +- There is **no `/health/ready` on port `7000`**. A readiness probe pointed at `:7000/health/ready` gets a `404` and the pod never becomes ready. +- Port `7000` does not open at all until the PDP is ready, so any probe against it **fails for the entire cold start** — which can be minutes. A liveness probe there will kill the container before it ever finishes starting. + +Port `7001` answers from the moment the process starts, which is exactly what makes it the safe probe target. +::: + +`/health/detail` is where you diagnose a lagging or stale PDP: it reports child-process restart counts and exit codes, the PDP's position in the change stream against the control plane's oldest retained change, whether the PDP is currently **serving stale** data and for how long, and whether decision-log shipping is healthy. + +:::note Staleness never blocks decisions +Neither staleness nor decision-log failures gate readiness. A PDP that cannot ship its audit logs keeps answering authorization queries — the design deliberately refuses to trade the primary function for the secondary one. +::: + +--- + +## Logs + +Structured logs go to stdout at `INFO` by default; `RUST_LOG` overrides (`info,edge_pdp=debug` is a good verbose setting). Both child processes' output is captured and re-emitted on the main log stream with attribution and correct log levels, so container logs are the single place to look. + +:::warning Child debug levels expose data +The child processes have their own log levels, and turning them all the way up exposes data: the control-plane client logs raw message payloads at `trace` — which means **your authorization data** — and the policy engine logs authorization request and response bodies at `debug`. Use these only for short, deliberate debugging sessions, and never leave them on in production. +::: + +### Decision logs + +Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that the PDP's local opt-out is not set — the local setting can only turn logging off, never on. + +--- + +## Security model + +**Requests are authenticated against the container's own key.** The bearer token on an authorization request is compared, in constant time, against the container's `PDP_API_KEY`. Permit's API is **never** contacted to authenticate or authorize a request — the code that could do so is not compiled into this binary at all. + +**One container serves exactly one environment.** The environment is fixed at startup from the API key. A valid key for a *different* environment does not authenticate, and there is no code path by which a caller can steer a request at data outside the container's own environment. + +**Credentials never reach the child processes.** Both OPA and the NATS leaf are started with a cleared environment; only the specific variables each one needs are re-added. Credential files are written `0600` with protections against symlink attacks from other containers sharing the volume, and are referenced by path rather than inlined into any generated config. + +**The blast radius of a compromised PDP is one environment's data.** It holds no credential that grants access to any other environment, and no ability to reach Permit's management API. + +--- + +## Related documentation + +- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile +- [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx new file mode 100644 index 00000000..09df0de9 --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx @@ -0,0 +1,61 @@ +--- +title: New Edge PDP — Feature Parity +sidebar_label: Feature Parity +description: Capability-by-capability comparison between the container PDP (permitio/pdp-v2) and the New Edge PDP (permitio/pdp-v3). +sidebar_position: 3 +--- + +# Feature Parity + +Capability-by-capability comparison between the [container PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) and the [New Edge PDP](./new-edge-pdp) (`permitio/pdp-v3`). + +The New Edge PDP is **wire-compatible on the endpoints it implements** — same paths, same request and response shapes, so existing SDK integrations that use those endpoints work unchanged. But it implements a **subset** of the container PDP's surface. + +Its capability envelope is that of the **managed Cloud PDP, deployed in your own network** — not a port of the `pdp-v2` container. + +**✅ Supported · 🚧 On the roadmap · ❌ Not supported** + +| Capability | Container PDP | New Edge PDP | +| --- | :-: | :-: | +| [**Check**](/how-to/enforce-permissions/check) — `POST /allowed` | ✅ | ✅ | +|   └ RBAC | ✅ | ✅ | +|   └ ReBAC, including role derivation | ✅ | ✅ | +|   └ Multi-tenancy | ✅ | ✅ | +|   └ ABAC — condition, user and resource sets | ✅ | ❌ | +| [**Bulk check**](/how-to/enforce-permissions/bulk-check) — `POST /allowed/bulk` | ✅ | ✅ | +| [**User permissions**](/how-to/enforce-permissions/user-permissions) — `POST /user-permissions` | ✅ | ✅ | +| [**Authorized users**](/how-to/enforce-permissions/authorized-users) — `POST /authorized_users` | ✅ | ✅ | +| [**All-tenants check**](/how-to/enforce-permissions/all-tenants-check) — `POST /allowed/all-tenants` | ✅ | ❌ | +| [**URL-based enforcement**](/how-to/enforce-permissions/url-mapping/url-mapping-check) — `POST /allowed_url` | ✅ | ❌ | +| **AuthZen access evaluation** — `POST /access/v1/evaluation` | ✅ | ✅ | +| **AuthZen bulk evaluations** — `POST /access/v1/evaluations` | ✅ | ✅ | +| **AuthZen subject / resource / action search** | ✅ | ✅ | +| **AuthZen discovery** — `GET /.well-known/authzen-configuration` | ✅ | ✅ | +| **Policy-as-Code / custom Rego bundles** | ✅ | ❌ | +| **Local facts / read-your-own-writes** | ✅ | ❌ | +| [**Local enforcement APIs**](/how-to/enforce-permissions/list-role-assignments) — `/local/*` | ✅ | ❌ | +| **Custom data sources** | ✅ | ❌ | +| **Kong and nginx integration** | ✅ | ❌ | +| [**Offline / air-gapped first boot**](/how-to/deploy/offline-mode) | ✅ | ❌ | +| **Decision logs to Permit** | ✅ | ✅ | +| **Health and readiness endpoints** | ✅ | ✅ | +| **Prometheus metrics** — `GET /metrics` | ✅ | ❌ | +| **OpenTelemetry (OTLP traces, metrics, logs)** | ❌ | 🚧 | + +--- + +## Notes on specific rows + +**Data filtering.** Filtering through the information graph — [`/user-permissions`](/how-to/enforce-permissions/user-permissions) and [`/authorized_users`](/how-to/enforce-permissions/authorized-users) — works on both PDPs, and covers most "which resources can this user see?" cases. See [Data Filtering](/how-to/enforce-permissions/data-filtering) for the available approaches. + +**Offline first boot.** A New Edge PDP that cannot reach the control plane on its **first** boot cannot start. Once it has started, it survives control-plane outages indefinitely, serving decisions from its local copy. + +**ABAC.** A check evaluated against an ABAC policy returns a deny on the New Edge PDP rather than an error. If your policies use condition sets, user sets, or resource sets, use the container PDP. + +--- + +## Related documentation + +- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile +- [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx new file mode 100644 index 00000000..ee54771f --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx @@ -0,0 +1,166 @@ +--- +title: New Edge PDP — How It Works +sidebar_label: How It Works +description: How the New Edge PDP keeps its local copy in sync, what consistency that gives you, how cold start and warm resume differ, and what it costs to run. +sidebar_position: 2 +--- + +# How the New Edge PDP Works + +How the [New Edge PDP](./new-edge-pdp) keeps its local copy of your policy and authorization data current, what consistency that gives you, and what it costs to run. + +--- + +## How policy and data stay in sync + +The container's NATS leaf node holds a persistent link to Permit's control plane and **sources** four independent planes onto local, on-disk storage: + +| Plane | Carries | +| --- | --- | +| **Change stream** | Transactions of authorization facts — users, tenants, resource instances, relationship tuples | +| **Policy files** | The compiled Rego bundle OPA evaluates | +| **Policy schema** | Role and permission definitions from your policy | +| **Snapshot** | A one-shot bulk transfer, used only during cold start | + +Each change arrives as a **transaction** — an ordered set of operations with a timestamp and a transaction ID — rather than as a "something changed, come and fetch it" signal. The data is in the message. + +### Retention is per-PDP, and server-side + +Each PDP has its own durable position in the change stream. The control plane **retains a change until that specific PDP has acknowledged applying it** — so a PDP that is restarting, disconnected, or slow does not lose updates, and does not need to re-fetch its whole data set to catch up. Interest is held by the subscription's *existence*, not by an open connection; a PDP that is disconnected for minutes resumes exactly where it left off. + +### Self-healing + +Two background tasks keep the local copy honest: + +- **A gap detector** compares the PDP's position in the change stream against the oldest change the control plane still holds. If the PDP has fallen so far behind that the stream can no longer bridge the difference, it signals for a rebuild. +- **A rebuild orchestrator** then builds a fresh copy of the database **in a side directory** and swaps it in atomically. + +Crucially, **the PDP keeps answering from the existing database while the rebuild runs**. The public port is never re-bound, no request is refused, and no decision waits on the rebuild. Staleness is surfaced on the health endpoint rather than turned into errors. + +:::info Design principle +The New Edge PDP **serves stale before it serves nothing**. A control-plane outage costs you freshness, never availability — decisions keep flowing from the local copy indefinitely. +::: + +--- + +## Consistency + +Consistency is a property of the design rather than something you configure. + +**Three mechanisms combine:** + +1. **A durably ordered log.** Changes are carried on an ordered, persisted stream. Nothing is dropped, and nothing is trimmed before every PDP that needs it has confirmed it. + +2. **Order-independent application.** Each fact carries `(timestamp, transaction id)`, and a write is applied **only if it is strictly newer than what is already stored**. Because the merge rule is convergent, the final state does not depend on the order in which changes were applied — which is what makes it safe to apply several transactions concurrently without risking a stale write landing on top of a fresh one. + +3. **Per-transaction atomicity.** All operations within a transaction are applied together, so a decision never observes half of a multi-part change. + +Together these give **convergence by construction**: two PDPs in the same environment that have seen the same set of changes hold the same state, regardless of the order or timing in which they received them, and re-delivering a change that was already applied is a no-op. + +:::note What this does and does not guarantee +The consistency model is **eventual, with a convergent merge**. It guarantees that PDPs converge, that no update is lost, and that replays are safe. + +It does **not** provide read-your-own-writes. The New Edge PDP is read-only with respect to your data: a fact written through Permit's API becomes visible once it propagates. If you need write-then-immediately-read semantics today, use the container PDP's [local facts uploader](/how-to/manage-data/local-facts-uploader). + +A PDP that restarts with a backlog becomes ready and begins serving while it is still catching up. Whether it is serving stale data, and by how long, is reported on the health endpoint. +::: + +--- + +## Propagation latency + +The sync path is structurally shorter than the container PDP's: + +| | Container PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | +| --- | --- | --- | +| Change notification | WebSocket notification | Push delivery on a durable per-PDP subscription | +| Data fetch | **A second call back to Permit's API** | None — the change travels *in* the message | +| Round trips per change | 2 or more | 1 | +| Missed while offline | Reconnect, then re-fetch | Retained server-side; resumes at its exact position | +| Calls to `api.permit.io` | Per update | **Never** | + +Removing the fetch leg removes both a round trip and a dependency: propagation no longer depends on the PDP being able to reach Permit's API, only on the event stream. + +:::caution Numbers not yet published +Permit has not yet published measured propagation-latency figures for the New Edge PDP. The improvement described above is architectural. If you have a latency target to validate against, [talk to us](mailto:support@permit.io) before designing around a specific number. +::: + +--- + +## Cold start and warm resume + +The PDP decides which path to take from the state of its persistent volume. + +### Warm resume — a restart with an intact volume + +The embedded database, the event store, and every subscription position are already on disk. The PDP reopens the database and resumes applying changes **from its last acknowledged position**. There is no snapshot transfer and no re-fetch. + +This is the normal case for a pod restart, a rolling update, or a short disconnection — and it is the path that benefits most from the design, because a disconnected PDP's changes were retained for it rather than discarded. + +### Cold start — first boot, or an unbridgeable gap + +On a genuinely fresh PDP — or when the gap detector determines the change stream can no longer bridge the difference — the PDP requests a **snapshot** of its environment. + +The snapshot is not a stream of individual records. It is a set of **pre-built database files**, transferred in chunks, checksum-verified, and then **bulk-loaded directly into the storage engine** — bypassing the normal per-record write path entirely. The snapshot also carries the Rego bundle and policy schema, so a cold-started PDP comes up with policy and data together. + +The boundary between the two is exact: the snapshot records the precise stream position it was cut at, and the PDP resumes the change stream from the very next change. Nothing is applied twice in a way that matters, and nothing is skipped. + +A checksum mismatch, a missing chunk, or an empty snapshot is **fail-closed** — the PDP refuses to come up on a partial data set rather than serving decisions against one. + +Bulk-loading pre-built database files is substantially faster than the container PDP's cold start, which fetches and applies its data set through the normal write path. + +:::tip Still give startup room +A cold start transfers and loads your environment's entire data set, so it is not instant — how long depends on how much data your environment holds. + +Set a **generous startup probe** and let readiness, not liveness, gate traffic. The health endpoint answers throughout, and the authorization port does not open until the PDP is ready, so there is no window in which it accepts a request it cannot answer correctly. +::: + +--- + +## Running at high volume + +What makes the New Edge PDP suitable for high request rates is mostly what it *doesn't* do per request: + +- **No network in the decision path** — every hop is loopback or local disk. +- **No control-plane call to authenticate** — the API key is verified against the PDP's own key in constant time. Permit's API is never contacted to authorize a request. +- **No dependency on Permit's availability** — a control-plane outage affects freshness, not throughput or availability. +- **Reads do not contend with syncing** — incoming changes are applied to the embedded database concurrently and independently of the request path. +- **The same evaluation core as the managed Cloud PDP**, which is already tuned for large-scale checks. + +:::caution Numbers not yet published +As with propagation latency, Permit has not published measured throughput figures for the New Edge PDP. The [Cloud PDP benchmarks](/concepts/pdp/cloud-pdp-benchmarks) measure a different deployment and should not be read as New Edge PDP numbers. +::: + +--- + +## Resource footprint + +The New Edge PDP changes **where** your authorization data lives, and that is the change that matters for sizing. + +| | Container PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | +| --- | --- | --- | +| Fact graph | In OPA's **in-memory** document | **On disk**, in an embedded database | +| Memory vs. data size | Scales with your data set | Bounded by a configurable cache | +| Processes | Python API server + OPAL client + OPA | Native binary + NATS leaf + OPA | +| Language runtime | CPython | None — compiled binary | +| Persistent storage | Not required | **Required** | + +The architectural consequence is that **memory stops scaling linearly with your data set**. On the container PDP, a large environment needs a large PDP, and very large data sets need sharding. On the New Edge PDP, the data set lives on disk and memory is governed by the database's cache size, which you control. + +:::caution Size generously to start, then tune +The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. + +A smaller footprint is possible by tuning the storage engine's cache and write-buffer sizes — [contact us](mailto:support@permit.io) for the current settings. Benchmark against your own data set before committing to a size. +::: + +### Disk + +Two paths must be on persistent storage — the embedded database and the event store. Size for **roughly twice your data set**, plus headroom: during a rebuild, two generations of the database exist side by side until the new one is swapped in. + +--- + +## Related documentation + +- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 98c56ee8..0775fc0d 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -1,8 +1,8 @@ --- title: New Edge PDP -sidebar_label: New Edge PDP +sidebar_label: Overview description: A single-binary PDP with an embedded database that keeps a local copy of your policy and authorization data in sync over a durable event stream — no network in the decision path. -sidebar_position: 4 +sidebar_position: 1 --- # New Edge PDP @@ -12,7 +12,7 @@ sidebar_position: 4 The **New Edge PDP** is a ground-up rewrite of the PDP runtime, shipped as `permitio/pdp-v3`. It runs one container per Permit environment, holds a **local, on-disk copy** of that environment's policy and authorization data, and answers authorization queries with **no network call in the decision path**. :::note Availability -The New Edge PDP is a **new deployment option**, not a replacement. The existing [Edge PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) remains fully supported and continues to be the right choice for the capabilities listed under [Feature compatibility](#feature-compatibility-and-roadmap) below. +The New Edge PDP is a **new deployment option**, not a replacement. The [container PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`) remains fully supported and continues to be the right choice for the capabilities listed under [Feature Parity](./new-edge-pdp-feature-parity). It is currently in early access and enabled per account. [Contact us](mailto:support@permit.io) to have it turned on. ::: @@ -21,7 +21,7 @@ It is currently in early access and enabled per account. [Contact us](mailto:sup ## Why a new runtime -Today's Edge PDP keeps your authorization data inside OPA's in-memory document, and refreshes it by reacting to a notification and then **fetching the changed data back from Permit's API**. That design is proven, but it has three structural ceilings: +The container PDP keeps your authorization data inside OPA's in-memory document, and refreshes it by reacting to a notification and then **fetching the changed data back from Permit's API**. That design is proven, but it has three structural ceilings: - **Memory scales with your data.** Because the fact graph lives in memory, a large environment needs a large PDP — which is why sharding exists for the biggest data sets. - **Every update costs a round trip.** A notification tells the PDP *that* something changed; a second call is needed to find out *what*. @@ -64,318 +64,32 @@ client → :7000 authorization API Every hop is loopback or local disk. **No step in answering an authorization query contacts Permit's API**, by construction — the New Edge PDP has no control plane other than its event stream, and cannot reach `api.permit.io` for a decision even if it wanted to. ---- - -## How policy and data stay in sync - -The container's NATS leaf node holds a persistent link to Permit's control plane and **sources** four independent planes onto local, on-disk storage: - -| Plane | Carries | -| --- | --- | -| **Change stream** | Transactions of authorization facts — users, tenants, resource instances, relationship tuples | -| **Policy files** | The compiled Rego bundle OPA evaluates | -| **Policy schema** | Role and permission definitions from your policy | -| **Snapshot** | A one-shot bulk transfer, used only during cold start | - -Each change arrives as a **transaction** — an ordered set of operations with a timestamp and a transaction ID — rather than as a "something changed, come and fetch it" signal. The data is in the message. - -### Retention is per-PDP, and server-side - -Each PDP has its own durable position in the change stream. The control plane **retains a change until that specific PDP has acknowledged applying it** — so a PDP that is restarting, disconnected, or slow does not lose updates, and does not need to re-fetch its whole data set to catch up. Interest is held by the subscription's *existence*, not by an open connection; a PDP that is disconnected for minutes resumes exactly where it left off. - -### Self-healing - -Two background tasks keep the local copy honest: - -- **A gap detector** compares the PDP's position in the change stream against the oldest change the control plane still holds. If the PDP has fallen so far behind that the stream can no longer bridge the difference, it signals for a rebuild. -- **A rebuild orchestrator** then builds a fresh copy of the database **in a side directory** and swaps it in atomically. - -Crucially, **the PDP keeps answering from the existing database while the rebuild runs**. The public port is never re-bound, no request is refused, and no decision waits on the rebuild. Staleness is surfaced on the health endpoint rather than turned into errors. - -:::info Design principle -The New Edge PDP **serves stale before it serves nothing**. A control-plane outage costs you freshness, never availability — decisions keep flowing from the local copy indefinitely. -::: - ---- - -## Consistency - -Consistency is a property of the design rather than something you configure. - -**Three mechanisms combine:** - -1. **A durably ordered log.** Changes are carried on an ordered, persisted stream. Nothing is dropped, and nothing is trimmed before every PDP that needs it has confirmed it. - -2. **Order-independent application.** Each fact carries `(timestamp, transaction id)`, and a write is applied **only if it is strictly newer than what is already stored**. Because the merge rule is convergent, the final state does not depend on the order in which changes were applied — which is what makes it safe to apply several transactions concurrently without risking a stale write landing on top of a fresh one. - -3. **Per-transaction atomicity.** All operations within a transaction are applied together, so a decision never observes half of a multi-part change. - -Together these give **convergence by construction**: two PDPs in the same environment that have seen the same set of changes hold the same state, regardless of the order or timing in which they received them, and re-delivering a change that was already applied is a no-op. - -:::note What this does and does not guarantee -The consistency model is **eventual, with a convergent merge**. It guarantees that PDPs converge, that no update is lost, and that replays are safe. - -It does **not** provide read-your-own-writes. The New Edge PDP is read-only with respect to your data: a fact written through Permit's API becomes visible once it propagates. If you need write-then-immediately-read semantics today, use the existing Edge PDP's [local facts uploader](/how-to/manage-data/local-facts-uploader). - -A PDP that restarts with a backlog becomes ready and begins serving while it is still catching up. Whether it is serving stale data, and by how long, is reported on the health endpoint. -::: - ---- - -## Propagation latency - -The sync path is structurally shorter than the existing Edge PDP's: - -| | Edge PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | -| --- | --- | --- | -| Change notification | WebSocket notification | Push delivery on a durable per-PDP subscription | -| Data fetch | **A second call back to Permit's API** | None — the change travels *in* the message | -| Round trips per change | 2 or more | 1 | -| Missed while offline | Reconnect, then re-fetch | Retained server-side; resumes at its exact position | -| Calls to `api.permit.io` | Per update | **Never** | - -Removing the fetch leg removes both a round trip and a dependency: propagation no longer depends on the PDP being able to reach Permit's API, only on the event stream. - -:::caution Numbers not yet published -Permit has not yet published measured propagation-latency figures for the New Edge PDP. The improvement described above is architectural. If you have a latency target to validate against, [talk to us](mailto:support@permit.io) before designing around a specific number. -::: - ---- - -## Cold start and warm resume - -The PDP decides which path to take from the state of its persistent volume. - -### Warm resume — a restart with an intact volume - -The embedded database, the event store, and every subscription position are already on disk. The PDP reopens the database and resumes applying changes **from its last acknowledged position**. There is no snapshot transfer and no re-fetch. - -This is the normal case for a pod restart, a rolling update, or a short disconnection — and it is the path that benefits most from the design, because a disconnected PDP's changes were retained for it rather than discarded. - -### Cold start — first boot, or an unbridgeable gap - -On a genuinely fresh PDP — or when the gap detector determines the change stream can no longer bridge the difference — the PDP requests a **snapshot** of its environment. - -The snapshot is not a stream of individual records. It is a set of **pre-built database files**, transferred in chunks, checksum-verified, and then **bulk-loaded directly into the storage engine** — bypassing the normal per-record write path entirely. The snapshot also carries the Rego bundle and policy schema, so a cold-started PDP comes up with policy and data together. - -The boundary between the two is exact: the snapshot records the precise stream position it was cut at, and the PDP resumes the change stream from the very next change. Nothing is applied twice in a way that matters, and nothing is skipped. - -A checksum mismatch, a missing chunk, or an empty snapshot is **fail-closed** — the PDP refuses to come up on a partial data set rather than serving decisions against one. - -Bulk-loading pre-built database files is substantially faster than the existing Edge PDP's cold start, which fetches and applies its data set through the normal write path. - -:::tip Still give startup room -A cold start transfers and loads your environment's entire data set, so it is not instant — how long depends on how much data your environment holds. - -Set a **generous startup probe** and let readiness, not liveness, gate traffic. The health endpoint answers throughout, and the authorization port does not open until the PDP is ready, so there is no window in which it accepts a request it cannot answer correctly. -::: - ---- - -## Running at high volume - -What makes the New Edge PDP suitable for high request rates is mostly what it *doesn't* do per request: - -- **No network in the decision path** — every hop is loopback or local disk. -- **No control-plane call to authenticate** — the API key is verified against the PDP's own key in constant time. Permit's API is never contacted to authorize a request. -- **No dependency on Permit's availability** — a control-plane outage affects freshness, not throughput or availability. -- **Reads do not contend with syncing** — incoming changes are applied to the embedded database concurrently and independently of the request path. -- **The same evaluation core as the managed Cloud PDP**, which is already tuned for large-scale checks. - -:::caution Numbers not yet published -As with propagation latency, Permit has not published measured throughput figures for the New Edge PDP. The [Cloud PDP benchmarks](/concepts/pdp/cloud-pdp-benchmarks) measure a different deployment and should not be read as New Edge PDP numbers. -::: - ---- - -## Resource footprint - -The New Edge PDP changes **where** your authorization data lives, and that is the change that matters for sizing. - -| | Edge PDP (`pdp-v2`) | New Edge PDP (`pdp-v3`) | -| --- | --- | --- | -| Fact graph | In OPA's **in-memory** document | **On disk**, in an embedded database | -| Memory vs. data size | Scales with your data set | Bounded by a configurable cache | -| Processes | Python API server + OPAL client + OPA | Native binary + NATS leaf + OPA | -| Language runtime | CPython | None — compiled binary | -| Persistent storage | Not required | **Required** | - -The architectural consequence is that **memory stops scaling linearly with your data set**. On the existing Edge PDP, a large environment needs a large PDP, and very large data sets need sharding. On the New Edge PDP, the data set lives on disk and memory is governed by the database's cache size, which you control. - -:::caution Size generously to start, then tune -The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. - -A smaller footprint is possible by tuning the storage engine's cache and write-buffer sizes — [contact us](mailto:support@permit.io) for the current settings. Benchmark against your own data set before committing to a size. -::: - -### Disk - -Two paths must be on persistent storage — the embedded database and the event store. Size for **roughly twice your data set**, plus headroom: during a rebuild, two generations of the database exist side by side until the new one is swapped in. - ---- - -## Feature compatibility and roadmap - -The New Edge PDP is **wire-compatible on the endpoints it implements** — same paths, same request and response shapes, so existing SDK integrations that use those endpoints work unchanged. But it implements a **subset** of the existing Edge PDP's surface. - -Its capability envelope is that of the **managed Cloud PDP, deployed in your own network** — not a port of the `pdp-v2` container. - -**✅ Supported · 🚧 On the roadmap · ❌ Not supported** - -### Permission checks - -| Capability | Endpoint | Edge PDP | New Edge PDP | -| --- | --- | :-: | :-: | -| [**Check**](/how-to/enforce-permissions/check) | `POST /allowed` | ✅ | ✅ | -|   └ RBAC | | ✅ | ✅ | -|   └ ReBAC, including role derivation | | ✅ | ✅ | -|   └ Multi-tenancy | | ✅ | ✅ | -|   └ ABAC — condition, user and resource sets | | ✅ | ❌ | -| [**Bulk check**](/how-to/enforce-permissions/bulk-check) | `POST /allowed/bulk` | ✅ | ✅ | -| [**User permissions**](/how-to/enforce-permissions/user-permissions) | `POST /user-permissions` | ✅ | ✅ | -| [**Authorized users**](/how-to/enforce-permissions/authorized-users) | `POST /authorized_users` | ✅ | ✅ | -| [**All-tenants check**](/how-to/enforce-permissions/all-tenants-check) | `POST /allowed/all-tenants` | ✅ | ❌ | -| [**Data filtering**](/how-to/enforce-permissions/data-filtering) via partial evaluation | — | ✅ | ❌ | -| [**URL-based enforcement**](/how-to/enforce-permissions/url-mapping/url-mapping-check) | `POST /allowed_url` | ✅ | ❌ | - -:::note Filtering without partial evaluation -Partial evaluation is not available, but graph-based filtering through `/user-permissions` and `/authorized_users` is — for many "which resources can this user see?" cases that is the same answer by a different route. -::: - -### Standards - -| Capability | Endpoint | Edge PDP | New Edge PDP | -| --- | --- | :-: | :-: | -| AuthZen access evaluation | `POST /access/v1/evaluation` | ✅ | ✅ | -| AuthZen bulk evaluations | `POST /access/v1/evaluations` | ✅ | ✅ | -| AuthZen subject / resource / action search | `POST /access/v1/search/{subject,resource,action}` | ✅ | ✅ | -| AuthZen discovery | `GET /.well-known/authzen-configuration` | ✅ | ✅ | - -### Policy and data - -| Capability | Edge PDP | New Edge PDP | -| --- | :-: | :-: | -| Policy-as-Code / custom Rego bundles | ✅ | ❌ | -| Local facts / read-your-own-writes | ✅ | ❌ | -| [Local enforcement APIs](/how-to/enforce-permissions/list-role-assignments) (`/local/*`) | ✅ | ❌ | -| Custom data sources | ✅ | ❌ | -| Kong and nginx integration endpoints | ✅ | ❌ | -| [Offline / air-gapped first boot](/how-to/deploy/offline-mode) | ✅ | ❌ | - -A New Edge PDP that cannot reach the control plane on its **first** boot cannot start. Once it has started, it survives control-plane outages indefinitely. - -### Operations - -| Capability | Edge PDP | New Edge PDP | -| --- | :-: | :-: | -| Decision logs to Permit | ✅ | ✅ | -| Health and readiness endpoints | ✅ | ✅ | -| Prometheus `/metrics` endpoint | ✅ | ❌ | -| OpenTelemetry (OTLP traces, metrics, logs) | ❌ | 🚧 | - ---- - -## Observability - -### Health and readiness - -Health is served on the **management port (`7001`)**, which is where all probes belong: - -| Endpoint | Auth | Behavior | -| --- | --- | --- | -| `GET /health` | None | Always `200`. Reports per-component status. Answers throughout startup. | -| `GET /health/ready` | None | `200` only when every gating component is up, otherwise `503`. | -| `GET /health/detail` | `PDP_API_KEY` bearer | Full diagnostic detail per component. | - -Wire **liveness** to `:7001/health` and **readiness** to `:7001/health/ready`. Nine components gate readiness — the control-plane link, each of the four sync planes, the embedded database, OPA, and both loopbacks. - -:::warning Probe `7001`, never `7000` -The authorization port (`7000`) also answers `GET /health` — and `GET /healthy`, an alias kept so tooling written for the existing Edge PDP keeps working. Neither is a substitute for the management port: - -- There is **no `/health/ready` on port `7000`**. A readiness probe pointed at `:7000/health/ready` gets a `404` and the pod never becomes ready. -- Port `7000` does not open at all until the PDP is ready, so any probe against it **fails for the entire cold start** — which can be minutes. A liveness probe there will kill the container before it ever finishes starting. - -Port `7001` answers from the moment the process starts, which is exactly what makes it the safe probe target. -::: - -`/health/detail` is where you diagnose a lagging or stale PDP: it reports child-process restart counts and exit codes, the PDP's position in the change stream against the control plane's oldest retained change, whether the PDP is currently **serving stale** data and for how long, and whether decision-log shipping is healthy. - -:::note Staleness never blocks decisions -Neither staleness nor decision-log failures gate readiness. A PDP that cannot ship its audit logs keeps answering authorization queries — the design deliberately refuses to trade the primary function for the secondary one. -::: - -### Logs - -Structured logs go to stdout at `INFO` by default; `RUST_LOG` overrides (`info,edge_pdp=debug` is a good verbose setting). Both child processes' output is captured and re-emitted on the main log stream with attribution and correct log levels, so container logs are the single place to look. - -:::warning Child debug levels expose data -The child processes have their own log levels, and turning them all the way up exposes data: the control-plane client logs raw message payloads at `trace` — which means **your authorization data** — and the policy engine logs authorization request and response bodies at `debug`. Use these only for short, deliberate debugging sessions, and never leave them on in production. -::: - -### Decision logs - -Decision logs ship from the OPA child directly to Permit over HTTPS, batched, with a bounded in-memory buffer. They require **both** that decision logging is enabled for your environment in Permit **and** that the PDP's local opt-out is not set — the local setting can only turn logging off, never on. - -:::note OpenTelemetry and `/metrics` -The New Edge PDP does not currently export OpenTelemetry traces, metrics, or logs, does not read `OTEL_*` environment variables, and does not serve a `/metrics` endpoint. OTLP export is on the roadmap — see [Feature compatibility](#feature-compatibility-and-roadmap). - -Observability today is structured stdout logs plus the health endpoints described above. If OTLP export matters for your deployment, [let us know](mailto:support@permit.io). -::: - ---- - -## Deployment requirements - -The New Edge PDP has real operational requirements that the existing Edge PDP does not. Read these before deploying. - -**Persistent storage is mandatory.** Both the embedded database directory and the event store must survive restarts. On ephemeral storage, every restart becomes a full cold start — minutes of startup, and a full snapshot transfer each time. - -**Set `terminationGracePeriodSeconds` to at least 40.** That is the combined shutdown budget for draining and for stopping the child processes, at default settings. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. - -**Set a generous startup probe.** Cold start can take minutes. Point liveness at `/health` and readiness at `/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. - -**Run one container per Permit environment.** The API key binds the container to exactly one environment; there is no multi-environment mode. - -**Memory: start at 4 GiB.** See [Resource footprint](#resource-footprint) for tuning. - -**Runs as non-root** (uid/gid `10001`). Ensure your mounted volumes are writable by that user. - -:::tip Running locally on macOS -Port `7000` collides with AirPlay Receiver on macOS. Map it to a different host port, for example `-p 7766:7000`, and point your SDK at `http://localhost:7766`. -::: - ---- - -## Security model - -**Requests are authenticated against the container's own key.** The bearer token on an authorization request is compared, in constant time, against the container's `PDP_API_KEY`. Permit's API is **never** contacted to authenticate or authorize a request — the code that could do so is not compiled into this binary at all. - -**One container serves exactly one environment.** The environment is fixed at startup from the API key. A valid key for a *different* environment does not authenticate, and there is no code path by which a caller can steer a request at data outside the container's own environment. - -**Credentials never reach the child processes.** Both OPA and the NATS leaf are started with a cleared environment; only the specific variables each one needs are re-added. Credential files are written `0600` with protections against symlink attacks from other containers sharing the volume, and are referenced by path rather than inlined into any generated config. - -**The blast radius of a compromised PDP is one environment's data.** It holds no credential that grants access to any other environment, and no ability to reach Permit's management API. +For how data reaches that database and what consistency it gives you, see [How It Works](./new-edge-pdp-how-it-works). --- ## Choosing between the two -| Choose the **existing Edge PDP** when you need | Choose the **New Edge PDP** when you need | +| Choose the **container PDP** when you need | Choose the **New Edge PDP** when you need | | --- | --- | | ABAC, condition sets, user or resource sets | The largest data sets, without sharding | | Policy-as-Code / custom Rego bundles | Bounded, tunable memory rather than memory that scales with data | | Read-your-own-writes / local facts | Decisions that never depend on reaching Permit's API | -| Data filtering via partial evaluation | Sync that survives long disconnections without a re-fetch | -| URL-based enforcement, Kong, or ext-authz | Fewer moving parts — one binary, no Python runtime | -| An OpenTelemetry or `/metrics` pipeline, or deployment without persistent storage | Observability through structured logs and detailed health endpoints | +| URL-based enforcement or Kong integration | Sync that survives long disconnections without a re-fetch | +| Custom data sources | Fewer moving parts — one binary, no Python runtime | +| A Prometheus `/metrics` pipeline, or deployment without persistent storage | Observability through structured logs and detailed health endpoints | Both connect to the same Permit environment and enforce the same policies you author in Permit. You can run them side by side while you evaluate. +See [Feature Parity](./new-edge-pdp-feature-parity) for the full comparison. + --- ## Related documentation -- [PDP Overview](/concepts/pdp/overview) — deployment models for the existing Edge PDP +- [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile +- [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [PDP Overview](/concepts/pdp/overview) — deployment models for the container PDP - [Cloud PDP Capabilities](/concepts/pdp/cloud-pdp-capabilities) — the managed PDP's capability envelope, which the New Edge PDP mirrors -- [Configurations](/concepts/pdp/configuration) — configuration reference for the existing Edge PDP - [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/sidebars.js b/sidebars.js index 04995884..3f330c3b 100644 --- a/sidebars.js +++ b/sidebars.js @@ -379,7 +379,19 @@ const sidebars = { "concepts/pdp/configuration", "concepts/pdp/cloud-pdp-capabilities", "concepts/pdp/cloud-pdp-benchmarks", - "concepts/pdp/new-edge-pdp", + { + type: "category", + label: "New Edge PDP", + link: { + type: "doc", + id: "concepts/pdp/new-edge-pdp", + }, + items: [ + "concepts/pdp/new-edge-pdp-how-it-works", + "concepts/pdp/new-edge-pdp-feature-parity", + "concepts/pdp/new-edge-pdp-deployment", + ], + }, "how-to/manage-data/local-facts-uploader", ], }, From 984c38aa8ef1637c3bd113250ea36aa9ca743ac3 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 14:26:35 +0300 Subject: [PATCH 08/11] Stop calling it a "single-binary" PDP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot flagged that the description says single-binary while the page documents two supervised child processes (OPA and the NATS leaf). It is a real contradiction as written. Rather than swap in "single-container", which is true of the container PDP too and so distinguishes nothing, the framing now leads with what actually is distinctive: an embedded on-disk database, kept in sync over a durable event stream, with no network in the decision path. The precise account of what runs in-process versus as a child stays in the architecture table, which was already accurate. Also drops "fewer moving parts" from the comparison table. Both runtimes run three processes — Python API server + OPAL client + OPA against Rust binary + NATS leaf + OPA — so that claim was false. Replaced with the difference that holds: a compiled runtime with no Python or OPAL layer. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 0775fc0d..04a1996e 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -1,13 +1,13 @@ --- title: New Edge PDP sidebar_label: Overview -description: A single-binary PDP with an embedded database that keeps a local copy of your policy and authorization data in sync over a durable event stream — no network in the decision path. +description: A PDP with an embedded on-disk database that keeps a local copy of your policy and authorization data in sync over a durable event stream — no network in the decision path. sidebar_position: 1 --- # New Edge PDP -**A single-binary Policy Decision Point with an embedded database, kept in sync over a durable event stream.** +**A Policy Decision Point with an embedded on-disk database, kept in sync over a durable event stream.** The **New Edge PDP** is a ground-up rewrite of the PDP runtime, shipped as `permitio/pdp-v3`. It runs one container per Permit environment, holds a **local, on-disk copy** of that environment's policy and authorization data, and answers authorization queries with **no network call in the decision path**. @@ -76,7 +76,7 @@ For how data reaches that database and what consistency it gives you, see [How I | Policy-as-Code / custom Rego bundles | Bounded, tunable memory rather than memory that scales with data | | Read-your-own-writes / local facts | Decisions that never depend on reaching Permit's API | | URL-based enforcement or Kong integration | Sync that survives long disconnections without a re-fetch | -| Custom data sources | Fewer moving parts — one binary, no Python runtime | +| Custom data sources | A compiled runtime, with no Python or OPAL layer | | A Prometheus `/metrics` pipeline, or deployment without persistent storage | Observability through structured logs and detailed health endpoints | Both connect to the same Permit environment and enforce the same policies you author in Permit. You can run them side by side while you evaluate. From 20676469bf3ada5e881b1c3170da219d55c64e91 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 14:42:53 +0300 Subject: [PATCH 09/11] Self-review fixes: correct the v2 process model and three smaller issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed every change on this branch against source. Findings: The container PDP's process model was wrong. I had it as "Python API server + OPAL client + OPA" with CPython against v3's compiled binary. In fact permitio/pdp-v2's Dockerfile runs CMD ["/app/pdp"] — the Rust pdp-server — which supervises Python Horizon (the OPAL client) through a watchdog, plus OPA. So v2 is already Rust-fronted on a Python base. Corrected to "Rust API server + Python Horizon (OPAL client) + OPA", and the runtime row now reads "Python runtime: required for the OPAL client / not present", which is the difference that actually holds. That also invalidated "a compiled runtime" as a reason to choose v3, since both are compiled at the front door. Replaced with dropping the Python and OPAL layer, which is real. The diagram labels the change stream WAL and LOCAL_WAL while the prose calls it the change stream, with nothing connecting the two. Added a short note under the diagram. The SVG's accessible title still said "Edge PDP Architecture" — the only remaining bare "Edge PDP" anywhere, and one only screen-reader users would hit. Renamed, and replaced the generic "generated by Archify" description with one that describes the diagram. Deployment requirements said to point probes at /health and /health/ready without a port, two paragraphs above a warning whose entire point is that the port matters. Both now say :7001. Verified and left alone: AuthZen routes are identical on both PDPs (pdp-server/src/api/authzen/mod.rs registers the same /access/v1/* paths), so the parity row is right; nine gating components matches both the source and the diagram's own label; 40s is 10+30 at defaults. All 38 internal links and anchors resolve, no prose is duplicated across the four pages, every table is structurally consistent, and the diagram was checked in-browser in both light and dark. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- docs/concepts/pdp/new-edge-pdp-deployment.mdx | 2 +- docs/concepts/pdp/new-edge-pdp-how-it-works.mdx | 4 ++-- docs/concepts/pdp/new-edge-pdp.mdx | 6 +++++- static/images/pdp/new-edge-pdp-architecture.svg | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/concepts/pdp/new-edge-pdp-deployment.mdx b/docs/concepts/pdp/new-edge-pdp-deployment.mdx index 5a27e430..b6d14556 100644 --- a/docs/concepts/pdp/new-edge-pdp-deployment.mdx +++ b/docs/concepts/pdp/new-edge-pdp-deployment.mdx @@ -19,7 +19,7 @@ The New Edge PDP has real operational requirements that the container PDP does n **Set `terminationGracePeriodSeconds` to at least 40.** That is the combined shutdown budget for draining and for stopping the child processes, at default settings. If the grace period is too short, the container is killed mid-flush and the durable event store is corrupted — forcing a cold start on the next boot. -**Set a generous startup probe.** Cold start can take minutes. Point liveness at `/health` and readiness at `/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. +**Set a generous startup probe.** Cold start can take minutes. Point liveness at `:7001/health` and readiness at `:7001/health/ready`, and give startup enough headroom that a first boot is not killed as a hang. **Run one container per Permit environment.** The API key binds the container to exactly one environment; there is no multi-environment mode. diff --git a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx index ee54771f..e9edda56 100644 --- a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx +++ b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx @@ -141,8 +141,8 @@ The New Edge PDP changes **where** your authorization data lives, and that is th | --- | --- | --- | | Fact graph | In OPA's **in-memory** document | **On disk**, in an embedded database | | Memory vs. data size | Scales with your data set | Bounded by a configurable cache | -| Processes | Python API server + OPAL client + OPA | Native binary + NATS leaf + OPA | -| Language runtime | CPython | None — compiled binary | +| Processes | Rust API server + Python Horizon (OPAL client) + OPA | Rust binary + NATS leaf + OPA | +| Python runtime | Required, for the OPAL client | Not present | | Persistent storage | Not required | **Required** | The architectural consequence is that **memory stops scaling linearly with your data set**. On the container PDP, a large environment needs a large PDP, and very large data sets need sharding. On the New Edge PDP, the data set lives on disk and memory is governed by the database's cache size, which you control. diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index 04a1996e..bf686a78 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -41,6 +41,10 @@ Everything runs inside one container. The only externally exposed listeners are Click the diagram to enlarge it.

+:::note Reading the diagram +The diagram labels the change stream by its internal name, the **WAL** (write-ahead log). `LOCAL_WAL` is this container's own durable copy of it. Elsewhere in these pages it is called the *change stream*. +::: + ### What runs where | Component | Kind | Role | @@ -76,7 +80,7 @@ For how data reaches that database and what consistency it gives you, see [How I | Policy-as-Code / custom Rego bundles | Bounded, tunable memory rather than memory that scales with data | | Read-your-own-writes / local facts | Decisions that never depend on reaching Permit's API | | URL-based enforcement or Kong integration | Sync that survives long disconnections without a re-fetch | -| Custom data sources | A compiled runtime, with no Python or OPAL layer | +| Custom data sources | To drop the Python and OPAL layer entirely | | A Prometheus `/metrics` pipeline, or deployment without persistent storage | Observability through structured logs and detailed health endpoints | Both connect to the same Permit environment and enforce the same policies you author in Permit. You can run them side by side while you evaluate. diff --git a/static/images/pdp/new-edge-pdp-architecture.svg b/static/images/pdp/new-edge-pdp-architecture.svg index 09f72026..6d883b42 100644 --- a/static/images/pdp/new-edge-pdp-architecture.svg +++ b/static/images/pdp/new-edge-pdp-architecture.svg @@ -35,8 +35,8 @@ - Edge PDP Architecture - An architecture diagram generated by Archify. + New Edge PDP Architecture + Architecture of the New Edge PDP: the authorization API, OPA, health server, query and bundle loopbacks, embedded database, change ingest and NATS leaf inside one container, with Permit.io and your policy store outside it. From 5aba704e756d9b4b6f30348932700e9cb320cb25 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 16:04:23 +0300 Subject: [PATCH 10/11] Split architecture out, deepen "why", drop sharding references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third round of review feedback from Omer. Architecture moves to its own subpage so the root page can stay thin. The root is now the general idea, why it exists and what it gives you; Architecture carries the diagram, the per-component responsibilities, the port table and the request path, plus a new section on how the read path and the sync path are kept separate. Root page is down to 87 lines. "Why a new runtime" now names the actual root cause rather than the symptoms. OPA's data store is a single in-memory JSON document, which sets two ceilings: it has no indexes and no concept of edges, so it cannot be optimized for graph traversal or for the relationship-heavy queries ReBAC and derived roles need; and it is expensive, which OPA's own documentation quantifies at roughly 20x the memory of the same data in a compact on-disk form. Both points are quoted and linked to the OPA docs rather than asserted. The third ceiling, the notify-then-fetch round trip, follows. Sharding is gone from these pages — all three references. Note that seven other files still mention it (how-to/deploy/overview, on-prem management, reference and installation, manage-data/loading-data, concepts/pdp/overview which has a "Sharded Edge PDPs" section, and concepts/pdp/configuration); those are outside this PR and need their own ticket. The resource-footprint comparison now carries the same 20x citation instead of pointing at sharding as the container PDP's answer for large data sets. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- .../pdp/new-edge-pdp-architecture.mdx | 84 +++++++++++++++++++ docs/concepts/pdp/new-edge-pdp-deployment.mdx | 5 +- .../pdp/new-edge-pdp-feature-parity.mdx | 5 +- .../pdp/new-edge-pdp-how-it-works.mdx | 7 +- docs/concepts/pdp/new-edge-pdp.mdx | 59 +++++-------- sidebars.js | 1 + 6 files changed, 118 insertions(+), 43 deletions(-) create mode 100644 docs/concepts/pdp/new-edge-pdp-architecture.mdx diff --git a/docs/concepts/pdp/new-edge-pdp-architecture.mdx b/docs/concepts/pdp/new-edge-pdp-architecture.mdx new file mode 100644 index 00000000..1bbd2d23 --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-architecture.mdx @@ -0,0 +1,84 @@ +--- +title: New Edge PDP — Architecture +sidebar_label: Architecture +description: What runs inside the New Edge PDP container, which component is responsible for what, and the path a single authorization request takes. +sidebar_position: 2 +--- + +# New Edge PDP Architecture + +What runs inside the [New Edge PDP](./new-edge-pdp) container, which component is responsible for what, and the path a single authorization request takes. + +Everything runs inside one container. The only externally exposed listeners are the authorization API and the health endpoint. + +![Architecture of the New Edge PDP: your app and orchestrator on the left, the single New Edge PDP container holding the authorization API, OPA, the health server, the query and bundle loopbacks, the embedded database, the change-ingest worker and the NATS leaf, with Permit.io and your policy store on the right](/images/pdp/new-edge-pdp-architecture.svg) + +

+ Click the diagram to enlarge it. +

+ +:::note Reading the diagram +The diagram labels the change stream by its internal name, the **WAL** (write-ahead log). `LOCAL_WAL` is this container's own durable copy of it. Elsewhere in these pages it is called the *change stream*. +::: + +--- + +## What runs where + +| Component | Kind | Role | +| --- | --- | --- | +| **Authorization API** | In-process | The public REST surface on port `7000`. Opens **only once the PDP is ready**. | +| **OPA policy engine** | Supervised child process | Evaluates Rego. Bound to loopback only — never reachable from outside the container. | +| **Embedded database** | In-process (SurrealDB over RocksDB) | Your environment's fact graph — users, tenants, resource instances, relationships — **on disk**. | +| **NATS leaf node** | Supervised child process | Durable, on-disk event store. Holds the link to Permit's control plane. | +| **Change ingest and repair** | In-process task | Applies incoming changes to the embedded database; detects and repairs gaps. | +| **Query / bundle loopbacks** | In-process | Serve OPA its graph data and its policy bundle over `127.0.0.1`. | +| **Health and readiness** | In-process | Port `7001`. Answers throughout startup, including a long cold start. | + +### Ports + +| Port | Bound to | Purpose | +| --- | --- | --- | +| `7000` | All interfaces | Authorization API — the only port your services need | +| `7001` | All interfaces | Health and readiness — the port your orchestrator probes | +| `7002` | Loopback | Query loopback: serves OPA its graph data | +| `7003` | Loopback | Bundle loopback: serves OPA its Rego bundle | +| `8181` | Loopback | OPA | +| `4222` / `8222` | Loopback | NATS leaf client and monitoring | + +Only `7000` and `7001` are exposed by the container image. Everything else is reachable solely from inside it. + +--- + +## The decision path + +``` +client → :7000 authorization API + → 127.0.0.1 OPA + → 127.0.0.1 query loopback + → embedded database (local disk) +``` + +Every hop is loopback or local disk. **No step in answering an authorization query contacts Permit's API**, by construction — the New Edge PDP has no control plane other than its event stream, and cannot reach `api.permit.io` for a decision even if it wanted to. + +This is also what separates policy evaluation from graph traversal. OPA evaluates the Rego policy, but whenever that policy needs to know whether a user reaches a resource through a chain of roles and relationships, it calls out to the query loopback, which answers from the embedded database. Relationship queries run against a database built for them rather than against a JSON document held in memory. + +--- + +## Separation of duties + +The container is deliberately split so that the thing serving decisions is never blocked by the thing keeping data fresh: + +- **The request path** — authorization API, OPA, query loopback, embedded database — only ever reads. +- **The sync path** — NATS leaf, change ingest, gap detector, rebuild orchestrator — only ever writes, on its own tasks. + +They meet at the embedded database, which is why a control-plane outage costs freshness but never availability, and why a rebuild can run underneath a PDP that is still answering. See [How It Works](./new-edge-pdp-how-it-works) for what happens along the sync path. + +--- + +## Related documentation + +- [New Edge PDP](./new-edge-pdp) — overview +- [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile +- [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/docs/concepts/pdp/new-edge-pdp-deployment.mdx b/docs/concepts/pdp/new-edge-pdp-deployment.mdx index b6d14556..2e77297c 100644 --- a/docs/concepts/pdp/new-edge-pdp-deployment.mdx +++ b/docs/concepts/pdp/new-edge-pdp-deployment.mdx @@ -2,7 +2,7 @@ title: New Edge PDP — Deployment sidebar_label: Deployment description: Deployment requirements, health and readiness probes, logging, and the security model for the New Edge PDP. -sidebar_position: 4 +sidebar_position: 5 --- # Deploying the New Edge PDP @@ -92,7 +92,8 @@ Decision logs ship from the OPA child directly to Permit over HTTPS, batched, wi ## Related documentation -- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [New Edge PDP](./new-edge-pdp) — overview +- [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx index 09df0de9..c2315364 100644 --- a/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx +++ b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx @@ -2,7 +2,7 @@ title: New Edge PDP — Feature Parity sidebar_label: Feature Parity description: Capability-by-capability comparison between the container PDP (permitio/pdp-v2) and the New Edge PDP (permitio/pdp-v3). -sidebar_position: 3 +sidebar_position: 4 --- # Feature Parity @@ -56,6 +56,7 @@ Its capability envelope is that of the **managed Cloud PDP, deployed in your own ## Related documentation -- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [New Edge PDP](./new-edge-pdp) — overview +- [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx index e9edda56..aac74b8a 100644 --- a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx +++ b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx @@ -2,7 +2,7 @@ title: New Edge PDP — How It Works sidebar_label: How It Works description: How the New Edge PDP keeps its local copy in sync, what consistency that gives you, how cold start and warm resume differ, and what it costs to run. -sidebar_position: 2 +sidebar_position: 3 --- # How the New Edge PDP Works @@ -145,7 +145,7 @@ The New Edge PDP changes **where** your authorization data lives, and that is th | Python runtime | Required, for the OPAL client | Not present | | Persistent storage | Not required | **Required** | -The architectural consequence is that **memory stops scaling linearly with your data set**. On the container PDP, a large environment needs a large PDP, and very large data sets need sharding. On the New Edge PDP, the data set lives on disk and memory is governed by the database's cache size, which you control. +The architectural consequence is that **memory stops scaling linearly with your data set**. On the container PDP the fact graph is raw JSON held in memory, which [OPA documents](https://www.openpolicyagent.org/docs/policy-performance) as costing roughly **20x** what the same data occupies in a compact on-disk form. On the New Edge PDP the data set lives on disk in exactly that compact form, and memory is governed by the database's cache size, which you set. :::caution Size generously to start, then tune The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. @@ -161,6 +161,7 @@ Two paths must be on persistent storage — the embedded database and the event ## Related documentation -- [New Edge PDP](./new-edge-pdp) — overview and architecture +- [New Edge PDP](./new-edge-pdp) — overview +- [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index bf686a78..bf2f2935 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -21,54 +21,40 @@ It is currently in early access and enabled per account. [Contact us](mailto:sup ## Why a new runtime -The container PDP keeps your authorization data inside OPA's in-memory document, and refreshes it by reacting to a notification and then **fetching the changed data back from Permit's API**. That design is proven, but it has three structural ceilings: +The container PDP holds your authorization data inside OPA, and **OPA's data store is a single in-memory JSON document**. That one design choice sets two ceilings. -- **Memory scales with your data.** Because the fact graph lives in memory, a large environment needs a large PDP — which is why sharding exists for the biggest data sets. -- **Every update costs a round trip.** A notification tells the PDP *that* something changed; a second call is needed to find out *what*. -- **Catching up after a disconnect is a re-fetch.** There is no server-side record of precisely what a given PDP has and hasn't seen. +### It is not a graph store -The New Edge PDP changes all three. Data lives in an **embedded on-disk database**, updates **arrive already containing the change**, and the control plane **durably retains** each PDP's unacknowledged changes until that specific PDP confirms it applied them. +A JSON document has no indexes and no concept of edges. It cannot be optimized for graph traversal, or for the granular, relationship-heavy queries that ReBAC and derived roles depend on — questions like *"does this user reach this resource through some chain of roles and relationships?"* ---- +The New Edge PDP moves those questions out of the JSON document entirely. Relationship data lives in an embedded database built for graph traversal, and OPA queries it over a local loopback while it evaluates policy. Policy evaluation and graph traversal each run on something designed for the job. -## Architecture +### It is expensive in memory -Everything runs inside one container. The only externally exposed listeners are the authorization API and the health endpoint. +Because the fact graph lives in memory, a large environment needs a large PDP. OPA's own documentation is direct about the cost: -![Architecture of the New Edge PDP: your app and orchestrator on the left, the single New Edge PDP container holding the authorization API, OPA, the health server, the query and bundle loopbacks, the embedded database, the change-ingest worker and the NATS leaf, with Permit.io and your policy store on the right](/images/pdp/new-edge-pdp-architecture.svg) +> Memory usage scales with the size of the policy (i.e., Rego) and data (e.g., JSON) that you load into OPA. Raw JSON data loaded into OPA uses approximately 20x more memory compared to the same data stored in a compact, serialized format (e.g., on disk). +> +> — [OPA documentation: Policy Performance](https://www.openpolicyagent.org/docs/policy-performance) -

- Click the diagram to enlarge it. -

+The New Edge PDP stores exactly that compact, serialized form on disk, and keeps a bounded cache in memory. Memory becomes a number you set rather than a function of how much data your environment holds. -:::note Reading the diagram -The diagram labels the change stream by its internal name, the **WAL** (write-ahead log). `LOCAL_WAL` is this container's own durable copy of it. Elsewhere in these pages it is called the *change stream*. -::: +### Updates cost a round trip -### What runs where +There is a third ceiling, in how data arrives rather than where it lives. The container PDP refreshes by reacting to a notification and then **fetching the changed data back from Permit's API**. A notification says *that* something changed; a second call is needed to find out *what*. And catching up after a disconnect means re-fetching, because there is no server-side record of what a given PDP has and hasn't seen. -| Component | Kind | Role | -| --- | --- | --- | -| **Authorization API** | In-process | The public REST surface on port `7000`. Opens **only once the PDP is ready**. | -| **OPA policy engine** | Supervised child process | Evaluates Rego. Bound to loopback only — never reachable from outside the container. | -| **Embedded database** | In-process (SurrealDB over RocksDB) | Your environment's fact graph — users, tenants, resource instances, relationships — **on disk**. | -| **NATS leaf node** | Supervised child process | Durable, on-disk event store. Holds the link to Permit's control plane. | -| **Change ingest and repair** | In-process task | Applies incoming changes to the embedded database; detects and repairs gaps. | -| **Query / bundle loopbacks** | In-process | Serve OPA its graph data and its policy bundle over `127.0.0.1`. | -| **Health and readiness** | In-process | Port `7001`. Answers throughout startup, including a long cold start. | +On the New Edge PDP, updates **arrive already containing the change**, and the control plane **durably retains** each PDP's unacknowledged changes until that specific PDP confirms it applied them. -### The decision path +--- -``` -client → :7000 authorization API - → 127.0.0.1 OPA - → 127.0.0.1 query loopback - → embedded database (local disk) -``` +## What it gives you -Every hop is loopback or local disk. **No step in answering an authorization query contacts Permit's API**, by construction — the New Edge PDP has no control plane other than its event stream, and cannot reach `api.permit.io` for a decision even if it wanted to. +- **Decisions that never leave the container.** Every hop in the request path is loopback or local disk, so no authorization query depends on reaching Permit. +- **Memory you control.** The data set lives on disk; a configurable cache bounds what is resident. +- **Sync that survives disconnection.** Changes are retained per-PDP, server-side, until that PDP acknowledges them — no re-fetch on reconnect. +- **Availability over freshness.** A control-plane outage costs you freshness, never the ability to answer. The PDP serves from its local copy indefinitely, and reports how stale it is. -For how data reaches that database and what consistency it gives you, see [How It Works](./new-edge-pdp-how-it-works). +See [Architecture](./new-edge-pdp-architecture) for what runs inside the container, and [How It Works](./new-edge-pdp-how-it-works) for how the local copy is kept current. --- @@ -76,8 +62,8 @@ For how data reaches that database and what consistency it gives you, see [How I | Choose the **container PDP** when you need | Choose the **New Edge PDP** when you need | | --- | --- | -| ABAC, condition sets, user or resource sets | The largest data sets, without sharding | -| Policy-as-Code / custom Rego bundles | Bounded, tunable memory rather than memory that scales with data | +| ABAC, condition sets, user or resource sets | Large data sets without a large memory footprint | +| Policy-as-Code / custom Rego bundles | Relationship queries served from a graph-capable store | | Read-your-own-writes / local facts | Decisions that never depend on reaching Permit's API | | URL-based enforcement or Kong integration | Sync that survives long disconnections without a re-fetch | | Custom data sources | To drop the Python and OPAL layer entirely | @@ -91,6 +77,7 @@ See [Feature Parity](./new-edge-pdp-feature-parity) for the full comparison. ## Related documentation +- [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model diff --git a/sidebars.js b/sidebars.js index 3f330c3b..274cb04c 100644 --- a/sidebars.js +++ b/sidebars.js @@ -387,6 +387,7 @@ const sidebars = { id: "concepts/pdp/new-edge-pdp", }, items: [ + "concepts/pdp/new-edge-pdp-architecture", "concepts/pdp/new-edge-pdp-how-it-works", "concepts/pdp/new-edge-pdp-feature-parity", "concepts/pdp/new-edge-pdp-deployment", From 232139f06821216784f5bd7f7f706035ef934758 Mon Sep 17 00:00:00 2001 From: David Shoen Date: Tue, 8 Sep 2026 16:17:15 +0300 Subject: [PATCH 11/11] Restore the configuration page, marked beta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Omer asked for the configuration page back, marked beta and open to change. Restored from 054aaec^ and brought up to date with the current structure. The beta framing is the point, and it resolves the objection that got the page removed. It now opens with a caution saying the surface is not stable, that variables may be renamed, replaced or removed before GA, that it should be treated as a working reference rather than a contract, and that readers should pin an image tag and re-read on upgrade — plus an invitation to tell us if they depend on a specific variable so we can flag it before it moves. Updated from the version that was removed: "container PDP" throughout instead of "existing Edge PDP", sidebar position after Deployment, and deep links into the pages that did not exist when it was written — the port table on Architecture, health-and-readiness and deployment-requirements on Deployment, resource-footprint on How It Works. The storage-engine tuning advice on How It Works now points back here instead of at support, and every page carries a Configuration entry in its related docs. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011ghyfrjy3SGKPV8ruaXE8W --- .../pdp/new-edge-pdp-architecture.mdx | 1 + .../pdp/new-edge-pdp-configuration.mdx | 105 ++++++++++++++++++ docs/concepts/pdp/new-edge-pdp-deployment.mdx | 1 + .../pdp/new-edge-pdp-feature-parity.mdx | 1 + .../pdp/new-edge-pdp-how-it-works.mdx | 3 +- docs/concepts/pdp/new-edge-pdp.mdx | 1 + sidebars.js | 1 + 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 docs/concepts/pdp/new-edge-pdp-configuration.mdx diff --git a/docs/concepts/pdp/new-edge-pdp-architecture.mdx b/docs/concepts/pdp/new-edge-pdp-architecture.mdx index 1bbd2d23..6346bcc3 100644 --- a/docs/concepts/pdp/new-edge-pdp-architecture.mdx +++ b/docs/concepts/pdp/new-edge-pdp-architecture.mdx @@ -82,3 +82,4 @@ They meet at the embedded database, which is why a control-plane outage costs fr - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [Configuration](./new-edge-pdp-configuration) — environment variable reference (beta) diff --git a/docs/concepts/pdp/new-edge-pdp-configuration.mdx b/docs/concepts/pdp/new-edge-pdp-configuration.mdx new file mode 100644 index 00000000..83ab7978 --- /dev/null +++ b/docs/concepts/pdp/new-edge-pdp-configuration.mdx @@ -0,0 +1,105 @@ +--- +title: New Edge PDP — Configuration +sidebar_label: Configuration +description: Environment variable reference for the New Edge PDP (permitio/pdp-v3) — credentials, storage, ports, logging, and storage-engine tuning. +sidebar_position: 6 +--- + +# New Edge PDP Configuration + +Environment variable reference for the [New Edge PDP](./new-edge-pdp) (`permitio/pdp-v3`). + +:::caution Beta — expect changes +The New Edge PDP is in early access and **this configuration surface is not yet stable**. Variables and defaults on this page may be renamed, replaced, or removed before general availability. + +Treat it as a working reference rather than a contract: **pin an image tag**, and re-read this page when you upgrade. If you are depending on a specific variable, [tell us](mailto:support@permit.io) so we can flag it before it moves. +::: + +For the [container PDP](/concepts/pdp/overview#run-an-edge-pdp-with-docker) (`permitio/pdp-v2`), see [Configurations](/concepts/pdp/configuration) instead. The two runtimes do not share a configuration surface, and **a variable of the same name may mean something different** on each. + +--- + +## Credentials + +| Variable | Default | Description | +| --- | --- | --- | +| `PDP_API_KEY` | **required** | Your environment-level Permit API key. | + +`PDP_API_KEY` is the entire credential. This single value: + +- binds the container to exactly one Permit environment, +- authenticates it to the control plane, +- carries the control-plane routing, so the PDP finds the right plane on its own, +- and is the bearer token the authorization API accepts from your services. + +:::tip No control-plane URL to set +Unlike the container PDP, there is **no `PDP_CONTROL_PLANE` value to configure**. The API key routes the PDP to the correct control plane automatically. +::: + +--- + +## Storage and ports + +| Variable | Default | Description | +| --- | --- | --- | +| `EDGE_DB_PATH` | `/var/lib/edge-pdp/db` | Embedded database directory. **Must be on a persistent volume.** | +| `EDGE_DATA_DIR` | `/var/lib/edge-pdp` | Event store and generated credentials. **Must be on a persistent volume.** | +| `EDGE_PUBLIC_PORT` | `7000` | Authorization API — the only port your services need. | +| `EDGE_HEALTH_PORT` | `7001` | Health and readiness. Probe this port, [never `7000`](./new-edge-pdp-deployment#health-and-readiness). | + +Both storage paths must be on persistent storage that survives restarts. On ephemeral storage every restart becomes a full cold start. See [Deployment requirements](./new-edge-pdp-deployment#deployment-requirements). + +Ports `7002`, `7003`, `8181`, `4222`, and `8222` are used internally and bind to loopback only — see [Architecture](./new-edge-pdp-architecture#ports). Expose **`7000` and `7001`** and nothing else. + +--- + +## Behavior and tuning + +| Variable | Default | Description | +| --- | --- | --- | +| `RUST_LOG` | `info` | Log level. Try `info,edge_pdp=debug` when debugging. | +| `PDP_DEBUG` | `false` | Include debug detail in authorization responses. | +| `OPA_TIMEOUT_MS` | `800` | Per-request policy evaluation timeout. | +| `EDGE_PARALLELISM` | `4` | How many change transactions are applied concurrently. | +| `EDGE_DECISION_LOG_OPT_OUT` | `false` | Set to `true` to stop shipping decision logs. | +| `EDGE_DRAIN_TIMEOUT_SECS` | `10` | Shutdown drain budget. | +| `EDGE_CHILD_TERMINATION_TIMEOUT_SECS` | `30` | Child-process shutdown budget. | + +Your `terminationGracePeriodSeconds` must be at least the sum of the last two — **40 seconds at defaults**. See [Deployment requirements](./new-edge-pdp-deployment#deployment-requirements). + +### Child process logging + +| Variable | Default | Description | +| --- | --- | --- | +| `EDGE_NATS_LOG_LEVEL` | follows `RUST_LOG` | Control-plane client log level. | +| `EDGE_OPA_LOG_LEVEL` | follows `RUST_LOG` | Policy engine log level. | + +:::warning Debug levels expose your data +`EDGE_NATS_LOG_LEVEL=trace` logs raw message payloads — which means **your authorization data** — and `EDGE_OPA_LOG_LEVEL=debug` logs authorization request and response bodies. Use these only for short, deliberate debugging sessions, and never leave them on in production. +::: + +--- + +## Storage engine + +The embedded database inherits storage-engine defaults tuned for cloud-scale workloads, not for a small container. These are the levers that matter for memory footprint: + +| Variable | Default | Effect | +| --- | --- | --- | +| `SURREAL_ROCKSDB_BLOCK_CACHE_SIZE` | `536870912` (512 MiB) | Read cache. The single largest lever on resident memory. | +| `SURREAL_ROCKSDB_WRITE_BUFFER_SIZE` | `268435456` (256 MiB) | In-memory write buffer before flushing to disk. | +| `SURREAL_ROCKSDB_MAX_WRITE_BUFFER_NUMBER` | `32` | How many write buffers may exist at once. | +| `SURREAL_ROCKSDB_BACKGROUND_THREADS` | `4` | Background compaction threads. | + +At these defaults a container given only a few hundred MiB will be OOM-killed on startup. Start at **4 GiB** and tune down against your own data set. See [Resource footprint](./new-edge-pdp-how-it-works#resource-footprint). + +--- + +## Related documentation + +- [New Edge PDP](./new-edge-pdp) — overview +- [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path +- [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile +- [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [Configurations](/concepts/pdp/configuration) — configuration reference for the container PDP diff --git a/docs/concepts/pdp/new-edge-pdp-deployment.mdx b/docs/concepts/pdp/new-edge-pdp-deployment.mdx index 2e77297c..dfde924b 100644 --- a/docs/concepts/pdp/new-edge-pdp-deployment.mdx +++ b/docs/concepts/pdp/new-edge-pdp-deployment.mdx @@ -96,4 +96,5 @@ Decision logs ship from the OPA child directly to Permit over HTTPS, batched, wi - [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP +- [Configuration](./new-edge-pdp-configuration) — environment variable reference (beta) - [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx index c2315364..8bbfeb72 100644 --- a/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx +++ b/docs/concepts/pdp/new-edge-pdp-feature-parity.mdx @@ -60,3 +60,4 @@ Its capability envelope is that of the **managed Cloud PDP, deployed in your own - [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [Configuration](./new-edge-pdp-configuration) — environment variable reference (beta) diff --git a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx index aac74b8a..84df2f01 100644 --- a/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx +++ b/docs/concepts/pdp/new-edge-pdp-how-it-works.mdx @@ -150,7 +150,7 @@ The architectural consequence is that **memory stops scaling linearly with your :::caution Size generously to start, then tune The New Edge PDP's **default** configuration is not tuned for a small footprint. It inherits cloud-scale storage-engine defaults — a 512 MiB block cache and a 256 MiB write buffer — so a container given only a few hundred MiB will be OOM-killed on startup. Permit currently sizes it at **4 GiB** of memory. -A smaller footprint is possible by tuning the storage engine's cache and write-buffer sizes — [contact us](mailto:support@permit.io) for the current settings. Benchmark against your own data set before committing to a size. +A smaller footprint is possible by tuning the storage engine's cache and write-buffer sizes — see [Storage engine](./new-edge-pdp-configuration#storage-engine). Benchmark against your own data set before committing to a size. ::: ### Disk @@ -165,3 +165,4 @@ Two paths must be on persistent storage — the embedded database and the event - [Architecture](./new-edge-pdp-architecture) — what runs inside the container and the request path - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [Configuration](./new-edge-pdp-configuration) — environment variable reference (beta) diff --git a/docs/concepts/pdp/new-edge-pdp.mdx b/docs/concepts/pdp/new-edge-pdp.mdx index bf2f2935..daaca888 100644 --- a/docs/concepts/pdp/new-edge-pdp.mdx +++ b/docs/concepts/pdp/new-edge-pdp.mdx @@ -81,6 +81,7 @@ See [Feature Parity](./new-edge-pdp-feature-parity) for the full comparison. - [How It Works](./new-edge-pdp-how-it-works) — sync, consistency, cold start, and resource profile - [Feature Parity](./new-edge-pdp-feature-parity) — capability comparison against the container PDP - [Deployment](./new-edge-pdp-deployment) — requirements, observability, and the security model +- [Configuration](./new-edge-pdp-configuration) — environment variable reference (beta) - [PDP Overview](/concepts/pdp/overview) — deployment models for the container PDP - [Cloud PDP Capabilities](/concepts/pdp/cloud-pdp-capabilities) — the managed PDP's capability envelope, which the New Edge PDP mirrors - [Monitoring PDPs](/how-to/monitoring-pdps) — fleet visibility in the Permit dashboard diff --git a/sidebars.js b/sidebars.js index 274cb04c..8d1d26ce 100644 --- a/sidebars.js +++ b/sidebars.js @@ -391,6 +391,7 @@ const sidebars = { "concepts/pdp/new-edge-pdp-how-it-works", "concepts/pdp/new-edge-pdp-feature-parity", "concepts/pdp/new-edge-pdp-deployment", + "concepts/pdp/new-edge-pdp-configuration", ], }, "how-to/manage-data/local-facts-uploader",