tangl.core artifacts

Immutable and semi-structured artifacts used for recording and template-based construction.

Related design docs

Related notes

Artifacts

Immutable ordered records and append-only ordered registries.

This module defines Record as a frozen, content-addressed artifact and OrderedRegistry as an append-only Registry specialization with range slicing over a comparable sort-key space.

See also

tangl.core.bases

Record composition traits (HasContent, HasOrder).

tangl.core.registry

Base registry behavior, selector filtering, and mapping semantics.

class Record(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, **kwargs)[source]

Frozen ordered artifact with content identity and optional origin reference.

Why

Records capture immutable runtime facts that should compare by content and remain orderable for stream-like processing.

Key Features

  • Three identity layers: stable uid from Entity, content equality from HasContent, and sequence ordering from HasOrder.

  • Frozen + flexible schema: frozen=True with extra='allow' supports arbitrary payload fields in derived record families.

  • External origin dereference: origin_id stores a producer reference, and origin() resolves it through an explicitly supplied registry.

Notes

origin_id is not a registry-aware pointer. Dereference requires passing the correct lookup registry at call time.

Example

>>> r = Record(content="foo")
>>> r.get_hashable_content()
'foo'
>>> try:
...     r.content = "bar"
... except ValidationError as e:
...     print(e)
1 validation error ...
class OrderedRegistry(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>, markers=<factory>)[source]

Append-only ordered registry with sort-key range slicing.

Why

Ordered registries provide deterministic range queries over members with sort_key() support while keeping the core primitive independent from higher-level stream marker/bookmark policies.

Key Features

  • append-only mutation model via append()/extend();

  • generic key accessors min_key() / max_key();

  • half-open range queries through get_slice() with optional selector composition.

Notes

Named bookmarks/sections are intentionally out of scope for this core type and should be layered above it (for example in VM/story stream services). Core keeps append/slice only; bookmark channels and destructive undo truncation are runtime-policy concerns.

class Snapshot(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, payload, admission_scope=None, **kwargs)[source]

Persistence convenience: a template that recreates an entity exactly.

A Snapshot is not part of the authoring loop. It is a persistence helper that reuses the template/materialization machinery to recreate a live entity with the same identifier and state.

  • materialize() preserves uid and rejects updates.

  • decompile() is not typically meaningful for snapshots.

Example

>>> e = Entity(label='abc')
>>> s = Snapshot.from_entity(e)
>>> ee = s.materialize()
>>> e is not ee and e == ee  # preserves uid
True
class BaseFragment(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, fragment_type='content', content=None, **kwargs)[source]

Record-based fragment envelope for journal/content payloads.

class EntityTemplate(payload: Entity)[source]

Template wrapper around an entity payload.

Why

EntityTemplate separates authoring-time prototypes from runtime entities. A template can be compiled, searched, stored, and materialized repeatedly without becoming part of the live graph itself.

Key Features

  • Supports authoring-loop transforms through compile and decompile.

  • Supports runtime instantiation through materialize().

  • Distinguishes template-kind matching from payload-kind matching so callers can query wrapper shape and produced entity kind independently.

  • Can participate in template registries and hierarchical groups used by provisioning and materialization.

API

  • has_template_kind() checks the wrapper record type.

  • has_payload_kind() checks the materialized entity kind.

  • has_kind() matches either axis as a convenience.

  • materialize() creates a live entity from the stored payload.

Example

>>> class PseudoEntity(Entity): ...
>>> data = {'label': 'abc'}
>>> templ = EntityTemplate.from_data(data, default_kind=PseudoEntity)
>>> templ.has_template_kind(EntityTemplate) and templ.has_payload_kind(PseudoEntity)
True
>>> templ.materialize()
<PseudoEntity:abc>
>>> class PseudoEntity2(PseudoEntity): ...
>>> templ.materialize(kind=PseudoEntity2, label="def")
<PseudoEntity2:def>
>>> templ.materialize().uid != templ.payload.uid  # fresh id by default
True
class TemplateRegistry(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>)[source]

Registry of templates with convenience materialization and authoring helpers.

TemplateRegistry is the primary container for authoring-loop operations:

  • compile(script) builds a flat registry from a list of script dicts.

  • decompile_all() emits a list of script dicts from top-level template groups.

Materialization helpers (materialize_one, materialize_all) provide a simple bridge into runtime, but they are not required for linting/compile/decompile.

Example

>>> tr = TemplateRegistry()
>>> tr.add(EntityTemplate.from_data({'label': 'abc'}))
>>> tr.add(EntityTemplate.from_data({'label': 'def'}))
>>> tr.materialize_one(Selector.from_identifier('abc'))
<Entity:abc>
>>> list(tr.materialize_all())
[<Entity:abc>, <Entity:def>]