Docs/Substrate/Data contracts

Data contracts

Personal data and runtime control state live in separate databases with separate owners and access paths.

Current schema contractLast updated July 17, 2026

The important boundary is not “two SQLite files.” It is that untrusted relational reads terminate at the personal data plane and cannot cross into credentials, schedules, approvals, or runtime internals.

Database boundary

DatabaseContainsConnection ownerApp access
.lamarck/data.dbD0 Events, D1 Documents, D2 TablesGuard serviceRead-only relational queries; authorized mutations through System API
.lamarck/system.dbConnector setup, schedules, runs, approvals, credential records, internal stateCore control-plane modulesNever readable
App ── system.query / mutate / writeDoc / writeEvent ──→ Guard ──→ data.db

Connector ── bound guard / state / auth capabilities ──→ Host
                                                        ├─→ data.db
                                                        └─→ system.db

App ─────────────────────────────── ✕ ─────────────────→ system.db

An App opens neither file. Guard's query engine owns the only data.db connection used by untrusted SQL, and that engine has no system.db handle or path.

D0 / D1 / D2 contract

D0 · Evidence

Append-only raw Event and audit history. Preserve what arrived and where it came from.

D1 · Narrative

Mutable Markdown current state with stable path identity and an editable working tree.

D2 · Model

Mutable SQLite Tables for structured current state, indexes, and derived read models.

The layers are not a promotion ladder where D0 is deleted after processing. Derived D2 data should remain traceable to inputs and rebuildable when interpretation changes.

D0 Events

CREATE TABLE events (
  id             TEXT PRIMARY KEY,
  schema_version TEXT NOT NULL,
  source         TEXT NOT NULL,
  type           TEXT NOT NULL,
  external_id    TEXT,
  started_at     INTEGER NOT NULL,
  ended_at       INTEGER,
  payload        JSON NOT NULL,
  created_at     INTEGER NOT NULL
);

CREATE UNIQUE INDEX idx_events_dedup
  ON events(source, external_id)
  WHERE external_id IS NOT NULL;
  • source is Host-injected runtime provenance, not caller input.
  • external_id is optional. When present, it is unique within a Source and makes capture retries idempotent.
  • started_at and ended_at describe the observed activity; created_at describes ingestion.
  • Database triggers reject UPDATE and DELETE on events.

Large text may be written to the content-addressed blob store and referenced from an Event payload. Queries return the raw reference; an App calls resolveContentRef only when it needs the full content.

D1 Documents

CREATE TABLE docs (
  id         TEXT PRIMARY KEY,
  content    TEXT NOT NULL DEFAULT '',
  metadata   JSON,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

A Document ID is an extensionless path such as research/weekly-review. Its materialized file is pages/research/weekly-review.md. Database-to-file and file-to-database synchronization are both part of the contract.

An App implicitly owns the Document prefix apps/<app-id>/. Additional exact IDs or prefixes are listed in permissions.writes.docs. Path validation prevents a grant from escaping the pages/ namespace.

D1 is current state. Replacing the content is expected; the related audit Event records that a mutation occurred.

D2 Tables

D2 consists of ordinary user-defined SQLite tables in data.db. Use normal relational design, indexes, foreign keys, and SQL queries.

-- structural change: explicit schema workflow
lamarck promote "
  CREATE TABLE sleep_summary (
    date TEXT PRIMARY KEY,
    deep_minutes INTEGER NOT NULL,
    source_event_id TEXT NOT NULL
  )
"

-- data read: System API or CLI
SELECT date, deep_minutes
FROM sleep_summary
ORDER BY date DESC;
  • system.query accepts relational SELECT/WITH; PRAGMA, ATTACH/DETACH, VACUUM, transaction control, and writes are rejected.
  • system.mutate accepts one INSERT, UPDATE, or DELETE on a Table declared in permissions.writes.tables.
  • system.transaction applies a serializable statement list atomically under the same grants.
  • CREATE, ALTER, and DROP are explicit structural actions through promote / demote, not incidental row writes.

Mutation and audit

Guard validates the active identity and permission ceiling, performs the durable mutation, and appends the corresponding D0 audit record. This keeps current state in D1/D2 while preserving the fact that it changed.

Audit does not turn operational noise into Timeline data. Connector retries, backfill warnings, readiness, and run status remain control-plane records in system.db.

Continue toSystem Modules