Skip to content

Core Concepts

An Entity is anything with identity. Entities have:

  • A unique ID
  • A type (defined by your domain)
  • Optional tenant scoping
  • Lifecycle states
  • Relationships to other entities

Key characteristic: Entities participate in economic activity. They accumulate Facts.

A Fact is an immutable record of something that happened. Facts have:

  • A timestamp (when it happened)
  • A type and subtype (what happened)
  • Links to entities involved
  • A reference to the Config version used (if applicable)
  • Data payload (event-specific details)

Key characteristic: Facts are append-only. They’re never modified or deleted.

A Config is a versioned setting that governs behavior. Configs have:

  • A type (what kind of setting)
  • A version (increments on change)
  • Effective/superseded timestamps
  • Settings payload

Key characteristic: When a Config changes, a new version is created. Facts reference the specific version used.

Each Entity with economic activity has a ledger—an append-only log of Facts. The ledger is the source of truth.

Entity: account_123
Ledger (Facts):
fact_001: deposit { amount: 100 }
fact_002: withdrawal { amount: 20 }
fact_003: deposit { amount: 50 }
Derived State:
Balance: 130 (computed from Facts)

Cached state is derived from Facts for performance. It’s:

  • Explicitly named (BudgetState, BalanceState, etc.)
  • Reconcilable against Facts
  • Disposable (can be rebuilt)
  • Never authoritative (Facts win on conflict)

Example:

class Account extends EntityLedger {
getBalance(): number {
// Try cache first
const cached = this.getCachedState<BalanceState>('BalanceState');
if (cached) return cached.balance;
// Rebuild from Facts
return this.recomputeBalance();
}
private recomputeBalance(): number {
const facts = this.getFacts({ type: ['deposit', 'withdrawal'] });
const balance = facts.reduce((sum, fact) => {
// Fold Facts into balance
}, 0);
// Cache result
this.setCachedState('BalanceState', { balance });
return balance;
}
}

z0 enforces tenant isolation at every layer:

  1. Authentication: tenant_id extracted from API key
  2. Data isolation: All queries filter by tenant_id
  3. Storage isolation: Per-tenant Durable Objects
  4. Audit trail: All actions create Facts

tenant_id comes from authentication, never from request parameters.

Entities use a generic schema with flexible index slots:

CREATE TABLE entities (
id TEXT PRIMARY KEY,
type TEXT,
data TEXT, -- Full JSON payload
-- Queryable fields (mapped via domain manifest)
ix_s_1 TEXT, -- String slot 1
ix_s_2 TEXT, -- String slot 2
ix_n_1 REAL, -- Number slot 1
...
);

Your domain manifest maps logical fields to slots:

{
entities: {
account: {
fields: {
status: { storage: 'ix_s_1' }, // status → ix_s_1
name: { storage: 'ix_s_2' }, // name → ix_s_2
balance: { storage: 'ix_n_1' } // balance → ix_n_1
}
}
}
}

This enables:

  • No schema migrations when adding new entity types
  • Multiple domains coexisting
  • Type-safe queries via domain-specific helpers

Facts are immutable. To correct a mistake:

// DON'T: Update the fact
await updateFact(factId, { amount: 60 }); // ❌ Violates immutability
// DO: Create a correction fact
await appendFact({
type: 'correction',
source_id: originalFactId,
data: {
previous_amount: 50,
corrected_amount: 60,
reason: 'Entry error'
}
});

Configs create new versions when changed:

// v1: Initial config
Config { id: 'cfg_pricing', version: 1, settings: { rate: 0.05 } }
// v2: Rate changed
Config { id: 'cfg_pricing', version: 2, settings: { rate: 0.06 } }
// Facts reference the version used
Fact {
type: 'charge',
config_id: 'cfg_pricing',
config_version: 1, // Used v1 rate (0.05)
data: { amount: 5.00 }
}

This enables:

  • Exact reproduction of historical calculations
  • Audit trail of config changes
  • No retroactive changes

Every economic Fact traces back through the system:

charge → outcome → invocation → config (pricing)
config (qualification)

Invariants ensure complete chains:

∀ Fact(charge) → ∃ Fact(outcome) WHERE charge.source_id = outcome.id
∀ Fact(charge) → config_id ≠ null AND config_version ≠ null

Errors are first-class:

try {
await processPayment(amount);
} catch (error) {
// Record error as Fact
await appendFact({
type: 'error',
subtype: 'payment_failed',
data: {
error_code: error.code,
error_message: error.message,
amount,
retry_attempt: 1
}
});
throw error;
}

This enables:

  • Economic impact of failures tracked
  • Error rates queryable
  • Audit trail of what went wrong