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:
| Namespace | Layer | Navigation key | Property |
|---|---|---|---|
fg.entities | 1 | EntityClass + identity kwargs, or a managed e_ref | entities |
fg.fields | 2 | Field descriptor + e_ref (+ optional value) | fields |
fg.assertions | 3 | asrt_id string | assertions |
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. ReturnsNonewhen no entity with the given identity is visible. - Raises:
SDKStoreErrorwhenentity_clsis not anEntitysubclass.
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 raisesSDKStoreError.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 raisesSDKStoreErrordirecting you tofg.assertions.where(_meta=...)orsnapshot.assertions.where(_meta=...).**field_filters: Any: exact-match field-value filters.
- Returns: an iterable of
EntitySnapshot. - Raises:
SDKStoreErrorwhenentity_clsis not anEntitysubclass; when a flat meta kwarg (source=,trace_id=,version=,meta=) is passed (use_meta={...}); when_metais not a dict; or when a non-empty_metais 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:
SDKStoreErrorwhenentity_clsis not anEntitysubclass.
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 deterministice_ref). - Raises:
SDKStoreErrorwhenentity_clsis not anEntitysubclass, 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 deterministice_ref). - Raises:
EntityAlreadyExistsError(codeENTITY_ALREADY_EXISTS) when an entity with the supplied identity is already visible in the active set;SDKStoreErrorwhenentity_clsis not anEntitysubclass 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 managede_refstring. 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:
SDKStoreErrorwhen the input shape matches neither form, when a Form Ae_refis not managed (UNRESOLVABLE_E_REF), or when a Form B identity bundle is incomplete;EntityNotFoundError(codeENTITY_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:
SDKStoreErrorwhenentity_clsis not anEntitysubclass.
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:
SDKStoreErrorwhenentity_clsis not anEntitysubclass, 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:
SDKStoreErrorwhenfieldis not aFielddescriptor or whene_refis not managed (UNRESOLVABLE_E_REF);CardinalityError(codeFIELD_CARDINALITY_MISMATCH) when the field is not single-cardinality;SDKValueError(codeFIELD_VALUE_VALIDATION_FAILED) when the value fails field validation;EntityNotFoundError(codeENTITY_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;CardinalityErroris 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:
SDKStoreErrorwhen no active assertion matches (no active assertion matching field=...) or when more than one matches (ambiguous active assertions matching field=..., directing you to passasrt_idtofg.assertions.retract). Retraction proceeds throughfg.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.retracton 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
Nonewhen none; for a multi-cardinality field, atupleof 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, orNonewhen the id is unknown. - Raises:
SDKStoreErrorwhenasrt_idis 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
AssertionRecordSetof the found records, sorted by id and de-duplicated. - Raises:
SDKStoreErrorwhenasrt_idsis astr/bytes, when it is not an iterable of non-empty strings, whenstrictis not abool, or whenstrict=Trueand 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 retractfg.assertions.field(field)#
- Parameters:
field: Field. - Returns: an
AssertionViewscoped to that field, carrying its active and full history records. - Raises:
SDKStoreErrorwhenfieldis not ansdk.Fielddescriptor (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: ansdk.Fielddescriptor.e_ref: ane_refstring.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
AssertionRecordSetof active records. - Raises:
SDKStoreErrorwhenfieldis not aFielddescriptor, whene_refis not a string, whenvalue_tagis not a string, or when_metais 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:
SDKStoreErrorwhen extra**kwargsare passed (directing entity-level delete tofg.entities.delete), whenasrt_idis not a non-empty string, when the target is an Identity Claim (immutable per INV-7c; carries the guard’scode), when the target is a<EntityType>:existsClaim (must be removed viafg.entities.delete), or when the id is unknown (ASSERTION_NOT_FOUND). Also raisesSDKStoreErrorwhen 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’se_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: anAssertionViewover 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 raisesAttributeError. field(name: str) -> AssertionView: the per-fieldAssertionView, equivalent tosnap.assertions.field(name).
AssertionRecord#
A frozen dataclass describing one assertion (one ledger Claim).
| Field | Type |
|---|---|
asrt_id | str |
value | Any |
value_tag | str |
is_active | bool |
entity_type | str |
field_name | str |
pred_id | str |
e_ref | str |
meta | AssertionMeta |
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. RaisesSDKStoreErrorwhenvalue_tagis not a string or_metais not a dict.at(t: str) -> AssertionRecordSet: records visible at ISO-8601 timet. RaisesSDKStoreErrorwhentis not valid ISO-8601 text.by_id(asrt_id: str) -> AssertionRecordSet: records matchingasrt_id. RaisesSDKStoreErrorwhenasrt_idis not a non-empty string.one() -> AssertionRecord: the single record. RaisesSDKStoreErrorwhen the set does not contain exactly one record.all() -> tuple[AssertionRecord, ...]: the records as a plain tuple.first() -> AssertionRecord | None: the first record, orNonewhen 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 ofall(deprecated; emits aDeprecationWarningonly whenFACTGRAPH_WARN_DEPRECATED=1).
Methods:
field(field) -> AssertionView: the field-scoped view forfield(a field name string or ansdk.Fielddescriptor). RaisesSDKStoreErrorwhen the field is not present in the view, when aFieldfrom a different entity type is passed, or when the argument is neither a name string nor ansdk.Field.by_id(asrt_id) -> AssertionRecord | None: the record withasrt_id, orNone. RaisesSDKStoreErrorwhenasrt_idis not a non-empty string.by_ids(asrt_ids, *, strict=False) -> AssertionRecordSet: same validation andstrictsemantics asfg.assertions.by_ids.where(*, field=..., e_ref=..., value=..., value_tag=..., _meta=...) -> AssertionRecordSet: filter active records. RaisesSDKStoreErrorwhene_ref,value_tag, or_metahave the wrong type.at(t: str) -> AssertionRecordSet: active records visible at ISO-8601 timet.- Attribute access by field name returns that field’s nested
AssertionView; an unknown name raisesAttributeError.
AssertionMeta#
A frozen dataclass holding the parsed metadata of one assertion, reachable as record.meta.
| Field | Type |
|---|---|
source | str | None |
trace_id | str | None |
ingested_at | datetime | None |
approved_by | str | None |
note | str | None |
derived_rule_id | str | None |
derived_rule_version | str | None |
candidate_id | str | None |
candidate_key | str | None |
candidate_kind | str | None |
raw | dict[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__callsrollback()if an exception propagated, otherwisecommit(). - All of
preview,commit, androllbackraiseEditorClosedErroronce 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. RaisesCardinalityError(operation="set") when the field is not single-cardinality.add(value, *, meta=None) -> EntityEditor: stage a multi-cardinality append. RaisesCardinalityError(operation="add") when the field is not multi-cardinality.retract(*, asrt_id, meta=None) -> EntityEditor: stage a retraction ofasrt_id.- Each method raises
EditorClosedErrorwhen 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:
SDKStoreErrorwhen 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. RaisesSDKStoreErrorwhenentity_clsis not anEntitysubclass 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. WhenobjectsisNoneall 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])