> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sifr.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Python Interop

> Configure and use embedded CPython from Sifr with explicit environment ownership, trust, blocking, resource, and zero-copy semantics.

Sifr can embed one uv-created CPython environment in a compiled application. Use it to call installed Python packages while keeping Sifr contracts: fallible work returns `Result`, Python objects are opaque and non-send by default, native extensions need explicit trust, and zero-copy APIs never silently copy.

This is not Python source compatibility and not a generic `dlopen` layer. Prefer `sifr.*` for Sifr code; use `sifr.python` only when you intentionally cross into Python packages.&#x20;

For library-first walkthroughs, start with [schwifty](/guides/interop/schwifty), [NumPy](/guides/interop/numpy), or [kafka-python](/guides/interop/kafka).

## Package Setup

The root application owns the Python environment. Sifr verifies and consumes it; it does not run `uv sync`, install packages, or fall back to host-global Python.

```toml theme={null}
[trust]
python = ["pandas", "pyarrow", "torch", "httpx"]
python-native = ["numpy", "pyarrow", "torch"]
```

For a normal uv project, Sifr finds the nearest ancestor with both `pyproject.toml` and `uv.lock`, then uses its `.venv` and platform interpreter. Non-standard layouts can override `venv`, `pyproject`, `lock`, or `interpreter` in the root `[python]` table. Sifr runs read-only `uv lock --check --offline` and probes the live interpreter; it never creates or mutates the environment.

Libraries may declare required import roots, but they do not choose the interpreter or venv:

```toml theme={null}
[python]
requires-imports = ["polars"]
```

`requires-imports` is only for raw or dynamic imports whose roots cannot be derived from static declarations or package-local bridge imports. Sifr normalizes all contributions into one per-root set and keeps every source for diagnostics.

Sifr rejects missing uv environments, dependency packages that select or authorize an environment, non-CPython interpreters, free-threaded CPython, stale or missing project metadata, missing required imports, and trusted native roots that fail to load.

## Trust Boundary

Python import roots and native extension roots are separate trust decisions.

| Key                         | Meaning                                                           |
| --------------------------- | ----------------------------------------------------------------- |
| `[python].requires-imports` | Underivable raw/dynamic roots the final app must provide.         |
| `[trust].python`            | Root-owned authorization to execute required Python roots.        |
| `[trust].python-native`     | Roots allowed to load native Python extension modules in process. |

Native extensions can abort the process, create threads, release the GIL, or call back into Python/Sifr. That is the explicit exception to Sifr-attributable no-panic guarantees.

Root apps may use wildcard Python trust during local control. Dependencies may publish requirements, but cannot select an environment or authorize execution. Native trust for a root that is not required is rejected as stale policy.

## Check And Doctor

Inspect the declaration-first Python plan before building:

```bash theme={null}
sifr python check
sifr python check --json
sifr python doctor
```

Both commands use frozen package resolution and the same driver plan as normal check/build. Reports include graph and source-content snapshot digests for the inputs that were checked.

| Target state      | Meaning                                                                     |
| ----------------- | --------------------------------------------------------------------------- |
| `verified`        | Import-root target proven in the selected interpreter.                      |
| `runtime-checked` | Hermetic embedded bridge target.                                            |
| `deferred`        | Library still missing environment or trust that the final app must provide. |

Resolution rules:

* A library without complete root authority is valid to inspect; unresolved obligations stay deferred.
* Ordinary `sifr check` uses the same deferral decision.
* A library with authorized imports and either explicit `[python]` paths or a discoverable uv project resolves immediately.
* Any package with runnable applications resolves immediately.
* Failures match ordinary `sifr check` diagnostics for the same snapshot.

The language server uses the same lowered plan and driver probe. Completion and hover show target, status, and protocol guidance; go-to-definition navigates to the checked Sifr declaration. Invalid, unsupported, or untyped declarations are never shown as verified. Cache invalidates on `sifr.toml`, custom pyproject/lock paths, `sifr.python-bindings.json`, `sifr.python-certifications.json`, or interpreter changes.

`sifr python doctor` emits deterministic patch-like suggestions for the root `[python]` and/or `[trust]` entries a consumer must provide. Neither command writes a manifest, grants trust, creates a venv, installs packages, or runs `uv sync`.

## Runtime Semantics

When Python interop is used, the generated binary initializes CPython once before user `main`, using validated probe metadata: executable, venv prefix, site-packages, `sys.path`, CPython version, SOABI, extension suffixes, platform, pointer width, and `libpython` when discoverable.

Rules:

* CPython main interpreter only; no subinterpreters.
* No normal shutdown finalization with `Py_FinalizeEx`.
* Reinitialization with the same config is allowed; conflicting config is rejected.
* Every CPython API operation that needs the GIL acquires it.
* Decref and resource release happen while holding the GIL.
* `py.Object`, buffers, Arrow capsules, DLPack tensors, and callbacks are tracked for ownership and release diagnostics.

## Typed Declarations

Use typed declarations for ordinary package calls. The Sifr signature is the conversion contract; every failure remains a checked `Result`:

```python theme={null}
from sifr.python import PythonError

@python(math.sqrt)
def sqrt(value: float) -> Result[float, PythonError]: ...
```

Every declaration error channel must use the canonical `PythonError` contract: exactly the string fields `message`, `kind`, `exception_type`, `traceback`, and `context`. Importing `PythonError` from `sifr.python` supplies that contract for ordinary, coroutine, callback, context, and zero-copy declarations.

Adaptation code belongs under the owning package's `src/python_bridges/`. A source target such as `bridge.identifiers.parse_gtin` resolves only against that package's inventoried bridge modules:

```python theme={null}
@python(bridge.identifiers.parse_gtin)
def parse_gtin(text: str) -> Result[GtinInfo, PythonError]: ...
```

Bridge rules:

* Ordinary static imports are inventoried; dynamic imports are rejected.
* Third-party roots enter the requirement set and need root `[trust].python`.
* Bridge sources and inventory are package archive inputs.
* Generated binaries embed the selected graph under isolated `__sifr_bridge__.p_*` namespaces.
* The reserved loader installs before user `main`; binaries do not read bridge files at runtime.
* Two dependencies can own the same bridge module path without collision.

The dynamic `Object` handle is the sealed identity from `sifr.python`. A local record named `Object` keeps ordinary record-conversion semantics when it has supported fields; it cannot impersonate the sealed Python handle.

## Dynamic Object Escape Hatch

Use the sealed dynamic object API only when a boundary cannot be a typed declaration or package-local bridge:

```python theme={null}
from sifr.python import Object, PythonError, from_value, import_module, kwarg, to_value

@blocking_io
def main() -> Result[None, PythonError]:
    try:
        math: Object = import_module("math")
        eighty_one: Object = from_value(81.0)
        root: Object = math.call_method("sqrt", [eighty_one], [])
        value: float = to_value(root)
        print(value)

        template: Object = from_value("{name}:{count}")
        name_kwarg: tuple[str, Object] = kwarg("name", "sifr")
        count_kwarg: tuple[str, Object] = kwarg("count", 3)
        rendered: Object = template.call_method(
            "format",
            [],
            [name_kwarg, count_kwarg],
        )
        text: str = to_value(rendered)
        print(text)
        return None
    except PythonError as e:
        raise e
```

Every Python boundary operation returns `Result`. Python exceptions become structured `py.PythonError` values (type, message, traceback, operation context, and conversion/resource/zero-copy metadata where relevant). They do not unwind through Sifr user code.

Conversion helpers:

* `from_value[T]` / `to_value[T]` use the same closed set as typed declarations: primitives, supported lists, tuples, `dict[str, T]`, closed records, options, opaque declarations, and sealed `Object`.
* `kwarg[T]` converts into the `(str, Object)` call shape.
* `Object.get_attr`, `get_item`, `call`, and `call_method` are checked method-style operations over that identity.

Ordinary `Object` values release when Sifr ownership ends, including return and error paths. Compatibility helpers such as `close` remain available for early release, but normal code does not need reverse-order close chains. Raw coroutines submitted with `run_coroutine_blocking` use the application-owned Python event loop; they do not create a per-call loop.

## Authoring Typed Bindings

`sifr python bind` creates symbol-selective, reviewable Sifr declarations from Python typing information. Whole-module generation is unsupported:

```bash theme={null}
sifr python bind math --symbols sqrt \
  --override typing/math.pyi
sifr python bind redis.client --symbols Redis
sifr python bind --check
```

Resolution order:

1. Package-local `--override` files
2. Selected `--stub-package` distributions
3. Installed `py.typed` inline sources
4. Package-local `--external-stub` files
5. Safe runtime introspection

Every selected symbol records its winning source, source hash, distribution version, SOABI, environment identity, and generated source digest in `sifr.python-bindings.json`. Generated `.sifr` files, the artifact, and consumed package-local typing sources are package archive inputs.

Generation fails closed on `Any`, bare `object`, unknown overloads, callable or generic shapes without a Sifr contract, missing annotations, unresolved types, and optional positional-only parameters the call plan cannot represent. It never substitutes dynamic `Object` or overwrites an unrelated package file. If the environment identity changes while adding or replacing one module, every retained binding is re-probed before the artifact adopts the new identity.

`bind --check` is frozen and read-only: it reruns resolution and generation in memory and rejects typing, environment, distribution, source, or generated-file drift. Ordinary package check/build also validates checked-in outputs and local typing hashes, and includes binding identity in its build cache key.

## Blocking And Async

Every public `sifr.python` operation is `@blocking_io`. Direct Python calls in async Sifr code are rejected unless offloaded through Sifr's blocking offload primitive.

```python theme={null}
from sifr.python import PythonError

@python.opaque(type=httpx.Response, cleanup=drop)
class Response:
    @python.attr(Self.status_code)
    def status_code(self) -> Result[int, PythonError]: ...

@python(httpx.get)
def get(url: str) -> Result[Response, PythonError]: ...

async def fetch_status() -> Result[int, PythonError]:
    handle = task.spawn_blocking(fetch_status_sync)
    return await handle.join()

@blocking_io
def fetch_status_sync() -> Result[int, PythonError]:
    try:
        response: Response = get("https://example.com")
        return response.status_code()
    except PythonError as e:
        raise e
```

Libraries with genuine coroutine APIs use typed async declarations. The generated application owns one asyncio loop on one dedicated thread; declaration calls submit to that loop without blocking a Sifr executor thread:

```python theme={null}
from sifr.python import PythonError

@python.opaque(type=httpx.AsyncClient, cleanup=async_close)
class AsyncClient:
    @python.coroutine(Self.aclose)
    async def aclose(own self) -> Result[None, PythonError]: ...

@python.coroutine(bridge.http.make_client)
async def make_client() -> Result[AsyncClient, PythonError]: ...

@python.coroutine(bridge.http.get_status)
async def get_status(client: AsyncClient, path: str) -> Result[int, PythonError]: ...
```

Async declaration rules:

* `@python.coroutine` is valid only on `async def`.
* Inputs and outputs use the same recursive typed conversion contract as sync declarations.
* Opaque values stay non-send.
* `cleanup=async_close` requires exactly one consuming `aclose(own self)` coroutine.
* The value cannot be abandoned, closed twice, or reused after close.

Cancellation:

* Cancelling a Sifr task cancels its exact asyncio task and waits for Python `finally` cleanup first.
* A terminal `CancelledError` caused by that request is the Sifr cancellation cause, not a catchable `PythonError`.
* If Python suppresses cancellation, its later return or different exception wins.
* Runtime shutdown drains registered tasks, traverses a reserved async-cleanup ordering slot, then stops and joins the loop thread.

Typed Python async context managers use the same owned loop and declare both protocol methods:

```python theme={null}
from sifr.python import ExitCause, ExitDecision, PythonError

@python.opaque(type=aiosqlite.Connection, cleanup=async_context)
class DatabaseSession:
    @python.context.aenter(Self.__aenter__)
    async def __aenter__(self) -> Result[DatabaseSession, PythonError]: ...

    @python.context.aexit(Self.__aexit__)
    async def __aexit__(
        own self,
        cause: ExitCause,
    ) -> Result[ExitDecision, PythonError]: ...
```

`cleanup=async_context` rules:

* Must be consumed by `async with`.
* Enter and exit run on the application-owned Python loop.
* A truthy `__aexit__` may suppress an originating Python exception.
* Ordinary Sifr errors, timeout, cancellation, and runtime faults remain primary.
* Cancellation waits for Python `finally` and masked async exit to finish.
* Exit runs exactly once on fallthrough, return, error, and cancellation paths.

`py.run_coroutine_blocking` is also `@blocking_io`. It is for Python-owned coroutine objects and returns only after Python finishes.

## Resources

Python resources must be closed or released explicitly when correctness depends on cleanup:

```python theme={null}
from sifr.python import ExitCause, ExitDecision, PythonError

@python.opaque(type=sqlite3.Connection, cleanup=context)
class Connection:
    @python.context.enter(Self.__enter__)
    def __enter__(self) -> Result[Connection, PythonError]: ...

    @python.context.exit(Self.__exit__)
    def __exit__(own self, cause: ExitCause) -> Result[ExitDecision, PythonError]: ...

    @python.attr(Self.total_changes)
    def total_changes(self) -> Result[int, PythonError]: ...

@python(sqlite3.connect)
def connect(database: str) -> Result[Connection, PythonError]: ...

def use_transaction() -> Result[int, PythonError]:
    try:
        with connect(":memory:") as connection:
            return connection.total_changes()
    except PythonError as e:
        raise e
```

Typed context protocols, callback owners, buffers, Arrow resources, and DLPack tensors carry cleanup in the declaration contract. Double close/release returns deterministic resource-state errors for types that are not intentionally idempotent.

## Callbacks

Attach a callback contract to the parameter that becomes the Python callable. The ordinary Sifr signature remains the conversion contract:

```python theme={null}
@python.callback(handler, lifetime=call, dispatch=current)
@python(bridge.cffi.apply)
def apply(
    handler: Callable[[int], int],
    value: int,
) -> Result[int, PythonError]: ...

@python.callback(handler, lifetime=call, dispatch=asyncio, concurrency=serial)
@python.coroutine(bridge.asyncio.apply)
async def apply_async(
    handler: AsyncCallable[[int], int],
    value: int,
) -> Result[int, PythonError]: ...
```

| Policy             | Meaning                                                                             |
| ------------------ | ----------------------------------------------------------------------------------- |
| `lifetime=call`    | Drain the callable before the declaration returns.                                  |
| `lifetime=result`  | Retain it in the returned opaque owner.                                             |
| `lifetime=Self`    | Retain it in the receiver.                                                          |
| `dispatch=current` | Non-send call-scoped captures allowed.                                              |
| `dispatch=foreign` | Accepts Python-created threads; requires sendable, thread-safe captures.            |
| `dispatch=asyncio` | `AsyncCallable` on the app-owned loop; cancellation propagates to the Sifr handler. |

Retained owners must have deterministic `close`, `async_close`, `context`, or `async_context` cleanup. Owner shutdown unregisters first, rejects new entries, drains accepted invocations, and releases captures exactly once.

Foreign and asyncio callbacks require `concurrency=serial | parallel`. Serial recursion fails with a stable callback error instead of blocking on its own lock or queue.

Handler errors cross Python as `SifrCallbackError` and return through the declared `Result` channel even if Python catches the exception. If Python also fails, the Python error stays primary and the first handler failure is secondary evidence. Lower-level `py.local_callback` and `py.threadsafe_callback` remain available for explicit dynamic object code.

## Zero-Copy Interop

Zero-copy APIs reject unsupported dtype, shape, stride, device, mutability, malformed capsule, double-consumption, and copy-required paths. Explicit copy APIs are separate and never claim view semantics.

### Buffers

Typed buffer declarations cover objects that implement Python's buffer protocol:

```python theme={null}
from sifr.python import PythonError

@python.buffer(numpy.arange, access=write, layout=c_contiguous)
def numpy_range(stop: int) -> Result[python.Buffer[int64], PythonError]: ...

def update(mut view: python.Buffer[int64]) -> Result[list[int64], PythonError]:
    try:
        changed: None = view.write(2, 42)
        copied: list[int64] = view.copy_slice(0, view.length())
        released: None = view.release()
        return copied
    except PythonError as error:
        raise error
```

Decorator targets may be import-root callables, package-local `bridge` callables, or `Self` on an opaque receiver.

* `Self` acquisition is read-only.
* Writable declarations must use a producer that returns a fresh or ownership-transferred exporter.
* Writable-producer parameters that can carry an existing `python.Object` or opaque Python identity must use `own`.
* `access=read | write` and `layout=any | c_contiguous | f_contiguous` are validated during acquisition with element format, item size, dimensions, shape, strides, and suboffsets.
* Supported element types are fixed-width and pointer-width signed and unsigned integers plus `float`.

`python.Buffer[T]` is affine and non-send. It retains its exporter and performs exactly one `PyBuffer_Release` on explicit `release()` or automatic drop. Writable access requires an exclusive `mut` receiver. `read` and `write` are bounds checked; `copy_slice` is an explicit copy. No borrowed Rust or Sifr slice can outlive the buffer.

### Arrow

Typed Arrow declarations return one of five affine, non-send resources: `python.ArrowArray`, `python.ArrowSchema`, `python.ArrowStream`, `python.ArrowDeviceArray`, or `python.ArrowDeviceStream`. The return type selects the protocol and capsule shape:

```python theme={null}
from sifr.python import PythonError

@python.arrow(pyarrow.array, schema=omitted)
def make_array(values: list[int]) -> Result[python.ArrowArray, PythonError]: ...

def use_array(values: list[int]) -> Result[None, PythonError]:
    try:
        array: python.ArrowArray = make_array(values)
        print(array.capsule_names())
        return array.release()
    except PythonError as error:
        raise error
```

Arrow ownership:

* `schema=parameter(name)` names a required keyword-only `own` `python.ArrowSchema` parameter.
* Sifr transfers that exact schema capsule to the Arrow protocol, but never forwards it to the producer call.
* Arrays own their schema/data capsule pair; device arrays own the schema/device-data pair.
* Owned Arrow values can cross another Python declaration only through an `own` parameter.
* That `own` parameter commits the move before the call and reconciles full, partial, or absent capsule consumption afterward.

Arrow declarations require package-authored executable no-copy certification for the exact target, environment, distribution versions, schema mode, and runtime producer module/type:

```bash theme={null}
sifr python certify arrow pyarrow.array \
  --fixture python_certifications/arrow_array.py
sifr python certify --check
```

The first command writes or updates package-root `sifr.python-certifications.json`. The second is read-only and reruns every recorded fixture, rejecting environment, fixture, producer, distribution, schema, pointer-identity, release-count, or copy-result drift. The artifact and package-local fixtures are archive and build-cache inputs. There is no Arrow copy switch or uncertain-producer fallback.

### DLPack

Typed DLPack declarations return affine `python.DlpackTensor[T]` resources:

```python theme={null}
from sifr.python import PythonError

@python.opaque(type=torch.Tensor, cleanup=drop)
class TorchTensor(NonSend):
    @python.dlpack(Self, device=cpu, stream=none)
    def export(self) -> Result[python.DlpackTensor[int64], PythonError]: ...

@python(torch.from_dlpack)
def consume(own tensor: python.DlpackTensor[int64]) -> Result[TorchTensor, PythonError]: ...
```

Producer targets may be import-root callables, package-local bridges, or `Self`.

Device and stream:

* `device=cpu | cuda | any` never requests a device change.
* CPU may use `stream=none`.
* CUDA and `any` require `stream=parameter(name)` — a required keyword-only borrowed `python.DlpackStream` from `@python.dlpack.stream`.
* Runtime acquisition validates the stream family/id before the one permitted `__dlpack__` call.
* A CPU producer always receives `stream=None`, including when `device=any` validated matching CPU stream metadata.

Transfer rules:

* Sifr passes `copy=False` and `max_version=(1, 0)` without an old-signature retry.
* Legacy or compatible 1.x capsules are accepted.
* Acquisition validates copy flags, dtype/lanes, device, dimensions, shape, strides, byte offset, and deleter state.
* Element types are the closed Sifr scalar set: fixed-width integers, `float` (DLPack float64), and `bool`.
* Sifr transfers the producer's managed tensor without reading or mutating its payload, so `READ_ONLY` is preserved for the consumer.
* The source capsule is immediately marked used.
* Passing `own python.DlpackTensor[T]` creates a fresh consumer capsule and commits the move even if the Python call later fails.
* An unconsumed capsule invokes its producer deleter exactly once.

DLPack producer claims use the same certification discipline as Arrow:

```bash theme={null}
sifr python certify dlpack torch.Tensor \
  --fixture python_certifications/dlpack_tensor.py
sifr python certify --check
```

The fixture must prove pointer identity, no copy, declared device and stream policy, exact distribution versions, and exactly one observed managed-tensor deleter call in the same run. Bridge targets stay keyed by their stable `bridge.*` spelling even though compiled binaries embed them under isolated runtime namespaces.

### Dynamic Zero-Copy Helpers

Lower-level dynamic APIs remain available when a typed declaration is not appropriate:

```python theme={null}
from sifr.python import ArrowCapsule, DlpackTensor, Object, PythonError, zero_copy_arrow_stream, zero_copy_dlpack_tensor

def dataframe_to_arrow_stream(df: Object) -> Result[ArrowCapsule, PythonError]:
    try:
        return zero_copy_arrow_stream(df)
    except PythonError as e:
        raise e

def torch_tensor_to_dlpack(tensor: Object) -> Result[DlpackTensor, PythonError]:
    try:
        return zero_copy_dlpack_tensor(tensor)
    except PythonError as e:
        raise e
```

Supported surfaces:

* `Py_buffer` for bytes-like objects, memoryview, NumPy, and other buffer protocol producers.
* Arrow PyCapsules for array, schema, stream, device-array, and device-stream protocol methods.
* DLPack capsules for NumPy, torch, and TensorFlow CPU tensor paths.
* Array-interface protocols with owner retention and metadata validation.

## Package Examples

Ordinary application code uses typed declarations. A package-local hermetic bridge is appropriate when a library workflow is dynamic internally; the Sifr boundary stays closed and typed.

```python biip / schwifty declarations theme={null}
from sifr.python import PythonError

@python.opaque(type=biip.gtin.Gtin, cleanup=drop)
class Gtin:
    @python.attr(Self.value)
    def value(self) -> Result[str, PythonError]: ...

@python.opaque(type=biip._parser.ParseResult, cleanup=drop)
class ParseResult:
    @python.attr(Self.gtin)
    def gtin(self) -> Result[Gtin, PythonError]: ...

@python(biip.parse)
def parse_identifier(text: str) -> Result[ParseResult, PythonError]: ...

@python.opaque(type=schwifty.BIC, cleanup=drop)
class Bic:
    @python.attr(Self.country_code)
    def country_code(self) -> Result[str, PythonError]: ...

@python(schwifty.BIC)
def bic(text: str) -> Result[Bic, PythonError]: ...
```

```python Dynamic library workflow behind a typed bridge theme={null}
from sifr.python import PythonError

class AppSummary:
    status: int
    framework: str

@python(bridge.web_app.build_summary)
def build_summary() -> Result[AppSummary, PythonError]: ...
```

```python pandas / Polars Arrow declarations theme={null}
from sifr.python import PythonError

@python.arrow(pandas.DataFrame, schema=omitted)
def pandas_frame(values: list[int]) -> Result[python.ArrowStream, PythonError]: ...

@python.arrow(polars.Series, schema=omitted)
def polars_series(values: list[int]) -> Result[python.ArrowStream, PythonError]: ...
```

```python PyTorch one-shot DLPack transfer theme={null}
from sifr.python import PythonError

@python.opaque(type=torch.Tensor, cleanup=drop)
class TorchTensor(NonSend):
    @python.dlpack(Self, device=cpu, stream=none)
    def export(self) -> Result[python.DlpackTensor[int64], PythonError]: ...

@python(torch.from_dlpack)
def consume_tensor(own value: python.DlpackTensor[int64]) -> Result[TorchTensor, PythonError]: ...
```

```python Foreign-thread Kafka callback theme={null}
from sifr.python import PythonError

@python.callback(handler, lifetime=call, dispatch=foreign, concurrency=serial)
@python(bridge.kafka_consumer.poll)
def poll(handler: Callable[[str], str]) -> Result[str, PythonError]: ...

def acknowledge(message: str) -> str:
    return "ack:" + message
```

```python Cloud client workflow behind a typed bridge theme={null}
from sifr.python import PythonError

class QueueResult:
    message_id: str
    body: str

@python(bridge.cloud.send_and_receive)
def send_and_receive(queue: str, body: str) -> Result[QueueResult, PythonError]: ...
```

Credentialed or service-backed behavior belongs in the external Python interop gate. The default local gate uses import, stub, contract, and matrix evidence.

## Diagnostics

Compiler diagnostics use stable URLs and structured JSON fields:

| Family                                                                                                                                                                           | Covers                                                                                                                                                                     |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`SIFR-PYENV-0001`](/errors/SIFR-PYENV-0001)–[`SIFR-PYENV-0011`](/errors/SIFR-PYENV-0011)                                                                                        | Environment selection, probing, interpreter, ABI, import, native-load, and metadata.                                                                                       |
| [`SIFR-PYTRUST-0001`](/errors/SIFR-PYTRUST-0001), [`SIFR-PYTRUST-0003`](/errors/SIFR-PYTRUST-0003)–[`SIFR-PYTRUST-0005`](/errors/SIFR-PYTRUST-0005)                              | Dependency wildcard rejection, stale native trust, dynamic-import annotation, and unauthorized required roots. [`SIFR-PYTRUST-0002`](/errors/diagnostic-codes) is retired. |
| [`SIFR-PYIMP-*`](/diagnostics/error-codes), [`SIFR-PYCALL-*`](/diagnostics/error-codes), [`SIFR-PYCONV-*`](/diagnostics/error-codes), [`SIFR-PYRES-*`](/diagnostics/error-codes) | Declaration, target, conversion, resource, and sequenced activation.                                                                                                       |
| [`SIFR-PYCB-*`](/diagnostics/error-codes)                                                                                                                                        | Callback lifetime, dispatch, conversion, and shutdown.                                                                                                                     |
| [`SIFR-PYZC-*`](/diagnostics/error-codes)                                                                                                                                        | Buffer, Arrow, and DLPack declaration, ownership, layout or stream policy, certification, capsule validation, transfer, and hidden-copy.                                   |

See [error codes](/diagnostics/error-codes) for the full catalog. Run `sifr --explain SIFR-PYCALL-0001` for a stable explanation of [`SIFR-PYCALL-0001`](/errors/SIFR-PYCALL-0001).
