ontolith.store¶
The StorageBackend port, plus the two shipped adapters.
ontolith.store ¶
Storage layer and backend abstractions.
DEFAULT_NAMESPACE
module-attribute
¶
The one namespace this project operates in today (KI-022).
Ontolith is still single-namespace throughout (ADR-0015) — Ontology
always writes to this namespace, and both backends seed a matching
namespace registry row for it at schema-creation time. Shared by
Ontology and both backends so that specific trio stays in sync by
construction; a handful of other unrelated "default" literals elsewhere
(e.g. REST/MCP route defaults, example scripts) are independent naming
choices, not instances of this constant, and aren't required to match it.
VECTOR_SCOPES
module-attribute
¶
Closed set of embedding scopes (SPEC §11.3): entity- and assertion-level.
Deliberately not an arbitrary caller-supplied string — both backends use
scope to name per-scope storage (SQLite: a vec0 virtual table per scope;
DuckDB: a plain table per scope), so an open string would mean dynamic DDL
driven by caller input. vector_upsert/vector_search MUST reject any scope
outside this set with ValidationError.
StorageBackend ¶
Bases: Protocol
Abstract port for storage adapters.
Concrete backends implement this protocol to provide: - Transaction management - Entity and assertion persistence - Query execution - Vector search (for hybrid retrieval)
The default implementation (M1) is SQLite + sqlite-vec. Alternative backends can be plugged in via this interface.
begin ¶
Begin a new transaction.
This port makes no promise about when a concurrent writer is
serialized against this one (at begin() versus at the first
conflicting statement) — that's a backend-specific locking detail,
not a cross-backend contract. See SQLiteBackend.begin()'s own
docstring (KI-084) for the default backend's specific choice and
why it matters for cross-process write safety.
Source code in src/ontolith/store/base.py
commit ¶
rollback ¶
transaction ¶
Context manager for atomic multi-write transactions.
Guarantees rollback on any exception. Prefer this over manual begin/commit/rollback to avoid wedged connections.
put_principal ¶
Persist a principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Principal
|
Principal to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_principal ¶
Retrieve a principal by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if found, None otherwise |
put_credential ¶
Persist a principal credential (hashed API-key token, ADR-0014).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential
|
PrincipalCredential
|
PrincipalCredential to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/base.py
get_principal_by_token_hash ¶
Resolve a principal via a credential's token hash.
Only unrevoked credentials resolve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_hash
|
str
|
SHA-256 hash of the raw bearer token |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if the hash matches an active credential, None otherwise |
Source code in src/ontolith/store/base.py
get_credential ¶
Retrieve a credential by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
PrincipalCredential | None
|
PrincipalCredential if found, None otherwise |
list_principals ¶
List all principals (KI-022).
Returns:
| Type | Description |
|---|---|
list[Principal]
|
All principals, most recently created first |
get_credentials_for_principal ¶
List all credentials (active and revoked) issued to a principal.
Never exposes the raw token — only credential metadata (id, created_at, revoked_at). Used to discover a credential ID to revoke.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal to list credentials for |
required |
Returns:
| Type | Description |
|---|---|
list[PrincipalCredential]
|
Credentials for this principal, most recently issued first |
Source code in src/ontolith/store/base.py
revoke_credential ¶
Mark a credential as revoked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential to revoke |
required |
revoked_at
|
datetime
|
Timestamp of revocation |
required |
revoked_by
|
str
|
Principal ID of the admin performing the revocation (KI-060) |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If the credential is not found |
Source code in src/ontolith/store/base.py
put_admin_event ¶
Persist an append-only admin-action event (KI-060, SPEC §17).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
AdminEvent
|
AdminEvent to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_admin_events ¶
Retrieve admin events, optionally filtered by actor or target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
actor
|
str | None
|
Filter to events performed by this principal ID |
None
|
target
|
str | None
|
Filter to events against this target |
None
|
Returns:
| Type | Description |
|---|---|
list[AdminEvent]
|
Matching events, oldest first |
Source code in src/ontolith/store/base.py
put_entity ¶
Persist an entity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity
|
Entity
|
Entity to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
put_assertion ¶
Persist an assertion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion
|
Assertion
|
Assertion to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_entity ¶
Retrieve an entity by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
Entity ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Entity | None
|
Entity if found, None otherwise |
get_entity_by_natural_key ¶
Retrieve an entity by its unique (namespace, concept, natural_key)
triple (KI-091) — the same uniqueness the entity table's own
UNIQUE(namespace, concept, natural_key) constraint enforces, used
to pre-check a conflict before put_entity rather than surfacing
one late as a redacted StorageError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to search within |
required |
concept
|
str
|
Concept name |
required |
natural_key
|
str
|
Natural key to look up |
required |
Returns:
| Type | Description |
|---|---|
Entity | None
|
Entity if one with this exact triple exists, None otherwise |
Source code in src/ontolith/store/base.py
get_assertion ¶
Retrieve a single assertion by ID, regardless of status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Assertion | None
|
Assertion if found, None otherwise |
Source code in src/ontolith/store/base.py
assertions ¶
assertions(
subject: str | None = None,
predicate: str | None = None,
status: str | None = "active",
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Assertion]
Query assertions with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str | None
|
Filter by subject entity ID |
None
|
predicate
|
str | None
|
Filter by predicate |
None
|
status
|
str | None
|
Filter by current status (ignored when as_of_time is set). Defaults to "active"; pass status=None for every status. |
'active'
|
as_of_time
|
datetime | None
|
If set, applies bitemporal filter: valid_from <= t < (valid_to or ∞) AND asserted_at <= t |
None
|
include_flagged
|
bool
|
When as_of_time is set, whether to include 'flagged' assertions (excluded by default); ignored when as_of_time is None (pass status=None there instead). |
False
|
include_history
|
bool
|
When as_of_time is set, whether to opt back into seeing a 'retracted' assertion once its own retraction event's timestamp is <= as_of_time (ADR-0049, KI-095; excluded by default) — mirrors include_flagged's shape (KI-098); ignored when as_of_time is None (pass status=None there instead). |
False
|
Returns:
| Type | Description |
|---|---|
list[Assertion]
|
List of matching assertions |
Source code in src/ontolith/store/base.py
set_assertion_status ¶
Update assertion status and optionally close validity window.
This is the ONLY allowed mutation on assertions (append-only invariant). Used for supersession and retraction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to update |
required |
status
|
str
|
New status (superseded, retracted, flagged) |
required |
valid_to
|
str | None
|
Optional validity end time (ISO format) |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
If update fails or assertion not found |
Source code in src/ontolith/store/base.py
put_assertion_event ¶
Persist an append-only assertion status-mutation event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
AssertionEvent
|
AssertionEvent to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_assertion_events ¶
Retrieve all status-mutation events for an assertion, oldest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion to retrieve events for |
required |
Returns:
| Type | Description |
|---|---|
list[AssertionEvent]
|
Events for this assertion, ordered by occurrence |
Source code in src/ontolith/store/base.py
get_assertion_events_by_successor ¶
Retrieve all 'superseded' events caused by a given successor assertion.
Recovers the full predecessor set for a supersession (KI-008): Assertion.supersedes only records the first predecessor when one incoming assertion supersedes several concurrently-overlapping ones, but every superseded predecessor gets its own event row here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
successor_id
|
str
|
Assertion ID that caused the supersession(s) |
required |
Returns:
| Type | Description |
|---|---|
list[AssertionEvent]
|
Events with this successor_id, ordered by occurrence. Each |
list[AssertionEvent]
|
event's assertion_id is one predecessor that was superseded. |
Source code in src/ontolith/store/base.py
list_namespaces ¶
List all registered namespaces (SPEC §12.2, KI-022).
Returns:
| Type | Description |
|---|---|
list[Namespace]
|
All namespaces, most recently created first |
put_schema ¶
Persist a schema version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
SchemaIR
|
Schema to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Note
Schema versions are never deleted (required for time-travel).
Source code in src/ontolith/store/base.py
get_schema ¶
Retrieve a schema version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
version
|
int | None
|
Specific version, or None for latest |
None
|
Returns:
| Type | Description |
|---|---|
SchemaIR | None
|
Schema if found, None otherwise |
Source code in src/ontolith/store/base.py
get_schema_at ¶
Retrieve the schema version effective at a point in time (KI-019).
Resolves the highest version whose applied_at <= at — i.e. the
schema that was current at time at, for bitemporal reconstruction
(SPEC §11.4: "Schema is resolved to the schema_version effective at
t"). put_schema already records applied_at via the backend's
injected Clock; this method is the first reader of that column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
at
|
datetime
|
Point in time to resolve the effective schema for |
required |
Returns:
| Type | Description |
|---|---|
SchemaIR | None
|
Schema effective at |
SchemaIR | None
|
by that time (including when the namespace has no schema at all, |
SchemaIR | None
|
or its first version postdates |
Source code in src/ontolith/store/base.py
entities ¶
entities(
namespace: str | None = None,
concept: str | None = None,
as_of_time: datetime | None = None,
) -> list[Entity]
Query entities with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str | None
|
Filter by namespace |
None
|
concept
|
str | None
|
Filter by concept |
None
|
as_of_time
|
datetime | None
|
If set, exclude entities created after this time |
None
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of matching entities |
Source code in src/ontolith/store/base.py
entities_where ¶
entities_where(
namespace: str,
concept: str,
predicate_filters: list[tuple[str, str, Any]],
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Entity]
Query entities matching all predicate filters in one SQL query.
Avoids the N+1 pattern of entities() + per-entity assertions() calls.
Each filter is (full_predicate, operator, value); ALL must match
(AND semantics) — a list, not a dict, since two different operators
can target the same predicate (e.g. an age range needs both a
"gte" and a "lt" filter). operator is one of:
"eq": equality. Matches either a literal property (value_lit) or a relation's target entity id (value_ref) — KI-030."contains": case-sensitive substring match againstvalue_litonly (KI-039) — relations have no defined substring semantics, so this operator never matches againstvalue_ref. Case-sensitive on both backends by construction: SQLite'sLIKEis case-insensitive by default and DuckDB's is not, soSQLiteBackendexplicitly setsPRAGMA case_sensitive_like = ONat connection time to make the two agree — a conformant third-party backend implementing this port must match that behavior, not SQLite's un-pragma'd default."gt"/"lt"/"gte"/"lte": numeric range againstvalue_litonly (KI-039) — a numeric cast (CAST/TRY_CAST, exact type backend-specific — see e.g.DuckDBBackend's docstring for whyDOUBLEnotREAL). Callers (in practice, onlyQueryBuilder) are responsible for restricting this to predicates declared numeric — see_RANGE_VALUE_TYPESinontolith.query.builder's docstring for why the backend itself doesn't validate that. A backend is NOT responsible for validating that already-storedvalue_litcontent actually parses as a number for a predicate declared numeric (KI-049) — implementations should fail safe (exclude the row) rather than raise for a value that doesn't parse, the wayTRY_CASTdoes; letting a raw conversion exception escape through this port violates SPEC §16's error taxonomy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
concept
|
str
|
Concept to filter by |
required |
predicate_filters
|
list[tuple[str, str, Any]]
|
List of |
required |
as_of_time
|
datetime | None
|
If set, applies bitemporal filter on assertions and entity creation |
None
|
include_flagged
|
bool
|
Whether to also match 'flagged' assertions (KI-081). Honored on both the current-state and the as_of_time path (excluded by default on both). On the as_of_time path this is point-in-time, not current status (KI-097): reconstructed from the assertion_event log the same way assertions() already does, so a query pinned to a time when an assertion was disputed correctly excludes it even after the dispute has since been resolved — and, the other direction, a time strictly before any dispute existed still includes an otherwise-undisputed value, even though the same assertion is flagged now. |
False
|
include_history
|
bool
|
Whether to also match 'superseded' and 'retracted' assertions (KI-081). On the current-state path this widens beyond 'active'. On the as_of_time path, 'superseded' is unaffected either way (its window already never restricts to 'active') — but 'retracted' does something under this flag now (ADR-0049, KI-095): the as_of_time branch additionally excludes a 'retracted' assertion once its own retraction event's timestamp is <= as_of_time; this parameter opts back out of that exclusion. |
False
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of entities where all filters match at the given time |
Source code in src/ontolith/store/base.py
entities_meeting_confidence ¶
entities_meeting_confidence(
namespace: str,
concept: str,
threshold: float,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion at or
above threshold confidence, active at as_of_time (or currently
active, if as_of_time is None).
Avoids the N+1 pattern of calling assertions() once per candidate
entity (QueryBuilder.min_confidence(), KI-028) — one SQL round trip
regardless of concept size. None confidence never qualifies
(ADR-0004). When as_of_time is None, "active" means
status = 'active', widened by include_flagged/include_history
exactly as entities_where()'s current-state path is (KI-093); when
as_of_time is set, it means the same bitemporal window
entities_where() uses (asserted_at <= as_of_time, valid_from/
valid_to bracketing as_of_time) — KI-036, so
kb.as_of(t).query(...).min_confidence(...) evaluates against a
coherent point-in-time view instead of always checking
current-active assertions regardless of t. status = 'flagged'
(a static contradiction, SPEC §10.3) is excluded point-in-time, not
by current status (KI-097): reconstructed from the assertion_event
log the same way assertions() already does, so a t before the
dispute existed still qualifies and a t during a dispute that has
since been resolved still correctly excludes — unless
include_flagged is set — matching entities_where()'s identical
as_of handling. include_history is mostly a no-op under
as_of_time, also matching entities_where(): that branch never
restricts to active in the first place, only conditionally
excludes flagged, so a superseded assertion whose window covers
t already qualifies without this flag. A retracted assertion is
the one exception (ADR-0049, KI-095): as_of_time additionally
excludes it once its own retraction event's timestamp is <=
as_of_time, and include_history opts back out of that
exclusion.
Always scoped by (namespace, concept) — this is what keeps the
query's parameter count constant regardless of how many entities
exist, unlike a mandatory id-list-bound design (a SQL IN (...)
with one placeholder per candidate hits both SQLite's
bound-variable limit and, on DuckDB, per-parameter bind overhead,
at real-world scale — see KI-028's own Fix text for why that design
was tried and reverted before this method first shipped).
candidate_ids, when given, is an optional narrowing hint on top
of that scope — not a replacement for it — for when the caller has
already narrowed to a small candidate set via .where()/
.semantic() (KI-028's fix otherwise forced even a single-candidate
.where() match to re-scan the entire concept; KI-037). A backend
MAY use it to cut real work (e.g. SQLite binds it as one
JSON-encoded parameter, avoiding the per-placeholder cost a literal
IN (...) would reintroduce) or ignore it and keep scanning — both
are correct, since the caller always re-intersects the returned set
against its own candidate list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to scope the scan to |
required |
concept
|
str
|
Concept to scope the scan to |
required |
threshold
|
float
|
Minimum confidence, 0.0-1.0 |
required |
as_of_time
|
datetime | None
|
If set, evaluate against this point in time instead of current state (KI-036) — see the flagged-status point-in-time reconstruction above |
None
|
candidate_ids
|
frozenset[str] | None
|
Optional narrowing hint (KI-037) — a backend may
use this to scope the scan below |
None
|
include_flagged
|
bool
|
Also count 'flagged' assertions (KI-093). Honored on both the current-state and as_of_time paths, matching entities_where(). |
False
|
include_history
|
bool
|
Also count 'superseded'/'retracted' assertions (KI-093). Widens the current-state path beyond 'active'. Under as_of_time, mostly a no-op — but not for 'retracted' (ADR-0049, KI-095), matching entities_where(). |
False
|
Returns:
| Type | Description |
|---|---|
set[str]
|
IDs of qualifying entities (may be a superset of any candidate |
set[str]
|
list the caller intends to intersect this against) |
Source code in src/ontolith/store/base.py
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | |
entities_meeting_trust ¶
entities_meeting_trust(
namespace: str,
concept: str,
min_trust: int,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion,
active at as_of_time (or currently active, if as_of_time is
None) — widened by include_flagged/include_history exactly as
entities_meeting_confidence()'s identical parameters are (KI-093)
— whose effective trust_level >= min_trust.
"Effective" (KI-047): when the qualifying assertion was made under
delegation (acting_as set), the comparison is
min(author.trust_level, acting_as.trust_level), not the author's
raw trust_level alone. SPEC §8.4 states this min() rule for
capability only ("the effective capability for the operation is
min(capability(author), capability(acting_as))"); the trust-min
is govern/policy.py's own conservative extension of that same
principle, applied here by analogy, not a separate SPEC mandate.
For a non-delegated assertion, this is simply the author's own
trust_level, unchanged from before this method considered
delegation at all.
If acting_as names a principal that doesn't resolve (there is no
FK from assertion.acting_as to principal.id, so this can only
happen via a direct put_assertion() call bypassing Ontology's
write paths — e.g. a legacy import — since Ontology's own paths
always validate the delegate exists before writing), implementations
MUST fall back to the author's own trust_level rather than
excluding the row or raising — i.e. treat an unresolvable delegate
the same as no delegate at all. This deliberately fails open,
unlike govern/policy.py's _resolve_delegation which fails
closed (raises AuthError) for the same input — policy
evaluation runs once, at write time, when rejecting the write
outright is cheap and correct; this method runs on every query
against already-committed data, where excluding or erroring on a
row for data that was already accepted would be a surprising,
un-auditable behavior change with no corresponding write.
Avoids the N+1 pattern of calling assertions() + get_principal()
once per (candidate entity, assertion) pair (QueryBuilder.
trust_at_least(), KI-028) — one SQL round trip regardless of
concept size. Scoped by (namespace, concept), with the same
optional candidate_ids narrowing hint (KI-037), for the same
reason as entities_meeting_confidence — see its docstring.
as_of_time bitemporally scopes which assertion qualifies, the
same way entities_meeting_confidence does (including its
flagged-status point-in-time reconstruction, KI-097) — but each
individual principal's own trust_level (author's and, if
delegated, acting_as's) is always its current value, never a
historical one (KI-036), and the min()
this method now takes of the two (KI-047) inherits that same
property. This is not an approximation: no code path updates a
principal's trust_level after creation, so "trust_level as of any
t at or after the principal's creation" and "trust_level now" are
the same value by construction (guarded by
tests/unit/test_principal_trust_immutability_invariant.py, which
fails the day a mutation path is added — that would mean this
method needs real principal versioning, not this shortcut). A
principal cannot author an assertion before it exists, so this
holds for every as_of_time an assertion's asserted_at could
satisfy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to scope the scan to |
required |
concept
|
str
|
Concept to scope the scan to |
required |
min_trust
|
int
|
Minimum effective trust level, 0-10 |
required |
as_of_time
|
datetime | None
|
If set, evaluate assertion existence against this point in time instead of current state (KI-036) |
None
|
candidate_ids
|
frozenset[str] | None
|
Optional narrowing hint (KI-037) — see
|
None
|
include_flagged
|
bool
|
Also count 'flagged' assertions (KI-093) — see
|
False
|
include_history
|
bool
|
Also count 'superseded'/'retracted' assertions
(KI-093) — see |
False
|
Returns:
| Type | Description |
|---|---|
set[str]
|
IDs of qualifying entities (may be a superset of any candidate |
set[str]
|
list the caller intends to intersect this against) |
Source code in src/ontolith/store/base.py
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 | |
vector_upsert ¶
Insert or replace the embedding vector for (scope, id).
The dimensionality of the first vector ever upserted into a scope establishes that scope's dimension for the life of the store; later upserts into the same scope must match it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
id
|
str
|
Entity or assertion ID the vector represents. |
required |
vec
|
list[float]
|
Embedding vector. |
required |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
StorageError
|
If persistence fails. |
Source code in src/ontolith/store/base.py
vector_search ¶
Return the k nearest ids to vec within scope, ascending distance.
Distance is L2 (Euclidean). Embedder implementations MUST return L2-unit-normalized vectors, which makes ascending-L2-distance order equivalent to descending-cosine-similarity order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
vec
|
list[float]
|
Query vector. |
required |
k
|
int
|
Maximum number of results. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[str, float]]
|
(id, distance) tuples, nearest first. Fewer than k if the scope |
list[tuple[str, float]]
|
has fewer than k vectors; empty list if the scope has never |
list[tuple[str, float]]
|
been populated. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
Source code in src/ontolith/store/base.py
put_proposal ¶
Persist a proposal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposal
|
Proposal
|
Proposal to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_proposal ¶
Retrieve a proposal by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposal_id
|
str
|
Proposal ID |
required |
Returns:
| Type | Description |
|---|---|
Proposal | None
|
Proposal if found, None otherwise |
proposals ¶
Query proposals, optionally filtered by state (SPEC §14.1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
str | None
|
Filter by proposal state (e.g. "require_review"); None returns proposals in every state |
None
|
Returns:
| Type | Description |
|---|---|
list[Proposal]
|
Matching proposals, most recently created first |
Source code in src/ontolith/store/base.py
update_proposal_state ¶
update_proposal_state(
proposal_id: str,
state: str,
decided_at: str | None = None,
policy_reason: str | None = None,
) -> None
Update proposal state after policy decision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposal_id
|
str
|
Proposal to update |
required |
state
|
str
|
New state (auto_accepted, require_review, rejected, etc.) |
required |
decided_at
|
str | None
|
ISO timestamp of the decision. Set unconditionally,
including to None — unlike policy_reason, passing None
clears the stored value rather than leaving it unchanged.
|
None
|
policy_reason
|
str | None
|
Human-readable reason from policy engine. If None, the stored value is left unchanged (not cleared) — the policy-engine reason set at proposal-creation time is distinct from, and not overwritten by, review actions recorded via put_proposal_event. |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
proposal_id does not name an existing proposal |
Source code in src/ontolith/store/base.py
update_proposal_reviewers ¶
Replace a proposal's assigned reviewers (SPEC §9.4's assign action).
Unlike update_proposal_state's policy_reason, there is no
"leave unchanged" sentinel here — reviewers is always replaced
wholesale with what's passed, including an empty list (which
clears every assignment). A dedicated method rather than folding
this into update_proposal_state: assign doesn't change
state, and update_proposal_state already has enough
state/decided_at/policy_reason parameters with their own distinct
semantics without adding a fourth (KI-078).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposal_id
|
str
|
Proposal to update |
required |
reviewers
|
list[str]
|
New reviewer list, replacing whatever was there before |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
proposal_id does not name an existing proposal |
Source code in src/ontolith/store/base.py
put_proposal_event ¶
Persist a structured review-action event (SPEC §9.4).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
ProposalEvent
|
ProposalEvent to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_proposal_events ¶
Retrieve all review events for a proposal, oldest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposal_id
|
str
|
Proposal to retrieve events for |
required |
Returns:
| Type | Description |
|---|---|
list[ProposalEvent]
|
Events for this proposal, ordered by occurrence |
Source code in src/ontolith/store/base.py
put_contradiction ¶
Persist a new contradiction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contradiction
|
Contradiction
|
Contradiction to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
get_open_contradiction ¶
Return the open contradiction for (namespace, subject, predicate), if any.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to search |
required |
subject
|
str
|
Subject entity ID |
required |
predicate
|
str
|
Predicate name |
required |
Returns:
| Type | Description |
|---|---|
Contradiction | None
|
Open Contradiction if one exists, None otherwise |
Source code in src/ontolith/store/base.py
update_contradiction_members ¶
update_contradiction_members(
contradiction_id: str,
member_ids: list[str],
metadata: dict[str, Any] | None = None,
) -> None
Replace the member_ids list (and, optionally, the metadata blob) on an existing contradiction (KI-071).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contradiction_id
|
str
|
Contradiction to update |
required |
member_ids
|
list[str]
|
New full list of member assertion IDs |
required |
metadata
|
dict[str, Any] | None
|
If given, replaces the contradiction's metadata blob
wholesale — the caller is expected to pass the complete
desired dict (e.g. built from a fresh read plus one
appended entry), matching member_ids' own
full-replacement convention rather than a merge/delta.
|
None
|
Source code in src/ontolith/store/base.py
get_contradiction ¶
Retrieve a contradiction by ID, regardless of state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contradiction_id
|
str
|
Contradiction ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Contradiction | None
|
Contradiction if found, None otherwise |
Source code in src/ontolith/store/base.py
contradictions ¶
Query contradictions, optionally filtered by state (SPEC §14.1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
str | None
|
Filter by contradiction state ("open" or "resolved"); None returns contradictions in every state |
None
|
Returns:
| Type | Description |
|---|---|
list[Contradiction]
|
Matching contradictions, most recently created first |
Source code in src/ontolith/store/base.py
resolve_contradiction ¶
Mark a contradiction as resolved (SPEC §10.3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contradiction_id
|
str
|
Contradiction to resolve |
required |
resolved_by
|
str
|
Principal ID who resolved it |
required |
resolved_at
|
datetime
|
Timestamp of resolution |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If the contradiction is not found or update fails |
Source code in src/ontolith/store/base.py
ontolith.store.sqlite ¶
SQLite storage backend.
Default storage adapter for Ontolith using SQLite3 with bitemporal schema.
SQLiteBackend ¶
SQLite implementation of StorageBackend.
Schema follows SPEC §12.2: - entity table with ULID primary key - assertion table with ULID primary key - Bitemporal columns (asserted_at, valid_from, valid_to) - Status tracking for append-only invariant
KI-066: assertion_event/proposal_event's append-only invariant
(SPEC §17) is backed here by three triggers per table — BEFORE
UPDATE, BEFORE DELETE, and BEFORE INSERT ... WHEN EXISTS(...) —
raising sqlite3.IntegrityError on any raw mutation attempt, not just
the port surface exposing no update/delete method. The BEFORE INSERT
trigger is what actually closes INSERT OR REPLACE: it is schema
state (persisted in the file, enforced on every connection), unlike
PRAGMA recursive_triggers = ON (also set below, as defense in depth)
which is per-connection and does not by itself stop a second raw
connection to the same file from reviving the REPLACE bypass. DuckDBBackend
has no equivalent — see its own docstring.
Initialize SQLite backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to SQLite database file (created if doesn't exist) |
required |
clock
|
Clock | None
|
Clock for timestamps (defaults to SystemClock) |
None
|
Source code in src/ontolith/store/sqlite/backend.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
begin ¶
Begin an explicit transaction (ADR-0010, KI-084).
Acquires self._lock (KI-023) — held across every subsequent
@_synchronized call until commit()/rollback() releases it, so no
other thread's operation can interleave with this transaction.
Only serializes this process's own threads — see KI-084's
docs/adr/ADR-0001-storage-default.md update for the
cross-process constraint this alone doesn't cover.
BEGIN IMMEDIATE, not a plain deferred BEGIN (KI-084): a
deferred transaction takes its read snapshot lazily, on first
statement. assert_literal/assert_ref's own conflict-routing
read (SPEC §10) and propose/propose_ref's auto-accept branch,
accept_proposal, and resubmit's auto-accept branch (via
_replay_proposal_operations) all do that read inside the
transaction() block this method opens — so a deferred BEGIN
let a concurrent writer (a second OS process; self._lock above
only protects this process's own threads) commit between that read
and this connection's own later write. The resulting write then
hit SQLITE_BUSY_SNAPSHOT — a stale-snapshot-upgrade failure
SQLite deliberately never routes through the busy handler, so it
failed immediately no matter how long busy_timeout (set in
__init__) allowed. BEGIN IMMEDIATE claims the write lock right
here, before any read this transaction goes on to do, so a
concurrent writer is instead serialized behind it — blocked and
retried by the busy handler, same as any other reachable
SQLITE_BUSY, for up to busy_timeout before genuinely failing.
This is not a blanket claim that every Ontology write is now
cross-process-safe — see KI-084's docs/known-issues.md entry and
its own KI-092 follow-up for exactly which write paths this does
and doesn't reach (several either write outside any transaction()
block at all, or, per the already-resolved KI-035, evaluate policy
against a read taken before the transaction opens).
Trade-off worth knowing (KI-084 review): self._lock is acquired
before the BEGIN IMMEDIATE call below, so while this call is
parked in SQLite's busy handler waiting out a cross-process writer,
every other @_synchronized call on this process — reads included
— blocks behind it too, for up to the full busy_timeout. This
can't be avoided by acquiring the lock later: only one Python
thread may safely touch the single shared self.conn at a time
regardless of which statement is running, so narrowing the lock's
span here would just reopen KI-023 (two threads issuing statements
on one connection concurrently) instead. The WAL claim elsewhere in
this file ("readers don't block behind writers") holds at the
SQLite level; it does not hold at this process's own read
availability once a begin() here is genuinely contended by
another process. See KI-084's docs/adr/ADR-0001-storage-default.md
update for the deployment-facing version of this same trade-off.
Source code in src/ontolith/store/sqlite/backend.py
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 | |
commit ¶
Commit the current explicit transaction.
Releases self._lock only on success. A failed commit leaves the transaction (and the lock) open: transaction()'s except block calls rollback() next, which is then the sole path that releases the lock — releasing here too on failure would double-release it (the lock is not reentrant-safe against being released twice), raising a RuntimeError that masks the real StorageError and leaves _in_transaction stuck True.
Source code in src/ontolith/store/sqlite/backend.py
rollback ¶
Rollback the current explicit transaction. Always releases self._lock.
Unlike commit(), this always resolves the transaction (successful or not) — it's the terminal cleanup path, including when called after a failed commit() (which deliberately did not release the lock itself, see commit()'s docstring). _in_transaction is reset unconditionally too, even if the underlying rollback itself fails: leaving it True after the lock is released would let a future caller believe it must skip autocommit for a transaction no one will ever commit or roll back again.
Source code in src/ontolith/store/sqlite/backend.py
transaction ¶
Context manager for atomic multi-write transactions (ADR-0010).
Usage
with backend.transaction(): backend.put_entity(entity) backend.put_assertion(assertion)
Source code in src/ontolith/store/sqlite/backend.py
put_principal ¶
Persist a principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Principal
|
Principal to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/sqlite/backend.py
get_principal ¶
Retrieve a principal by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if found, None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
list_principals ¶
List all principals (KI-022).
Returns:
| Type | Description |
|---|---|
list[Principal]
|
All principals, most recently created first |
Source code in src/ontolith/store/sqlite/backend.py
list_namespaces ¶
List all registered namespaces (SPEC §12.2, KI-022).
Returns:
| Type | Description |
|---|---|
list[Namespace]
|
All namespaces, most recently created first |
Source code in src/ontolith/store/sqlite/backend.py
put_credential ¶
Persist a principal credential (hashed API-key token).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential
|
PrincipalCredential
|
PrincipalCredential to persist (token_hash, never the raw token) |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/sqlite/backend.py
get_principal_by_token_hash ¶
Resolve a principal via a credential's token hash.
Only unrevoked credentials resolve. This is the sole read path used for MCP authentication — it never trusts a caller-supplied principal ID directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_hash
|
str
|
SHA-256 hash of the raw bearer token |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if the hash matches an active (unrevoked) credential, |
Principal | None
|
None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
get_credential ¶
Retrieve a credential by ID (never exposes the raw token or hash to callers).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
PrincipalCredential | None
|
PrincipalCredential if found, None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
get_credentials_for_principal ¶
List all credentials (active and revoked) issued to a principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal to list credentials for |
required |
Returns:
| Type | Description |
|---|---|
list[PrincipalCredential]
|
Credentials for this principal, most recently issued first |
Source code in src/ontolith/store/sqlite/backend.py
revoke_credential ¶
Mark a credential as revoked. Idempotent-safe: re-revoking an
already-revoked credential is a true no-op, not a silent
re-stamp — it doesn't overwrite revoked_by/revoked_at with a
second caller's values (KI-060: that would launder the first
revocation's real attribution).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential to revoke |
required |
revoked_at
|
datetime
|
Timestamp of revocation |
required |
revoked_by
|
str
|
Principal ID of the admin performing the revocation |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If the credential is not found |
Source code in src/ontolith/store/sqlite/backend.py
put_admin_event ¶
Persist an append-only admin-action event (KI-060).
Source code in src/ontolith/store/sqlite/backend.py
get_admin_events ¶
Retrieve admin events, optionally filtered by actor or target, oldest first.
Source code in src/ontolith/store/sqlite/backend.py
put_entity ¶
Persist an entity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity
|
Entity
|
Entity to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/sqlite/backend.py
put_assertion ¶
Persist an assertion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion
|
Assertion
|
Assertion to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/sqlite/backend.py
get_entity ¶
Retrieve an entity by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
Entity ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Entity | None
|
Entity if found, None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
get_entity_by_natural_key ¶
Retrieve an entity by its unique (namespace, concept, natural_key) triple.
Source code in src/ontolith/store/sqlite/backend.py
assertions ¶
assertions(
subject: str | None = None,
predicate: str | None = None,
status: str | None = "active",
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Assertion]
Query assertions with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str | None
|
Filter by subject entity ID |
None
|
predicate
|
str | None
|
Filter by predicate |
None
|
status
|
str | None
|
Filter by current status (ignored when as_of_time is set). Defaults to "active"; pass status=None for every status. |
'active'
|
as_of_time
|
datetime | None
|
If set, applies bitemporal filter: asserted_at <= t AND valid_from <= t AND (valid_to IS NULL OR valid_to > t) |
None
|
include_flagged
|
bool
|
When as_of_time is set, whether to include 'flagged' assertions (excluded by default — a flagged assertion is disputed, not confirmed-valid; pass True for explicit audit/history views); ignored when as_of_time is None. |
False
|
include_history
|
bool
|
When as_of_time is set, whether to opt back into seeing a 'retracted' assertion once its own retraction event's timestamp is <= as_of_time (excluded by default, ADR-0049/KI-095) — mirrors include_flagged's shape (KI-098); ignored when as_of_time is None. |
False
|
Returns:
| Type | Description |
|---|---|
list[Assertion]
|
List of matching assertions |
Source code in src/ontolith/store/sqlite/backend.py
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 | |
get_assertion ¶
Retrieve a single assertion by ID, regardless of status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Assertion | None
|
Assertion if found, None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
set_assertion_status ¶
Update assertion status and optionally close validity window.
This is the ONLY allowed mutation on assertions (append-only invariant).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to update |
required |
status
|
str
|
New status (superseded, retracted, flagged) |
required |
valid_to
|
str | None
|
Optional validity end time (ISO format) |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
If update fails or assertion not found |
Source code in src/ontolith/store/sqlite/backend.py
put_schema ¶
Persist a schema version.
Also registers schema.namespace in the namespace registry if
not already present (KI-022) — a namespace that only ever has a
schema applied, never an entity, is still discoverable via
list_namespaces().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
SchemaIR
|
Schema to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/sqlite/backend.py
get_schema ¶
Retrieve a schema version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
version
|
int | None
|
Specific version, or None for latest |
None
|
Returns:
| Type | Description |
|---|---|
SchemaIR | None
|
Schema if found, None otherwise |
Source code in src/ontolith/store/sqlite/backend.py
get_schema_at ¶
Retrieve the schema version effective at a point in time (KI-019).
Orders by applied_at (the actual "effective at" moment), with version as a tiebreak for same-timestamp rows under a coarse or injected Clock — not by version alone, so this stays correct even if a future write path ever persisted schema rows out of temporal order relative to their version numbers.
Source code in src/ontolith/store/sqlite/backend.py
entities ¶
entities(
namespace: str | None = None,
concept: str | None = None,
as_of_time: datetime | None = None,
) -> list[Entity]
Query entities with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str | None
|
Filter by namespace |
None
|
concept
|
str | None
|
Filter by concept |
None
|
as_of_time
|
datetime | None
|
If set, exclude entities created after this time |
None
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of matching entities |
Source code in src/ontolith/store/sqlite/backend.py
put_proposal ¶
Persist a proposal.
Source code in src/ontolith/store/sqlite/backend.py
get_proposal ¶
Retrieve a proposal by ID.
Source code in src/ontolith/store/sqlite/backend.py
proposals ¶
Query proposals, optionally filtered by state (SPEC §14.1).
Source code in src/ontolith/store/sqlite/backend.py
update_proposal_state ¶
update_proposal_state(
proposal_id: str,
state: str,
decided_at: str | None = None,
policy_reason: str | None = None,
) -> None
Update proposal state after policy decision.
policy_reason=None leaves the stored value unchanged (COALESCE), it does not clear it — see the port docstring for why.
Source code in src/ontolith/store/sqlite/backend.py
update_proposal_reviewers ¶
Replace a proposal's assigned reviewers (SPEC §9.4's assign action).
Source code in src/ontolith/store/sqlite/backend.py
put_proposal_event ¶
Persist a structured review-action event (SPEC §9.4).
Source code in src/ontolith/store/sqlite/backend.py
get_proposal_events ¶
Retrieve all review events for a proposal, oldest first.
Source code in src/ontolith/store/sqlite/backend.py
put_assertion_event ¶
Persist an append-only assertion status-mutation event.
Source code in src/ontolith/store/sqlite/backend.py
get_assertion_events ¶
Retrieve all status-mutation events for an assertion, oldest first.
Source code in src/ontolith/store/sqlite/backend.py
get_assertion_events_by_successor ¶
Retrieve all 'superseded' events caused by a given successor assertion.
Source code in src/ontolith/store/sqlite/backend.py
put_contradiction ¶
Persist a new contradiction.
Source code in src/ontolith/store/sqlite/backend.py
get_open_contradiction ¶
Return the open contradiction for (namespace, subject, predicate), if any.
Source code in src/ontolith/store/sqlite/backend.py
update_contradiction_members ¶
update_contradiction_members(
contradiction_id: str,
member_ids: list[str],
metadata: dict[str, Any] | None = None,
) -> None
Add member IDs to an existing open contradiction (KI-071: optionally replace metadata too, in the same UPDATE).
Source code in src/ontolith/store/sqlite/backend.py
get_contradiction ¶
Retrieve a contradiction by ID, regardless of state.
Source code in src/ontolith/store/sqlite/backend.py
contradictions ¶
Query contradictions, optionally filtered by state (SPEC §14.1).
Source code in src/ontolith/store/sqlite/backend.py
resolve_contradiction ¶
Mark a contradiction as resolved (SPEC §10.3).
Source code in src/ontolith/store/sqlite/backend.py
entities_where ¶
entities_where(
namespace: str,
concept: str,
predicate_filters: list[tuple[str, str, Any]],
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Entity]
Query entities matching all predicate filters in one SQL query.
Uses correlated subqueries so each filter hits the
idx_assertion_pred_value/idx_assertion_pred_ref indexes instead of
doing one round-trip per entity. "eq" matches either a literal
property (value_lit) or a relation's target entity id
(value_ref) — KI-030: relation filters like employer="org-123"
are equality checks against value_ref, not traversal into the
target entity's own properties. The two are checked via a UNION ALL
of two single-column point lookups rather than one value_lit = ?
OR value_ref = ? predicate — SQLite's planner doesn't reliably pick
a seekable plan for the latter (falls back to a full table SCAN on
the as_of branch; confirmed via EXPLAIN QUERY PLAN), which turned
every .where() call — not just relation filters — into an
unindexed scan. "contains"/"gt"/"lt"/"gte"/"lte" (KI-039)
check value_lit only — see this port method's own docstring for
why relations don't get a UNION ALL branch for those.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
concept
|
str
|
Concept to filter by |
required |
predicate_filters
|
list[tuple[str, str, Any]]
|
List of |
required |
as_of_time
|
datetime | None
|
If set, applies bitemporal filter on assertions and entity creation |
None
|
include_flagged
|
bool
|
Also match 'flagged' assertions — honored on both the current-state and as_of_time paths (KI-081; excluded by default on both, see assertions()). On the as_of_time path this is point-in-time, not current status (KI-097): reconstructed from the assertion_event log the same way assertions() already does, so a query pinned to a time when an assertion was disputed correctly excludes it even after the dispute has since been resolved — and, the other direction, a time strictly before any dispute existed still includes an otherwise-undisputed value, even though the same assertion is flagged now. |
False
|
include_history
|
bool
|
Also match 'superseded'/'retracted' assertions
(KI-081). On the current-state path this widens the status
set beyond 'active'. On the as_of_time path, 'superseded' is
unaffected either way (its window already never restricts to
'active') — but 'retracted' does something under this flag
now (ADR-0049, KI-095): as_of_time excludes a retracted
assertion once its own retraction event's |
False
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of entities where all filters match at the given time |
Source code in src/ontolith/store/sqlite/backend.py
1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 | |
entities_meeting_confidence ¶
entities_meeting_confidence(
namespace: str,
concept: str,
threshold: float,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion at or
above threshold confidence, active at as_of_time (KI-036) or
currently active if as_of_time is None. candidate_ids, if given,
narrows the scan below (namespace, concept) (KI-037) via a single
JSON-encoded bound parameter rather than one placeholder per id.
include_flagged/include_history widen "active" the same way
entities_where() does (KI-093) — see its docstring.
Source code in src/ontolith/store/sqlite/backend.py
2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 | |
entities_meeting_trust ¶
entities_meeting_trust(
namespace: str,
concept: str,
min_trust: int,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion,
active at as_of_time (KI-036) or currently active if as_of_time
is None, whose effective trust_level >= min_trust (KI-047) —
min(author.trust_level, acting_as.trust_level) when the assertion
was made under delegation, matching govern/policy.py's identical
formula for effective trust (by analogy with SPEC §8.4's capability
rule), or just author.trust_level when it wasn't. A dangling
acting_as (no resolvable delegate) falls back to author.trust_level
via coalesce — see StorageBackend.entities_meeting_trust's
docstring for why. candidate_ids narrows the scan the same way as
entities_meeting_confidence (KI-037) — see its docstring.
include_flagged/include_history widen "active" the same way
entities_where() does (KI-093) — see its docstring.
Source code in src/ontolith/store/sqlite/backend.py
2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 | |
vector_upsert ¶
Insert or replace the embedding vector for (scope, id).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
id
|
str
|
Entity or assertion ID the vector represents. |
required |
vec
|
list[float]
|
Embedding vector. |
required |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
StorageError
|
If persistence fails. |
Source code in src/ontolith/store/sqlite/backend.py
vector_search ¶
Return the k nearest ids to vec within scope, ascending distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
vec
|
list[float]
|
Query vector. |
required |
k
|
int
|
Maximum number of results. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[str, float]]
|
(id, distance) tuples, nearest first. Empty list if the scope |
list[tuple[str, float]]
|
has never been populated. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
Source code in src/ontolith/store/sqlite/backend.py
ontolith.store.duckdb ¶
DuckDB storage backend.
Second pluggable storage adapter (M3, ADR-0016), proving the StorageBackend port abstraction against a structurally different embedded engine than SQLite.
DuckDBBackend ¶
DuckDB implementation of StorageBackend.
Schema follows SPEC §12.2, identical table shape to SQLiteBackend: - entity table with ULID primary key - assertion table with ULID primary key - Bitemporal columns (asserted_at, valid_from, valid_to) - Status tracking for append-only invariant
KI-066: unlike SQLiteBackend, assertion_event/proposal_event's
append-only invariant (SPEC §17: "the audit trail MUST NOT be
mutable") is enforced here only by this port's own surface exposing
no update/delete method — a convention, not a store-level guarantee.
DuckDB (verified against 1.5.4) has no CREATE TRIGGER support at
all, so the three triggers per table (BEFORE UPDATE, BEFORE
DELETE, BEFORE INSERT ... WHEN EXISTS(...)) SQLiteBackend installs
have no DuckDB equivalent; code holding this backend's raw
duckdb.DuckDBPyConnection can still UPDATE/DELETE/INSERT OR
REPLACE either audit table directly. Not fixable without a different
mechanism (e.g. a superuser-only schema plus a restricted role — not
available in DuckDB's embedded, single-user connection model either).
Initialize DuckDB backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to DuckDB database file (created if doesn't exist) |
required |
clock
|
Clock | None
|
Clock for timestamps (defaults to SystemClock) |
None
|
Source code in src/ontolith/store/duckdb/backend.py
begin ¶
Begin an explicit transaction.
Acquires self._lock (KI-046) — held across every subsequent @_synchronized call until commit()/rollback() releases it, so no other thread's operation can interleave with this transaction.
Source code in src/ontolith/store/duckdb/backend.py
commit ¶
Commit the current explicit transaction.
Releases self._lock only on success — mirrors SQLiteBackend's own asymmetric release (KI-023, ADR-0010's update): an unconditional release here would double-release the lock when transaction()'s except clause calls rollback() next after a failed commit(), which RLock.release() rejects with RuntimeError, masking the real StorageError.
Source code in src/ontolith/store/duckdb/backend.py
rollback ¶
Rollback the current explicit transaction. Always releases self._lock.
Unlike commit(), this always resolves the transaction (successful or not) — it's the terminal cleanup path, including when called after a failed commit() (which deliberately did not release the lock itself, see commit()'s docstring).
Source code in src/ontolith/store/duckdb/backend.py
transaction ¶
Context manager for atomic multi-write transactions.
Usage
with backend.transaction(): backend.put_entity(entity) backend.put_assertion(assertion)
Source code in src/ontolith/store/duckdb/backend.py
put_principal ¶
Persist a principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Principal
|
Principal to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/duckdb/backend.py
get_principal ¶
Retrieve a principal by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if found, None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
list_principals ¶
List all principals (KI-022).
Returns:
| Type | Description |
|---|---|
list[Principal]
|
All principals, most recently created first |
Source code in src/ontolith/store/duckdb/backend.py
list_namespaces ¶
List all registered namespaces (SPEC §12.2, KI-022).
Returns:
| Type | Description |
|---|---|
list[Namespace]
|
All namespaces, most recently created first |
Source code in src/ontolith/store/duckdb/backend.py
put_credential ¶
Persist a principal credential (hashed API-key token).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential
|
PrincipalCredential
|
PrincipalCredential to persist (token_hash, never the raw token) |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/duckdb/backend.py
get_principal_by_token_hash ¶
Resolve a principal via a credential's token hash.
Only unrevoked credentials resolve. This is the sole read path used for MCP authentication — it never trusts a caller-supplied principal ID directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_hash
|
str
|
SHA-256 hash of the raw bearer token |
required |
Returns:
| Type | Description |
|---|---|
Principal | None
|
Principal if the hash matches an active (unrevoked) credential, |
Principal | None
|
None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
get_credential ¶
Retrieve a credential by ID (never exposes the raw token or hash to callers).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
PrincipalCredential | None
|
PrincipalCredential if found, None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
get_credentials_for_principal ¶
List all credentials (active and revoked) issued to a principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal_id
|
str
|
Principal to list credentials for |
required |
Returns:
| Type | Description |
|---|---|
list[PrincipalCredential]
|
Credentials for this principal, most recently issued first |
Source code in src/ontolith/store/duckdb/backend.py
revoke_credential ¶
Mark a credential as revoked. Idempotent-safe: re-revoking an
already-revoked credential is a true no-op — see SQLiteBackend's
identical method for why (KI-060: doesn't launder attribution by
overwriting revoked_by on a second call).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credential_id
|
str
|
Credential to revoke |
required |
revoked_at
|
datetime
|
Timestamp of revocation |
required |
revoked_by
|
str
|
Principal ID of the admin performing the revocation |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If the credential is not found |
Source code in src/ontolith/store/duckdb/backend.py
put_admin_event ¶
Persist an append-only admin-action event (KI-060).
Source code in src/ontolith/store/duckdb/backend.py
get_admin_events ¶
Retrieve admin events, optionally filtered by actor or target, oldest first.
Source code in src/ontolith/store/duckdb/backend.py
put_entity ¶
Persist an entity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity
|
Entity
|
Entity to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/duckdb/backend.py
put_assertion ¶
Persist an assertion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion
|
Assertion
|
Assertion to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/duckdb/backend.py
get_entity ¶
Retrieve an entity by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_id
|
str
|
Entity ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Entity | None
|
Entity if found, None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
get_entity_by_natural_key ¶
Retrieve an entity by its unique (namespace, concept, natural_key) triple.
Source code in src/ontolith/store/duckdb/backend.py
assertions ¶
assertions(
subject: str | None = None,
predicate: str | None = None,
status: str | None = "active",
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Assertion]
Query assertions with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str | None
|
Filter by subject entity ID |
None
|
predicate
|
str | None
|
Filter by predicate |
None
|
status
|
str | None
|
Filter by current status (ignored when as_of_time is set). Defaults to "active"; pass status=None for every status. |
'active'
|
as_of_time
|
datetime | None
|
If set, applies bitemporal filter: asserted_at <= t AND valid_from <= t AND (valid_to IS NULL OR valid_to > t) |
None
|
include_flagged
|
bool
|
When as_of_time is set, whether to include 'flagged' assertions (excluded by default — a flagged assertion is disputed, not confirmed-valid; pass True for explicit audit/history views); ignored when as_of_time is None. |
False
|
include_history
|
bool
|
When as_of_time is set, whether to opt back into seeing a 'retracted' assertion once its own retraction event's timestamp is <= as_of_time (excluded by default, ADR-0049/KI-095) — mirrors include_flagged's shape (KI-098); ignored when as_of_time is None. |
False
|
Returns:
| Type | Description |
|---|---|
list[Assertion]
|
List of matching assertions |
Source code in src/ontolith/store/duckdb/backend.py
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 | |
get_assertion ¶
Retrieve a single assertion by ID, regardless of status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to retrieve |
required |
Returns:
| Type | Description |
|---|---|
Assertion | None
|
Assertion if found, None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
set_assertion_status ¶
Update assertion status and optionally close validity window.
This is the ONLY allowed mutation on assertions (append-only invariant).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assertion_id
|
str
|
Assertion ID to update |
required |
status
|
str
|
New status (superseded, retracted, flagged) |
required |
valid_to
|
str | None
|
Optional validity end time (ISO format) |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
If update fails or assertion not found |
Source code in src/ontolith/store/duckdb/backend.py
put_schema ¶
Persist a schema version.
Also registers schema.namespace in the namespace registry if
not already present (KI-022) — a namespace that only ever has a
schema applied, never an entity, is still discoverable via
list_namespaces().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
SchemaIR
|
Schema to persist |
required |
Raises:
| Type | Description |
|---|---|
StorageError
|
If persistence fails |
Source code in src/ontolith/store/duckdb/backend.py
get_schema ¶
Retrieve a schema version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
version
|
int | None
|
Specific version, or None for latest |
None
|
Returns:
| Type | Description |
|---|---|
SchemaIR | None
|
Schema if found, None otherwise |
Source code in src/ontolith/store/duckdb/backend.py
get_schema_at ¶
Retrieve the schema version effective at a point in time (KI-019).
Orders by applied_at (the actual "effective at" moment), with version as a tiebreak for same-timestamp rows under a coarse or injected Clock — not by version alone, so this stays correct even if a future write path ever persisted schema rows out of temporal order relative to their version numbers.
Source code in src/ontolith/store/duckdb/backend.py
entities ¶
entities(
namespace: str | None = None,
concept: str | None = None,
as_of_time: datetime | None = None,
) -> list[Entity]
Query entities with optional filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str | None
|
Filter by namespace |
None
|
concept
|
str | None
|
Filter by concept |
None
|
as_of_time
|
datetime | None
|
If set, exclude entities created after this time |
None
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of matching entities |
Source code in src/ontolith/store/duckdb/backend.py
put_proposal ¶
Persist a proposal.
Source code in src/ontolith/store/duckdb/backend.py
get_proposal ¶
Retrieve a proposal by ID.
Source code in src/ontolith/store/duckdb/backend.py
proposals ¶
Query proposals, optionally filtered by state (SPEC §14.1).
Source code in src/ontolith/store/duckdb/backend.py
update_proposal_state ¶
update_proposal_state(
proposal_id: str,
state: str,
decided_at: str | None = None,
policy_reason: str | None = None,
) -> None
Update proposal state after policy decision.
policy_reason=None leaves the stored value unchanged (COALESCE), it does not clear it — see the port docstring for why.
Source code in src/ontolith/store/duckdb/backend.py
update_proposal_reviewers ¶
Replace a proposal's assigned reviewers (SPEC §9.4's assign action).
Source code in src/ontolith/store/duckdb/backend.py
put_proposal_event ¶
Persist a structured review-action event (SPEC §9.4).
Source code in src/ontolith/store/duckdb/backend.py
get_proposal_events ¶
Retrieve all review events for a proposal, oldest first.
Source code in src/ontolith/store/duckdb/backend.py
put_assertion_event ¶
Persist an append-only assertion status-mutation event.
Source code in src/ontolith/store/duckdb/backend.py
get_assertion_events ¶
Retrieve all status-mutation events for an assertion, oldest first.
Source code in src/ontolith/store/duckdb/backend.py
get_assertion_events_by_successor ¶
Retrieve all 'superseded' events caused by a given successor assertion.
Source code in src/ontolith/store/duckdb/backend.py
put_contradiction ¶
Persist a new contradiction.
Source code in src/ontolith/store/duckdb/backend.py
get_open_contradiction ¶
Return the open contradiction for (namespace, subject, predicate), if any.
Source code in src/ontolith/store/duckdb/backend.py
update_contradiction_members ¶
update_contradiction_members(
contradiction_id: str,
member_ids: list[str],
metadata: dict[str, Any] | None = None,
) -> None
Add member IDs to an existing open contradiction (KI-071: optionally replace metadata too, in the same UPDATE).
Source code in src/ontolith/store/duckdb/backend.py
get_contradiction ¶
Retrieve a contradiction by ID, regardless of state.
Source code in src/ontolith/store/duckdb/backend.py
contradictions ¶
Query contradictions, optionally filtered by state (SPEC §14.1).
Source code in src/ontolith/store/duckdb/backend.py
resolve_contradiction ¶
Mark a contradiction as resolved (SPEC §10.3).
Source code in src/ontolith/store/duckdb/backend.py
vector_upsert ¶
Insert or replace the embedding vector for (scope, id).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
id
|
str
|
Entity or assertion ID the vector represents. |
required |
vec
|
list[float]
|
Embedding vector. |
required |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
StorageError
|
If persistence fails. |
Source code in src/ontolith/store/duckdb/backend.py
vector_search ¶
Return the k nearest ids to vec within scope, ascending distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Embedding scope. Must be one of VECTOR_SCOPES. |
required |
vec
|
list[float]
|
Query vector. |
required |
k
|
int
|
Maximum number of results. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[str, float]]
|
(id, distance) tuples, nearest first. Empty list if the scope |
list[tuple[str, float]]
|
has never been populated. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
scope is not in VECTOR_SCOPES, or vec's length does not match the scope's already-established dimension. |
Source code in src/ontolith/store/duckdb/backend.py
entities_where ¶
entities_where(
namespace: str,
concept: str,
predicate_filters: list[tuple[str, str, Any]],
as_of_time: datetime | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> list[Entity]
Query entities matching all predicate filters in one SQL query.
Uses correlated subqueries so each filter hits the
idx_assertion_pred_value/idx_assertion_pred_ref indexes instead of
doing one round-trip per entity. "eq" matches either a literal
property (value_lit) or a relation's target entity id
(value_ref) — KI-030: relation filters like employer="org-123"
are equality checks against value_ref, not traversal into the
target entity's own properties. The two are checked via a UNION ALL
of two single-column point lookups rather than one value_lit = ?
OR value_ref = ? predicate, matching the SQLite backend (whose
planner doesn't reliably pick a seekable plan for the OR form).
"contains"/"gt"/"lt"/"gte"/"lte" (KI-039) check
value_lit only — see this port method's own docstring for why
relations don't get a UNION ALL branch for those. Numeric range
comparisons cast to DOUBLE, not REAL — this module's own header
comment notes DuckDB's REAL is 4-byte single precision, unlike
SQLite's always-8-byte REAL, which would silently round values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
namespace
|
str
|
Namespace to query |
required |
concept
|
str
|
Concept to filter by |
required |
predicate_filters
|
list[tuple[str, str, Any]]
|
List of |
required |
as_of_time
|
datetime | None
|
If set, applies bitemporal filter on assertions and entity creation |
None
|
include_flagged
|
bool
|
Also match 'flagged' assertions — honored on both the current-state and as_of_time paths (KI-081; excluded by default on both, see assertions()). On the as_of_time path this is point-in-time, not current status (KI-097): reconstructed from the assertion_event log the same way assertions() already does, so a query pinned to a time when an assertion was disputed correctly excludes it even after the dispute has since been resolved — and, the other direction, a time strictly before any dispute existed still includes an otherwise-undisputed value, even though the same assertion is flagged now. |
False
|
include_history
|
bool
|
Also match 'superseded'/'retracted' assertions (KI-081). On the current-state path this widens the status set beyond 'active'. On the as_of_time path, 'superseded' is unaffected either way (its window already never restricts to 'active') — but 'retracted' does something under this flag now (ADR-0049, KI-095): as_of_time excludes a retracted assertion once its own retraction event's "at" is <= as_of_time (a stale, un-narrowed window otherwise keeps matching indefinitely); include_history opts back out of that exclusion. |
False
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
List of entities where all filters match at the given time |
Source code in src/ontolith/store/duckdb/backend.py
1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 | |
entities_meeting_confidence ¶
entities_meeting_confidence(
namespace: str,
concept: str,
threshold: float,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion at or
above threshold confidence, active at as_of_time (KI-036) or
currently active if as_of_time is None. candidate_ids, if given,
narrows the scan below (namespace, concept) via unnest() (KI-037)
— QueryBuilder only ever passes a set bounded by
_CANDIDATE_HINT_MAX (1000), the range measured to be a genuine win
here (~2-3x at 10k-50k entities); an earlier, unbounded version of
this hint measured unnest() as a regression on both very small
candidate sets (~1k entities, where everything is already fast) and
large ones (thousands of candidates against a 10k-entity concept,
100x slower) — bounding the hint's size, not avoiding
unnest()altogether, is what makes it a reliable win.include_flagged/include_historywiden "active" the same wayentities_where()does (KI-093) — see its docstring.
Source code in src/ontolith/store/duckdb/backend.py
2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 | |
entities_meeting_trust ¶
entities_meeting_trust(
namespace: str,
concept: str,
min_trust: int,
as_of_time: datetime | None = None,
candidate_ids: frozenset[str] | None = None,
include_flagged: bool = False,
include_history: bool = False,
) -> set[str]
IDs of entities in (namespace, concept) with >=1 assertion,
active at as_of_time (KI-036) or currently active if as_of_time
is None, whose effective trust_level >= min_trust (KI-047) —
min(author.trust_level, acting_as.trust_level) when the assertion
was made under delegation, matching govern/policy.py's identical
formula for effective trust (by analogy with SPEC §8.4's capability
rule), or just author.trust_level when it wasn't. A dangling
acting_as (no resolvable delegate) falls back to author.trust_level
via coalesce — see StorageBackend.entities_meeting_trust's
docstring for why. candidate_ids narrows the scan the same way as
entities_meeting_confidence (KI-037) — see its docstring.
include_flagged/include_history widen "active" the same way
entities_where() does (KI-093) — see its docstring.
Source code in src/ontolith/store/duckdb/backend.py
2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 | |
ontolith.store.migrations ¶
Backend-agnostic storage-format migration reporting types (SPEC §15, ADR-0052).
Each concrete adapter (store/sqlite/migrations.py, store/duckdb/migrations.py)
owns its own migration registry — the actual up/down DDL, which is
inherently backend-specific — and returns results shaped as the dataclasses
below so a caller (the CLI, or an SDK user) can report on either backend the
same way. See ADR-0052 for the full design: why format_version exists
separately from the per-namespace schema_version SPEC §6.4 already tracks,
why opening an out-of-date file is refused rather than silently upgraded, and
why reversible is declared per migration rather than assumed.
MigrationStep
dataclass
¶
One migration's outcome within a MigrationReport.
applied is False for every step in a dry-run report (SPEC §15's
"dry-run mode" requirement) — the step is pending, not yet run against
the file. In a real (non-dry-run) report every step has applied=True;
a migration whose up() itself fails raises instead of appearing here
half-applied, so a report is always either fully successful or absent.
description
instance-attribute
¶
Human-readable summary of what this migration does (and, for an irreversible one, why it can't be undone).
reversible
instance-attribute
¶
Whether this migration's down() can restore the prior version.
See ADR-0052's Decision section for what "reversible" means here —
the DDL rewrite is invertible; data held only in a column a migration
drops is still lost on down(), same as any additive migration's
inverse.
applied
instance-attribute
¶
True once this step has actually run; False in a dry-run report.
MigrationReport
dataclass
¶
MigrationReport(
from_version: int,
to_version: int,
steps: tuple[MigrationStep, ...],
dry_run: bool,
)
Result of a migrate_file(..., dry_run=...) call, either backend.
from_version
instance-attribute
¶
The format_version the file was at before this call (as read, or inferred for a file that predates format_version tracking — see ADR-0052).
to_version
instance-attribute
¶
The backend's current format_version (CURRENT_FORMAT_VERSION).
steps
instance-attribute
¶
Every migration between from_version and to_version, in the order
applied (or that would be applied, for a dry run). Empty when the file
was already current.
dry_run
instance-attribute
¶
Whether this report describes a preview (no writes made) or a real migration that already ran.