Ledger schema and views

Every table and view in the ledger with its columns and what it holds, plus the query shapes SQL steps take, generated from spec/ledger.sql

What you'll learn

You're about to write SQL against the ledger and need the views to read first, the tables behind them, and a query shape to start from.

  • the views a query reads first
  • every table and what its rows hold
  • the query shapes a sql step usually takes

The ledger is one SQLite file with 11 tables and 5 views, as spec/ledger.sql states them. Each object's own page has its columns and the DDL. Read-only SQL (sql/transform, sql/filter, {query:} values, gtme query) is written against these. Prefer current_values and group_membership; the raw tables are for provenance. A sql/* step's result must carry identity_id; :run_id is bound when referenced.

Views

The read surface. A sql/* step, a {query:} value, and gtme query are written against these first.

ObjectColumnsHolds
field_value_ranksid, identity_id, field, value, source, confidence, run_id, created_at, rankfield_values ranked per (identity, field): confidence DESC, newest first
current_fieldsidentity_id, field, value, source, confidence, run_id, created_atrank-1 field_values — the current value per (identity, field), value still JSON-encoded
group_membersgroup_id, identity_idcurrent membership as (group_id, identity_id)
current_valuesidentity_id, field, value, source, confidence, run_id, created_atcurrent_fields with value unwrapped from JSON — the view to query for plain values
group_membershipgroup_name, group_id, identity_idcurrent membership keyed by group_name — the view to query for membership

Tables

The rows the views rank and join. Read them for provenance.

ObjectColumnsHolds
identitiesid, entity_type, identity_key, created_atone row per person/company; identity_key per SPEC §4
field_valuesid, identity_id, field, value, source, confidence, run_id, referent, created_atappend-only facts: (identity_id, field, value JSON, source, confidence, run_id, created_at)
relationsfrom_id, relation, to_id, created_attyped edges between identities, such as works_at (person → company)
runsid, pipeline, config_json, started_at, finished_at, status, dry, pid, hostone row per gtme run; config_json is the resolved pipeline
run_recordsrun_id, identity_id, state, verdictsa run's membership: state = last completed step id, verdicts = {step_id: pass|fail}
step_eventsid, run_id, step_id, identity_id, event, detail, created_atper-record trail: claimed|done|failed|skipped_cache|dry_run|simulated, detail JSON
costsid, run_id, step_id, identity_id, provider, amount_usd, basis, detail, created_atspend per step/record; basis measured|estimated (ADR-046: measured only when read back from vendor-reported cost metadata; a rate multiplied out is estimated)
deliveriesid, identity_id, target, scope, idempotency, run_id, created_at, status, sent_at, variables_hashsends and handoffs, UNIQUE(target, scope, idempotency) — scope is the resolved idempotency_scope config value (ADR-044, '' = unscoped); status accepted|confirmed|contradicted|sent; variables_hash drives redeliver: on_change (ADR-045)
groupsid, name, note, created_at, entity_typenamed associations (ADR-021); entity_type is the members' type (ADR-054); character derived from events
group_eventsid, group_id, identity_id, event, detail, run_id, created_atappend-only added|removed|touched events per (group, identity)
payloadsid, identity_id, adapter, run_id, content_type, body, created_at, expires_atraw vendor responses, a purgeable cache tier (ADR-030) — never facts

Query shapes

The queries a sql/* step or a config value is usually one of, from gtme help --agent.

cross-type membership gate (sql/filter)

Keep people whose company is in a group — membership by ledger facts, via relations.

SELECT r.from_id AS identity_id
FROM relations r
JOIN group_membership gm ON gm.identity_id = r.to_id AND gm.group_name = 'target-accounts'
WHERE r.relation = 'works_at'

cross-record aggregate (sql/transform, company pipeline)

fan-in: count a company's known people into a field; declare provides: [acct.people_count].

SELECT r.to_id AS identity_id, count(*) AS "acct.people_count"
FROM relations r
WHERE r.relation = 'works_at'
GROUP BY r.to_id

per-record derivation (sql/transform)

Derive one field from another; declare provides: [sql.shout].

SELECT identity_id, upper(value) AS "sql.shout"
FROM current_values WHERE field = 'full_name'

Config value (one column → a list)

with: {domains: {query: ...}} — the list a source is parameterised by; zero rows fails plan.

SELECT DISTINCT v.value
FROM current_values v
JOIN group_membership gm ON gm.identity_id = v.identity_id AND gm.group_name = 'qualified'
WHERE v.field = 'company_domain'

Used in