UUniCAS DOCS
Console GitHub
UNICAS / DOCUMENTATIONEdit source

#UniCAS Architecture

Status: implemented service architecture; compatibility and retention tooling continue to evolve

Date: 2026-08-21

This document defines the content-addressed storage architecture implemented by UniCAS. Downstream applications may store their own opaque document and asset formats without making those formats part of the service contract.

The node encoding referenced below is defined in CAS Binary Format.

The public App/Space v1 contract, current D1 schema, and R2 object keys use App and Space dimensions. Historical binding, Durable Object class, audit-port, and audit-record names remain isolated implementation details rather than public aliases.

#1. Goals

The CAS stores an App-and-Space-scoped Merkle DAG for persistent SValue document roots and binary assets.

The design must provide:

  • immutable, content-addressed nodes;
  • App-and-Space isolation and authenticated service access;
  • deduplication within one App-and-Space partition;
  • child references between nodes;
  • leases that protect uncommitted nodes from garbage collection;
  • separate child and business-root reference counts;
  • transactional reference updates inside D1;
  • idempotent root-reference updates;
  • deterministic reference extraction from document operations and snapshots;
  • read-only CAS access from document-type implementations.

The CAS does not attempt to provide a distributed transaction spanning a document Durable Object, D1, and R2. Cross-system failures are handled with ordering, rollback, retry, and business-level compensation.

#2. App and Space isolation

App.status = suspended is a reversible App-wide data-plane deny override. Issuer resolution includes the owning App's status for external and managed issuers, and the Space verifier denies every operation with APP_SUSPENDED. Cached authority refreshes after 30 seconds and cannot authorize beyond a 60-second hard stale bound on refresh failure. Administrator recovery routes remain available. Suspension does not change retention, membership, or data; see operations.

appId is the top-level trust and storage namespace. Within an App, spaceId identifies data ownership. Root Ref revisions order events across Spaces within the App, without an additional namespace.

  • Every public data-plane route carries both appId and spaceId.
  • Identical content in different App-and-Space partitions is stored independently.
  • Storage usage and GC are calculated per App-and-Space partition.
  • App administrators can read a derived App total through the control plane; this aggregation does not expose Space identity or grant data-plane access.
  • A configured trusted JWT issuer maps to one stable App; verified Space claims, permissions, and path Space must agree before storage access.

The current Cloudflare physical compatibility mapping is:

  • D1 keys include (stack_id, tenant_id, digest).
  • R2 objects use stacks/{stackId}/tenants/{tenantId}/nodes/{digest}.

A CAS Durable Object named from the physical composite corresponding to (appId, spaceId) coordinates mutable Space operations through a short explicit mutation gate:

  • lease claims and extensions;
  • upload completion;
  • child-reference creation;
  • root-reference count updates;
  • garbage collection.

D1 and R2 remain the durable stores. Canonical request bodies stream to R2 outside the mutation gate; only lease begin/finalize, Root Ref updates, and GC mutations queue. Active upload reservations fence GC during that unlocked interval. Content, metadata, and usage reads do not enter the mutation gate. The Durable Object does not replace App/Space-aware logical keys or their current physical compatibility keys in the shared D1 and R2 bindings.

#3. Node model

A logical node consists of three storage classes.

#3.1 Immutable content

The node's own content bytes are immutable and stored in R2.

stacks/{stackId}/tenants/{tenantId}/nodes/{sha256Digest}

This is the current physical compatibility key, not the v2 public route. R2 stores only the content bytes, not mutable lifecycle state.

#3.2 Immutable metadata

D1 stores metadata that participates in node identity:

export type CasHash = string; // 64 lowercase hexadecimal SHA-256 characters

export interface CasNodeMetadata {
  readonly hash: CasHash;
  /** Byte length of this node's own R2 content. */
  readonly size: number;
  readonly contentType: string;
  /** Ordered child references. Duplicates are significant. */
  readonly refs: readonly CasHash[];
}

The ordered refs list is part of the Merkle identity. Each occurrence contributes one child reference. D1 must retain ordering, even if it also maintains a grouped edge-count index.

#3.3 Mutable state

D1 stores lifecycle state that does not participate in the digest:

export interface CasNodeState {
  readonly leaseStartedAt: number;
  readonly leaseExpiresAt: number;
  readonly childRefCount: number;
  readonly rootRefCount: number;
}

All fields default to 0.

  • childRefCount counts references from immutable metadata of other CAS nodes.
  • rootRefCount counts references held by document deltas, retained custom snapshots, or other business roots.
  • Both counts must remain non-negative.
  • GC requires both counts to equal zero.

#4. Node readiness

No explicit status column is used. D1/R2 presence defines readiness.

D1 row R2 content Meaning
absent absent unknown
present absent not ready
present present ready

The canonical R2 key is written only after the service has verified content length and the complete logical-node digest. Consequently, presence at the canonical key is the durable proof that upload validation completed. Failed or interrupted uploads must never write unverified bytes to the canonical key; implementations may buffer a bounded node or use a temporary R2 key while validating.

A node must be ready before it can:

  • be read;
  • receive a root reference;
  • be referenced by a newly inserted node;
  • be used by a document operation.

A ready D1 row without canonical R2 content is a storage-integrity fault and never falls back to upload instructions. The recoverable inverse is a verified canonical R2 object whose ready D1 transaction was interrupted; a later lease finishes publication.

#5. Digest and identity

Node addresses use the complete 256-bit SHA-256 digest.

sha256(canonical logical node bytes)

The canonical logical bytes include:

  1. binary format/version and immutable flags;
  2. own content size;
  3. content type;
  4. ordered child digests;
  5. own content bytes.

Mutable lease and reference-count fields are excluded.

The binary preimage format is specified in CAS Binary Format. The service validates that supplied metadata and uploaded content reproduce the requested digest.

#6. Lease semantics

A lease guarantees that CAS GC will not delete the node before leaseExpiresAt.

It does not guarantee that:

  • the node is ready;
  • the node will be deleted immediately after expiration;
  • an expired upload attempt can still complete.

The state represents the most recent uninterrupted lease period:

leaseStartedAt = start of the current uninterrupted lease period
leaseExpiresAt = current guaranteed protection deadline

When no lease has ever existed, both are 0.

A claim requests a duration, but the service chooses the actual expiry. It may extend continuous leases more aggressively to reduce D1 write frequency.

Successful v2 responses form a strict union discriminated by state: ready, awaiting_upload, awaiting_replacement_upload, or validated_awaiting_children. Only ready carries lease timestamps.

Rules:

  • A ready node renews or extends its lease without re-uploading content.
  • The lease request carries only leaseDurationMs; canonical bytes go to the returned write-once R2 target.
  • The client never carries an upload generation or upload ID.
  • Leases are not explicitly released.
  • Lease state is stored directly on the node row; there is no separate lease table.

#7. Node creation

A node is created or leased through its expected hash. Creation follows these steps:

  1. A lease call creates or reuses a short-lived internal generation and returns a presigned write-once temporary-object target.
  2. The caller PUTs one complete canonical node block directly to R2.
  3. The caller repeats the same lease request. UniCAS checks the 32 MiB limit, computes the complete SHA-256 digest, parses immutable metadata and ordered refs, and persists generation-fenced validation evidence.
  4. If any child is not ready, the parent remains staged and the response lists every distinct unready child hash. Later parent leases recheck only child readiness.
  5. Once children are ready, UniCAS writes the immutable canonical object and atomically inserts metadata and edges, increments child counts, establishes the lease, and removes the reservation.

R2 is published before the D1 row. A crash after R2 and before D1 leaves an orphan object; retry is idempotent and completes the row. A D1 row is never committed without canonical R2 content.

No operation may add a reference to a parent that is not ready.

#8. Garbage collection

A node is eligible when:

childRefCount == 0
AND rootRefCount == 0
AND leaseExpiresAt <= now

GC runs through the same Space CAS queue as lease and reference operations.

For each eligible node:

  1. Exclude nodes with an unexpired upload reservation and re-check eligibility while holding the per-Space mutation gate.
  2. Delete the R2 object. R2 deletion is idempotent; missing content is allowed.
  3. In one D1 transaction:
    • re-check both reference counts and lease expiry;
    • delete ordered edge rows;
    • decrement each child's childRefCount by edge occurrence count;
    • reject and roll back if any child count would become negative;
    • delete the node row.
  4. Newly zero-referenced children become candidates for a later GC pass.

If R2 deletion succeeds but the D1 transaction fails, the row remains as a not-ready node. A future lease can require re-upload, or a later GC pass can retry deletion and metadata cleanup.

The App-and-Space queue closes the lease/GC race: a lease cannot be granted between GC's eligibility decision and R2 deletion.

#9. Reference-count updates

#9.1 Child references

Child references are immutable node metadata.

  • They are incremented transactionally when a node row is first inserted.
  • They are decremented transactionally when that node row is collected.
  • Duplicate child hashes count as multiple references.
  • A child must be ready when the parent row is inserted.

#9.2 Root references

Business systems update root references in a batch:

export type CasReferences = Readonly<Record<CasHash, number>>;
export type CasRefChanges = Readonly<Record<CasHash, number>>;

export interface CasRootRefUpdate {
  readonly requestId: string;
  readonly changes: CasRefChanges; // signed non-zero integer deltas
}

The canonical service operation is:

POST /v1/cas/root-refs?appId={appId}&spaceId={spaceId}
Authorization: Bearer <capability>

The request body does not carry App, Space, or domain identity. CAS maps the globally unique verified issuer to App authority, requires query appId to match that authority, requires one signed grant to select query spaceId and contain the exact operation permission. No additional namespace is selected.

The update operation:

  1. validates a non-empty, bounded request ID;
  2. canonicalizes and hashes the change set;
  3. checks the idempotency table;
  4. if the same request ID and payload already succeeded, returns success;
  5. if the same request ID has a different payload, returns conflict;
  6. verifies every positively referenced target is ready;
  7. verifies every resulting rootRefCount is non-negative;
  8. allocates the next App-global audit revision;
  9. applies aggregate changes, appends one Space-bearing audit event, updates the App/Space projection, and records idempotency in one D1 transaction.

Every hash in changes must identify an existing D1 node, including hashes with negative deltas. Each delta must be a non-zero safe integer within configured per-request bounds. The service checks addition overflow before applying it. Empty change sets, unknown hashes, non-integer values, overflow, and results below zero reject the entire batch.

The API is a stable CAS service contract. Whether a Gateway exposes it is an independent ingress policy decision.

A deterministic request ID is recommended, for example:

doc:{documentId}:version:{version}:add-refs
doc:{documentId}:truncate:{firstVersion}-{lastVersion}:remove-refs
snapshot:{documentId}:{version}:add-refs

Idempotency is scoped to (appId, spaceId, requestId) and is required for timeout and retry safety. It does not introduce owner entities; aggregate root counts remain scoped only by App, Space, and hash. Business domains own logical-reference lifecycle; CAS stores counts and audit facts.

#9.3 Reconciliation

Reconciliation is an operator-assisted comparison, not an automatic repair:

  1. The business system exports its intended balances for one stable App and records the source watermark.
  2. An administrator reads the CAS App balance and ordered events through a consistent audit revision, optionally filtering one Space at a time.
  3. The operator compares intended and CAS-recorded counts by Space and hash, then uses event request IDs and business records to explain differences.
  4. A confirmed business bookkeeping error is repaired through a new normal signed-delta operation with its own deterministic request ID.
  5. The operator reads a new audit revision and verifies the intended result.

Do not update audit projections directly, derive aggregate lifecycle state from audit balances, or silently force the aggregate to match business claims. The offline cutover compares old projections with every authoritative aggregate, preserves old histories in a read-only archive, and records an App checkpoint. It never seeds a sentinel namespace or invents a cross-history order. See the Root Ref migration and replay bridge.

#10. Space client TypeScript API

import type {
  AppCasClient,
  SpaceNodeLeaseOptions,
} from "@unicas/space-client";

declare const cas: AppCasClient;
declare const spaceId: string;
declare const hash: string;

await cas.readNode(spaceId, hash);
await cas.readMetadata(spaceId, hash);
await cas.readContent(spaceId, hash, { offset: 0, length: 1024 });

const leaseOptions: SpaceNodeLeaseOptions = {
  durationMs: 60_000,
  signal: null,
};
await cas.leaseNode(spaceId, hash, leaseOptions);
await cas.listRootRefs(spaceId, { limit: 100 });
await cas.usage(spaceId);
await cas.gc(spaceId, { maxNodes: 100 });

An AppCasClient is created with one appId, an asynchronous capability provider, and an optional immutable-node cache strategy. Every method receives the target spaceId; one client can therefore serve several Spaces while keeping the App fixed. No compatibility client translates the removed Space-bound API, retired Stack/Tenant routes, or credentials.

leaseNode() is the only lease operation. It returns the current lease state and, when an upload is required, direct PUT instructions. The thin transport does not create canonical bytes or perform that upload. Use the blob client for the complete store/retain workflow. readNode, updateRootRefs, and the remaining operations are listed in the reviewed TypeScript API reference.

Blob chunk size and index fanout are local client implementation choices, bounded by protocol defaults. They are not sent as trusted lease parameters; CAS validates the canonical body against its configured node byte and ref limits.

#11. Authenticated HTTP API

CAS owns its native Space and App administrator route contracts. @unicas/service provides one cloud-neutral HTTP actor that matches both protocols and receives storage/concurrency strategies through explicit platform ports. @unicas/service-cloudflare wraps that actor as the only production Worker and public endpoint. The same Worker serves /v1/cas, /admin, MCP/OAuth, and the admin UI while preserving credential isolation between route classes. D1, R2, KV, and Durable Object bindings are Cloudflare adapter concerns; keyed actor ports preserve the single-writer semantics required by Space and App Root Ref operations. Node, usage, and GC routes retain the canonical /cas resource-family segment; Root Refs is a sibling Space operation. A Gateway or other shared ingress may map selected Space operations to another path, but that mapping and allowlist are not part of the CAS protocol.

Space service routes accept JWT capabilities from configured App issuers. Each App registers one stable issuer with multiple rotation keys selected by kid; the issuer maps uniquely to App authority. The Space verifier checks issuer, key, signature, algorithm, CAS data-plane audience, time bounds, permissions, and operation-specific claims before any DO, D1, or R2 access. The issuer-derived App must match the path, and token Space must equal path Space. The controlled authority registry is cached for 30 seconds; records older than the 60-second hard stale/revocation bound fail closed when they cannot be refreshed.

Root Ref writers require the exact update permission in a grant selecting the Space. Unknown legacy claims and caller-supplied forwarding headers cannot introduce an authorization or storage dimension.

Top-level /admin routes use a provider-registry-backed BFF session plus deny-by-default Platform Access. Google OIDC, Microsoft personal-account OIDC, and GitHub OAuth adapters resolve an exact External Identity to one stable Account before credential issuance. Active platform authority or App membership admits the Account; blockedAt overrides both for every linked identity. App members retain equal App-local administrator authority, but membership never grants apps.create or platform.admin. Browser and CLI requests recheck Account admission on every operation. Remote MCP checks admission on OAuth grant issuance and every request; delegated OAuth scopes never substitute for current authority. Space JWTs are never accepted by admin routes even if they contain admin-looking scopes, and OIDC admin sessions are never accepted by Space routes. The @unicas/service-cloudflare worker owns the admin BFF (src/admin-bff): OIDC callback, secure session, CSRF boundary, and admin BFF routes; the @unicas/admin-webui package is the browser UI only. Browser code never receives OIDC client secrets or storage bindings.

An email-bound pending App or platform invitation may start a special provider continuation for an Account without full admission. Google and GitHub can produce short-lived verified-email evidence. Microsoft token email fields are never evidence; the BFF sends a six-digit challenge to the invitation's exact stored address through the Cloudflare Email binding. Challenge state binds the invitation hash, exact External Identity, and authentication event, and stores only a salted code hash. The resulting session may call only the matching acceptance route plus logout. Acceptance consumes challenge evidence and the invitation in one D1 transaction, then rotates the session. General login uses Account admission, not email allowlisting.

Node upload is driven by one hash-addressed lease operation. The lease body contains only the requested duration; canonical bytes go directly to a short-lived, write-once R2 target returned by the service.

#11.1 Read content

GET /v1/cas/nodes/{sha256}?appId={appId}&spaceId={spaceId}
Authorization: Bearer <CAS capability>

Responses:

  • 200 with binary content for a ready node;
  • 404 for unknown or not-ready nodes.

#11.2 Read metadata

GET /v1/cas/nodes/{sha256}/metadata?appId={appId}&spaceId={spaceId}
Authorization: Bearer <CAS capability>

Returns immutable metadata and mutable state. Unknown nodes return 404.

#11.3 Lease or obtain upload instructions

POST /v1/cas/nodes/{sha256}/lease?appId={appId}&spaceId={spaceId}
Authorization: Bearer <CAS capability>
Content-Type: application/json

{"leaseDurationMs":900000}

The client sends the complete canonical node bytes only to the returned R2 target, then repeats this exact lease request. The service validates the full SHA-256 digest, envelope, content type, ordered child hashes, and size before publication. The response state is one of ready, awaiting_upload, awaiting_replacement_upload, or validated_awaiting_children.

{
  "state": "ready",
  "hash": "...",
  "leaseStartedAt": 1787100000000,
  "leaseExpiresAt": 1787100900000
}

#11.4 Extend an existing lease

POST /v1/cas/nodes/{sha256}/lease?appId={appId}&spaceId={spaceId}
Authorization: Bearer <CAS capability>
Content-Type: application/json

{"leaseDurationMs":900000}

The same request extends a ready lease without reading or parsing canonical content. A missing node returns upload instructions rather than 404.

#11.5 Space usage and GC

GET  /v1/cas/usage?appId={appId}&spaceId={spaceId}
POST /v1/cas/gc?appId={appId}&spaceId={spaceId}
Authorization: Bearer <CAS capability>

Gateway exposure remains policy-owned. GC is advisory; triggering it does not guarantee that every eligible node is removed in one call.

App administrators can read the same six accounting dimensions aggregated across the App without a Space capability:

GET /admin/apps/{appId}/usage
Cookie: cas_admin_session=<HttpOnly OIDC-backed session>

The read is authorized by App membership and remains available while the App is suspended. Each usage-bearing Space owns one mutable D1 summary row that is updated transactionally with node, lease, storage-observation, and upload- reservation changes. The App query sums only those rows, so its work scales with Space count rather than node count and performs no R2 or internal Space requests. Identical hashes in different Spaces count independently.

Physical bytes and readiness reflect the latest completed service observation of each canonical R2 object. A bounded scheduled reconciler backfills legacy rows and revisits the oldest observations. Until every node in the requested App has a completed observation, the Admin read fails with 503 SERVICE_UNAVAILABLE instead of returning a partial physical total.

#11.6 Root Refs

Business services apply signed non-zero count deltas:

POST /v1/cas/root-refs?appId={appId}&spaceId={spaceId}
Authorization: Bearer <capability>
Content-Type: application/json

{
  "requestId": "session:sessionId:version:7:roots",
  "changes": {
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": -1,
    "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": 1
  }
}

CAS does not store logical owner identities. cas_nodes.root_ref_count is the authoritative aggregate. Every newly accepted update atomically changes the aggregate, allocates an App-global revision, appends one event containing the affected Space, updates the App/Space/hash projection, and stores the App/Space/requestId idempotency result. Both aggregate and current projection counts are non-negative; zero projection rows are omitted.

Logical owner assignment is not part of the CAS contract. The aggregate count and signed-delta API are the only live root lifecycle model.

There is no compatibility route or hidden namespace for Root Ref writes. Old histories remain read-only; unique old requests replay their original revision without applying another delta. New requests use the App-global stream after its explicit checkpoint. The offline cutover and post-write rollback limits are defined in the migration guide.

#11.7 App admin audit API

Root Ref audit reads are formal App-level admin contracts:

GET /admin/apps/{appId}/root-refs
GET /admin/apps/{appId}/root-ref-events
Cookie: cas_admin_session=<HttpOnly OIDC-backed session>

Both reads support an optional exact spaceId filter and include spaceId in every row/event. Revisions are monotonic per App across Spaces. All App members can use the MVP admin surface; finer-grained control-plane roles are deferred.

The admin BFF and UI are served by @unicas/service-cloudflare (src/admin-bff + admin-webui assets). The ordinary Space client cannot accept OIDC sessions or call admin routes.

#11.8 Canonical binary codec

The portable full-node representation in CAS Binary Format remains available to export/import tooling, offline verification, migration, and fixtures. CAS exposes no portable-node HTTP route or handler.

#12. SValue and SBlob

All document states, queries, operations, and query results use the restricted SValue model. Binary values are stored nodes represented by branded SBlob handles:

interface SBlob {
  readonly [privateSignature]: true;
  readonly hash: string;
}

type SValue =
  | string | number | boolean | null | SBlob
  | readonly SValue[]
  | { readonly [key: string]: SValue };

SValue version 1 is deterministic RFC 8949 CBOR with media type:

application/vnd.unidocs.svalue+cbor;version=1

SBlob is CBOR tag 65536 around exactly 32 hash bytes. Encoding walks values in deterministic order and returns both bytes and ordered direct child refs. Duplicate handles remain duplicate refs. Decoding uses strict limits, a bounded pre-allocation tokenizer, deterministic re-encoding, and byte equality.

For this reserved content type, CAS derives refs from tags and rejects caller-supplied metadata that differs. Doctypes never implement reference extractors.

#13. DocumentType integration

The runtime binds one authenticated SBlob context per document and curries it into the doctype factory:

interface DocumentTypeContext {
  makeSBlob(
    hash: string,
    loadData: () => Promise<SBlobData>,
  ): Promise<SBlob>;
  makeSBlob(data: SBlobData): Promise<SBlob>;
  readSBlob(blob: SBlob): Promise<SBlobData>;
}

type DocumentTypeFactory<TDoc, TQuery, TOp> =
  (context: DocumentTypeContext) => DocumentType<TDoc, TQuery, TOp>;

The hash-first overload leases an existing node without invoking loadData. For a missing node it calls the callback once, derives SValue refs when needed, leases distinct children, verifies the complete logical digest, and uploads.

readSBlob verifies metadata, content length, SValue refs, and digest. Reads and in-flight promises use a bounded context-scoped cache; returned bytes are copies. The doctype sees no Principal or Space identity, HTTP, lease, root-count, or CAS metadata API.

Core defines only Context -> DocumentType. A doctype that needs options owns an outer Options -> Factory function.

interface DocumentType<TDoc, TQuery, TOp> {
  init(): Promise<SValueType<TDoc>>;
  query(query: SValueType<TQuery>, doc: SValueType<TDoc>): Promise<SValue>;
  apply(
    operations: readonly SValueType<TOp>[],
    doc: SValueType<TDoc>,
  ): Promise<SValueType<TDoc>>;

  formats: Readonly<Record<string, DocumentFormat<TDoc>>>;
  defaultFormat: string;
}

Named formats perform external import/export only. TDoc itself is the snapshot shape. There is no snapshotFormat: the runtime always persists encodeSValue(TDoc) and always restores it with decodeSValue. Neither defaultFormat nor any DocumentFormat.save participates in snapshot I/O. Agent tools are a separate contract:

interface DocumentAgent {
  tools: Readonly<Record<string, AgentToolDefinition>>;
  instructions: string;
  toolCall(name: string, parameters: JsonValue): Promise<AgentToolResult>;
}

The context-bound handler converts JSON DTOs such as {hash} to typed domain operations containing SBlob. Provider-neutral result content may reference an SBlob internally; a provider renderer reads it and emits the model vendor's media representation. SBlob never appears in JSON or directly on the provider wire.

#14. Delta transaction flow

Each committed version stores a retained SValue delta root. Selected versions also store an independent retained TDoc snapshot root.

Apply is an outbox state machine:

  1. authenticate the calling service and resolve immutable session identity;
  2. settle an older pending version and check baseVersion;
  3. decode and validate already-canonical operations;
  4. store the operation batch as an SValue delta root;
  5. run doctype apply against immutable current state;
  6. optionally store the resulting TDoc as a snapshot root;
  7. write one local svalue_pending row containing hashes and canonical bytes;
  8. idempotently apply signed deltas that acquire the delta and optional snapshot Root Refs using a deterministic request ID;
  9. insert local committed rows and delete the pending row;
  10. publish the new in-memory state and return success.

If CAS times out, pending bytes allow recovery to re-ensure nodes. If local finalization fails after CAS success, recovery retries the same signed-delta request ID and finalizes the pending row on restart. There is no compensating-delete window.

Version 1 does not persist a TDoc head on every delta. Active SBlob leases protect the in-memory state between snapshots. Normal startup loads the latest standalone snapshot and replays retained delta roots. Default snapshot cadence is 20 deltas; the snapshot endpoint may retain the current version opportunistically.

#15. Snapshot and history lifecycle

  • Committing a retained delta acquires one Root Ref for its root hash.
  • Committing a retained snapshot acquires one independent Root Ref for its root hash.
  • Delta and snapshot roots may share descendants; redundant protection is intentional.
  • A restore is a retained delta containing {kind: "restore", doc: SBlob}; replay jumps to that standalone state instead of recording an empty operation.
  • A clone within the same (appId, spaceId) partition retains the source snapshot DAG. Cross-Space or cross-App clone requires an authorized recursive DAG copy into the destination partition.
  • Before future history truncation, the surviving boundary receives a standalone snapshot; removed delta/snapshot roots are aggregated into one idempotent negative Root Refs update.
  • Snapshot replacement uses one atomic update containing old -1 and new +1.

DOCX TDoc is a two-level OpenXML Merkle manifest:

interface DocxDoc {
  readonly kind: "openxml-package";
  readonly files: Readonly<Record<AbsoluteOpcPath, SBlob>>;
}

The doctype uses public Ariadng ZipReader, ZipWriter, and Document.open/save to convert between the manifest and a cached in-memory Document. Apply clones a working document and stores changed package leaves; unchanged hashes reuse nodes. ZIP paths, content types, CRC, entry count, compression ratio, part size, and total size are bounded and validated.

PSD follows the same separation. Its TDoc is PsdStoredDoc, an SValue tree of canvas/layer metadata and pixel SBlobs. PsdDoc, which contains resident or lazy pixel sources, is only a context-scoped editing/render cache. Import and apply externalize that model back to PsdStoredDoc; export materializes it and writes PSD bytes. PSD bytes and the doctype-local JSON IR are never snapshots.

#16. Query and transport values

Query results are SValue. Inline Uint8Array and the old base64 QueryValue escape layer are removed. Binary query state is an existing SBlob or a named format download.

Query/apply/history accept SValue CBOR. A temporary JSON adapter remains for values with no SBlob. Responses containing SBlob require an SValue Accept header and return 406 to JSON-only callers.

Implemented validation includes unit vectors, CAS/SDK tests, Markdown and DOCX restart recovery, rollback, same-partition clone behavior, native SValue image operations, JSON agent tool-hash conversion, provider-rendered multimodal SBlob results, and independent delta plus snapshot retention of shared image blobs.

Deferred operational work:

  • automatic GC scheduling and quotas;
  • legacy owner-table migration/drop verification and count-repair tooling;
  • production migration and reconciliation of legacy snapshot R2/root counts;
  • history truncation and document deletion APIs;
  • CBOR tag registration before version 1 production persistence.