Three-layer API: entities, fields, assertions#

The write and read surface of a FactGraph (alias of SDKStore) is divided into three navigation layers, each exposed as a read-only namespace on the graph instance fg.

Each namespace is selected by a fixed navigation key. Passing the wrong kind of key raises SDKStoreError with a message that names the correct layer (citing ADR-API §4.1.1). The layers are:

NamespaceLayerNavigation keyProperty
fg.entities1EntityClass + identity kwargs, or a managed e_refentities
fg.fields2Field descriptor + e_ref (+ optional value)fields
fg.assertions3asrt_id stringassertions

All three namespaces reject attribute assignment: fg.entities.x = ..., fg.fields.x = ..., and fg.assertions.x = ... each raise FrozenSnapshotError.

This page describes the namespace methods, the value types they return (EntitySnapshot, AssertionRecord, AssertionRecordSet, AssertionView, AssertionMeta), and the transaction surfaces fg.entities.edit (EntityEditor) and fg.batch. For schema-side definition of Entity, Field, and Identity descriptors see schema.md. For the error catalogue see errors.md. The legacy accept / CandidateSet / Inference flow is not part of this surface and is not covered here.

Layer 1: fg.entities#

Entity-macro operations. The first positional argument must be an Entity subclass (for get, where, match, ref, create, exists, edit) or, for delete, either an Entity subclass or a managed e_ref string. When the first argument is a str, a Field, or any other type where an Entity subclass is required, SDKStoreError is raised pointing to fg.assertions.* (for str/asrt_id) or fg.fields.* (for Field).

fg.entities.get(entity_cls, **identity_kwargs)#

Read one entity snapshot by identity values.

  • Parameters: entity_cls: type[Entity]; **identity_kwargs: Any (the identity field values).
  • Returns: EntitySnapshot | None. Returns None when no entity with the given identity is visible.
  • Raises: SDKStoreError when entity_cls is not an Entity subclass.
snap = fg.entities.get(User, user_id="alice", tenant_id="acme")
snap.user_id   # "alice"
snap.name      # "Alice"

fg.entities.where(entity_cls, *, policy=_POLICY_TOMBSTONE, limit=None, _meta=None, **field_filters)#

Find entity snapshots by exact field filters.

  • Parameters:
    • entity_cls: type[Entity].
    • policy (keyword only): a removed parameter. Passing any value other than the internal default raises SDKStoreError.
    • limit: int | None = None (keyword only): maximum number of snapshots to return.
    • _meta: dict[str, Any] | None = None (keyword only): accepted in the signature but entity-query metadata projection is not implemented; a non-empty dict raises SDKStoreError directing you to fg.assertions.where(_meta=...) or snapshot.assertions.where(_meta=...).
    • **field_filters: Any: exact-match field-value filters.
  • Returns: an iterable of EntitySnapshot.
  • Raises: SDKStoreError when entity_cls is not an Entity subclass; when a flat meta kwarg (source=, trace_id=, version=, meta=) is passed (use _meta={...}); when _meta is not a dict; or when a non-empty _meta is supplied.
results = list(fg.entities.where(User, status="active"))

fg.entities.match(entity_cls, template, *, limit=None, **port_constraints)#

Match visible snapshots against an application Rule or AND RuleExpr template. See rules.md and evaluation.md.

  • Parameters: entity_cls: type[Entity]; template: Any (rule or rule expression); limit: int | None = None (keyword only); **port_constraints: Any.
  • Raises: SDKStoreError when entity_cls is not an Entity subclass.

fg.entities.ref(entity_cls, **identity_values)#

Return a managed e_ref (an idref_v1 token string) for the entity identified by the supplied identity kwargs, recording the identity into the store so later fg.fields.set / fg.fields.add can resolve the e_ref. Does not write Claims to the ledger.

  • Parameters: entity_cls: type[Entity]; **identity_values: Any.
  • Returns: str (the deterministic e_ref).
  • Raises: SDKStoreError when entity_cls is not an Entity subclass, or when the identity bundle is incomplete (missing identity field: <EntityType>.<name>).
e_ref = fg.entities.ref(User, user_id="alice", tenant_id="acme")

fg.entities.create(entity_cls, *, meta=None, **identity)#

Emit the Identity Claims and the <EntityType>:exists Claim atomically.

  • Parameters: entity_cls: type[Entity]; meta: dict[str, Any] | None = None (keyword only); **identity: Any (the complete identity bundle).
  • Returns: str (the deterministic e_ref).
  • Raises: EntityAlreadyExistsError (code ENTITY_ALREADY_EXISTS) when an entity with the supplied identity is already visible in the active set; SDKStoreError when entity_cls is not an Entity subclass or the identity bundle is incomplete.
e_ref = fg.entities.create(User, user_id="alice", tenant_id="acme")

fg.entities.delete(e_ref_or_cls, *, meta=None, **identity)#

Whole-entity revoke: atomically retracts all Active Identity Claims and Field Claims under the entity, plus any <EntityType>:exists Claim. This is the only path that may retract Identity and :exists Claims. The first argument has two accepted forms; a tuple or any other shape is rejected.

  • Form A: fg.entities.delete(e_ref: str, *, meta=None). Pass a managed e_ref string. Identity kwargs are not accepted in this form.
  • Form B: fg.entities.delete(EntityClass, *, meta=None, **identity). Pass the entity class and the full identity bundle.
  • Returns: int (the number of Active Claims retracted).
  • Raises: SDKStoreError when the input shape matches neither form, when a Form A e_ref is not managed (UNRESOLVABLE_E_REF), or when a Form B identity bundle is incomplete; EntityNotFoundError (code ENTITY_NOT_FOUND) when the target entity is not visible.
count = fg.entities.delete(e_ref)
count = fg.entities.delete(User, user_id="alice", tenant_id="acme")

fg.entities.exists(entity_cls, **identity)#

Return whether the entity is visible via active Identity Claims.

  • Parameters: entity_cls: type[Entity]; **identity: Any.
  • Returns: bool.
  • Raises: SDKStoreError when entity_cls is not an Entity subclass.

fg.entities.edit(entity_cls, **identity_kwargs)#

Open an EntityEditor for an existing entity. The entity must already be materialized.

  • Parameters: entity_cls: type[Entity]; **identity_kwargs: Any.
  • Returns: EntityEditor (see below).
  • Raises: SDKStoreError when entity_cls is not an Entity subclass, or when called on an attached (database-backed) runtime.

Layer 2: fg.fields#

Per-cell mutations and reads. The first positional argument must be a Field descriptor; retract, delete, and get also accept an Identity descriptor for reads and history. Passing a str, an Entity subclass, or any other type raises SDKStoreError pointing to fg.assertions.* (Layer 3) or fg.entities.* (Layer 1). set and add reject Identity descriptors entirely, because Identity Claims are immutable.

set, add, and retract reject calls on an attached (database-backed) writable runtime with SDKStoreError.

fg.fields.set(field, e_ref, value, *, meta=None)#

Write a single-cardinality Field value.

  • Parameters: field: Field; e_ref: str; value: Any; meta: dict[str, Any] | None = None (keyword only).
  • Returns: str (the new assertion id).
  • Raises: SDKStoreError when field is not a Field descriptor or when e_ref is not managed (UNRESOLVABLE_E_REF); CardinalityError (code FIELD_CARDINALITY_MISMATCH) when the field is not single-cardinality; SDKValueError (code FIELD_VALUE_VALIDATION_FAILED) when the value fails field validation; EntityNotFoundError (code ENTITY_NOT_FOUND) when the target entity is not visible.
asrt_id = fg.fields.set(User.name, e_ref, "Alice", meta={"source": "seed"})

fg.fields.add(field, e_ref, value, *, meta=None)#

Append a multi-cardinality Field value.

  • Parameters: field: Field; e_ref: str; value: Any; meta: dict[str, Any] | None = None (keyword only).
  • Returns: str (the new assertion id).
  • Raises: as for set; CardinalityError is raised when the field is not multi-cardinality.
asrt_id = fg.fields.add(User.tags, e_ref, "vip")

fg.fields.retract(field, e_ref, value, *, meta=None)#

Retract the unique active assertion matching (field, e_ref, value). Accepts a Field or Identity descriptor.

  • Parameters: field: Field | Identity; e_ref: str; value: Any; meta: dict[str, Any] | None = None (keyword only).
  • Returns: str | None (the revoker assertion id from the underlying retract).
  • Raises: SDKStoreError when no active assertion matches (no active assertion matching field=...) or when more than one matches (ambiguous active assertions matching field=..., directing you to pass asrt_id to fg.assertions.retract). Retraction proceeds through fg.assertions.retract, so its guard errors apply.

fg.fields.delete(field, e_ref, *, meta=None)#

Retract all active assertions for (field, e_ref). Accepts a Field or Identity descriptor. Partial-failure behaviour is fail-fast on the first error.

  • Parameters: field: Field | Identity; e_ref: str; meta: dict[str, Any] | None = None (keyword only).
  • Returns: int (the number of assertions retracted).
  • Raises: the guard errors from fg.assertions.retract on the first failing claim.

fg.fields.get(field, e_ref)#

Return the current value for (field, e_ref) from active claims.

  • Parameters: field: Field; e_ref: str.
  • Returns: for a single-cardinality field, the last active value or None when none; for a multi-cardinality field, a tuple of the active values in canonical order.

Layer 3: fg.assertions#

Per-write introspection and retraction by assertion id.

fg.assertions.by_id(asrt_id)#

  • Parameters: asrt_id: str.
  • Returns: an AssertionRecord, or None when the id is unknown.
  • Raises: SDKStoreError when asrt_id is not a non-empty string.

fg.assertions.by_ids(asrt_ids, *, strict=False)#

  • Parameters: asrt_ids: Iterable[str]; strict: bool = False (keyword only).
  • Returns: an AssertionRecordSet of the found records, sorted by id and de-duplicated.
  • Raises: SDKStoreError when asrt_ids is a str/bytes, when it is not an iterable of non-empty strings, when strict is not a bool, or when strict=True and the input contains a duplicate id or an id that is not found.

fg.assertions.active#

Property. Returns an AssertionRecordSet of all records whose is_active is true.

fg.assertions.all#

Property. Returns an AssertionRecordSet of all assertion records (active and revoked), in canonical claim order.

{record.asrt_id for record in fg.assertions.active}
fg.assertions.all.by_id(name_asrt).one().is_active   # False after retract

fg.assertions.field(field)#

  • Parameters: field: Field.
  • Returns: an AssertionView scoped to that field, carrying its active and full history records.
  • Raises: SDKStoreError when field is not an sdk.Field descriptor (string names are rejected as ambiguous).

fg.assertions.where(*, field=..., e_ref=..., value=..., value_tag=..., _meta=...)#

Filter active assertion records by canonical Layer 3 criteria. All parameters are keyword only and each defaults to an internal sentinel meaning “not filtered”.

  • Parameters:
    • field: an sdk.Field descriptor.
    • e_ref: an e_ref string.
    • value: an exact value match.
    • value_tag: a value-tag string.
    • _meta: a dict of metadata key/value pairs that must all match.
  • Returns: an AssertionRecordSet of active records.
  • Raises: SDKStoreError when field is not a Field descriptor, when e_ref is not a string, when value_tag is not a string, or when _meta is not a dict.
records = fg.assertions.where(
    field=User.tags,
    e_ref=e_ref,
    value="vip",
    value_tag="string",
    _meta={"source": "tagger"},
)

fg.assertions.retract(asrt_id, *, meta=None, **kwargs)#

Retract one assertion by id.

  • Parameters: asrt_id: str; meta: dict[str, Any] | None = None (keyword only); **kwargs (rejected).
  • Returns: str | None (the revoker assertion id).
  • Raises: SDKStoreError when extra **kwargs are passed (directing entity-level delete to fg.entities.delete), when asrt_id is not a non-empty string, when the target is an Identity Claim (immutable per INV-7c; carries the guard’s code), when the target is a <EntityType>:exists Claim (must be removed via fg.entities.delete), or when the id is unknown (ASSERTION_NOT_FOUND). Also raises SDKStoreError when called on an attached writable runtime.

Returned types#

EntitySnapshot#

A read-only view of one entity. Returned by fg.entities.get and yielded by fg.entities.where. Assignment raises FrozenSnapshotError.

Attributes and access:

  • ref: str: the entity’s e_ref.
  • entity_type: str: the entity type name.
  • identity_available: bool: whether identity kwargs are known.
  • identity (property): dict[str, Any] of the known identity kwargs (a copy).
  • assertions: an AssertionView over the entity’s field assertions (entity scope).
  • Attribute access by field or identity name (snap.name, snap.user_id) returns the current value; an unknown name raises AttributeError.
  • field(name: str) -> AssertionView: the per-field AssertionView, equivalent to snap.assertions.field(name).

AssertionRecord#

A frozen dataclass describing one assertion (one ledger Claim).

FieldType
asrt_idstr
valueAny
value_tagstr
is_activebool
entity_typestr
field_namestr
pred_idstr
e_refstr
metaAssertionMeta

AssertionRecordSet#

A tuple subclass of AssertionRecord. Indexing returns an AssertionRecord; slicing returns an AssertionRecordSet. +, radd, and * with a tuple / int return an AssertionRecordSet. Calling the set (records()) returns the same set.

Methods:

  • where(*, value=..., value_tag=..., _meta=...) -> AssertionRecordSet: filter the set. Raises SDKStoreError when value_tag is not a string or _meta is not a dict.
  • at(t: str) -> AssertionRecordSet: records visible at ISO-8601 time t. Raises SDKStoreError when t is not valid ISO-8601 text.
  • by_id(asrt_id: str) -> AssertionRecordSet: records matching asrt_id. Raises SDKStoreError when asrt_id is not a non-empty string.
  • one() -> AssertionRecord: the single record. Raises SDKStoreError when the set does not contain exactly one record.
  • all() -> tuple[AssertionRecord, ...]: the records as a plain tuple.
  • first() -> AssertionRecord | None: the first record, or None when empty.

AssertionView#

A read-only view over assertions, either entity-scoped (a map of field views) or field-scoped. Construction is internal; instances come from snapshot.assertions, snapshot.assertions.field(name), and fg.assertions.field(field). Assignment raises FrozenSnapshotError.

Properties:

  • is_entity_scope: bool: true when the view is entity-scoped (no single field bound).
  • active: AssertionRecordSet: active records (across all fields when entity-scoped).
  • all: AssertionRecordSet: full history records (active and revoked).
  • history: AssertionRecordSet: alias of all (deprecated; emits a DeprecationWarning only when FACTGRAPH_WARN_DEPRECATED=1).

Methods:

  • field(field) -> AssertionView: the field-scoped view for field (a field name string or an sdk.Field descriptor). Raises SDKStoreError when the field is not present in the view, when a Field from a different entity type is passed, or when the argument is neither a name string nor an sdk.Field.
  • by_id(asrt_id) -> AssertionRecord | None: the record with asrt_id, or None. Raises SDKStoreError when asrt_id is not a non-empty string.
  • by_ids(asrt_ids, *, strict=False) -> AssertionRecordSet: same validation and strict semantics as fg.assertions.by_ids.
  • where(*, field=..., e_ref=..., value=..., value_tag=..., _meta=...) -> AssertionRecordSet: filter active records. Raises SDKStoreError when e_ref, value_tag, or _meta have the wrong type.
  • at(t: str) -> AssertionRecordSet: active records visible at ISO-8601 time t.
  • Attribute access by field name returns that field’s nested AssertionView; an unknown name raises AttributeError.

AssertionMeta#

A frozen dataclass holding the parsed metadata of one assertion, reachable as record.meta.

FieldType
sourcestr | None
trace_idstr | None
ingested_atdatetime | None
approved_bystr | None
notestr | None
derived_rule_idstr | None
derived_rule_versionstr | None
candidate_idstr | None
candidate_keystr | None
candidate_kindstr | None
rawdict[str, Any]

AssertionMeta.from_raw(raw: dict[str, Any]) -> AssertionMeta (classmethod) builds an instance from a raw metadata dict: it extracts the typed fields where present and stores a copy of the original dict in raw. ingested_at is parsed from an integer nanosecond timestamp into a UTC datetime; derived_rule_id / derived_rule_version also accept the derivation_id / derivation_version keys.

Transaction surfaces#

EntityEditor#

Returned by fg.entities.edit(...). Stages field mutations against one existing entity and commits them as a single batch. The editor backs a single-handle fg.batch transaction internally.

Attributes: ref: str (the entity e_ref); entity_type: str.

Field and identity access: attribute access by field name returns a FieldEditor for a Field descriptor, an IdentityEditor for an Identity descriptor, or the staged identity value when the name is an identity key. Access after the editor is closed raises EditorClosedError.

Methods:

  • preview() -> BatchPlan: the staged plan without applying it.
  • commit(*, meta=None) -> BatchCommitResult: apply the staged mutations and close the editor.
  • rollback() -> None: close the editor without applying.
  • Context-manager use: __enter__ returns the editor; __exit__ calls rollback() if an exception propagated, otherwise commit().
  • All of preview, commit, and rollback raise EditorClosedError once the editor is closed.
editor = fg.entities.edit(User, user_id="u1", tenant_id="t1")
editor.name.set("Bob")
editor.commit()

FieldEditor#

A staged-mutation handle for one field of an EntityEditor. Each method returns the owning EntityEditor for chaining.

  • set(value, *, meta=None) -> EntityEditor: stage a single-cardinality write. Raises CardinalityError (operation="set") when the field is not single-cardinality.
  • add(value, *, meta=None) -> EntityEditor: stage a multi-cardinality append. Raises CardinalityError (operation="add") when the field is not multi-cardinality.
  • retract(*, asrt_id, meta=None) -> EntityEditor: stage a retraction of asrt_id.
  • Each method raises EditorClosedError when the editor is closed.

IdentityEditor#

A read/guard handle for an identity field of an EntityEditor. value (property) returns the staged identity value; the handle supports repr, str, bool, and == against the value. set, add, and retract all raise SDKStoreError (code INV_7C_IDENTITY_PROTECTED) because Identity is immutable.

fg.batch(*, meta=None)#

Open a batch transaction (SDKBatchTx) for staging writes across one or more entities and committing them together.

  • Parameters: meta: dict[str, Any] | None = None (keyword only).
  • Returns: SDKBatchTx.
  • Raises: SDKStoreError when called on an attached writable runtime.

SDKBatchTx supports use as a context manager (with fg.batch() as tx:). Its public methods are:

  • entity(entity_cls, *, meta=None, **identity_values) -> ManagedEntityHandle: stage (or fetch the existing) handle for an entity. Raises SDKStoreError when entity_cls is not an Entity subclass or the identity is incomplete.
  • save(obj, *, include_deps=True, meta=None) -> BatchCommitResult: commit a single handle (and its dependencies).
  • preview(*, objects=None, include_deps=True, commit_meta=None) -> BatchPlan: the staged plan without applying it. When objects is None all staged handles are selected.
  • commit(*, objects=None, include_deps=True, commit_meta=None) -> BatchCommitResult: preview and apply.

A ManagedEntityHandle exposes handle_id, e_ref, identity_values, path, and bind(**identity_values). Attribute access by field name returns a ManagedFieldHandle whose set(value, *, meta=None) / add(value, *, meta=None) / retract(assertion_id, *, meta=None) stage operations and return the owning handle. A set on a multi-cardinality field, or an add on a single-cardinality field, raises SDKStoreError. Access to an Identity descriptor returns a guard whose set / add / retract raise SDKStoreError (identity fields are immutable; bind identity through tx.entity(...)).

commit returns a BatchCommitResult with plan: BatchPlan and apply_result: BatchApplyResult; apply_result carries refs_by_handle_id: dict[int, str] and assertion_ids: list[str].

with fg.batch() as tx:
    country = tx.entity(Country, code="DE")
    country.name.set("Germany")
    user = tx.entity(User, user_id="u-2", locale="zh")
    user.lives_in.set(country)
    tx.commit(objects=[user])
comments powered by Disqus