osyra_ome
Private pre-release / network boundary: The currently usable customer surface is offline verification of an existing signed artifact against an out-of-band trust anchor. Memory/network RPC APIs and host snippets on this page describe protocol and interface shape; the public Edge bridge, package publication, and release acceptance remain pending. Do not treat
api.osyra.ai:443examples as a released customer route.
Osyra Memory Engine (OME) Python SDK.
Verified Memory client SDK exposing four namespaced sub-clients (``verify``, ``memory``, ``proof``, ``witness``) per design doc §3.2. The wired surface is ``verify.receipt`` + ``memory`` write/read/tombstone; the ``proof`` and ``witness`` namespaces (and ``verify.bundle_manifest|proof| witness`` + ``memory.batch_write|export_bundle|import_bundle``) are typed ``NotImplementedError`` v0.2 surfaces per ADR-344.
This is the canonical cross-language reference implementation for the TypeScript sibling at ``@osyra/ome`` (AP00014); both ship synchronously per the cross-language parity gate (ADR-045).
Public surface (curated re-exports; per design doc §3.2 + ADR-044 public API stability commitment):
>>> from osyra_ome import OmeClient, OmeAsyncClient >>> client = OmeClient(target="api.osyra.ai:443", token="...") >>> claim_id = client.memory.write_claim(claim) >>> ok = client.verify.receipt(signed.encoded_cbor, signed.sig, trusted_pub) >>> client.close()
For async use, see :class:`osyra_ome.OmeAsyncClient`.
Top-level convenience re-export: ``verify_receipt`` (a thin functional alias of ``client.verify.receipt`` that does not require an open ``OmeClient``). Available under ``osyra_ome.verify_receipt`` (and as a sub-module import at ``osyra_ome.verify.receipt.verify_receipt``); strengthens the BC-13 license-boundary by giving Apache-2.0 consumers a one-line import path.
License: Osyra Customer SDK License (NDA-restricted). See LICENSE.md.
Constants
| Name | Value |
| --- | --- |
| __version__ | '1.0.0' |
Functions
extract_attested_claim_fields
def extract_attested_claim_fields(encoded_cbor: bytes) -> AttestedClaimFieldsExtract the attested ``content_hash`` (+ ``embedding_hash``) from a preimage.
Strict-canonical-decodes the Form-B claim signing-preimage bytes (the bytes the Ed25519 signature attests), asserts the claim dsTag, and reads the short-key ``ch`` / ``eh`` byte-string fields from the claim map.
Args:
encoded_cbor: The claim signing-preimage bytes
(SignedClaim.encoded_cbor; the bytes verify_receipt covers).
Returns:
An :class:AttestedClaimFields carrying the authentic 32-byte
content_hash and, when present (non-zero), the authentic 32-byte
embedding_hash.
Raises:
OmeWireFormatError: the bytes are not strict-canonical CBOR, are not a
2-tuple, carry the wrong dsTag (cross-protocol confusion), the
payload is not a CBOR map, or ch is absent / not a 32-byte
byte string. Fail-CLOSED: there is no path that returns a partial
or default result.
verify_body_against_signed_claim
def verify_body_against_signed_claim(body: bytes, encoded_cbor: bytes, signature: bytes, trusted_pub_key: bytes) -> bytesBind a read-back ``body`` to the signature-attested content hash.
The single mandatory read-path control for a body-bearing claim. Verifies the signature, extracts the ATTESTED ``ch`` from the SIGNED preimage, and asserts ``SHA-256(body) == ch`` in constant time. On success returns the ``body`` unchanged (so the call site can write ``body = verify_body_...``).
Args:
body: The raw claim body bytes returned by the read path (e.g.
GetClaimBody).
encoded_cbor: The claim signing-preimage bytes (the bytes the signature
attests; from the SignedClaim for THIS body's claim).
signature: The 64-byte detached Ed25519 signature over encoded_cbor.
trusted_pub_key: The caller's trusted 32-byte Ed25519 public key. REQUIRED
— a missing / wrong-length key fails closed (the in-band
signer_pub is never a trust anchor).
Returns:
body unchanged, once it is proven to match the signature-attested
content hash.
Raises:
OmeVerifyError: trusted_pub_key is missing / not 32 bytes, the
signature does not verify over encoded_cbor under the trusted
key, or SHA-256(body) does not equal the attested content hash.
OmeWireFormatError: encoded_cbor is empty / not strict-canonical /
wrong dsTag / missing the attested ch (propagated from
:func:extract_attested_claim_fields and :func:verify_receipt).
verify_ocv_composite
def verify_ocv_composite(receipt_bytes: bytes, *, expected_signer_pub: bytes, leaf_signer_pubs: Sequence[bytes] | None = None) -> boolVerify an OCV-1.0 composite receipt locally (offline, structural + crypto).
Proves the composite is a **well-formed, single-(outer)-signed, tenant-uniform, depth-1, correctly-bound chain** (see the module docstring's HONEST SCOPE for the boundary). It does NOT re-decide the per-kind leaf semantics or burn nonces — those are the L5 Verifier-of-Record's job (AP00016 ``verify_composite.go``).
Args:
receipt_bytes: Canonical-CBOR bytes of the OUTER EAT COSE_Sign1
envelope (the composite receipt).
expected_signer_pub: The 32-byte Ed25519 public key the caller trusts
as the composite verifier-of-record (the OUTER envelope signer).
REQUIRED — the SDK fails CLOSED with no implicit trust.
leaf_signer_pubs: The rotation-tolerant candidate key set the leaves'
COSE_Sign1 signatures are verified against (the first that verifies
each leaf wins). V1 composites are tenant-uniform, so the leaves
typically share the outer workspace anchor. Defaults to
[expected_signer_pub] when omitted (the V1 single-anchor case).
Returns:
True on successful verification.
Raises: OmeWireFormatError: the bytes are not well-formed strict-canonical CBOR, the envelope/claim/ocv-body shape is wrong, or a tree / binding-spec is malformed. OmeVerifyError: the outer or a leaf COSE signature does not verify, a tenant/depth/alg invariant is violated, or a committed-digest binding does not hold (anti-splice).
verify_oeraser
def verify_oeraser(oeraser_body: bytes, *, destruction_authority_pubkeys: Sequence[bytes], now: int) -> boolRe-decide an OERaseR verified-forgetting receipt body, offline and fail-closed.
Re-derives the four legs (erasurePinned ∧ retentionPermits ∧ keyDestroyed ∧ noResurrection) + the erasure_binding terminal weld over the disclosed witnesses in ``oeraser_body``, trusting ONLY the relying-party-pinned key-destruction authority set. Byte-identical to the Go reference (``AP00016``) + Java emitter (``AP00011``) — the frozen ENG-2316 KAT is the cross-language oracle.
Args:
oeraser_body: The canonical-CBOR oeraser-body bytes (§4.2.1) — the opaque EAT
receipt body. The frozen ENG-2316 KAT unit.
destruction_authority_pubkeys: The relying party's PINNED set of 32-byte raw Ed25519
key-destruction-authority public keys (rotation-tolerant — the attestation must
verify under ANY one). An EMPTY set fails closed: with no pinned authority, the
keyDestroyed leg cannot be satisfied (KEY_NOT_DESTROYED). This is the trust
anchor — the DPA pins the authority it trusts, NOT the controller's say-so.
now: The attested clock value (unsigned-64-bit seconds) the retention window is
evaluated against. Supplied by the relying party; the re-decision NEVER reads a
clock of its own, so it is reproducible offline.
Returns:
True on a full accept (all four legs + the terminal weld passed). NEVER returns
False — every reject raises, so a caller cannot accidentally trust a receipt that
did not fully re-decide.
Raises:
OmeWireFormatError: the body is not strict-canonical CBOR, or violates the fixed
keyasint schema / a field byte-length (OSY-EAT-6308), or now /
destruction_authority_pubkeys are the wrong type.
OmeVerifyError: a leg or the terminal weld failed — carries the same OSY-EAT-NNNN
code the Go re-decider emits (6370 KEY_NOT_DESTROYED, 6371 RETENTION_FORBIDS,
6372 LEGAL_HOLD_ACTIVE, 6373 ERASURE_ROOT_MISMATCH, 6374 TOMBSTONE_INCONSISTENT,
6375 RESURRECTION_DETECTED, 6376 UNVERIFIABLE_POLICY).
verify_receipt
def verify_receipt(preimage_bytes: bytes, signature: bytes, trusted_pub_key: bytes) -> boolVerify a SignedClaim receipt: a DETACHED bare-Ed25519 signature.
The signature covers the wire signing-preimage bytes VERBATIM — the Form-B 2-tuple ``canonical_cbor([tstr("osyra-ome-claim-v1"), claimMap])`` (claim 2-tuple wire shape per ADR-157 + ADR-008; canonical-CBOR strict-decode per ``AP00011-OME-FILE-FORMAT-V1.md`` §4.2). Verification is::
Ed25519.verify(trusted_pub_key, signature, preimage_bytes)over ``preimage_bytes`` exactly as supplied — **the preimage is NEVER re-encoded** (that is the CRYPTO-2 fix). The preimage is canonical- decoded only to confirm its 2-tuple shape and to bind the dsTag (a Claim signature must never be accepted as a Witness/Bundle — cross- protocol confusion guard).
Args:
preimage_bytes: The raw wire signing-preimage bytes (the bytes the
signature attests). For a :class:~osyra_ome.types.SignedClaim
this is its encoded_cbor field.
signature: The 64-byte detached Ed25519 signature.
trusted_pub_key: The 32-byte Ed25519 public key the caller trusts
(the trust anchor — CRYPTO-1 / A1). The envelope's in-band
signer_pub is attacker-controlled and is NOT used here.
Returns:
True iff the signature verifies over preimage_bytes under
trusted_pub_key; False on a genuine Ed25519 verify-false
over a well-formed, correctly-tagged preimage.
Raises:
OmeWireFormatError: the preimage is not bytes, is empty, is not
strict-canonical CBOR, is not a 2-tuple, or carries the wrong
dsTag (cross-protocol confusion).
OmeVerifyError: signature is not 64 bytes, trusted_pub_key
is not 32 bytes / not a valid Ed25519 point.
Classes
class AttestedClaimFields
Bases: NamedTuple
The signature-attested hash fields decoded from a claim preimage.
Attributes:
content_hash: The signature-attested content hash (SHA-256; 32 bytes).
Always present (a claim preimage MUST carry ch).
embedding_hash: The signature-attested embedding hash (SHA-256;
32 bytes), present ONLY when the claim carries a NON-ZERO embedding
digest. The proto always encodes an eh field; a 32-byte zero
digest means "no embedding" and is reported as None here (a
chat-message claim has no embedding). Mirrors the TS
AttestedClaimFields.embeddingHash? optionality.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| content_hash | bytes | |
| embedding_hash | bytes | None | |
class BundleManifest
Bases: _OmeBaseModel
`.ome` ZIP bundle manifest (mirrors ome.v1 export bundle manifest).
The first chunk of an ``ExportBundle`` stream is this manifest; subsequent chunks are raw ZIP bytes. Signature uses dsTag "osyra-ome-bundle-v1".
v1.0.0 surface: just enough fields for ``client.verify.bundle_manifest(path)`` to validate the manifest signature against the workspace key.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| claim_count | int | Field(default=0, ge=0) |
| created_ms | int | Field(ge=0) |
| encoded_cbor | bytes | Field(min_length=1) |
| sig | _Ed25519Sig | |
| signer_pub | _Ed25519PubOptional | b'' |
| spec_version | str | Field(min_length=1) |
| workspace_id | str | Field(min_length=1) |
class Claim
Bases: _OmeBaseModel
A signed-or-unsigned memory record (mirrors ome.v1.Claim proto).
Fields map 1:1 to the canonical .proto message (see proto comments for OAP-MEM v0.1 §4.1 wire-key mapping). Names use snake_case per Python convention; canonical CBOR encoding goes through the osyra-claims-common artifact (NOT this class directly) so the Python+TS public surface need not be the wire-format authority.
Hashing fields (``content_hash``, ``embedding_hash``) are 32-byte SHA-256 digests; ``id`` is 16-byte UUID v7. Length constraints are enforced by Pydantic at construction time — invalid bytes raise ``ValidationError`` (Pydantic v2's structured exception) BEFORE the object is exposed to the user. This is a hard fail-closed gate.
Note: the v1.0.0 SDK does NOT enforce the per-field W3C DID NFC normalization rules described in the proto comment — Python's default ``str`` is NFC on most platforms (CPython on Linux/macOS), which is correct, but Windows behaviour is platform-dependent. Customers running on Windows MUST call ``unicodedata.normalize("NFC", s)`` on issuer_did / subject_id strings before constructing Claims. Codified in ADR-044 v0.2 platform compatibility table.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| body | bytes | Field(default=b'', max_length=16 * 1024 * 1024) |
| content_hash | _Sha256 | |
| embedding_hash | _Sha256 | |
| id | _UuidV7Bytes | |
| issuer_did | str | Field(min_length=1) |
| kind | ClaimKind | |
| parents | tuple[_UuidV7Bytes, ...] | Field(default=()) |
| subject_id | str | Field(min_length=1) |
| ts | int | Field(ge=0) |
| workspace_id | str | Field(min_length=1) |
class ClaimKind
Bases: IntEnum
OAP-MEM v0.1 ClaimKind discriminator (mirrors ome.v1.ClaimKind enum).
Wire encoding: int32 with the enum value. Cross-language parity: Python's ``IntEnum`` accepts both name and value at deserialization; Pydantic v2 serializes by value on JSON output. The TS sibling declares this as a `const enum` with the same numeric values, so over-the-wire compatibility is preserved.
Values match the proto exactly to avoid translation bugs: UNSPECIFIED = 0 # reserved sentinel ASSERTION = 1 DERIVATION = 2 TOMBSTONE = 3 CONSOLIDATION = 4 # OME extension above OAP-MEM v0.1
Higher values (5..15) are RESERVED for V1.5+ extensions per the proto convention.
class CompletenessWitness
Bases: _OmeBaseModel
Top-K signed completeness witness over a workspace JMT.
Signature uses dsTag "osyra-ome-cw-v1". The verify-side mini-kernel re-runs signature verification BEFORE root comparison (M2 from the /osyra-security review — avoids the root-equality oracle side-channel).
Note: the v1.0.0 surface exposes only the fields needed for ``client.verify.witness(bytes)``. Full witness structure including gap proofs lands in v0.2 via the ``WitnessTopResponse`` proto.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| encoded_cbor | bytes | Field(min_length=1) |
| sig | _Ed25519Sig | |
| signer_pub | _Ed25519PubOptional | b'' |
| snapshot_at_unix_ms | int | Field(ge=0) |
| workspace_jmt_root | _Sha256 | |
class GatedCapabilityGrant
Bases: BaseModel
One delegated-capability grant: the agent may perform ``action`` on ``key``.
The §3 L1 capability leg. ``action`` is ``"read"`` | ``"write"``.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| action | str | |
| key | str | |
class GatedDecision
Bases: BaseModel
The sanitized gate decision projection (mirrors the edge ``AmgateDecision`` type).
Every field is carried on the wire — the SDK fabricates nothing. ``allowed`` is the admit/deny verdict; ``stage`` / ``code`` / ``reason`` explain it (``reason`` is server-sanitized and never leaks secrets).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| allowed | bool | |
| code | str | |
| decision_id | str | Field(alias='decisionId') |
| key | str | |
| op | str | |
| reason | str | |
| stage | str | |
| timestamp | str | |
class GatedMemoryClient
A producer client for the attested-memory gate over the edge GraphQL surface.
Construct with the edge GraphQL URL + the same bearer JWT the gRPC ``OmeClient`` uses; the edge derives the workspace server-authoritatively from that token. Pass an ``http_client`` to inject a custom (or mock) transport; otherwise the client owns one and closes it on :meth:`close` / context exit.
Methods
__init__
def __init__(self, edge_url: str, *, token: str | None = None, workspace_id: str | None = None, timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS, http_client: httpx.Client | None = None) -> Noneclose
def close(self) -> NoneClose the owned HTTP client (a no-op for an injected ``http_client``).
gated_read
def gated_read(self, key: str, *, capability: list[GatedCapabilityGrant] | None = None, source_kind: str | None = None) -> GatedReadResultRead ``key`` through the attested-memory READ-gate.
Returns the gate outcome (``admitted`` + the base64-decoded ``value`` iff admitted + the decision). A denied read returns ``admitted=False`` / ``value=None`` — not an exception. The confused-deputy request-supplied key/epoch probe is intentionally unreachable from this surface (the schema has no such field).
gated_write
def gated_write(self, key: str, value: bytes, *, capability: list[GatedCapabilityGrant] | None = None) -> GatedWriteResultCommit ``value`` at ``key`` through the attested-memory WRITE-gate.
Returns the gate outcome (``committed`` + the decision). A policy deny returns ``committed=False`` — NOT an exception. Raises :class:`OmeAuthError` / :class:`OmeTransportError` / etc. on auth / transport / malformed-response failures.
class GatedReadResult
Bases: BaseModel
The outcome of a gated read.
``value`` is present iff ``admitted`` (a denied read carries none).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| admitted | bool | |
| decision | GatedDecision | |
| value | bytes | None | |
class GatedWriteResult
Bases: BaseModel
The outcome of a gated write. A denied write is a provable no-op (``committed=False``).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| committed | bool | |
| decision | GatedDecision | |
| epoch | str | |
class InferenceClient
A producer client for the edge inference SSE stream + its OIR receipt.
Construct with the edge inference stream URL (``…/api/v1/inference/stream``) + the same bearer JWT the gRPC ``OmeClient`` uses. Pass an ``http_client`` to inject a custom (or mock) transport; otherwise the client owns one and closes it on :meth:`close` / context exit.
Methods
__init__
def __init__(self, inference_url: str, *, token: str | None = None, workspace_id: str | None = None, timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS, http_client: httpx.Client | None = None) -> Noneclose
def close(self) -> NoneClose the owned HTTP client (a no-op for an injected ``http_client``).
complete
def complete(self, model: str, *, prompt: str | None = None, messages: list[InferenceMessage] | None = None, max_tokens: int | None = None, temperature: float | None = None, top_p: float | None = None, stop: list[str] | None = None) -> InferenceResultDrive an inference to completion and return the assembled output + the OIR receipt.
Consumes the whole SSE stream. Raises :class:`OmeAuthError` / :class:`OmePolicyError` / :class:`OmeTransportError` / :class:`OmeWireFormatError` on auth / policy / transport / malformed failures; a normal stream returns an :class:`InferenceResult` (with ``oir_b64=None`` when the gateway did not emit a receipt).
stream
def stream(self, model: str, *, prompt: str | None = None, messages: list[InferenceMessage] | None = None, max_tokens: int | None = None, temperature: float | None = None, top_p: float | None = None, stop: list[str] | None = None) -> Iterator[str]Yield each delta token as it arrives (the terminal OIR frame carries no delta).
class InferenceMessage
Bases: BaseModel
One OpenAI-shaped chat turn.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| content | str | |
| role | str | |
class InferenceResult
Bases: BaseModel
The assembled outcome of a streamed inference.
``output`` is the ordered concatenation of the delta tokens (the authoritative output the edge hashes into the OIR — ADR-286). ``oir_b64`` is the base64 OIR receipt when the gateway emitted one, else ``None`` (emit dark / not configured — NEVER a fabricated receipt).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| finish_reason | str | None | None |
| oir_b64 | str | None | Field(default=None) |
| output | str | |
| usage | TokenUsage | None | None |
class JMTProof
Bases: _OmeBaseModel
JMT inclusion or non-membership proof (mirrors ome.v1.JMTProof).
Layout matches OAP-MEM v0.1 §4.4 (``JMTProof`` CDDL). The verify-side mini-kernel implements the verification algorithm; this model is just the data container. ``depth`` MUST equal ``len(levels)``.
``absent_kind`` discriminates non-membership proofs: 0 = UNSPECIFIED (rejected by server with OSY-MEM-5152) 1 = ABSENT_EMPTY_SLOT (path terminates at a nil child) 2 = ABSENT_DIFFERENT_LEAF (path terminates at a leaf with different key)
``other_key`` + ``other_value`` are populated ONLY when ``absent_kind == 2``; empty otherwise.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| absent_kind | int | Field(default=0, ge=0, le=2) |
| depth | int | Field(ge=0, le=64) |
| levels | tuple[tuple[bytes, ...], ...] | Field(default=()) |
| other_key | bytes | Field(default=b'', max_length=16) |
| other_value | bytes | Field(default=b'', max_length=32) |
class OmeAsyncClient
Async gRPC client for the Osyra Memory Engine (OME) service.
Functional mirror of :class:`osyra_ome.OmeClient` — same constructor kwargs, same namespaces, async-def methods.
Lifecycle:
- Construction creates an idle
grpc.aio.Channel; first call dials. await close()performs graceful shutdown (5s default).- Supports async context-manager protocol:
async with OmeAsyncClient(...) as client:.
Methods
__init__
def __init__(self, target: str, *, token: str | None = None, workspace_id: str | None = None, workspace_tier: str | None = None, timeout_seconds: float = DEFAULT_DEADLINE_SECONDS, insecure: bool = False, channel_options: list[tuple[str, object]] | None = None) -> Noneclose (async)
async def close(self, *, grace_seconds: float = DEFAULT_CLOSE_GRACE_SECONDS) -> NoneGracefully shutdown the gRPC channel.
class OmeAuthError
Bases: OmeError
Token expired / invalid / missing.
Raised when the bearer token attached to the gRPC metadata is rejected by the Edge Gateway (JWT signature invalid, exp claim in the past, missing required scope, etc.).
Server codes: ``OSY-AUTH-1001..1099`` (per osyra-response-common error-code registry). Local code: ``OSY-SDK-1000``.
Note: this is auth-N (identity), NOT auth-Z (policy). For policy denials see :class:`OmePolicyError`.
class OmeBodyUnavailableError
Bases: OmeError
A claim was found, but its body bytes are not available on this read path.
Raised when a caller (typically a body-bearing agent-framework adapter such as the LangGraph checkpoint saver or the AutoGen memory store) resolves a *known* ``claim_id`` to a :class:`~osyra_ome.types.Claim` whose ``body`` is empty (``b""``).
ADAPT2-1 (ENG-2225): the v0.1 read surface (``client.memory.get_claim``, wired over ``ListClaims``) returns a sanitized *projection* that NEVER carries the opaque claim body — ``body=b""`` on every read (see :meth:`osyra_ome.OmeClient.MemoryNamespace.get_claim`). A body-bearing adapter that reconstructs application state from the body therefore has NOTHING to reconstruct from. Returning ``None`` / an empty list in that case is a SILENT data-loss: an agent resuming a thread would behave as though the thread were brand-new, discarding every checkpoint it had written. This SDK fails CLOSED instead — it raises so the caller can diagnose the gap rather than silently lose memory.
Distinct from :class:`OmeNotFoundError` (the claim does not resolve at all) — here the claim *exists* and its metadata verified; only the body transport is missing. Body-bearing read-back is deferred to v0.2 via a dedicated ``GetClaimBody`` gRPC RPC (NOT the aspirational REST path that earlier docstrings referenced and that the server does not implement).
Local code: ``OSY-SDK-5302``.
class OmeBundleError
Bases: OmeError
`.ome` ZIP bundle integrity or manifest failure.
Raised by:
- ``client.verify.bundle_manifest(path)`` — manifest decode/verify failed
- ``client.memory.export_bundle/import_bundle(...)`` — server bundle RPCs
Server codes: ``OSY-MEM-5251..5254``. Local code: ``OSY-SDK-5250``.
class OmeClient
Sync gRPC client for the Osyra Memory Engine (OME) service.
Construction:
client = OmeClient(
target="api.osyra.ai:443",
token="ey...", # bearer JWT (Edge-validated)
workspace_id="ws-...", # binds Postgres GUC for tenancy
timeout_seconds=30.0,
insecure=False, # set True ONLY for local dev
)Attributes: verify, memory, proof, witness — namespaced sub-clients per design doc §3.2 (frozen API surface from v0.1.0; ADR-044).
Thread-safety: a single :class:`OmeClient` instance is safe to share across threads. The underlying gRPC channel is thread-safe; the only state-mutating operation is ``close()`` which is itself guarded by an internal lock.
Security:
- TLS by default —
insecure=Truerequires explicit opt-in. - Bearer token attached as
authorizationmetadata on every call (per Edge Gateway contract). NEVER logged or returned in exceptions (we redact the token-prefix in repr). - Hostname verification is non-optional (uses grpc's default SSL credentials with system root CAs).
Methods
__init__
def __init__(self, target: str, *, token: str | None = None, workspace_id: str | None = None, workspace_tier: str | None = None, timeout_seconds: float = DEFAULT_DEADLINE_SECONDS, insecure: bool = False, channel_options: list[tuple[str, object]] | None = None) -> Noneclose
def close(self, *, grace_seconds: float = DEFAULT_CLOSE_GRACE_SECONDS) -> NoneGracefully shutdown the gRPC channel.
Idempotent — calling close() a second time is a no-op. After close, all sub-client methods raise ``RuntimeError("client closed")``.
class OmeError
Bases: Exception
Root error type for the Osyra OME SDK.
Catch this to handle any SDK-originated error generically. Subclasses carry the same constructor signature, so callers can inspect ``.code`` + ``.message`` on every raised exception.
Attributes:
message: Human-readable description of the failure.
code: Stable OSY-*-NNNN error code from the server, or one of
the SDK-internal codes (OSY-SDK-NNNN) for client-side
failures (e.g. CBOR decode that never reached the wire).
None only when raised speculatively from a non-coded
context (rare; treat as best-effort).
cause: Optional underlying exception (set by SDK internals
via raise ... from exc; accessible via __cause__
too for compatibility with PEP 3134).
Methods
__init__
def __init__(self, message: str, *, code: str | None = None, cause: BaseException | None = None) -> Noneclass OmeNotFoundError
Bases: OmeError
Resource not found in caller's workspace.
Raised when:
- ``client.memory.get_claim(id)`` — claim_id doesn't resolve OR the RLS-filtered read returned empty (workspace-existence is NEVER leaked across tenants)
- any ``ProveTombstoned`` / ``GetReceipt`` resolved to empty
Server codes: ``OSY-MEM-5181..5183``, ``5301``, ``5129``. Local code: ``OSY-SDK-5301``.
Security: this exception NEVER discriminates "doesn't exist" from "exists but in another workspace" — both surface the same code and message. Leaking that distinction would create a cross-tenant enumeration oracle (ADR-040 / ENG-1215 anti-enumeration policy).
class OmePolicyError
Bases: OmeError
OPA / authz policy denial.
Raised when the server's OPA policy gate denied the operation (e.g. ``RevokeSignerPub`` requires ``owner`` or ``security-admin`` role + recent MFA challenge). NOT the same as ``OmeAuthError`` (which is about the token itself) or ``OmeNotFoundError`` (which masks workspace-existence on RLS-filtered reads).
Server codes: ``OSY-MEM-5127..5128``. Local code: ``OSY-SDK-5127``.
class OmeRateLimitError
Bases: OmeError
Rate limit or tier-quota exceeded.
Raised when the workspace's tier (FREE/PRO/ENTERPRISE) quota is exhausted (e.g. monthly claim-write count) OR when a per-RPC rate limit was hit (e.g. ``RevokeSignerPub`` 10 calls/hour).
Server codes: ``OSY-RATE-2001..2099``, ``OSY-MEM-5137``. Local code: ``OSY-SDK-2000``.
Callers MAY retry with exponential backoff using ``Retry-After`` metadata (v0.2 ships a helper for this).
class OmeServerError
Bases: OmeError
Server-side ``INTERNAL`` or ``UNAVAILABLE`` failure.
Raised when the gRPC trailer reports a Status the SDK classifies as "the server is misbehaving" (``INTERNAL``, ``UNAVAILABLE``, ``UNKNOWN``). Distinct from :class:`OmeTransportError` (network / TLS / deadline) and :class:`OmePolicyError` / :class:`OmeAuthError` (semantic refusals with a stable OSY-* code).
Server codes: usually empty; the gRPC Status alone is the signal. Local code: ``OSY-SDK-9100``.
Callers SHOULD log the underlying ``rpc_err.details()`` and surface to operators rather than auto-retrying — retrying a server-internal bug often just doubles the log volume without changing the outcome.
class OmeTransportError
Bases: OmeError
gRPC transport / network / timeout / TLS failure.
Raised when the gRPC channel cannot reach the server, the TLS handshake fails, the deadline expired, or the server returned a Status outside the application-coded range. Distinct from :class:`OmeServerError` which is raised on ``INTERNAL``/``UNAVAILABLE`` server-side failures that come back with a Status trailer.
The split exists so callers can write resilient retry loops:
- ``OmeTransportError`` (network) → retry with backoff is usually safe.
- ``OmeServerError`` (server bug) → retry might mask the symptom; prefer reporting + escalation.
Distinct from ``client_closed`` raised by methods called after ``OmeClient.close()`` — that's a ``RuntimeError`` (client lifecycle bug, not transport).
Local code: ``OSY-SDK-9000``.
class OmeVerifyError
Bases: OmeError
Ed25519 signature verification failed.
Raised by:
- ``client.verify.receipt(bytes)`` — local Ed25519 check failed
- ``client.memory.write_claim(...)`` — server returned 5101
- ``client.verify.proof/witness/bundle_manifest(...)`` — local checks
Server codes: ``OSY-MEM-5101..5103``. Local code: ``OSY-SDK-5101``.
Security: do NOT downgrade this to a string-typed error — verify-side callers MUST be able to discriminate "the body decoded but signature failed" (this exception) from "transport error before verify ran" (``OmeTransportError``). Conflating them is a textbook fail-open vulnerability.
class OmeWireFormatError
Bases: OmeError
Request/wire-format error — the bytes or request are malformed.
Raised when bytes presented to the verify-side mini-kernel are not well-formed canonical CBOR, OR when they decode but violate the OAP-MEM v0.1 schema. Also the typed error for a server-rejected request shape — gRPC ``INVALID_ARGUMENT`` / ``OUT_OF_RANGE`` map here (the request is invalid; retrying it unchanged will never succeed), distinct from a transport fault. Distinct from ``OmeVerifyError`` — this is structural; that is cryptographic.
Server codes: ``OSY-MEM-5102``. Local code: ``OSY-SDK-5102``.
class OmeWorkspaceError
Bases: OmeError
Workspace binding / GUC / RLS violation.
Raised when the server rejects a request because the caller's workspace_id doesn't match the resource's owning workspace, or when the Postgres GUC ``ome.workspace_id`` could not be bound (server bug, but surfaces to the client).
Server codes: ``OSY-MEM-5310..5312``. Distinct from ``OmeAuthError`` (which is about identity, not tenancy).
class SignedClaim
Bases: _OmeBaseModel
A Claim with a DETACHED Ed25519 signature (mirrors ome.v1.SignedClaim).
**Wire-truth invariant (C1 / CRYPTO-2 re-model, ENG-2228).** The signature is a DETACHED bare-Ed25519 signature over the wire signing-PREIMAGE bytes (``encoded_cbor``) VERBATIM — the Form-B 2-tuple ``canonical_cbor([tstr("osyra-ome-claim-v1"), claimMap])`` (the claim 2-tuple wire shape per ADR-157 + ADR-008; canonical-CBOR strict-decode per ``AP00011-OME-FILE-FORMAT-V1.md`` §4.2). The ``claim`` field is a DECODED VIEW for ergonomics; it is **NOT** what the signature attests — verification runs over ``encoded_cbor`` and the signature is never re-derived by re-encoding ``claim``. To verify, pass ``signed.encoded_cbor`` + ``signed.sig`` + a caller-trusted public key to :func:`osyra_ome.verify_receipt`. Mirror of TS ``SignedClaim`` (``{ claim, signature, signerPub?, encodedCbor }``).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| claim | Claim | |
| encoded_cbor | bytes | Field(default=b'') |
| sig | _Ed25519Sig | |
| sig_alg | str | Field(default='Ed25519', min_length=1) |
| signer_pub | _Ed25519PubOptional | b'' |
class TokenUsage
Bases: BaseModel
Token accounting reported on the terminal frame (may be absent).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| completion_tokens | int | 0 |
| prompt_tokens | int | 0 |
| total_tokens | int | 0 |
class TombstoneReasonCode
Bases: IntEnum
Reason a claim was tombstoned (mirrors ome.v1.TombstoneReasonCode).
ENG-583: enumerated reason codes keep free-text PII out of the audit-pipeline boundary. ``UNSPECIFIED`` is a reserved sentinel that the server rejects (OSY-MEM-5184) and the SDK refuses client-side before the wire — pass one of the concrete reasons instead. Values match the proto exactly; the TS sibling (AP00014) declares the same set (ADR-296 D2).
UNSPECIFIED = 0 # reserved sentinel — never a valid input GDPR_ART17_DSAR = 1 LEGAL_HOLD_EXPIRY = 2 CUSTOMER_OFFBOARDING = 3 OPERATOR_REDACTION = 4 REGULATORY_OTHER = 5
class TombstoneReceipt
Bases: _OmeBaseModel
Receipt returned by ``client.memory.tombstone(...)``.
Mirrors ome.v1.TombstoneResponse. Distinguishes the post-redaction root + era-binding + audit-correlation tuple from the original pre-tombstone state. The salt is NEVER on the wire (preimage- confirmation defense per ADR-016).
Attributes
| Name | Type | Default |
| --- | --- | --- |
| audit_correlation_id | str | Field(min_length=1) |
| era_index | int | Field(ge=0) |
| new_root | _Sha256 | |
| redacted_at_unix_ms | int | Field(ge=0) |
| tombstoned_at_sequence | int | Field(ge=0, default=0) |
class WrittenClaim
Bases: _OmeBaseModel
The outcome of ``client.memory.write_claim_signed`` (wire: SignAndInsert).
Carries the persisted ``claim_id`` AND the SERVER-MINTED :class:`SignedClaim` whose ``encoded_cbor`` is the signing preimage the server's Ed25519 signature attests. This is the v0.1-wire source of the attested preimage a body-bearing read path needs to bind a returned body to the SIGNATURE (ENG-2413): ``GetClaimBody`` returns only ``{body, content_hash}`` (the ``content_hash`` is unsigned/forgeable), so a caller who wants signature-bound verify-on-read MUST hold the ``SignedClaim`` from the write and pass it to ``get_claim_body(..., signed_claim=...)``.
Mirror of the AP00014 TS store pattern, where the adapter caches the ``SignedClaim`` it wrote and re-binds the read body to its ``encodedCbor``.
Attributes
| Name | Type | Default |
| --- | --- | --- |
| claim_id | str | Field(min_length=1) |
| signed_claim | SignedClaim | |