# contractlane-operator-sdk

Python SDK for the Contract Lane Operator API.

## Install
```bash
pip install contractlane-operator-sdk
```

Requires Python `>=3.10`.

## Quickstart
```python
from contractlane_operator_sdk import ClientOptions, OperatorClient

client = OperatorClient(
    ClientOptions(
        base_url="https://localhost",
        session_token=lambda: "session-token",
        operator_token=lambda: "operator-token",
        challenge_headers=lambda _: {
            "X-Signup-Challenge": "signup-challenge-token",
            "X-Operator-Challenge": "proof-challenge",
            "X-Operator-Challenge-Token": "challenge-provider-token",
        },
    )
)
```

### 1) Human signup/auth
```python
start = client.public.signup.start({"email": "owner@example.com", "org_name": "Acme"})
session_id = start.data["signup_session"]["session_id"]

client.public.signup.verify({"session_id": session_id, "verification_code": "123456"})
client.public.signup.complete({"session_id": session_id, "project_name": "Default", "agent_name": "Primary"})
```

### 2) Admin actor + credential issue
```python
org = client.operator.admin.create_org({"name": "Acme", "admin_email": "owner@example.com"})
org_id = org.data["org"]["org_id"]
project = client.operator.admin.create_project(org_id, {"name": "Project A"})
project_id = project.data["project"]["project_id"]
actor = client.operator.admin.create_actor(project_id, {"name": "Bot", "scopes": ["cel.contracts:write"]})
actor_id = actor.data["actor"]["actor_id"]
client.operator.admin.issue_credential({"actor_id": actor_id, "upstream_token": "upstream-token"})
```

### 3) Agent-first enrollment
```python
challenge = client.public.agent_enrollment.challenge(
    {"public_key_jwk": {"kty": "OKP", "crv": "Ed25519", "x": "..."}}
)
challenge_id = challenge.data["challenge"]["challenge_id"]
client.public.agent_enrollment.start(
    {
        "challenge_id": challenge_id,
        "signature": "base64url-signature",
        "sponsor_email": "owner@example.com",
        "requested_scopes": ["cel.contracts:write"],
    }
)
```

### 4) Gateway contract + counterparty action
```python
client.gateway.cel.create_envelope(
    {
        "template_id": "tpl_123",
        "variables": {"amount": "100.00"},
        "counterparty": {"email": "counterparty@example.com"},
    }
)
client.gateway.cel.set_counterparty("ctr_123", {"email": "counterparty@example.com"})
client.gateway.cel.advanced_action("ctr_123", "SEND_FOR_SIGNATURE", {})
```

### 5) Public signing + actor key lifecycle
```python
client.public.signing.resolve("sign_tok")
client.public.signing.accept("sign_tok", {"challenge_id": "chal_123", "signature": "base64url-signature"})

client.operator.actor_keys.challenge(
    "act_123",
    {"public_key_jwk": {"kty": "OKP", "crv": "Ed25519", "x": "..."}},
)
client.operator.actor_keys.list("act_123")
client.operator.admin.list_actors_compat("prj_123")
```

### 6) Active envelopes
```python
client.operator.envelopes.list({"include_terminal": False, "page_size": 20})  # active only
client.operator.envelopes.list({"include_terminal": True, "sort_by": "updated_at", "sort_order": "desc"})  # active + terminal
client.operator.envelopes.get("ctr_123")
client.operator.envelopes.get_by_envelope_id("env_123")
```

## Notes
- Mutating operator/public endpoints auto-inject `Idempotency-Key`.
- `request_id` is available via `response.meta.request_id`.
- Challenge hooks can provide `X-Signup-Challenge`, `X-Operator-Challenge`, and `X-Operator-Challenge-Token`; per-request headers can override hook values.
- Failures raise `APIError` with `status`, `code`, `message`, `request_id`, `meta`, `raw_body`.
- Template enable accepts optional `enabled_by_actor_id`; if provided it must be an active actor id in the project.
- `operator.history.get(envelope_id)` normalizes missing history records to a pending shape.
- `operator.envelopes.*` reads active lifecycle index state (in-flight + optional terminal).
- `operator.history.*` remains finalized/indexed audit feed.
- Prefer `gateway.cel.set_counterparty()` over deprecated plural/staged wrappers (`set_counterparties`).

See `SDK_BEHAVIOR_MATRIX.md` for canonical/deprecated method parity and backend-behavior notes.

## Integration Tests
Run against a live operator stack:
```bash
python -m pytest tests/integration -m integration
```

Default `pytest` run skips integration tests (`-m "not integration"`).

Strictness controls:
- `TEST_ADMIN_STRICT_SUCCESS=1`: require admin flow success; otherwise unauthorized admin environments are treated as non-blocking.
- `TEST_GATEWAY_STRICT_SUCCESS=1`: require gateway create/action/proof success; otherwise `404` is treated as environment limitation.

## Publish (PyPI)
```bash
. .venv/bin/activate
pip install -U build twine
python -m build
python -m twine upload dist/*
```

If using token auth, set:
```bash
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=<pypi-token>
```
