Searchable, application-level encryption for building privacy-first apps. Encrypt fields in your
+ app, keep them fully queryable in Postgres, and let built-in zero-knowledge key management handle the
+ rest — CipherStash can never see your data or your keys.
+
+
+
+
+
+
+
+
⭐ Star this repo if encryption you can actually query is your thing!
-## What is the stack?
+
+
+> **CipherStash never sees your plaintext — or your keys.** Data is encrypted in your app with a unique
+> key per value, and keys are derived inside your application via [ZeroKMS][zerokms] — so a database
+> breach leaks only ciphertext. [See the security architecture →][security-architecture]
+
+## Encrypted fields. Real queries. Your tools.
+
+The `email` column below is stored as ciphertext with a unique key per row — and the search still works,
+because the query runs on the ciphertext. No decrypt-and-scan, no query rewrites.
+
+**Supabase** — same Supabase.js calls; filters are encrypted on the way in, results decrypted on the way out:
+
+```typescript
+const db = encryptedSupabase({ encryptionClient, supabaseClient });
+
+const { data } = await db.from("users", users)
+ .select("id, name, email")
+ .ilike("email", "%@acme.com"); // encrypted free-text match
+```
+
+**Prisma Next** — declare encrypted columns in `schema.prisma`, query with type-safe operators:
+
+```prisma
+model User {
+ id String @id
+ email cipherstash.EncryptedString()
+}
+```
+
+```typescript
+const rows = await db.orm.User
+ .where((u) => u.email.cipherstashIlike("%@acme.com"))
+ .all();
+```
+
+**Drizzle** — encrypted column types in your table, auto-encrypting operators in your queries:
+
+```typescript
+export const usersTable = pgTable("users", {
+ id: integer("id").primaryKey(),
+ email: types.TextMatch("email"), // → eql_v3.text_match — the type is the config
+});
+
+const results = await db.select().from(usersTable)
+ .where(await ops.ilike(usersTable.email, "%@acme.com"));
+```
+
+## Quick starts
+
+Pick the guide for the stack you're already on. Each takes about 5 minutes, starts on the
+**free developer tier** ([sign up][signup]), and begins with the same setup wizard:
+
+```bash
+npx stash init
+```
+
+| Quick start | Guide |
+|---|---|
+| **Supabase** | [Supabase quickstart →][supabase] |
+| **Prisma Next** | [Prisma Next quickstart →][prisma-next] |
+| **Drizzle ORM** | [Drizzle quickstart →][drizzle] |
+| **Raw PostgreSQL (`pg`)** | [PostgreSQL quickstart →][encryption] |
+| **DynamoDB** | [DynamoDB quickstart →][dynamodb] |
+
+> The Stack also ships a `stash` CLI for auth, schema, and database setup. See the [SDK reference][reference].
+
+## What's in the Stack
+
+### 🔐 Searchable encryption
+
+Encrypt individual fields and still run real queries against them — all on ciphertext, in PostgreSQL:
+
+| Query type | Operations | Docs |
+|---|---|---|
+| **Equality** | `=`, `IN` | [Equality queries →][query-equality] |
+| **Free-text search** | `LIKE` / `ILIKE`, match | [Text search →][query-match] |
+| **Range & ordering** | `<`, `>`, `BETWEEN`, `ORDER BY`, `MIN`/`MAX` | [Range queries →][query-range] |
+| **Encrypted JSON** | field access (`->`, `->>`), containment (`@>`) | [JSON queries →][query-json] |
-- [Encryption](https://cipherstash.com/docs/stack/cipherstash/encryption): Field-level encryption for TypeScript apps with searchable encrypted queries, zero-knowledge key management, and first-class ORM support.
+With [EQL v3][eql], the column type *is* the configuration. Declare a column with the encrypted type
+that names its data type and the operations it supports, and it's ready to query — there's no per-column
+search configuration to maintain in your client:
-## Quick look at the stack in action
+```sql
+CREATE TABLE users (
+ id serial PRIMARY KEY,
+ username text, -- plaintext — business as usual
+ email eql_v3.text_match, -- encrypted · free-text search
+ ssn eql_v3.text_eq, -- encrypted · equality
+ salary eql_v3.int4_ord, -- encrypted · range + ORDER BY
+ preferences eql_v3.json -- encrypted · field access + containment
+);
+```
-**Encryption**
+Encrypted types exist for text, integers, floats, numerics, dates, timestamps, booleans, and JSON, so
+your schema documents itself — and encrypted data stays indexable with standard Postgres indexes. No
+special index engine, no SQL rewrites. ORMs pick the types up transparently: declare the column as
+encrypted in `schema.prisma` or your Drizzle table and the Stack handles the rest. Only raw `pg` needs
+a client-side [schema][schema] — declared with the same type names:
```typescript
-import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack";
+import { encryptedTable, types } from "@cipherstash/stack/eql/v3";
-// 1. Define your schema
const users = encryptedTable("users", {
- email: encryptedColumn("email").equality().freeTextSearch(),
+ email: types.TextMatch("email"), // ↔ eql_v3.text_match
+ salary: types.Int4Ord("salary"), // ↔ eql_v3.int4_ord
});
+```
+
+→ [Searchable encryption][searchable-encryption] · [Schema][schema] · [Encrypt & decrypt][encrypt-decrypt] · [Bulk & model operations][model-ops]
+
+### 🔑 Authentication
+
+How you authenticate to ZeroKMS depends on who's asking for keys:
+
+- **Device auth** — browser-based login for local development: `npx stash auth login` opens your
+ browser and saves credentials to your local CipherStash profile. No secrets in your repo or shell.
+- **Access key auth** — service-level credentials for servers, workers, and CI, supplied via `CS_*`
+ environment variables.
+- **OIDC federation** — federate your identity provider's JWT so every key request authenticates *as the
+ signed-in user*, not as your app. Supported providers: **Supabase Auth**, **Clerk**, **Okta**, and **Auth0**
+ (any OIDC-compliant provider works).
-// 2. Initialize the client
+```typescript
+// Access key (default) — reads CS_* env vars, no config needed
const client = await Encryption({ schemas: [users] });
-// 3. Encrypt
-const encryptResult = await client.encrypt("secret@example.com", {
- column: users.email,
- table: users,
+// OIDC federation — every ZeroKMS request authenticates as the end user
+const client = await Encryption({
+ schemas: [users],
+ config: { strategy: OidcFederationStrategy.create(workspaceCrn, () => getUserJwt()) },
});
-if (encryptResult.failure) {
- // Handle errors your way
-}
-
-// 4. Decrypt
-const decryptResult = await client.decrypt(encryptResult.data);
-if (decryptResult.failure) {
- // Handle errors your way
-}
-// decryptResult.data => "secret@example.com"
```
-## Install
+→ [Authentication][auth]
-```bash
-npm install @cipherstash/stack
-# or
-yarn add @cipherstash/stack
-# or
-pnpm add @cipherstash/stack
-# or
-bun add @cipherstash/stack
+### 🗝️ Built-in key management
+
+Key management is built in, powered by [ZeroKMS][zerokms] — no AWS KMS to wire up, no key vault to run,
+no rotation schedule to babysit:
+
+- **A unique key for every value** — not one key per table or per database.
+- **Automatic key rotation** — handled for you, with zero downtime.
+- **CipherStash can never see your keys.** Keys are *derived inside your application*; neither plaintext
+ keys nor plaintext data ever leave your infrastructure.
+- **Fast at scale** — bulk key operations handle up to 10,000 keys in a single call, up to 14× faster
+ than AWS KMS at peak ([benchmarks][benches]).
+- **Every decryption is logged** — a built-in audit trail of who decrypted what, and when.
+
+## Advanced features
+
+### 👤 Identity-aware encryption
+
+Building on OIDC federation, you can bind a record's encryption key to the end user's identity, so only
+*that* user can decrypt their data: `.withLockContext({ identityClaim })` ties the data key to a claim in
+the user's JWT, enforced cryptographically by ZeroKMS.
+
+```typescript
+// Bind the data key to a claim — the same claim is required to decrypt
+await client
+ .encrypt("alice@example.com", { table: users, column: users.email })
+ .withLockContext({ identityClaim: ["sub"] });
```
-> [!IMPORTANT]
-> **You need to opt out of bundling when using `@cipherstash/stack`.**
-> It uses Node.js specific features and requires the native Node.js `require`.
-> Read more about bundling in the [documentation](https://cipherstash.com/docs/stack/deploy/bundling).
+→ [Identity-aware encryption][identity]
+
+### 🗂️ Keysets for multitenancy & sovereignty
+
+Partition your keys into **keysets** — independent key hierarchies within a single workspace. Give each
+tenant its own keyset for cryptographic tenant isolation (revoking a keyset renders that tenant's data
+permanently unreadable), or pin keysets to a region to meet data-sovereignty requirements without
+re-architecting your app. [Keysets →][keysets]
+
+## How it works
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Encryption happens in your application. Ciphertext is stored as an [EQL][eql] payload in your database;
+plaintext and keys never reach CipherStash. Per-value keys are issued in bulk by ZeroKMS (so millions
+of unique keys stay fast), and every decryption is logged for compliance.
+
+→ [Security architecture][security-architecture] · [ZeroKMS][zerokms]
+
+## Performance
+
+Encrypted queries stay fast — latency is flat from 10k to 10M rows. Measured in [cipherstash/benches][benches]:
+
+| Operation | Median latency (up to 10M rows) |
+|---|---|
+| Equality lookup | ~0.1 ms |
+| Range query | ~0.5 ms |
+| JSON field equality | ~0.1 ms |
+
+
+
+## Why CipherStash
+
+- **Trusted data access** — only your end-users can access their sensitive data, enforced cryptographically.
+- **Shrink the blast radius** — a breached vulnerability exposes only what one user can decrypt, not your whole table.
+- **Audit trail built in** — every decryption event is recorded, no extra tooling to bolt on.
+- **Meet compliance faster** — exceed the encryption requirements of SOC 2 and ISO 27001, with FIPS-compliant
+ cryptography and BYOK for teams that need it.
+
+## FAQ
-## Features
+
+Can CipherStash ever see my data, or my encryption keys?
-- **[Searchable encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)**: query encrypted data with equality, free text search, range, and [JSONB queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption#jsonb-queries-with-searchablejson).
-- **[Type-safe schema](https://cipherstash.com/docs/stack/cipherstash/encryption/schema)**: define encrypted tables and columns with `encryptedTable` / `encryptedColumn`
-- **[Model & bulk operations](https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt#model-operations)**: encrypt and decrypt entire objects or batches with `encryptModel` / `bulkEncryptModels`.
-- **[Identity-aware encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/identity)**: authenticate as the end user with `OidcFederationStrategy` and bind the data key to their identity with `.withLockContext({ identityClaim })` for policy-based access control.
+No, never. Encryption and decryption happen in your application, and keys are derived within your own
+environment. Plaintext and keys never leave your control and never reach CipherStash.
+
-## Integrations
+
+How well does it scale?
-- [Encryption + Drizzle](https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle)
-- [Encryption + Supabase](https://cipherstash.com/docs/stack/cipherstash/encryption/supabase)
-- [Encryption + DynamoDB](https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb)
+Latency stays flat as data grows — exact-match lookups hold at ~0.1 ms and range queries at ~0.5 ms from
+10k up to 10M rows ([cipherstash/benches][benches]). ZeroKMS handles keys in bulk (up to 10,000 per
+call), so key management isn't the bottleneck.
+
-## Use cases
+
+What does migration look like?
-- **Trusted data access**: ensure only your end-users can access their sensitive data using identity-bound encryption
-- **Reduce breach impact**: limit the blast radius of exploited vulnerabilities to only the data the affected user can decrypt
+Install EQL on your Postgres database (`npx stash init` and the [quick starts](#quick-starts) handle
+this), declare the columns you want protected with the encrypted type that fits each one (for example
+`eql_v3.text_match` for searchable text or `eql_v3.int4_ord` for range queries), and encrypt values in
+your app before writing. You can adopt it column-by-column — no big-bang rewrite — and your existing
+Postgres indexes keep working.
+
-## Documentation
+
+Do I have to change how I write queries?
-- [Documentation](https://cipherstash.com/docs)
-- [Quickstart](https://cipherstash.com/docs/stack/quickstart)
-- [SDK and API reference](https://cipherstash.com/docs/stack/reference)
+No. Query encrypted columns with the same Supabase.js, Prisma, or Drizzle calls you use today — there
+are no SQL rewrites.
+
+
+
+Do I need to run a KMS or key vault?
+
+No. Key management is built in through ZeroKMS. If you want to control the root key, Bring Your Own Key
+lets you root it in your own KMS.
+
+
+
+Does it work with Supabase Auth and Row Level Security?
+
+Yes. It integrates with Supabase Auth and runs alongside RLS — it complements them, it doesn't replace
+them.
+
+
+
+I already use Row Level Security — do I need this?
+
+RLS and CipherStash solve different problems, and they're strongest together. RLS decides which rows a
+role may query, but the data underneath is plaintext — so anything that bypasses RLS reveals it in the
+clear: a leaked `service_role` key, a misconfigured policy, a SQL injection running as an elevated role,
+a stolen backup, or the database host itself. CipherStash stores only ciphertext and keeps the keys
+outside the database, so those same bypasses reveal nothing readable. Keep RLS for authorization; add
+CipherStash so a bypass never becomes a breach.
+
+
+
+Is there a free tier?
+
+Yes — a free developer tier, so you can build encryption in from day one.
+
+
+## Start free
+
+Encryption is far cheaper to design in than to retrofit — and it's what unlocks regulated and enterprise
+customers. The developer tier is **free**, so you can add encryption from your very first migration:
+
+```bash
+npx stash init
+```
+
+Signing up is the wizard's first step if you don't have an account yet — or
+[create your free account][signup] in the browser first, and `stash init` will pick it up.
+
+## Install
+
+```bash
+npm install @cipherstash/stack # or: yarn / pnpm / bun add @cipherstash/stack
+```
+
+> [!IMPORTANT]
+> **Opt out of bundling `@cipherstash/stack`.** It uses native Node.js features (a Rust FFI module) and the
+> native `require`. [Bundling guide →][bundling]
-## Contributing
+**Requirements:** Node.js ≥ 18.
-Contributions are welcome and highly appreciated. However, before you jump right into it, we would like you to review our [Contribution Guidelines](CONTRIBUTE.md) to make sure you have a smooth experience contributing.
+## Documentation & community
-## Security
+- 📚 [Documentation][docs] · [Quickstart][quickstart] · [SDK reference][reference]
+- 🧩 [Example apps][examples]
+- 💬 [Discord community][discord]
-For our full security policy, supported versions, and contributor guidelines, see [SECURITY.md](./SECURITY.md).
+## Contributing · Security · License
-## License
+Contributions are welcome — see [CONTRIBUTE.md][contribute]. For our security policy and responsible
+disclosure, see [SECURITY.md][security-policy]. [MIT licensed][license].
-This project is [MIT licensed](./LICENSE.md).
+
+[signup]: https://cipherstash.com/signup?utm_source=github&utm_medium=stack_readme
+[docs]: https://cipherstash.com/docs/stack?utm_source=github&utm_medium=stack_readme
+[quickstart]: https://cipherstash.com/docs/stack/quickstart?utm_source=github&utm_medium=stack_readme
+[reference]: https://cipherstash.com/docs/stack/reference?utm_source=github&utm_medium=stack_readme
+[encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption?utm_source=github&utm_medium=stack_readme
+[searchable-encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme
+[schema]: https://cipherstash.com/docs/stack/cipherstash/encryption/schema?utm_source=github&utm_medium=stack_readme
+[encrypt-decrypt]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme
+[model-ops]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme#model-operations
+[supabase]: https://cipherstash.com/docs/stack/cipherstash/encryption/supabase?utm_source=github&utm_medium=stack_readme
+[drizzle]: https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle?utm_source=github&utm_medium=stack_readme
+[prisma-next]: https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next?utm_source=github&utm_medium=stack_readme
+[dynamodb]: https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb?utm_source=github&utm_medium=stack_readme
+[identity]: https://cipherstash.com/docs/stack/cipherstash/encryption/identity?utm_source=github&utm_medium=stack_readme
+[security-architecture]: https://cipherstash.com/docs/stack/reference/security-architecture?utm_source=github&utm_medium=stack_readme
+[zerokms]: https://cipherstash.com/docs/stack/cipherstash/kms?utm_source=github&utm_medium=stack_readme
+[bundling]: https://cipherstash.com/docs/stack/deploy/bundling?utm_source=github&utm_medium=stack_readme
+
+[query-equality]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme#equality
+[query-match]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme#free-text-search
+[query-range]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme#range-and-ordering
+[query-json]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme#json
+[auth]: https://cipherstash.com/docs/stack/cipherstash/authentication?utm_source=github&utm_medium=stack_readme
+[keysets]: https://cipherstash.com/docs/stack/cipherstash/kms?utm_source=github&utm_medium=stack_readme#keysets
+[benches]: https://github.com/cipherstash/benches
+[eql]: https://github.com/cipherstash/encrypt-query-language
+[discord]: https://discord.gg/5qwXUFb6PB
+[examples]: ./examples
+[contribute]: ./CONTRIBUTE.md
+[security-policy]: ./SECURITY.md
+[license]: ./LICENSE.md
diff --git a/docs/images/architecture-dark.svg b/docs/images/architecture-dark.svg
new file mode 100644
index 00000000..9539c163
--- /dev/null
+++ b/docs/images/architecture-dark.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/docs/images/architecture-light.svg b/docs/images/architecture-light.svg
new file mode 100644
index 00000000..f5fe3404
--- /dev/null
+++ b/docs/images/architecture-light.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/docs/images/architecture-stacked-dark.svg b/docs/images/architecture-stacked-dark.svg
new file mode 100644
index 00000000..0eeddd76
--- /dev/null
+++ b/docs/images/architecture-stacked-dark.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/docs/images/architecture-stacked-light.svg b/docs/images/architecture-stacked-light.svg
new file mode 100644
index 00000000..1aeab253
--- /dev/null
+++ b/docs/images/architecture-stacked-light.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/docs/plans/readme-go-live-checklist.md b/docs/plans/readme-go-live-checklist.md
new file mode 100644
index 00000000..3b89b2b6
--- /dev/null
+++ b/docs/plans/readme-go-live-checklist.md
@@ -0,0 +1,66 @@
+# Root README — go-live checklist
+
+The refreshed root `README.md` is **doc-driven**: it describes the EQLv3-based Stack slightly ahead of
+current code capabilities. Before the README merges to `main` (or is published anywhere), confirm each
+claim below is actually implemented and each placeholder is resolved.
+
+## API surfaces shown in code examples
+
+- [ ] **Drizzle EQL v3 types** — the sizzle uses `types.TextMatch("email")` in `pgTable` (mirroring the
+ `types` namespace from stack PR [#541](https://github.com/cipherstash/stack/pull/541)). The current
+ published `@cipherstash/drizzle` API is still the SEM-style `encryptedType("email", { … })`
+ options object — confirm the v3 `types` surface has shipped and the import path/namespace name match.
+- [ ] **Supabase** — `encryptedSupabase({ encryptionClient, supabaseClient })` with transparent
+ `.ilike()` on encrypted columns (encrypt-filters-in / decrypt-results-out), as published.
+- [ ] **Prisma Next** — `cipherstash.EncryptedString()` in `schema.prisma` and the `cipherstashIlike`
+ operator, as published.
+- [ ] **Raw SDK** — the searchable-encryption section shows
+ `import { encryptedTable, types } from "@cipherstash/stack/eql/v3"` with `types.TextMatch` /
+ `types.Int4Ord`; confirm the subpath and namespace ship as in PR #541, and that quick-start guides
+ cover this surface.
+
+## EQL v3 claims
+
+- [ ] **Domain type names** — `eql_v3.text_match`, `eql_v3.text_eq`, `eql_v3.int4_ord`, `eql_v3.json`
+ (CREATE TABLE example + FAQ) match the exact names on the `eql_v3` branch of
+ `encrypt-query-language` (messaging brief flags these as illustrative until confirmed).
+- [ ] **"The type is the configuration"** — ORMs genuinely need no per-column search config; raw `pg`
+ manual client schema is the only exception.
+- [ ] **Scalar coverage** — "text, integers, floats, numerics, dates, timestamps, booleans, and JSON"
+ are all available (note: int8/bigint was still pending lossless FFI I/O as of PR #541).
+- [ ] **Standard Postgres indexes** — the "stays indexable with standard Postgres indexes" claim holds
+ for the shipped types.
+
+## CLI & auth claims
+
+- [ ] **`npx stash init`** — signs up users without an account as its first step (Start free CTA), and
+ installs EQL on the target database (FAQ migration answer).
+- [ ] **Device auth** — `npx stash auth login` browser flow + local profile, as described.
+- [ ] **OIDC federation providers** — Supabase Auth, Clerk, Okta, and Auth0 all verified working.
+
+## Key management claims
+
+- [ ] **Automatic key rotation** — shipped and accurate to describe as "handled for you, zero downtime".
+- [ ] **Keysets** — per-tenant keysets, revocation ("renders tenant data permanently unreadable"), and
+ region pinning for sovereignty are shipped (Advanced features section).
+- [ ] **Audit logging** — "every decryption is logged" is true for all auth modes.
+
+## Performance section
+
+- [ ] **Numbers refreshed on EQL v3** — current figures come from the EQL 2.3 run in
+ `cipherstash/benches`; re-run on v3 before launch ([CIP-3296](https://linear.app/cipherstash/issue/CIP-3296)).
+- [ ] **Flat-latency chart** embedded ([CIP-3361](https://linear.app/cipherstash/issue/CIP-3361),
+ spec: Asset 3 in `readme-visual-assets.md`).
+
+## Links & assets (mechanical, pre-merge)
+
+- [ ] **Placeholder doc links** — query-type anchors (`#equality`, `#free-text-search`,
+ `#range-and-ordering`, `#json`), `[auth]` page, and `[keysets]` anchor all resolve once the EQL v3
+ docs land (marked with a TODO comment above the link definitions).
+- [ ] **Architecture diagram URLs** — restore the absolute
+ `https://raw.githubusercontent.com/cipherstash/stack/main/…` prefix on the four `` paths
+ (currently relative for PR preview; TODO comment in place, also flagged in `readme-visual-assets.md`).
+- [ ] **Social preview card** — upload the 1280×640 og:image via repo Settings → General → Social
+ preview, and set the About description to the brief's one-liner ("Searchable, application-level
+ encryption for building privacy-first apps.") — it doubles as the `og:description`. Verify with a
+ fresh link paste (spec: Asset 4 in `readme-visual-assets.md`).
diff --git a/docs/plans/readme-visual-assets.md b/docs/plans/readme-visual-assets.md
new file mode 100644
index 00000000..a4a5d185
--- /dev/null
+++ b/docs/plans/readme-visual-assets.md
@@ -0,0 +1,218 @@
+# README visual assets — spec
+
+Two visual assets for the refreshed root `README.md`. Both target the gaps competitor
+READMEs leave open:
+
+1. **Architecture diagram** — security/infra READMEs (Infisical, Vault) bury their architecture
+ off-README. A clear "how it works" diagram is the single biggest trust signal we can add.
+2. **Type-safety autocomplete GIF** — none of the TS-first leaders (Prisma, Drizzle, React Email)
+ *show* their type-safety story; they only describe it. An autocomplete GIF beats all of them.
+
+## Shared conventions
+
+- **Host in-repo** under `docs/images/` so assets are version-controlled.
+- **Reference with absolute URLs** (`https://raw.githubusercontent.com/cipherstash/stack/main/docs/images/...`).
+ Relative paths render on GitHub but **break on the npm package page** — npm needs absolute URLs.
+- **Light + dark variants** using GitHub's mode switch:
+ ```html
+
+
+ ```
+- **Brand**: use the CipherStash palette and logo; match the dark-theme look of cipherstash.com.
+- **Accessibility**: every asset needs descriptive `alt` text (provided below). For the GIF, keep motion
+ calm and the loop short (respect users who dislike motion).
+
+---
+
+## Asset 1 — Architecture diagram ("How it works")
+
+**Goal.** In one glance, prove the core trust claim: *plaintext and root keys never reach CipherStash; the
+database only ever holds ciphertext; every decryption is audited.*
+
+**Placement.** Under the `## How it works` heading, above the "Security architecture" doc link.
+
+**Format.** SVG preferred (crisp, tiny, theme-able). Target ~1400px wide, responsive height.
+
+**Layout (left → right data flow):**
+
+```
+┌─────────────────────────┐ ciphertext ┌──────────────────────────┐
+│ YOUR APP (TypeScript) │ ── EQL JSON payload ──▶ │ YOUR DATABASE │
+│ @cipherstash/stack │ │ PostgreSQL / JSONB │
+│ • encrypt / decrypt │ ◀── encrypted rows ─── │ • stores ciphertext only│
+│ • search on ciphertext │ │ • searchable (EQL) │
+└───────────┬─────────────┘ └──────────────────────────┘
+ │ per-value key requests (bulk)
+ ▼
+┌─────────────────────────┐ root key ┌──────────────────────────┐
+│ ZeroKMS │ ───────────▶ │ YOUR AWS KMS │
+│ • unique key per value │ │ • root key never leaves │
+│ • bulk key ops (fast) │ └──────────────────────────┘
+│ • decryption audit log │
+└─────────────────────────┘
+```
+
+**Trust-boundary callouts to overlay (the persuasive part):**
+- A dashed "trust boundary" line around *Your App + Your Database + Your AWS KMS* labelled
+ **"Plaintext and root keys never leave your boundary."**
+- A badge on ZeroKMS: **"CipherStash never sees plaintext."**
+- A small tag near the audit log: **"Every decryption logged → SOC 2 / ISO 27001 evidence."**
+
+**Alt text:**
+> "CipherStash architecture: encryption and decryption happen in your TypeScript app; only ciphertext
+> (EQL JSON) is stored in your PostgreSQL database. ZeroKMS issues a unique key per value, rooted in your
+> own AWS KMS. Plaintext and root keys never reach CipherStash, and every decryption is logged for audit."
+
+**Tooling.** Figma, Excalidraw, or draw.io → export SVG (light + dark). Keep text as real text (not
+outlines) where possible for crispness and accessibility.
+
+**Interim option (ship today, no designer needed).** GitHub renders Mermaid natively, so this can go in
+immediately and be swapped for the designed SVG later:
+
+```mermaid
+flowchart LR
+ App["Your App (TypeScript) @cipherstash/stack encrypt · decrypt · search"]
+ DB[("Your Database PostgreSQL / JSONB ciphertext only")]
+ ZKMS["ZeroKMS unique key per value bulk ops · audit log"]
+ KMS["Your AWS KMS root key never leaves"]
+
+ App -- "ciphertext (EQL JSON)" --> DB
+ App -- "per-value key requests" --> ZKMS
+ ZKMS -- "root key" --> KMS
+
+ subgraph Boundary["Your trust boundary — plaintext & root keys never leave"]
+ App
+ DB
+ KMS
+ end
+```
+
+---
+
+## Asset 2 — Type-safety / autocomplete GIF
+
+**Goal.** Show the DX payoff in motion: an encrypted field stays **fully typed and queryable** — encryption
+adds security without taking away autocomplete, inference, or compile-time safety.
+
+**Placement.** Inside the `### 🔐 Searchable encryption` pillar, or a short "Developer experience" callout.
+
+**Storyboard (single seamless loop, ≤ 10s):**
+1. Show a schema: `encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch() })`.
+2. Type `await client.encryptModel(user, users)` and hover the result — tooltip shows the **schema-aware
+ return type**: `email → Encrypted`, `id → string`, `createdAt → Date` (only schema fields change type).
+3. Start typing a query: `.where(await ops.eq(usersTable.email, "` — show autocomplete offering the typed
+ operator and the column.
+4. Briefly trigger a **red squiggle** by accessing a field not in the schema (or wrong type) — proving
+ errors are caught at compile time.
+
+**Recording specs:**
+- VS Code, clean theme (record a **dark** primary; a light alt is nice-to-have).
+- Font size 16–18px; minimap off; hide activity/status bar clutter; zoom so code is legible on mobile.
+- Crop tight to the editor region. Width 1280–1440px.
+- Length 8–12s, seamless loop. **File budget < 5 MB** (ideally < 3 MB) so the README stays fast.
+
+**Formats:**
+- Ship a **`.gif`** for universal rendering (works on npm and GitHub).
+- Optionally also provide an `.mp4`/`.webm` and embed via `