Entities
One declaration expands into the whole record kit — versioned latest-wins state, fail-closed conflicts, soft delete with revival, and generated read queries.
Record-shaped data — the CRUD-like backbone of most apps — normally costs a hand-written
kit: observation kinds, a mapper, current-state rules, and read queries. An entity block
replaces that kit with one declaration. The expansion is deterministic at load time into
ordinary V1 artifacts; nothing downstream has entity-specific semantics.
entity customer {
key customer_id: text
field name: text
field plan: text index
}
What one block generates
- Observation kinds —
customer.created,customer.updated,customer.deleted. Payloads carry the key, an integerversion, and the full field set on writes. Appends go through your ordinary write policies — there is no second write path. - A generated mapper that owns those kinds exclusively (your app mappers never
receive them), emitting namespace-contained
source.customer.*atoms. - Current-state rules — event union → activity → max-aggregate current version →
customer.record(latest version wins), pluscustomer.tombstoned(soft delete; a higher-version write revives) and fail-closedcustomer.conflict. - Spectator read queries —
customer-get,customer-list, andcustomer-by-plan(one perindexmarker), exposed tostudio, api. Agent exposure stays hand-authored with decision coverage, deliberately.
Inspect the generated artifacts anytime:
tarski entity expand
Materialized expansions live under generated/entities/<entity>/, written only when
changed.
Conflicts fail closed
Entities use optimistic concurrency: writers own version monotonicity. If two divergent
writes arrive at the same version, the runtime does not pick a winner — customer.record
derives nothing for that key until a higher version resolves the conflict, and
customer.conflict makes the situation visible. Clients render the conflict and offer a
next-version write. The Team Tasks example is the reference implementation of this
pattern, UI included.
Overrides
Generated artifacts can be ceded to hand-written code explicitly:
entity customer {
key customer_id: text
field name: text
omit query customer-list
}
Collisions between generated and hand-written names are load errors naming both
provenances — nothing is silently shadowed. Typed E_ENTITY_* diagnostics come from a
block scanner that runs before the contract parser.
V1 boundaries
- Field types are
text,int,float, andbool. - Updates carry the full field set; field-level patch is a compatible future extension.
*.deletedis domain evidence (soft delete); byte erasure remains the job of privacy-retention tombstones.- Modules and plugin packs do not carry entity blocks in V1.