# Getting started (/docs/getting-started) This page is the spine of the setup guide. Commands and package names are stable; the version-pinned install reference is still expanding. ## Requirements [#requirements] * **Bun** (the runtime and package manager — never npm or yarn). * **Postgres** (Neon over HTTP is the reference host; the data layer is swappable). * A Postgres database you can run migrations against. ## Install [#install] ```bash bun create caisson@latest my-app cd my-app bun install ``` The generator scaffolds the base substrate and the editions you select. Generation is metered as codegen-credits; your AI agent can drive it through the MCP server. ## Wire a tenant [#wire-a-tenant] Every data path goes through `withTenant` — the sole entry point that sets the RLS tenant context. There is no way to query tenant data without it; that is the point. ```ts import { withTenant } from "@caisson/tenancy-rls"; await withTenant(tenantId, async (db) => { // Inside this scope, RLS is enforced. Outside it, queries fail closed. return db.query.invoices.findMany(); }); ``` ## Run the gate [#run-the-gate] One standards gate builds, lints, and tests every package. A package ships only through it. ```bash bun run check ``` ## Next steps [#next-steps] * Read the **base substrate** docs for `auth`, `tenancy-rls`, `billing`, and `credits`. * Read the **Compliance** docs to turn on WORM evidence and the audit chain. * Bring your own framework — the `@caisson/*` packages never import one. # Caisson documentation (/docs) Caisson is a composable monorepo library: an audited base substrate plus premium editions (Compliance, AI Production Kit, Local-first AI, Agentic-Dev), a `create-caisson` generator, and a support service. Editions are compositions of the same packages — never forks. These docs are the manual: how each package works, how to compose it, and the contract it upholds. ## Start here [#start-here] * **[Getting started](/docs/getting-started)** — install the base, wire a tenant, run the gate. * **Base substrate** — `auth`, `tenancy-rls`, `billing`, `credits`, `kernel`, and the rest of the table-stakes core, framed under the differentiators. * **Compliance** — `audit-worm`, `field-crypto`, `compliance`: the fail-closed data layer and the evidence-pack generator. ## What "fail-closed by construction" means [#what-fail-closed-by-construction-means] The guarantees are wired and tested before your first customer, not backfilled after your first audit: * **Tenancy** — Postgres row-level security with FORCE. A query with no tenant context returns nothing. * **Evidence** — S3 Object-Lock WORM. Evidence cannot be altered or deleted before retention expires. * **Audit** — an append-only SHA-256 chain. Tampering breaks the link, and the break is provable. Every page is available as raw markdown for your AI agent — see [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt). # Agentic-Dev (/docs/agentic-dev) Agentic-Dev is a roadmap edition, sequenced **post-wedge**. Compliance leads acquisition; the governed-agent kernel ships after it. This page is the spine of the manual — the contract is stable, and the version-pinned API reference is on the roadmap. `@caisson/agent-dev` is the governed-agent kernel. It is the layer your AI coding agent runs inside, not a layer that replaces your judgment with autonomy. Every agent, skill, and rule is a typed artifact; every act transition is a legal move on a state machine; every side effect passes one dispatcher. The kernel governs the loop — it does not hand the agent the keys. The same kernel underpins the `create-caisson` generator and the buyer-facing MCP server, so the rules that govern your own workflow are the rules that govern code generation. ## What the kernel governs [#what-the-kernel-governs] * **Typed agent/skill/rule schema.** An agent, a skill, and a rule are each a validated artifact — model, tools, and capabilities declared up front. An undeclared capability or a tool the agent is not granted is a load-time error, not a runtime surprise. * **Lifecycle state machine.** Acts advance only along declared transitions. An illegal move — `execute` before `plan`, `ship` before `verify` — is rejected at the boundary, not caught after the side effect. * **Local hybrid memory.** Recall runs locally over a hybrid index (vector plus keyword). The agent's context stays on the machine unless you wire an explicit egress. * **Hooks dispatcher.** Side effects route through a single deterministic dispatcher — one audited place to gate, log, or deny an action before it leaves the process. ## The contract it upholds [#the-contract-it-upholds] The kernel makes one promise: an agent cannot take an action it was not typed to take. A capability the schema does not declare is unreachable; a transition the state machine does not permit does not fire; a side effect the dispatcher denies does not happen. Governance is enforced at load and at each boundary, not asserted in a README. ## Usage [#usage] A typed agent declares its model, the capabilities it is allowed to exercise, and the transitions it may drive. The kernel validates the definition before the agent runs, then constrains it to the declared moves. ```ts import { defineAgent } from "@caisson/agent-dev"; import type { LifecycleState } from "@caisson/agent-dev"; // Capabilities and tools are declared up front and validated at load. // An undeclared capability is unreachable — not a runtime surprise. const reviewer = defineAgent({ name: "code-reviewer", model: "opus", capabilities: ["code_review"], // The lifecycle state machine: only these transitions are legal. transitions: { spec: ["plan"], plan: ["execute"], execute: ["verify"], verify: ["ship"], }, }); // Legal move — advances along a declared edge. const next: LifecycleState = reviewer.advance("plan"); // Illegal move — rejected at the boundary, before any side effect. reviewer.advance("ship"); // throws: no edge spec → ship ``` Side effects do not bypass the kernel. A hook registered on the dispatcher sees every action before it runs and can gate it. ```ts import { onDispatch } from "@caisson/agent-dev"; // One deterministic dispatcher — one place to gate, log, or deny. onDispatch((action, ctx) => { if ( action.kind === "shell" && !ctx.agent.capabilities.includes("shell_exec") ) { return { allow: false, reason: "agent not typed for shell_exec" }; } return { allow: true }; }); ``` These snippets are illustrative — they show the shape of the contract, not the shipped surface. The full, version-pinned API reference (schema fields, state-machine config, the memory and dispatcher interfaces) is on the roadmap. ## Related [#related] Install the base, wire a tenant, and run the standards gate. The full manual: every package, how to compose it, and the contract it upholds. # AI Production Kit (/docs/ai-kit) A single model call costs a fraction of a cent — invisible until a retry loop fires it ten thousand times and the bill lands at the end of the month. The same week, a reworded prompt drifts the summarizer's accuracy down and nobody notices until a customer does. Both failures are cheap to prevent at the call site and expensive to find in production. `@caisson/ai-kit` is the layer that caps the spike, gates the regression, and versions the prompt. ```bash $ POST /v1/ai/complete HTTP/1.1 402 Payment Required { "error": "spend_cap_exceeded", "tenant": "acme", "window": "month", "capCredits": 250000, "spentCredits": 250000 } ``` The cap is enforced before the provider is called, in integer credit units, fail-closed — over budget returns 402, never an unbounded charge. ## What it does [#what-it-does] * **Provider-agnostic config** — one settings file; switch model or provider without touching call sites. * **Token metering** — every call is recorded with Postgres-atomic accounting, so usage is exact under concurrency. * **Spend caps + circuit breaker** — per-tenant budgets that deny over-limit calls and trip the breaker on a failing provider. * **Eval harness in CI** — prompts are scored against a fixture set; a regression fails the build, not production. * **Prompt registry** — prompts are versioned and append-only, so a change is a diff you can roll back. * **Guardrails + agent setup** — input/output guards plus an agent that walks env, providers, and DB through first-run configuration. ## The contract it upholds [#the-contract-it-upholds] Metering is Postgres-atomic: no double-count, no lost write when calls land concurrently — the ledger reconciles to the cent. Spend enforcement is fail-closed at the call site, so an unmetered path is a build error, not a leak. And the eval gate runs in CI on every change: ```bash $ bun run eval FAIL prompts/summarize@v3 faithfulness 0.71 gate >= 0.80 1 regression — exit 1. Build blocked. ``` A prompt regression stops at the pull request. It does not reach a customer. ## Usage [#usage] Illustrative — final API names may still change. ```ts import type { TenantId } from "@caisson/tenancy-rls"; import { aiConfig, withSpendCap } from "@caisson/ai-kit"; // Provider-agnostic: model and provider live in config, not at the call site. const ai = aiConfig({ provider: "anthropic", model: "claude" }); // Every call is metered (Postgres-atomic) and capped. Over budget -> 402, fail-closed. export async function summarize(tenant: TenantId, input: string) { return withSpendCap(tenant, () => ai.complete({ prompt: "summarize@v3", input }), ); } ``` This page is the spine, not the manual. The full `@caisson/ai-kit` API reference — config schema, the metering ledger, spend-cap policy, the eval-harness CI contract, and the prompt registry — is still expanding. Package names and the fail-closed guarantees above are stable. # AI config (/docs/base/ai-config) `@caisson/ai-config` is one typed configuration surface for AI providers. It is provider-agnostic: the base depends on the config shape, not on a single vendor's SDK, so the driver is swappable without touching call sites. ## The contract [#the-contract] Configuration is declared once, validated at the boundary, and read everywhere through the same typed accessor — no provider details leak into feature code. ```ts import { defineAiConfig } from "@caisson/ai-config"; export default defineAiConfig({ provider: "openai-compatible", // Provider-agnostic by construction; the driver behind this is swappable. }); ``` Setup is **agent-assisted** — the shipped [MCP server](/docs/base/mcp-server) can drive configuration on your behalf, so an AI agent wires the providers instead of you hand-editing every field. The provider driver matrix (which models, which capabilities, which limits) is on the roadmap and not yet locked. Treat the example provider above as illustrative. ## Related [#related] Drives agent-assisted setup, auth-gated and entitlement-scoped. This page covers the essentials. The full `@caisson/ai-config` API reference — the config schema and the supported providers — is still expanding. # Auth (/docs/base/auth) `@caisson/auth` wraps [better-auth](https://better-auth.com), self-hosted. It owns its own Drizzle tables — users, sessions, accounts — migrated through the same standards gate as the rest of the base. There is no third-party identity tenant holding your users. ## The contract [#the-contract] Auth issues **EdDSA-signed JWTs** and publishes a **JWKS** endpoint, so any other plane verifies a token without sharing a private secret. The token carries the tenant claim that `withTenant` reads: auth is the only producer of that claim, and [`tenancy-rls`](/docs/base/tenancy-rls) is the only consumer. That single seam is why a request can cross from the control plane to the data plane without a shared-secret handshake. ```ts import { auth } from "@caisson/auth"; // EdDSA-signed session token; the public JWKS lets any plane verify it. const session = await auth.api.getSession({ headers }); // session.tenantId is the claim tenancy-rls reads — nothing else sets it. ``` The verifying plane fetches the key set, never a secret: ```bash $ curl https://app.example.com/.well-known/jwks.json { "keys": [ { "kty": "OKP", "crv": "Ed25519", "kid": "…", "x": "…" } ] } ``` ## Related [#related] The sole consumer of the tenant claim. `withTenant` is the only RLS entry point. Auth-gated tool calls verify the same EdDSA-JWT. This page covers the essentials. The full `@caisson/auth` API reference — every export, option, and the JWKS rotation procedure — is still expanding. # Billing (/docs/base/billing) `@caisson/billing` wires Stripe and Stripe Tax, with the operator as the merchant of record — Stripe Tax computes and collects tax on the sale. Stripe sits behind a `BillingProvider` port, so the rest of the base depends on the port, not the vendor. ## The contract [#the-contract] Webhooks are verified against the **raw request body** with a timing-safe HMAC compare. A body that has been parsed and re-serialized fails verification — the signature is over the exact bytes Stripe sent, not the JSON your framework reconstructed. ```ts import { verifyWebhook } from "@caisson/billing"; // Raw body — never the parsed JSON. HMAC compared with timingSafeEqual. const event = verifyWebhook(rawBody, req.headers["stripe-signature"]); ``` Because billing rides a port, a test driver stands in for Stripe under the standards gate — suites assert on charge and webhook behavior without a network call. ## Related [#related] Metered spend rides the integer credit ledger, granted on verified billing events. Money-moving side-effects are enqueued, not run inline in the webhook handler. This page covers the essentials. The full `@caisson/billing` API reference — the `BillingProvider` port, the Stripe driver, and the webhook event map — is still expanding. # Credits (/docs/base/credits) `@caisson/credits` is the metering layer. Balances are **integer units, never floats** — there is no rounding to argue about in a dispute. Every movement is a row in an **append-only ledger**; the balance is the ledger's fold, not a mutable column you can overwrite. ## The contract [#the-contract] The debit happens **before** the metered work, not after. If the balance is short, the call returns HTTP **402** and nothing runs — no half-spent tokens, no negative wallet. ```ts import { debit } from "@caisson/credits"; // Debit before the work. No balance → 402 raised, the metered call never starts. await debit({ accountId, amount: 1, idempotencyKey }); ``` Idempotency is enforced by two unique constraints, so a retried webhook or a double-clicked request moves the balance exactly once: ```sql -- Inbound grants: a source event is applied once. UNIQUE (source_event_id, event_type) -- Client-initiated debits: one key, one debit. UNIQUE (account_id, idempotency_key) ``` ## Related [#related] The 402 credit-gate error shape lives in the typed CaissonError hierarchy. Credit grants are applied on verified Stripe webhook events. This page covers the essentials. The full `@caisson/credits` API reference — the ledger schema, grant and refund flows, and the balance projection — is still expanding. # Email (/docs/base/email) `@caisson/email` is the transactional-email port. [Resend](https://resend.com) is the default driver, and it sits behind the port like every other vendor in the base — call sites depend on the port, not the SDK. ## The contract [#the-contract] A **test driver** captures sends in memory, so a suite asserts on what would have been emailed — recipient, template, payload — without a network call or a live key. ```ts import { sendEmail } from "@caisson/email"; await sendEmail({ to: user.email, template: "verify-email", data: { url }, }); ``` Under the standards gate the same call resolves to the test driver, so email behavior is verified deterministically: ```ts import { testEmail } from "@caisson/email/testing"; // The send is captured, not transmitted. expect(testEmail.outbox).toHaveLength(1); ``` ## Related [#related] Send email from a job to keep request handlers fast. Verification and reset mail ride this port. This page covers the essentials. The full `@caisson/email` API reference — the email port, the Resend driver, and the template contract — is still expanding. # Jobs (/docs/base/jobs) `@caisson/jobs` is the background-work port. [Trigger.dev](https://trigger.dev) (self-hostable) is the default driver; a test driver runs jobs deterministically under the standards gate, so suites assert on job behavior without a queue. ## The contract [#the-contract] **Billing and credit side-effects are enqueued, never run inline** in a request handler. A webhook returns fast, and the money-moving work runs as a job — so a dropped connection mid-request never leaves a half-applied charge or a half-granted credit. ```ts import { enqueue } from "@caisson/jobs"; // The webhook handler returns immediately; the credit grant runs as a job. await enqueue("grant-credits", { accountId, sourceEventId }); ``` The same `enqueue` call hits the test driver under `bun run check`, so the enqueue contract is verified without standing up Trigger.dev. ## Related [#related] Grants land idempotently on (source\_event\_id, event\_type). Webhook handlers enqueue rather than charge inline. This page covers the essentials. The full `@caisson/jobs` API reference — the job port, the Trigger.dev driver, and the test driver — is still expanding. # Kernel (/docs/base/kernel) `@caisson/kernel` is the shared spine every package imports. It defines a typed `CaissonError` hierarchy — each failure has a stable, documented shape instead of an ad-hoc string — and hosts the **one standards/conformance gate** every package ships through. ## The contract [#the-contract] Two error shapes are load-bearing across the base: * The **credit gate** raises a **402** with a documented body, not a bare 500. * A **tenancy denial returns 404**, not 403 — refusing to confirm a row exists *is* the policy. There is no existence leak to probe. ```ts import { InsufficientCreditsError, NotFoundError } from "@caisson/kernel"; // 402 — the credit gate. Documented body, never a bare 500. throw new InsufficientCreditsError({ required: 1, available: 0 }); // Tenancy denial → 404. We never confirm the row exists. throw new NotFoundError(); ``` The 402 carries a stable, machine-readable body so an agent can react to it: ```jsonc // HTTP 402 { "error": "insufficient_credits", "required": 1, "available": 0 } ``` A package becomes part of the base only by passing the one gate: ```bash $ bun run check # build · lint · test — every package, one gate ``` ## Related [#related] Raises the 402 credit-gate error defined here. Denials surface as the 404 with no existence leak. This page covers the essentials. The full `@caisson/kernel` API reference — the complete CaissonError hierarchy and the conformance-gate rules — is still expanding. # MCP server (/docs/base/mcp-server) `@caisson/mcp-server` is the buyer-facing MCP server your AI agent connects to — to drive generation, configuration, and the metered operations the base exposes. It is a first-class surface, not a side door. ## The contract [#the-contract] Every tool call is **auth-gated** with the same EdDSA-JWT issued by [`@caisson/auth`](/docs/base/auth), and **entitlement-scoped**: an agent can only reach the tools the buyer's entitlements cover. A token without an entitlement does not see the tool — scoping happens before dispatch, not inside it. ```jsonc // .mcp.json — the agent connects with the buyer's token. { "mcpServers": { "caisson": { "url": "https://mcp.example.com", "headers": { "Authorization": "Bearer ${CAISSON_TOKEN}" }, }, }, } ``` A call outside the token's entitlements is refused, not silently downgraded: ```text $ caisson-agent call generate.edition --edition compliance ERROR entitlement_required: token is not scoped for "compliance" ``` ## Related [#related] Issues the EdDSA-JWT every tool call verifies. Agent-assisted setup runs through these tools. This page covers the essentials. The full `@caisson/mcp-server` API reference — the tool catalog, the entitlement model, and the generation flow — is still expanding. # Tenancy (RLS) (/docs/base/tenancy-rls) `@caisson/tenancy-rls` is the fail-closed multi-tenant layer. Every tenant table runs Postgres row-level security with `FORCE`, and `withTenant` is the **sole** entry point that sets the tenant context. A query that reaches the database with no context set returns nothing — never another tenant's rows. ## The contract [#the-contract] The policy is `FORCE`d, so it applies even to the table owner. The isolation guarantee is a test in the suite, not a hope: ```sql ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; ALTER TABLE invoices FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON invoices USING (tenant_id = current_setting('app.tenant', true)::uuid); ``` With no `app.tenant` set, the same query that works inside a tenant scope is denied: ```text $ psql -c "select * from invoices" ERROR: permission denied for table invoices DETAIL: RLS policy "tenant_isolation" forbids SELECT with no app.tenant set — fail-closed by default. ``` `withTenant` sets the context, runs your callback inside it, and tears it down — there is no API that reads tenant data without it: ```ts import { withTenant } from "@caisson/tenancy-rls"; await withTenant(tenantId, async (db) => { // Inside this scope RLS is enforced. Outside it, queries fail closed. return db.query.invoices.findMany(); }); ``` ## Related [#related] The only producer of the tenant claim `withTenant` reads. A tenancy denial returns 404 — no existence leak. This page covers the essentials. The full `@caisson/tenancy-rls` API reference — the cross-tenant isolation test, migration helpers, and policy templates — is still expanding. # UI (/docs/base/ui) `@caisson/ui` is the typed token floor. It ships a set of `--cs-*` custom properties in **OKLCH**, delivered as exactly two themes — one light, one dark. This docs site renders on those same tokens. ## The contract [#the-contract] You re-skin by **swapping token values**, never by forking a component. A component reads a token; it never hard-codes a hex value, so a brand change is a set of new variables, not a patch across the component tree. ```css :root { --cs-bg: oklch(99% 0 0); --cs-fg: oklch(20% 0 0); --cs-accent: oklch(62% 0.19 256); } /* Re-skin = new values here. Components read the tokens; they never fork. */ ``` Styled primitives — buttons, inputs, cards as shipped components — are on the roadmap. Today the package is the token contract only. Treat any primitive shown elsewhere as illustrative. ## Related [#related] Install the base and run the standards gate. This page covers the essentials. The full `@caisson/ui` API reference — the complete token map and the theming contract — is still expanding. # create-caisson (/docs/cli/create-caisson) The command names and the generation model are stable; the version-pinned registry and the full CLI flag reference are still expanding. ## What it does [#what-it-does] `@caisson/cli` is the generator. You pick an edition and the modules you want; it assembles a tailored codebase by pulling versioned module sources from the Caisson registry. Editions are compositions of the same audited base — the generator composes, it never forks. ```bash bun create caisson@latest my-app cd my-app bun install ``` The first prompt picks your edition (Compliance, AI Production Kit, Local-first AI, Agentic-Dev) and the à-la-carte modules layered on top. The base substrate — `auth`, `tenancy-rls`, `billing`, `credits` — is always wired in. ## The registry contract [#the-registry-contract] Every module the CLI emits is pulled from a **versioned** registry, not copied from a moving template. A generation resolves and pins the module versions it used, so the same inputs reproduce the same tree. The CLI and the buyer's AI agent read from the one registry — there is no second, drifting source. ```bash $ bun create caisson@latest my-app resolved 11 modules from registry @ 2026.6.0 scaffold compliance edition + base substrate -> ./my-app metered 11 codegen-credits lockfile caisson.lock (pinned module versions) written ``` The contract it upholds: a generation is **reproducible** (pinned versions in `caisson.lock`) and **metered** (codegen-credits are integer units, never floats — a run debits an exact count, or it fails closed before writing). ## Drive it from your agent [#drive-it-from-your-agent] The shipped MCP server is auth-gated. Your Claude Code or Cursor agent authenticates, reads the same registry, and runs the same generation the CLI does — so the agent can scaffold and reconfigure the codebase without you leaving the editor. ```ts import type { GenerateResult } from "@caisson/cli"; // Illustrative: your agent calls the auth-gated MCP server, which resolves // modules from the versioned registry and meters the run. const result: GenerateResult = await caisson.generate({ edition: "compliance", modules: ["audit-worm", "field-crypto"], target: "./my-app", }); // result.creditsSpent — integer codegen-credits debited for this run // result.lockfile — the pinned module versions this generation resolved ``` Install the base, wire a tenant, and run the standards gate. The full manual — every package and the contract it upholds. The full `create-caisson` flag reference, registry version-pinning rules, and the MCP tool schemas the buyer's agent calls are still expanding. # audit-worm (/docs/compliance/audit-worm) `@caisson/audit-worm` does two jobs an auditor asks about by name: it writes evidence to S3 Object-Lock in compliance mode, and it records every privileged action into an append-only SHA-256 chain. Stored evidence cannot be changed before its retention period ends, and the log of who-did-what cannot be rewritten without the break showing. ## What it does [#what-it-does] * **WORM evidence storage** — evidence objects land in an S3 bucket with Object-Lock in *compliance mode*. Once an object is written under a retention period, it cannot be overwritten or deleted before that period expires — not by the application, not by an admin, not by a leaked root key. * **Append-only audit chain** — each event hashes its payload together with the previous entry's hash. The chain verifies end to end; editing any past entry breaks every hash after it. ## The guarantee [#the-guarantee] Compliance-mode Object-Lock means immutability is enforced by the storage layer, not by your code. The audit chain makes tampering *detectable*, not merely discouraged — verify the chain and a single edited row surfaces with its sequence number: ```bash $ caisson audit verify --tenant tenant_4f2c ✗ chain broken at seq 1184 expected prev = 9f3a…c021 found prev = 51bd…7e08 (entry was edited after it was written) ``` ## Usage [#usage] ```ts import { appendEvent, putEvidence, verifyChain } from "@caisson/audit-worm"; // Append a privileged action to the SHA-256 chain. await appendEvent({ tenantId, actor: "user_4f2c", action: "invoice.export", }); // Write an evidence object under a retention lock — immutable until it expires. await putEvidence({ key: `evidence/${tenantId}/soc2-access-review.pdf`, body: pdf, retainUntil: "2033-06-27T00:00:00Z", }); // Verify the chain is intact before you hand the log to an auditor. const result = await verifyChain(tenantId); if (!result.ok) { throw new Error(`audit chain broken at seq ${result.brokenAt}`); } ``` This page covers the essentials. The full API reference — every event type, the Object-Lock retention modes, and the chain-verification contract — is still expanding. # compliance (/docs/compliance/compliance) `@caisson/compliance` turns the guarantees the other modules enforce into something you can hand an auditor: a signed evidence pack that maps each control to a cited clause, generated from a config-as-code registry of the modules in scope. The evidence is the artifact the other packages already produce — fail-closed RLS, WORM storage, per-tenant encryption — not a questionnaire you fill in by hand. ## What it does [#what-it-does] * **Evidence-pack generator** — emits a SOC 2 / HIPAA evidence pack: one entry per control, each citing the clause it satisfies and the module that supplies the evidence. The pack is hashed and signed, so an auditor can confirm it wasn't altered after generation. * **Config-as-code module registry** — declare which frameworks and modules are in scope in a typed config file. The registry is version-controlled and diffable; scope changes show up in a pull request, not in a spreadsheet. ## Map controls to modules [#map-controls-to-modules] ```ts import { defineComplianceConfig } from "@caisson/compliance"; export default defineComplianceConfig({ frameworks: ["soc2", "hipaa"], modules: ["tenancy-rls", "audit-worm", "field-crypto"], }); ``` ## The signed evidence pack [#the-signed-evidence-pack] Generate the pack and each control resolves to a cited clause and a module — for example **SOC 2 CC6.1 (logical access)** mapped to fail-closed RLS, and **HIPAA §164.312(a)(1) (access control)** mapped to tenancy plus per-tenant field encryption: ```bash $ caisson compliance pack --framework soc2 ``` ```json { "framework": "SOC 2", "generatedAt": "2026-06-27T14:02:11Z", "controls": [ { "id": "CC6.1", "title": "Logical access controls", "module": "tenancy-rls", "evidence": "Fail-closed RLS with FORCE on every tenant table", "status": "satisfied" } ], "signature": "sha256:7d8f2a1c…a14e" } ``` The HIPAA pack maps **§164.312(a)(1)** to the same access-control surface — `tenancy-rls` for isolation and `field-crypto` for at-rest protection of identifiers. ## The modules behind the evidence [#the-modules-behind-the-evidence] WORM evidence storage and the append-only SHA-256 audit chain the pack cites. Per-tenant field encryption — the evidence for HIPAA §164.312(a)(1) at rest. This page covers the essentials. The full API reference — the control-to-clause map for every framework, the registry schema, and the signature verification flow — is still expanding. # field-crypto (/docs/compliance/field-crypto) `@caisson/field-crypto` encrypts individual fields with a key derived per tenant. A single root key lives in your KMS; each tenant's subkey is derived from it with HKDF, using the tenant id as the salt. One tenant's key cannot decrypt another tenant's ciphertext — the isolation is in the key derivation, not in an application check that could be skipped. ## What it does [#what-it-does] * **Per-tenant field encryption** — encrypt and decrypt named fields (a patient SSN, a bank token) scoped to a tenant. The plaintext is never stored. * **HKDF key derivation** — per-tenant subkeys are derived deterministically from the KMS root key with HKDF. The tenant id is the salt, so the derivation is reproducible for one tenant and useless for any other. ## The guarantee [#the-guarantee] The encryption key for tenant A is a pure function of the root key and A's id. There is no shared field key, and no path where tenant B's subkey decrypts A's data: ```ts import { encryptField, decryptField } from "@caisson/field-crypto"; // tenantId is the HKDF salt — the derived subkey is scoped to this tenant alone. const ciphertext = await encryptField(tenantId, patient.ssn); const plain = await decryptField(tenantId, ciphertext); // Same ciphertext, a different tenant's id → derives a different subkey → fails closed. await decryptField(otherTenantId, ciphertext); // throws: authentication tag mismatch ``` ## The KMS port [#the-kms-port] The root key is reached through a `FieldKeyProvider` port. The AWS KMS adapter ships today; GCP, Azure, and Vault are a seam — the same port, an adapter to implement. ```ts import type { FieldKeyProvider } from "@caisson/field-crypto"; import { AwsKmsKeyProvider } from "@caisson/field-crypto/aws"; const keys: FieldKeyProvider = new AwsKmsKeyProvider({ keyId: process.env.KMS_KEY_ID, }); ``` The AWS KMS adapter is shipped. GCP, Azure, and HashiCorp Vault adapters are roadmap — the `FieldKeyProvider` port is stable, so they slot in without touching call sites. This page covers the essentials. The full API reference — the HKDF parameters, the cipher suite, and the `FieldKeyProvider` contract for writing your own adapter — is still expanding. # Local-first AI (/docs/local-first) `@caisson/local-ai` is the **Local-first AI** edition: a compute seam, a privacy gate, on-device vector search, an offline license check, and a local store. Inference and retrieval run on the machine that holds the data. Your data never leaves the device. ## What it does [#what-it-does] * **Compute seam** — one swappable point that decides where a model runs. The default target is the local device; a remote target is an explicit opt-in, never the fallback. * **Privacy gate** — the egress boundary. A call that would cross the network is denied unless the policy allows it, so "on-device" is enforced in code, not promised in a README. * **On-device vector search** — approximate nearest-neighbor over a local index using `sqlite-vec`. Embeddings and queries stay in the local store. * **Offline license** — license verification that does not call home, so the edition keeps working on an air-gapped machine. * **Local store** — the SQLite-backed home for vectors, documents, and the license record. ## The guarantee [#the-guarantee] The privacy gate is the contract: data crosses the network boundary only on an explicit allow. The gate fails closed — with no allow rule, a remote call is denied, not silently downgraded. ```ts import { createLocalAI } from "@caisson/local-ai"; import type { LocalAIConfig } from "@caisson/local-ai"; const config: LocalAIConfig = { // The gate fails closed: no network egress without an explicit allow rule. privacy: "device-only", vector: { backend: "sqlite-vec" }, }; const ai = createLocalAI(config); // Embeddings and ANN search run on-device against the local store. const hits = await ai.search("quarterly variance", { topK: 5 }); ``` ## License [#license] `@caisson/local-ai` ships under the **Caisson Commercial License** (`LicenseRef-Caisson-Commercial`) — own the source, build unlimited products, no redistribution of the kit. The same one-time perpetual model as every other edition; your data never leaves the device. Local-first AI is **from $499**, one-time. See [pricing](/pricing) for the editions, the everything bundle, and per-module options. This page previews the `@caisson/local-ai` surface. The full API reference — compute-seam adapters, the privacy-gate policy schema, the `sqlite-vec` index format, and the offline-license record — is still expanding. ## Next steps [#next-steps] Install the base, wire a tenant, and run the standards gate.