Skip to content

ontolith.plugins

ontolith.plugins

Plugin capability isolation (ADR-0015).

Public surface for plugin authors and hosts: - PluginManifest/PluginCapabilities/PluginKind — what a plugin declares - ReadOnlyView/WriteView — capability-scoped facades a plugin receives - PluginRegistry/LoadedPlugin — discovery and least-privilege loading - Importer/Exporter/Reasoner/Validator/Connector — protocol interfaces a plugin implements (SPEC §13.2) - ValidatorKbView — the structural read view Validator.validate()'s kb parameter accepts (KI-042, ADR-0029); useful for precisely typing a custom Validator's own validate() signature

PluginKind module-attribute

PluginKind = Literal[
    "importer",
    "exporter",
    "reasoner",
    "validator",
    "connector",
]

Plugin kinds covered by capability isolation.

Deliberately excludes StorageBackend/AuthProvider/PolicyStrategy/Embedder plugins: those are infrastructure-extension points the framework calls INTO (e.g. a StorageBackend plugin IS the persistence substrate underneath WriteView, not a principal-scoped actor calling through it) — they don't fit the capability-scoped-view model this module implements. See ADR-0015 "Plugin kinds in scope" and ADR-0020 (Embedder specifically: its Protocol takes no kb view parameter at all, so it structurally cannot participate in this module's ReadOnlyView/WriteView dispatch; it lives in core/embedder.py, injected into Ontology directly like Clock/IdProvider).

PluginCapabilities

Bases: BaseModel

Capabilities a plugin requests.

Attributes:

Name Type Description
storage Literal['read', 'propose', 'write']

Requested ceiling on the read/propose/write lattice (identity.principal._CAPABILITY_ORDER). Enforced by PluginRegistry — the plugin's effective capability is min(this, the capability granted at registration), further capped to "read" for read-only plugin kinds regardless of what is requested here.

network bool

Declares intent to make network calls. Enforced via OS syscall denial when False and the plugin runs isolated on Linux (ADR-0051); declared but not enforced with isolate=False or on a non-Linux platform.

filesystem bool

Declares intent to touch the filesystem. Same enforcement story as network (ADR-0051).

PluginManifest

Bases: BaseModel

A plugin's declared identity and capability request.

Attributes:

Name Type Description
name str

Plugin identifier. Becomes the id of the service-kind Principal PluginRegistry creates/reuses for this plugin.

version str

Plugin version string.

kind PluginKind

What kind of plugin this is — determines the ceiling on effective storage capability (read-only kinds are capped at "read" regardless of requested/granted capability) and which view type (ReadOnlyView vs WriteView) the plugin receives.

capabilities PluginCapabilities

Requested capabilities (see PluginCapabilities).

Connector

Bases: Protocol

Syncs KB state with an external system through a governed WriteView.

sync

sync(kb: WriteView) -> object

Reconcile the external system's state with the KB via kb.

Source code in src/ontolith/plugins/ports.py
def sync(self, kb: WriteView) -> object:
    """Reconcile the external system's state with the KB via `kb`."""
    ...

Exporter

Bases: Protocol

Exports KB data to an external target through a ReadOnlyView.

export

export(kb: ReadOnlyView, target: object) -> object

Read KB state via kb and write it out to target.

Source code in src/ontolith/plugins/ports.py
def export(self, kb: ReadOnlyView, target: object) -> object:
    """Read KB state via `kb` and write it out to `target`."""
    ...

Importer

Bases: Protocol

Imports external data into the KB through a governed WriteView.

import_

import_(source: object, kb: WriteView) -> object

Read source and write the resulting entities/assertions via kb.

Source code in src/ontolith/plugins/ports.py
def import_(self, source: object, kb: WriteView) -> object:
    """Read `source` and write the resulting entities/assertions via `kb`."""
    ...

Reasoner

Bases: Protocol

Derives new assertions from existing KB state through a WriteView.

Derived assertions MUST be submitted via kb.propose()/kb.propose_ref() — WriteView has no other write path. This is what SPEC §13.2's "reasoner- derived assertions MUST enter through the proposal path" means in practice: there is no bypass to forget to block.

derive

derive(kb: WriteView) -> None

Inspect KB state via kb and propose any derived assertions.

Source code in src/ontolith/plugins/ports.py
def derive(self, kb: WriteView) -> None:
    """Inspect KB state via `kb` and propose any derived assertions."""
    ...

Validator

Bases: Protocol

Validates an assertion against custom rules through a read-only KB view.

assertion is the thing under validation when a Validator is invoked per-assertion (e.g. via Ontology.validators — KI-042, ADR-0029). A Validator invoked for whole-entity completeness instead (e.g. via Ontology.completeness_validators) receives an assertion that is only a subject stand-in — implementations with that shape (like RequiredFieldsValidator) should read assertion.subject and re-query kb for current state, and must not rely on assertion's other fields describing anything currently true or just-committed.

validate

validate(
    assertion: Assertion, kb: ValidatorKbView
) -> list[str]

Return a list of validation error messages, empty if assertion is valid.

Source code in src/ontolith/plugins/ports.py
def validate(self, assertion: Assertion, kb: ValidatorKbView) -> list[str]:
    """Return a list of validation error messages, empty if `assertion` is valid."""
    ...

ValidatorKbView

Bases: Protocol

Structural read view a Validator may query (KI-042, ADR-0029).

Deliberately narrower than the concrete ReadOnlyView class this protocol is otherwise modeled on: Ontology itself (ontology.py) already exposes this exact method shape and is what gets passed directly as kb when a Validator registered via Ontology's own validators/completeness_validators constructor parameters runs synchronously inside the write path (as opposed to a Validator loaded through PluginRegistry.register(), which still receives a real, capability-scoped ReadOnlyView) — this path is always trusted and unsandboxed, regardless of ADR-0051: never pass a PluginRegistry- loaded plugin's LoadedPlugin.instance (an IsolatedPluginProxy when isolate=True) into validators/completeness_validators — Ontology itself isn't a ReadOnlyView/picklable, so it can't cross the sandbox boundary at all; call loaded.instance.validate(assertion, loaded.view) directly instead, the same way every other plugin kind is called through PluginRegistry. Importing Ontology here to spell that out as a second concrete type would be circular — ontology.py needs Validator's type for its own constructor parameters, and this module already imports ReadOnlyView/WriteView from plugins/views.py, which itself imports Ontology. This minimal Protocol is the common shape both ReadOnlyView and Ontology already satisfy structurally, with no inheritance or import required — the same pattern govern/policy.py's KbView uses for the same reason (KI-017, ADR-0025).

get_entity

get_entity(entity_id: str) -> Entity | None

Retrieve an entity by ID.

Source code in src/ontolith/plugins/ports.py
def get_entity(self, entity_id: str) -> Entity | None:
    """Retrieve an entity by ID."""
    ...

assertions

assertions(
    subject: str | None = None,
    predicate: str | None = None,
    status: str | None = "active",
) -> list[Assertion]

Query assertions.

Source code in src/ontolith/plugins/ports.py
def assertions(
    self,
    subject: str | None = None,
    predicate: str | None = None,
    status: str | None = "active",
) -> list[Assertion]:
    """Query assertions."""
    ...

query

query(concept: str) -> QueryBuilder

Create a query builder for a concept.

Source code in src/ontolith/plugins/ports.py
def query(self, concept: str) -> QueryBuilder:
    """Create a query builder for a concept."""
    ...

as_of

as_of(t: datetime | str) -> AsOfView

Return a read-only bitemporal view at time t (SPEC §11.4).

Source code in src/ontolith/plugins/ports.py
def as_of(self, t: datetime | str) -> "AsOfView":
    """Return a read-only bitemporal view at time t (SPEC §11.4)."""
    ...

LoadedPlugin dataclass

LoadedPlugin(
    manifest: PluginManifest,
    principal_id: str,
    view: ReadOnlyView,
    instance: object,
)

A discovered plugin bound to a capability-scoped view.

Attributes:

Name Type Description
manifest PluginManifest

The plugin's declared manifest.

principal_id str

The service-Principal id this plugin acts as.

view ReadOnlyView

ReadOnlyView or WriteView scoped to principal_id, per the plugin's effective capability.

instance object

The plugin's one protocol entrypoint, callable the same way the real object would be (instance.import_(...), instance.export(...), etc.). When registered with isolate=True (the default), this is an IsolatedPluginProxy (ADR-0051) that runs the call in a sandboxed child process — not the real, directly-instantiated plugin object, so isinstance(instance, SomePluginClass) no longer holds; use instance.plugin_class for that check instead. With isolate=False, this is the real, unsandboxed plugin instance, exactly as before ADR-0051.

PluginRegistry

PluginRegistry(kb: Ontology)

Discovers plugins via entry points and loads them with least privilege.

Source code in src/ontolith/plugins/registry.py
def __init__(self, kb: Ontology) -> None:
    self._kb = kb

register

register(
    entry_point_name: str,
    *,
    author: str,
    granted_capability: str = "propose",
    isolate: bool = True,
    require_enforcement: bool = False,
) -> LoadedPlugin

Discover, load, and sandbox a plugin by entry-point name.

Parameters:

Name Type Description Default
entry_point_name str

Name of the entry point under the "ontolith.plugins" group.

required
author str

Principal ID performing the registration — must hold admin capability.

required
granted_capability str

Ceiling on what storage capability this plugin may be granted, regardless of what its manifest requests. Defaults to "propose" (least privilege) — further capped to "read" for read-only plugin kinds.

'propose'
isolate bool

Run the plugin's protocol entrypoint in a sandboxed child process (ADR-0051), not this process. Defaults to True — deny-by-default, the same posture default temporality/MCP's read-propose-flag-only surface/capability floors already take elsewhere in this project. Pass False only for a plugin whose source/target argument genuinely can't cross a process boundary (an unpicklable, non-.write-shaped live object) or a fully trusted first-party plugin where the per-call subprocess overhead isn't worth paying — this restores the pre-ADR-0051 behavior exactly: the real, unsandboxed plugin instance, network/filesystem fully unenforced regardless of platform.

True
require_enforcement bool

Refuse to register (PluginError) rather than merely warn if OS-level capability enforcement can't even be attempted for this registration — isolate=False, or enforcement.enforcement_available() is False on this host/platform (currently: not Linux, or pyseccomp/ libseccomp missing). Defaults to False (today's behavior: warn and proceed) since flipping the default would refuse every registration on macOS/Windows outright — security review finding, M4 Workstream 7: an operator who needs a real guarantee that capabilities.network=False/ filesystem=False is enforced, not just declared, had no way to ask for one; the registration-time warning (_warn_if_unenforced_capabilities_requested) is the only signal otherwise, and it's advisory only. This same flag is also threaded through to every isolated call (sandbox.runner.run_isolated's own require_enforcement parameter): with require_enforcement=True, a call whose enforcement was available in principle at registration but whose specific attempt to install the filter fails at call time (e.g. a container's own outer seccomp profile blocks it) also refuses, rather than merely warning as it does with the default False — this call-time check is the narrower residual registration alone can't predict, not a second, independent guarantee (round-2 review finding: an earlier version of this docstring wrongly implied the call-time check fires even with require_enforcement= False, which the registered behavior — and this flag's own default-preserves-today's-behavior framing above — both contradict).

What this flag does NOT guarantee (round-3 review finding, M4 Workstream 7): it protects against a NON-ADVERSARIAL failure to install the filter — a container's outer seccomp profile blocking it, an unresolvable syscall name, missing libseccomp. It is NOT a guarantee against a plugin whose own module-level code or __init__ actively tries to defeat it: reproduced directly — a plugin's own code, which necessarily runs before the full (filesystem-inclusive) filter is even attempted (see sandbox.runner._child_main's own docstring for why), can reassign sandbox.enforcement.apply_capability_ enforcement itself, or any of the module-level names that function's own body depends on (_install_seccomp_filter, EnforcementResult, the syscall lists), or poison sys.modules["pyseccomp"], to make a completely unenforced call report success. A function reference captured before loading (round 3) defeats only the single most literal reassignment — round 4 found and corrected an earlier overclaim that this narrowed the attack in any meaningful way; it doesn't, since every other name the captured function resolves is equally reachable in the same shared module namespace — see KI-109's own extended text. Treat require_enforcement=True as hardening against misconfiguration and infrastructure limits, not as a security boundary against a hostile plugin — the process isolation boundary itself (a picklable-message IPC channel, no live object graph reachable) is what defends against that, unchanged by this flag either way.

False

Returns:

Type Description
LoadedPlugin

LoadedPlugin bound to a capability-scoped view.

Raises:

Type Description
AuthError

author is not a known principal

CapabilityError

author lacks admin capability

ValidationError

granted_capability is not a known capability level

PluginError

entry point not found or ambiguous, manifest missing/invalid, a write-capable plugin's effective capability resolved to "read", the plugin's name is already occupied by an unrelated principal, or require_enforcement=True but OS-level enforcement can't be attempted for this registration at all

Note

Logs a logging.WARNING (KI-014) if the plugin's manifest declares capabilities.network/.filesystem and either isolate=False was passed or no OS-level enforcement is available for this platform/capability (ADR-0051) — the declaration alone doesn't restrict anything in that case. Only logged on successful registration (the plugin actually becomes a standing in-process actor), not on a failed attempt.

Records a register_plugin AdminEvent on successful registration (KI-060), same "only on success" timing as the warning above. If this is the plugin's first registration (no existing principal), _ensure_principal's own create_principal(..., author=author) call also records a separate create_principal event — two events for one register() call in that case, one for each distinct action that actually happened.

Source code in src/ontolith/plugins/registry.py
def register(
    self,
    entry_point_name: str,
    *,
    author: str,
    granted_capability: str = "propose",
    isolate: bool = True,
    require_enforcement: bool = False,
) -> LoadedPlugin:
    """Discover, load, and sandbox a plugin by entry-point name.

    Args:
        entry_point_name: Name of the entry point under the
            "ontolith.plugins" group.
        author: Principal ID performing the registration — must hold
            `admin` capability.
        granted_capability: Ceiling on what storage capability this
            plugin may be granted, regardless of what its manifest
            requests. Defaults to "propose" (least privilege) —
            further capped to "read" for read-only plugin kinds.
        isolate: Run the plugin's protocol entrypoint in a sandboxed
            child process (ADR-0051), not this process. Defaults to
            `True` — deny-by-default, the same posture default
            temporality/MCP's read-propose-flag-only surface/capability
            floors already take elsewhere in this project. Pass
            `False` only for a plugin whose `source`/`target` argument
            genuinely can't cross a process boundary (an unpicklable,
            non-`.write`-shaped live object) or a fully trusted
            first-party plugin where the per-call subprocess overhead
            isn't worth paying — this restores the pre-ADR-0051
            behavior exactly: the real, unsandboxed plugin instance,
            network/filesystem fully unenforced regardless of platform.
        require_enforcement: Refuse to register (`PluginError`) rather
            than merely warn if OS-level capability enforcement can't
            even be attempted for this registration — `isolate=False`,
            or `enforcement.enforcement_available()` is False on this
            host/platform (currently: not Linux, or `pyseccomp`/
            libseccomp missing). Defaults to `False` (today's behavior:
            warn and proceed) since flipping the default would refuse
            every registration on macOS/Windows outright — security
            review finding, M4 Workstream 7: an operator who needs a
            real guarantee that `capabilities.network=False`/
            `filesystem=False` is enforced, not just declared, had no
            way to ask for one; the registration-time warning
            (`_warn_if_unenforced_capabilities_requested`) is the only
            signal otherwise, and it's advisory only. This same flag is
            also threaded through to every isolated *call*
            (`sandbox.runner.run_isolated`'s own `require_enforcement`
            parameter): with `require_enforcement=True`, a call whose
            enforcement was available in principle at registration but
            whose specific attempt to install the filter fails at call
            time (e.g. a container's own outer seccomp profile blocks
            it) also refuses, rather than merely warning as it does
            with the default `False` — this call-time check is the
            narrower residual registration alone can't predict, not a
            second, independent guarantee (round-2 review finding: an
            earlier version of this docstring wrongly implied the
            call-time check fires even with `require_enforcement=
            False`, which the registered behavior — and this flag's own
            default-preserves-today's-behavior framing above — both
            contradict).

            **What this flag does NOT guarantee (round-3 review
            finding, M4 Workstream 7):** it protects against a
            NON-ADVERSARIAL failure to install the filter — a
            container's outer seccomp profile blocking it, an
            unresolvable syscall name, missing libseccomp. It is NOT a
            guarantee against a plugin whose own module-level code or
            `__init__` actively tries to defeat it: reproduced directly
            — a plugin's own code, which necessarily runs before the
            full (filesystem-inclusive) filter is even attempted (see
            `sandbox.runner._child_main`'s own docstring for why),
            can reassign `sandbox.enforcement.apply_capability_
            enforcement` itself, or any of the module-level names that
            function's own body depends on (`_install_seccomp_filter`,
            `EnforcementResult`, the syscall lists), or poison
            `sys.modules["pyseccomp"]`, to make a completely unenforced
            call report success. A function reference captured before
            loading (round 3) defeats only the single most literal
            reassignment — round 4 found and corrected an earlier
            overclaim that this narrowed the attack in any meaningful
            way; it doesn't, since every other name the captured
            function resolves is equally reachable in the same shared
            module namespace — see KI-109's own extended text. Treat
            `require_enforcement=True` as hardening against
            misconfiguration and infrastructure limits, not as a
            security boundary against a hostile plugin — the process
            isolation boundary itself (a picklable-message IPC channel,
            no live object graph reachable) is what defends against
            that, unchanged by this flag either way.

    Returns:
        LoadedPlugin bound to a capability-scoped view.

    Raises:
        AuthError: author is not a known principal
        CapabilityError: author lacks admin capability
        ValidationError: granted_capability is not a known capability level
        PluginError: entry point not found or ambiguous, manifest
            missing/invalid, a write-capable plugin's effective
            capability resolved to "read", the plugin's name is
            already occupied by an unrelated principal, or
            require_enforcement=True but OS-level enforcement can't be
            attempted for this registration at all

    Note:
        Logs a `logging.WARNING` (KI-014) if the plugin's manifest
        declares `capabilities.network`/`.filesystem` and either
        `isolate=False` was passed or no OS-level enforcement is
        available for this platform/capability (ADR-0051) — the
        declaration alone doesn't restrict anything in that case. Only
        logged on successful registration (the plugin actually becomes
        a standing in-process actor), not on a failed attempt.

        Records a `register_plugin` `AdminEvent` on successful
        registration (KI-060), same "only on success" timing as the
        warning above. If this is the plugin's first registration
        (no existing principal), `_ensure_principal`'s own
        `create_principal(..., author=author)` call also records a
        separate `create_principal` event — two events for one
        `register()` call in that case, one for each distinct action
        that actually happened.
    """
    self._kb.require_admin(author)

    if require_enforcement and (not isolate or not enforcement.enforcement_available()):
        reason = (
            "isolate=False was passed for this registration"
            if not isolate
            else "no OS-level enforcement is available on this host/platform "
            "(not Linux, or pyseccomp/libseccomp is missing)"
        )
        raise PluginError(
            f"require_enforcement=True but OS-level capability enforcement cannot be "
            f"attempted for this registration ({reason}) — refusing to register rather "
            "than silently proceed with capabilities.network/.filesystem unenforced "
            "(ADR-0051)."
        )

    plugin_obj = self._load_entry_point(entry_point_name)
    manifest = self._load_manifest(plugin_obj, entry_point_name)
    effective_capability = self._effective_capability(manifest, granted_capability)

    if manifest.kind in _WRITE_CAPABLE_KINDS and effective_capability == "read":
        raise PluginError(
            f"Plugin {manifest.name!r} (kind={manifest.kind!r}) needs at least "
            f"'propose' storage capability to function, but resolved to 'read' "
            f"(manifest requested {manifest.capabilities.storage!r}, granted "
            f"{granted_capability!r}). Increase capabilities.storage in the "
            "manifest or the granted_capability passed to register()."
        )

    principal_id = self._ensure_principal(
        manifest, effective_capability, author, entry_point_name
    )
    view = self._build_view(principal_id, effective_capability)
    # Warn/record only once registration actually succeeds (review
    # finding for the warning, same reasoning extends to the audit
    # event, KI-060): this plugin is now live in-process with
    # unenforced network/filesystem access, which is the claim the
    # warning makes — a registration attempt that fails before this
    # point never becomes a standing in-process actor at all, and
    # shouldn't be recorded as though one was created.
    self._warn_if_unenforced_capabilities_requested(manifest, isolate)
    self._kb.record_admin_event(author, "register_plugin", manifest.name)

    instance: object = (
        IsolatedPluginProxy(
            entry_point_name,
            manifest,
            type(plugin_obj),
            self._kb.observability,
            require_enforcement=require_enforcement,
        )
        if isolate
        else plugin_obj
    )
    return LoadedPlugin(
        manifest=manifest,
        principal_id=principal_id,
        view=view,
        instance=instance,
    )

ReadOnlyView

ReadOnlyView(kb: Ontology, principal_id: str)

Read-only facade over Ontology, scoped to a single principal id.

Source code in src/ontolith/plugins/views.py
def __init__(self, kb: Ontology, principal_id: str) -> None:
    self._kb = kb
    self._principal_id = principal_id

principal_id property

principal_id: str

The principal id this view is bound to.

get_entity

get_entity(entity_id: str) -> Entity | None

Retrieve an entity by ID.

Source code in src/ontolith/plugins/views.py
def get_entity(self, entity_id: str) -> Entity | None:
    """Retrieve an entity by ID."""
    return self._kb.get_entity(entity_id)

schema

schema() -> SchemaIR | None

The current schema for this view's namespace, or None if no schema has been applied yet.

Added for the RDF/OWL exporter (SPEC §13.3), the first reference plugin needing schema access rather than just entity/assertion data — schema is namespace-scoped, non-sensitive metadata (no principal-specific governance concern the way write access is), so this is a plain passthrough with no capability narrowing. Delegates to Ontology.schema(), not self._kb.backend directly — every other method on this view delegates to an Ontology method too (this module's own docstring calls it "a safe method subset of Ontology"); reaching past that to the storage port would have been the only exception.

Source code in src/ontolith/plugins/views.py
def schema(self) -> SchemaIR | None:
    """The current schema for this view's namespace, or `None` if no
    schema has been applied yet.

    Added for the RDF/OWL exporter (SPEC §13.3), the first reference
    plugin needing schema access rather than just entity/assertion
    data — schema is namespace-scoped, non-sensitive metadata (no
    principal-specific governance concern the way write access is),
    so this is a plain passthrough with no capability narrowing.
    Delegates to `Ontology.schema()`, not `self._kb.backend` directly —
    every other method on this view delegates to an `Ontology` method
    too (this module's own docstring calls it "a safe method subset of
    Ontology"); reaching past that to the storage port would have been
    the only exception.
    """
    return self._kb.schema()

assertions

assertions(
    subject: str | None = None,
    predicate: str | None = None,
    status: str | None = "active",
) -> list[Assertion]

Query assertions.

Source code in src/ontolith/plugins/views.py
def assertions(
    self,
    subject: str | None = None,
    predicate: str | None = None,
    status: str | None = "active",
) -> list[Assertion]:
    """Query assertions."""
    return self._kb.assertions(subject=subject, predicate=predicate, status=status)

query

query(concept: str) -> QueryBuilder

Create a query builder for a concept.

Source code in src/ontolith/plugins/views.py
def query(self, concept: str) -> QueryBuilder:
    """Create a query builder for a concept."""
    return self._kb.query(concept)

as_of

as_of(t: datetime | str) -> AsOfView

Return a read-only bitemporal view at time t (SPEC §11.4).

Source code in src/ontolith/plugins/views.py
def as_of(self, t: datetime | str) -> AsOfView:
    """Return a read-only bitemporal view at time t (SPEC §11.4)."""
    return self._kb.as_of(t)

WriteView

WriteView(kb: Ontology, principal_id: str)

Bases: ReadOnlyView

Governed-write facade over Ontology, scoped to a single principal id.

Adds create_entity/propose/propose_ref/retract — never the direct-write bypass (assert_literal/assert_ref). author is always the view's own principal id. propose/propose_ref/retract go through Ontology's SPEC §10 conflict routing and policy evaluation; create_entity is capability-gated (Ontology.create_entity requires >= propose) but is NOT itself SPEC §10 governed — entities are structural records, not assertions, and have no proposal/review path in this codebase.

Source code in src/ontolith/plugins/views.py
def __init__(self, kb: Ontology, principal_id: str) -> None:
    self._kb = kb
    self._principal_id = principal_id

create_entity

create_entity(
    concept: str, natural_key: str | None = None
) -> Entity

Create a new entity, authored by this view's principal.

Source code in src/ontolith/plugins/views.py
def create_entity(self, concept: str, natural_key: str | None = None) -> Entity:
    """Create a new entity, authored by this view's principal."""
    return self._kb.create_entity(concept, author=self._principal_id, natural_key=natural_key)

propose

propose(
    subject: str,
    predicate: str,
    value: str,
    value_type: str,
    *,
    confidence: float | None = None,
    source: str | None = None,
    rationale: str | None = None,
) -> tuple[Proposal, Decision]

Submit a literal assertion through the proposal/policy path (SPEC §9).

Source code in src/ontolith/plugins/views.py
def propose(
    self,
    subject: str,
    predicate: str,
    value: str,
    value_type: str,
    *,
    confidence: float | None = None,
    source: str | None = None,
    rationale: str | None = None,
) -> tuple[Proposal, Decision]:
    """Submit a literal assertion through the proposal/policy path (SPEC §9)."""
    return self._kb.propose(
        subject,
        predicate,
        value,
        value_type,
        self._principal_id,
        confidence=confidence,
        source=source,
        rationale=rationale,
    )

propose_ref

propose_ref(
    subject: str,
    predicate: str,
    target: str,
    *,
    confidence: float | None = None,
    source: str | None = None,
    rationale: str | None = None,
) -> tuple[Proposal, Decision]

Submit a reference (relation) assertion through the proposal/policy path (SPEC §9).

Source code in src/ontolith/plugins/views.py
def propose_ref(
    self,
    subject: str,
    predicate: str,
    target: str,
    *,
    confidence: float | None = None,
    source: str | None = None,
    rationale: str | None = None,
) -> tuple[Proposal, Decision]:
    """Submit a reference (relation) assertion through the proposal/policy path (SPEC §9)."""
    return self._kb.propose_ref(
        subject,
        predicate,
        target,
        self._principal_id,
        confidence=confidence,
        source=source,
        rationale=rationale,
    )

retract

retract(assertion_id: str) -> tuple[Proposal, Decision]

Propose retraction of an assertion through the policy path (SPEC §9).

Source code in src/ontolith/plugins/views.py
def retract(self, assertion_id: str) -> tuple[Proposal, Decision]:
    """Propose retraction of an assertion through the policy path (SPEC §9)."""
    return self._kb.retract(assertion_id, self._principal_id)