Living checklist for the docs overhaul. Tracked in Linear under
CIP-3307; the full IA rationale
(design principles, audience doors, correctness strategy) lives in
CipherStash docs IA v1.md in the content repo. Tick items here as they land
on the v2 branch. Legend: [ ] todo · [x] done · 🚧 stub exists · ⛔ blocked
on a product decision (see CIP-3307 checklist).
- New IA lives in
content/docs, served from the site root (/docs/<section>/…). - The legacy tree (
content/stack) is served alongside it at/docs/stack/…until every section migrates, then deleted (CIP-3335). - The full legacy→v2 redirect map is
v2-redirects.mjs, gated behindENABLE_V2_REDIRECTS=1(flipped on at merge).bun run validate-redirectsenforces that every legacy page has a mapping. - Frontmatter facets (
type,components,audience,integration,verifiedAgainst,reviewBy) are defined insource.config.ts(v2docs). - Moving a page = move the file into
content/docs, update its facets, fix inbound links, confirm itsv2-redirects.mjsentry, tick it here.
Lowercase, hyphens, no trailing slashes, no version numbers in paths.
Integrations are flat (no category segment). Error pages (future, miette)
live at /docs/errors/<code> — permanent, never restructured (CIP-3338).
The migration checklist below tracks what moves; this section fixes where things go and why, so placement stops being re-litigated per PR.
- Mode — what the reader is trying to do. The four Diátaxis kinds (tutorial, how-to, reference, explanation), plus our three audience doors (integrations, solutions, security) which serve the same placement role. A page has exactly one mode; the mode decides its sidebar home.
- Component — a layer of the product stack, drawn from the
componentsfacet enum:encryption(Stack SDK),platform(CTS + ZeroKMS),eql,proxy,cli. A page can touch several components at once. - Facet — structured frontmatter that classifies a page along one axis
(
type,components,audience,integration). Queryable; never shown raw to users; independent of nav position. - Hub — a per-component page at
/components/<component>that gathers every page tagged with that component. A generated view, not a subtree. Not every component has a hub: hubs are for layers of the stack (encryption,platform,eql,proxy).cliis a component but a cross-cutting tool, not a layer, so it has no hub. - Locator — the one hand-written paragraph (plus mini stack diagram) at the top of a hub, orienting the reader: what the component is, what sits above and below it. Prose, nothing else.
The sidebar is organized only by mode. The by-component view lives only on the hubs.
CipherStash is a dependency stack, not a set of independent product pillars: integrations sit on the Stack SDK and Proxy, both consume EQL, and EQL sits on the Platform (CTS + ZeroKMS). That is a graph. A sidebar is a tree. So we split the two axes rather than forcing the graph into the tree.
- Mode axis (the tree). Every page has exactly one home, chosen by what the
reader is doing: Diátaxis mode (
get-started,guides,reference,concepts) plus three audience doors (integrations,solutions,security). The sidebar is built frommeta.jsonand nothing else. - Component axis (the graph). Which layers a page touches is the
componentsfacet — never a sidebar section. The dependency graph is expressed by facets and surfaced by hub pages, so a shared layer like EQL is documented once and referenced everywhere.
Why the pillar layouts (Supabase, Clerk, GitHub) don't transfer: those are feature-pillar products whose parts are independent, so each pillar can be its own subtree. Our parts are layers in a dependency stack — a shared layer has no single natural parent, so it cannot be a tree node without duplication.
Two questions, in this order, for every page:
- Which mode is this? → decides the folder (its one sidebar home).
- Which components does it touch? → sets the
componentsfacet.
Never the reverse. If you are tempted to file a page under a component, that is the signal to file it by mode and tag the component instead.
- The sidebar tree is
meta.jsononly. Facets never affect nav position (already asserted by the schema comment insource.config.ts). - A page appears in exactly one place in the tree. Cross-cutting reuse happens through links and hubs, never by duplicating a page under a second parent.
- Shared mechanics have exactly one canonical page; every other page links,
never restates. This generalises the existing EQL rule (mechanics live in
/reference/eql/core-concepts; category pages link to it) to every component. - Each component has exactly one hub at
/components/<component>. A hub is a facet-driven view: it links to canonical pages and owns no mechanics. /get-started/what-is-cipherstashrenders the components map; its nodes link to the hubs. The hubs are the only component-first surface, and they hold no primary content — so they do not recreate the legacy component-first tree (the oldroot: trueEncryption/KMS/Proxy/Platform tabs).
Hubs live at the top-level path /components/<component> for a short, memorable
front door, but are anchored in the sidebar under Get started (Fumadocs takes
nav position from meta.json independently of the URL). If you would rather the
URL mirror the nav, use /get-started/components/<component> instead — either
works; pick one and add it to v2-redirects.mjs.
Mode-first, seven top-level sections. compare is folded into concepts (each
comparison already belongs to a door: aws-kms and vault are security-review
material, fhe and rls-and-tde are concepts). Component hubs are anchored under
Get started, not given their own top-level tab.
Get started
├─ What is CipherStash mental model · components map · audience router
├─ Quickstart
├─ Choose your stack platform × ORM × auth matrix
├─ Examples
└─ Components ← hub views (facet-driven, link-only)
├─ Stack SDK
├─ Proxy
├─ EQL (shared)
└─ Platform (CTS + ZeroKMS)
Integrations flat; category grid on the index
├─ Supabase (Database · Auth · Dashboard)
├─ Drizzle · Prisma · Next.js · TypeScript
├─ Clerk · Auth0 · Okta
├─ AWS (RDS/Aurora · DynamoDB)
└─ Serverless · Docker
Concepts
├─ Privacy-first design
├─ Application-level encryption
├─ Searchable encryption canonical leakage model
├─ EQL typed-column model
├─ Key management
├─ Threat modelling
└─ Compare aws-kms · fhe · rls-and-tde · hashicorp-vault
Guides
├─ Development local setup · schema design · testing & CI · onboarding
├─ Migration encrypt existing data · adopt incrementally · key rotation
├─ Deployment production · serverless & bundling · proxy deployment
└─ Troubleshooting query performance · runtime errors · cli · proxy
Security
├─ Architecture one reconciled ZeroKMS story
├─ ZeroKMS · CTS · Stack SDK · Proxy
├─ Threat scenarios
├─ Availability · Audit logging · Key ownership
└─ Compliance HIPAA · SOC 2 · GDPR
Solutions
└─ Protecting PII · Healthcare/HIPAA · AI & RAG · Data residency · Provable access
Reference
├─ EQL core-concepts · numbers · dates · text · json · … · joins
├─ Stack SDK overview · usage · generated package API
├─ Auth lock-contexts · cts-tokens · oidc · access-keys
├─ CLI · Proxy · Workspace
└─ Benchmarks · Agent skills · Glossary
One per component, at /components/<component>. Same template every time:
- Locator — one paragraph plus the stack diagram with this layer highlighted: what it is, what it sits on, what sits on it.
- Start here — the one or two canonical entry pages.
- By mode — grouped links (Concepts · Guides · Reference · Security · Integrations that use it). Every entry links to its canonical mode-home.
- Nothing else. No mechanics, no examples that live elsewhere.
The "By mode" lists are generated, not hand-maintained: filter
source.getPages() to pages whose components facet includes this component,
then group by type. "Integrations that use it" is the subset whose frontmatter
also carries an integration block. This is why the facet earns its keep — the
hubs cost nothing to maintain once a page is correctly tagged.
Tagging rule — eql is conditional, not automatic. Stack encrypts values
in three modes: general-purpose (a value in your app), Postgres columns, and
non-Postgres stores like DynamoDB. Only the Postgres path involves EQL. So tag
eql only when the page is actually about queryable-in-Postgres ciphertext.
General-purpose and DynamoDB pages are components: [encryption, platform] with
no eql — DynamoDB (encryptedDynamoDB) is the canonical no-EQL example and
the reason "Stack depends on EQL" is wrong. EQL is the Postgres searchability
layer, not a layer every Stack page sits on.
- Locator: the TypeScript SDK; encrypt and decrypt values in your app. Sits on the Platform. Add EQL when the data lives in Postgres and you want the ciphertext queryable there — but Stack also encrypts general-purpose values and non-Postgres stores (e.g. DynamoDB) with no EQL at all.
- Start here: Quickstart · Reference → Stack SDK (client + configuration)
- Concepts: application-level encryption · searchable encryption · key management
- Guides: schema design · encrypt existing data · testing & CI · serverless & bundling
- Reference:
/reference/stack/*(overview · usage · generated package API) - Security:
/security/stack-sdk - Integrations (auto): Supabase · Drizzle · Prisma · DynamoDB · Next.js …
- Locator: the no-app-changes path; sits in front of Postgres and speaks EQL for you.
- Start here: Reference → Proxy (configuration) · Guides → Proxy deployment
- Concepts: application-level encryption · searchable encryption
- Guides: proxy deployment · going to production
- Reference:
/reference/proxy/*(configuration · message-flow · multitenant · errors) - Security:
/security/proxy - Integrations (auto): AWS RDS/Aurora · Docker
- See also: EQL hub (shared dependency)
- Locator: the Postgres searchability layer. Makes ciphertext queryable in Postgres by declaring an encrypted column's capability in the schema. The Proxy always speaks EQL; the Stack SDK uses it only on its Postgres path (not for general-purpose or DynamoDB encryption).
- Start here: Reference → EQL (install) · EQL core-concepts
- Concepts: EQL (typed-column model) · searchable encryption (leakage model)
- Reference: the whole
/reference/eql/*tree - Guides: troubleshooting → query performance
- Consumed by: Proxy hub (always) · Stack SDK hub (Postgres path only)
- Anti-drift: mechanics live in
/reference/eql/core-concepts; this hub links only.
- Locator: the base everything relies on — key management (ZeroKMS) and identity-bound access (CTS).
- Start here: Security → Architecture · Concepts → Key management
- Concepts: key management
- Reference:
/reference/auth/*(lock-contexts · cts-tokens · oidc · access-keys) - Security: architecture · zerokms · cts · availability · audit logging · key ownership
- Integrations (auto): Clerk · Auth0 · Okta
Auth is the concern most likely to feel like it needs its own section — it spans the SDK, the Proxy, local dev, the Platform, and every auth provider. It doesn't get one. It's the case that shows the model absorbing a cross-cutting concern without bending: auth isn't a layer, it's a concern that shows up on layers, so it lives as facets and links, never as a tree section or a hub.
"Auth" names several distinct things. Keep them separate:
| Thing | What it is | components |
Canonical home |
|---|---|---|---|
| CTS | Identity service in the Platform | platform |
/security/cts, /reference/auth/* |
@cipherstash/auth |
Stack package (identity-aware encryption) | [encryption, platform] |
/reference/stack/auth |
| Proxy stack-auth | How auth works inside the Proxy | [proxy, platform] |
/reference/proxy/*, /security/proxy |
| Clerk / Auth0 / Okta | Auth-provider integrations | [platform] |
/integrations/* (category: auth-provider) |
None of these is filed under an "auth" section, because there isn't one. Each is filed by mode (service → Security/Reference, package → Reference, providers → Integrations) and tagged by component. The through-line is reassembled by queries, not by a subtree:
- The Platform hub gathers all of it — everything above carries
platform. - Faceted search on
integration.category: auth-providergives the providers. /solutions/provable-accessis the explanatory page that ties identity-bound access together in prose; everything else links to it (anti-drift rule).
Naming caution: /reference/auth/* means the CTS service; the @cipherstash/auth
package lives at /reference/stack/auth. Two different things both called
"auth" — keep the paths distinct so contributors don't merge them.
The components facet enum is [encryption, platform, eql, proxy, cli]
(collapsing the former auth and zerokms into a single platform, matching
the product story "Platform = CTS + ZeroKMS"). This maps 1:1 onto the hubs:
encryption → Stack SDK, platform, eql, proxy.
Two notes:
- The
encryptionfacet value and its hub title Stack SDK differ on purpose — the facet names the capability, the hub names the product. Document once, move on. - Collapsing
auth/zerokmsintoplatformmeans you can't isolate the CTS/identity pages by facet query anymore — aplatformquery returns all of Platform. That distinction still lives in nav location (/security/ctsvs/security/zerokms) and inintegration.category: auth-providerfor the Clerk/Auth0/Okta pages, so it isn't lost — it just isn't on thecomponentsaxis.
- Section scaffold 🚧
-
/get-started/what-is-cipherstash— mental model, components map, audience router -
/get-started/quickstart— rewritten on EQL v3 (fixescs_match_v1, broken scaffold imports) -
/get-started/choose-your-stack— static matrix v1 (platform × ORM × auth) -
/get-started/examples— runnable example apps index -
/docslanding page 🚧 — nowcontent/docs/index.mdxrendered inside the docs nav (the old standalone(home)route is deleted; recoverable from git history). CIP-3327 refines the content (what-is + audience router)
- Section scaffold 🚧 (index + supabase stub with facet exemplar)
-
/integrationsindex — category grid w/ setup badges -
/integrations/supabase— flagship tutorial (CIP-3328) -
/integrations/supabase/database -
/integrations/supabase/auth -
/integrations/supabase/dashboard-experience— Table Editor, expose eql schema - ⛔
/integrations/supabase/edge-functions— pending Deno/FFI answer - ⛔
/integrations/supabase/realtime— pending product verification -
/integrations/drizzle— overview and generated@cipherstash/stack-drizzleAPI reference -
/integrations/prisma— Prisma ORM 8 RC, EQL v3, Prisma Postgres, Prisma Compute, and generated API reference -
/integrations/aws/rds-aurora— Proxy path -
/integrations/aws/dynamodb— Stack 1.0 helper, equality lookups, and legacy reads -
/integrations/clerk -
/integrations/auth0— end-to-end example (Clerk parity) -
/integrations/okta— end-to-end example (Clerk parity) -
/integrations/typescript— thin router to Stack SDK reference -
/integrations/serverless— Vercel/Lambda, bundling, CS_CONFIG_PATH -
/integrations/docker - ⛔
/integrations/edge-workers— pending Deno/workerd answer
- Section scaffold 🚧
-
/concepts/privacy-first-design -
/concepts/application-level-encryption— vs TDE/pgcrypto/RLS -
/concepts/searchable-encryption— REWRITE with honest leakage model (canonical leakage page) -
/concepts/eql— the typed-column model (declare capability in the schema) -
/concepts/key-management— per-value keys, split control, revocation, isolation, and audit -
/concepts/threat-modelling
Folded into Concepts (see the sidebar spec above): the comparison pages live at
/concepts/compare/*, not a top-level /compare tab. /stack/reference/comparisons
and the old /compare/* paths redirect there (v2-redirects.mjs).
- Section scaffold 🚧 (moved under
concepts/) -
/concepts/compare/aws-kms(port) -
/concepts/compare/fhe(port) -
/concepts/compare/zerokms-vs-hsm(ZeroKMS vs hardware security modules) -
/concepts/compare/rls-and-tde(new — expand the Supabase-listing RLS contrast) -
/concepts/compare/hashicorp-vault(in flight ondocs/vault-comparisonbranch — land there or here, then port)
- Section scaffold 🚧 (development, migration, deployment, troubleshooting)
-
/guides/development/local-setup— profiles, device auth, workspaces, keys -
/guides/development/schema-design— which encrypted type/variant per column (CIP-3327) -
/guides/development/testing-and-ci(port deploy/testing) -
/guides/development/team-onboarding(port) -
/guides/migration— the backfill guide, runnable (CIP-3329) - ⛔
/guides/migration/upgrading-from-eql-v2— REQUIRED; mechanics pending product answer (CIP-3329) -
/guides/migration/adopting-incrementally(CIP-3329) -
/guides/migration/key-rotation-operations -
/guides/deployment— production rollout, bundling, CI, onboarding, and Proxy deployment consolidated into one page -
/guides/troubleshootingindex — symptom-based router -
/guides/troubleshooting/query-performance— seq-scan diagnosis, typed-operand gotcha -
/guides/troubleshooting/runtime-errors -
/guides/troubleshooting/cli(port) -
/guides/troubleshooting/proxy(port)
- Section scaffold 🚧
-
/security/cryptography— ONE reconciled ZeroKMS mechanism story (kills the 3 conflicting accounts) -
/security/zerokms -
/security/cts— auth layer architecture (hidden from nav until the section is expanded) -
/security/stack-sdk -
/security/proxy -
/security/threat-scenarios - ⛔
/security/availability-and-continuity— DR (port) + SLA + exit story; pending SLA answer -
/security/audit-logging— Proxy event mechanics; retention/export remains deployment-specific and the nav stays hidden - ⛔
/security/key-ownership— BYOK/self-hosted; pending product answer -
/security/complianceindex — capability/responsibility mapping (nav remains hidden) -
/security/compliance/hipaa— BAA scope, §164.312 mapping (CIP-3332) -
/security/compliance/soc2— verify Type II report exists -
/security/compliance/gdpr
- Section scaffold 🚧
-
/solutions/protecting-pii(new) -
/solutions/healthcare-hipaa(new; pairs with compliance/hipaa) -
/solutions/ai-and-rag— source protection, retrieval flow, and isolation boundaries -
/solutions/data-residency(port) -
/solutions/provable-access(port)
- Section scaffold 🚧 (eql, stack, auth, cli, proxy, workspace)
- EQL (v3 rewrite — CIP-3326; Tailwind-shaped: install → core concepts → type categories → indexes → query patterns). Anti-drift rule: shared mechanics (typed operands, blockers, envelope, variant model, ORE-equality) live ONLY in core-concepts — category/query pages link, never restate:
-
/reference/eql— install (single SQL file, permissions split, dbdev, Docker) -
/reference/eql/core-concepts— variant model, payload anatomy (absorbs cipher-cell), typed-operand rule, fail-loud blockers, term leakage pointer -
/reference/eql/numbers— int*/float*/numeric -
/reference/eql/dates-and-times— date/timestamp (same traits as numbers, distinct semantics) -
/reference/eql/text— all six text variants; owns the no-LIKE treatment -
/reference/eql/json— ste_vec + sv payload shape + containment/path queries -
/reference/eql/booleans— storage-only variants (bool has only that one) -
/reference/eql/indexes— functional indexes on extractors; Supabase-compatible -
/reference/eql/filtering— =, IN, ranges, token match, containment -
/reference/eql/sorting— ORDER BY, extractor sort-key form, pagination -
/reference/eql/grouping-and-aggregates— GROUP BY/DISTINCT, min/max, no SUM/AVG -
/reference/eql/joins— equijoins, the same-keyset constraint - ⛔
/reference/eql/query-performance— port the EQL repo performance guide once rewritten for v3 upstream (v3 branch folded it into database-indexes.md; verify nothing from the v2 guide on main was lost) — see CIP-3351 - Stack SDK:
-
/reference/stack— core package overview and usage -
/reference/stack/api-reference— generated@cipherstash/stackAPI -
/integrations/supabase/api-reference— generated@cipherstash/stack-supabaseAPI (CIP-3328) - Auth (CIP-3330):
-
/reference/auth/lock-contexts -
/reference/auth/cts-tokens -
/reference/auth/oidc-configuration -
/reference/auth/access-keys+/reference/auth/clients - CLI / Proxy / Workspace:
-
/reference/cli/*(port 9 pages) -
/reference/proxy/*(configuration, message-flow, multitenant, errors) -
/reference/workspace/billing+/members+/configuration - Cross-cutting:
-
/reference/benchmarks— listing numbers + methodology (CIP-3334) -
/reference/agent-skills(port; expand per CIP-3339) -
/reference/glossary(port) - Repoint
scripts/generate-docs.tsTypeDoc output →content/docs/reference/stack
-
v2branch + this checklist -
v2docscollection + facet schema (source.config.ts) - Root catch-all routes (
src/app/[...slug]), llms.mdx mirror, sitemap/llms.txt include v2 -
v2-redirects.mjs(flag-gated) +validate-redirectsgate in prebuild -
/quickstartvanity redirect - OG images for v2 pages (route only covers legacy tree)
- Correctness CI: snippet type-checking, SQL-vs-EQL-Docker, terminology lint (CIP-3337)
- llms.txt curation + Cloudflare AI crawl policy + md-degradation check (CIP-3339)
- EQL 3.0.4 release alignment (CIP-3352) — the EQL reference and CLI target the released v3 schema and payload.
- Stack SDK and Supabase-wrapper v3 alignment (CIP-3355) —
@cipherstash/stackand@cipherstash/stack-supabase1.0.0 are released, and the integration docs target their stable v3 surfaces. - Flip
ENABLE_V2_REDIRECTS=1, deletecontent/stack+/stackroutes + legacy loader (CIP-3335) - Consistency sweep + Supabase listing v3 revision (CIP-3335)