SDK — Storage

A biome that needs persistence has two choices:

  • Ship a full relational database schema in a biome-owned API — own the migrations, own the queries, own the operational surface.
  • Declare collections in xema-biome.json's storage block — let the shared biome data plane (biome-storage-api) host them, with per-tenant isolation, encryption, and a closed filter grammar already wired in.

This page documents the second path. The two patterns coexist — a biome may use both.


Why the shared data plane

biome-storage-api is a multi-tenant managed database service that gives biomes:

  • per-(org, project, environment) isolation — a biome installed twice (once org, once sandbox) sees two disjoint datastores;
  • field-level encryption at rest;
  • per-tenant quotas;
  • a closed filter / index grammar — no raw SQL, no injection surface;
  • an opaque collection layer — biomes never receive a DB handle, only typed row APIs.

Suitable for: feature flags, small lookup tables, per-install metadata, cached integration data, light bookkeeping.

Not suitable for: heavy joins, full-text search at scale, write-heavy event ingest, multi-collection transactions.


Declaring collections

The storage block sits inside the xema block of xema-biome.json:

jsonc
1{
2  "xema": {
3    "ships": {
4      "content": ["biomeStorageSchemas"]
5    },
6    "storage": {
7      "namespace": "<biomeId>",
8      "collections": [
9        {
10          "name": "incidents",
11          "schemaPath": "storage/incidents.schema.json"
12        }
13      ],
14      "isolation": "org",
15      "uninstallPolicy": "retain"
16    }
17  }
18}
FieldRequiredPurpose
namespaceyesStable per-biome namespace; usually the biome id
collections[].nameyesCollection slug; unique within the namespace
collections[].schemaPathyesPath to the collection's JSON schema, relative to the biome root
isolationyesClosed enum: org | project | sandbox — the tenant grain rows are partitioned on
uninstallPolicyyesClosed enum: retain | drop-on-uninstall — what biome-host-api does to the collection when the biome is removed

Schemas declared in xema-biome.json are forwarded to biome-storage-api at install time. Breaking-schema upgrades (incompatible field type changes, dropped indexed columns) are rejected with a 409 — bump the biome's major version and run the migration from your biome service on startup, guarded by a stored schema version. (The manifest's onUpgrade hook is declared but never invoked — see Lifecycle Hooks.)


Collection schema shape

The schema file is a JSON document describing fields, encrypted columns, and indexes:

jsonc
1{
2  "fields": {
3    "id":     { "type": "string", "primaryKey": true },
4    "body":   { "type": "string", "encrypted": true },
5    "status": { "type": "enum", "values": ["open", "closed"] },
6    "openedAt": { "type": "timestamp" }
7  },
8  "indexes": [
9    { "fields": ["status"] },
10    { "fields": ["openedAt"] }
11  ]
12}

Closed-set rules:

  • fields[*].type is a closed enum: string | number | boolean | enum | timestamp | json.
  • enum fields require a non-empty values array.
  • encrypted: true fields are stored encrypted at rest via the platform-common encryption helpers; reads decrypt transparently for the calling biome.
  • Indexes are explicit; the data plane refuses to query on un-indexed predicates.

Isolation grain

isolationPartition key
org(orgId, namespace, collection)
project(orgId, projectId, namespace, collection)
sandbox(orgId, projectId, sandboxId, namespace, collection)

Authorization scopes the namespace to the partition key the calling subject is allowed to access. A biome installed in both org and sandbox zones sees two distinct datastores — a sandbox install can never read production rows.

Cross-environment reads are rejected at the gateway, not at the SQL layer. There is no opt-out.


uninstallPolicy

ValueBehaviour on BiomeInstallation deletion
retainRows survive uninstall; reinstalling the biome (same namespace) sees the prior data
drop-on-uninstallThe data plane drops the rows synchronously as part of the uninstall transition

retain is the safe default for biomes that hold user data; drop-on-uninstall is appropriate for cache-style biomes whose rows are derivable from upstream sources.


Reading and writing

Biome code never opens a DB handle. Every read and write is a capability call:

CapabilityPurpose
biome-storage:collection.write@1Insert / update / delete rows in one of the biome's declared collections
biome-storage:collection.read@1Read rows, with a closed predicate grammar (only indexed predicates allowed)

The capabilities are declared in requiresCapabilities[] and gated by Stage-1 install consent. At runtime the gateway resolves the calling subject's BiomeInstallGrant, derives the partition key from the environment, and routes the call to biome-storage-api.


Cross-biome data access

A biome never reads another biome's storage directly. To expose data across biomes:

  1. The source biome declares a read capability (e.g. biome:incidents.list@1) in exposesCapabilities[].
  2. The source biome's API or lifecycle hook implements the capability, internally reading its own storage.
  3. The consumer biome declares the same ref in requiresCapabilities[].
  4. The consumer biome invokes the ref through the capability gateway; the source biome decides what to return.

The Object Registry indexes exposed datasets so they appear in XVFS listings.



Previous: ← Backend I ship Next: UI I contribute →