Files
CoreRasurae 9dc3f7910b
CI / lint (push) Successful in 1m7s
CI / typecheck (push) Successful in 1m8s
CI / security (push) Successful in 1m7s
CI / quality (push) Successful in 41s
CI / integration_tests (push) Successful in 1m13s
CI / build (push) Successful in 1m22s
CI / unit_tests (push) Successful in 3m13s
CI / coverage (push) Successful in 3m9s
CI / status-check (push) Successful in 3s
docs(registry): add MkDocs API documentation and usage examples for Package Registry Client
Created comprehensive MkDocs-formatted API documentation under docs/registry/
covering all public API surfaces from the registry subsystem with real-life
constructive examples:

- index.md: Architecture overview, module relationships, quickstart
- types.md: PackageType, PackageId, PackageReference, PackageContent
- client.md: RegistryClient with all 4 endpoints, auth modes, async patterns
- canonical.md: Canonicalizer pipeline — NFC, RFC-8785, SHA-1, lifecycle stripping
- resolver.md: ReferenceResolver — 3 reference schemes, version alias resolution
- exceptions.md: RegistryError hierarchy — all 9 typed exceptions with HTTP mapping
- cache.md: RegistryCache — LRU eviction, TTL, SHA-1 tamper detection, singleflight
- integration.md: 4 end-to-end workflows (ordering pipeline, email categorization,
  CI/CD verification, multi-tenant provisioning)

Updated mkdocs.yml nav tree with docs/registry/ entries.

Refs: #51
2026-06-17 14:09:28 +00:00

6.9 KiB

RegistryClient

Async HTTP client implementing all 4 API endpoints of the Package Registry Standard v1.0.0 §8.

from cleveractors.registry import RegistryClient

Constructor

RegistryClient(
    base_url: str,
    api_key: Optional[str] = None,
    timeout: float = 30.0,
    allow_insecure: bool = False,
)
Parameter Type Default Description
base_url str required Registry server base URL (e.g. "https://registry.example.com")
api_key str | None None Optional API key for authenticated operations (§9.2)
timeout float 30.0 Request timeout in seconds
allow_insecure bool False Suppress the HTTP-without-TLS security warning

The constructor emits a warning when an API key is used over HTTP without allow_insecure=True, because the standard requires HTTPS in production (§12.2).


Endpoints

The client implements all 4 endpoints defined by the standard:

Method Endpoint Standard Ref
get_package(package_id) GET /packages/{id} §8.2.1
resolve_package(type, ns, name, version) GET /{type}/{ns}/{name}?version={v} §8.2.2
browse(type, namespace) GET /browse §8.4.1
discover() GET /.well-known/cleverthis-packages §8.4.2

All methods are async and return dictionaries. They map HTTP errors to typed exceptions from the exception hierarchy.


get_package

async def get_package(self, package_id: str) -> dict[str, Any]

Retrieve raw package content by its globally unique Package ID.

content = await client.get_package(
    "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
)
print(content["name"])  # e.g. "order-validator"

Raises: PackageNotFoundError, InvalidPackageIdError, RegistryNetworkError.


resolve_package

async def resolve_package(
    self,
    package_type: str,
    namespace: str,
    name: str,
    version: Optional[str] = None,
) -> dict[str, Any]

Resolve a named package reference to its concrete Package ID. Performs version alias resolution according to §4.2.

# Resolve with a concrete version
result = await client.resolve_package(
    package_type="actor",
    namespace="acme",
    name="order-validator",
    version="v1.0.0",
)
print(result["package_id"])  # "pkg_act_..."

# Resolve with the `latest` alias (default when version is None)
latest = await client.resolve_package(
    package_type="skill",
    namespace="acme",
    name="code-analysis",
)
print(latest["package_id"])

Returns a dict with keys package_id (str) and type (str).

Raises: PackageNotFoundError, VersionNotFoundError, RegistryNetworkError.


browse

async def browse(
    self,
    package_type: Optional[str] = None,
    namespace: Optional[str] = None,
) -> dict[str, Any]

Browse published packages with optional type and namespace filters.

# List all actor packages
results = await client.browse(package_type="actor")
for pkg in results["packages"]:
    print(f"{pkg['name']} ({pkg['namespace']}) — {pkg['description']}")

# List all packages in a namespace
results = await client.browse(namespace="acme")
print(f"Found {results['count']} packages in acme namespace")

Returns a dict with keys packages (list of package summaries) and count (int).

Raises: RegistryNetworkError.


discover

async def discover(self) -> dict[str, Any]

Retrieve registry metadata via the well-known discovery endpoint (§8.4.2).

meta = await client.discover()
print(meta["name"])              # "CleverThis Package Registry"
print(meta["version"])           # "1.0.0"
print(meta["supported_types"])   # ["actor", "graph", "stream", ...]
print(meta["authentication"])    # ["anonymous", "api_key"]

Raises: RegistryNetworkError.


Authentication

The client supports two authentication modes defined in §9:

Mode How to use When
Anonymous Omit api_key Reading public packages
API Key Pass api_key="..." Publishing or accessing private packages

The API key is sent as a Bearer token in the Authorization header:

Authorization: Bearer <api_key>

Error Mapping

HTTP errors from the server are automatically converted to typed Python exceptions. See exceptions.md for the complete mapping.

HTTP Status Exception
400 InvalidPackageIdError
401 AuthenticationRequiredError
403 AccessDeniedError
404 PackageNotFoundError
409 ConflictError
5xx RegistryNetworkError

Server-side structured error responses (§13.1) are parsed and the type field is matched against the error-type-to-exception map, allowing the server to indicate specific error conditions (e.g. VersionNotFound) even when the HTTP status code is less specific.


Async Context Manager

RegistryClient supports async context management for automatic cleanup:

async with RegistryClient(base_url="https://registry.example.com") as client:
    content = await client.get_package("pkg_act_...")
# client.close() is called automatically on exit

Alternatively, call close() explicitly:

client = RegistryClient(base_url="https://registry.example.com")
try:
    content = await client.get_package("pkg_act_...")
finally:
    await client.close()

Real-Life Example: Supplier Agent Registration

Registering a supplier agent that validates part numbers in purchase orders by pulling an order-validation template from the package registry:

import asyncio
from typing import Any
from cleveractors.registry import RegistryClient, RegistryCache, CacheFactory


async def register_supplier_agent() -> dict[str, Any]:
    client = RegistryClient(
        base_url="https://registry.example.com",
        api_key="supplier-api-key",
    )
    cache = RegistryCache(client, max_size=256, ttl=300.0)

    # 1. Discover registry capabilities
    meta = await client.discover()
    if "actor" not in meta.get("supported_types", []):
        raise RuntimeError("Registry does not support actor packages")

    # 2. Resolve the order-validator template to its concrete ID
    resolved = await cache.resolve_package(
        package_type="actor",
        namespace="acme",
        name="order-validator",
        version="v1.0.0",
    )
    package_id = resolved["package_id"]

    # 3. Fetch the full template content
    content = await cache.get_package(package_id)
    print(f"Loaded template: {content.get('name', package_id)}")
    print(f"Stats — hits: {cache.stats.hits}, misses: {cache.stats.misses}")

    await cache.close()
    return content


if __name__ == "__main__":
    result = asyncio.run(register_supplier_agent())
    print(f"Agent config: {list(result.keys())}")