Metadata-Version: 2.4
Name: solcis-sdk
Version: 1.0.0a4
Summary: Reference Policy Enforcement Point (PEP) client for Solcis — decide one action against the `policy` engine and enforce it, fail-closed.
Author: Solcis
License: MIT
Keywords: policy,enforcement,pep,authorization,agent-safety
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: blake3==1.0.9
Requires-Dist: pynacl==1.6.2
Dynamic: license-file

# solcis-sdk

The reference **Policy Enforcement Point (PEP)** client for [Solcis](https://solcis.ai), published
on PyPI as `solcis-sdk`. The Node (`@solcis/sdk` on npm) and .NET (`Solcis.Sdk` on NuGet.org)
clients carry the same API.

A PEP is the point in *your* application where — right before an agent does something risky (send
data, call a tool, spend money) — you ask the policy evaluator *"is this allowed?"* and obey. This
client runs the installed `policy` binary as a subprocess, asks it to decide **one** action, reads
the JSON answer, and **enforces** it. It is all-local and **fails
closed**: any problem is treated as `BLOCK`.

## Install

```sh
pip install solcis-sdk
```

While the SDK is in alpha, that installs the newest alpha release. Pin the version you install, as with
any dependency (`pip install solcis-sdk==<version>`). The package runs no install-time scripts, and its
only runtime additions are the crypto pins the receipt verifier needs (`blake3` + `PyNaCl`).

If you were given a wheel file instead, `pip install ./solcis_sdk-<version>-py3-none-any.whl` installs
the same package.

The package's version is its own, set separately from the version of the Solcis platform you deploy: a
lower number is still the right client. Compatibility rests on the **decision contract** the two were
built against, which this client checks on every call and answers with a **BLOCK** where an engine was
built to a different one.

> **Note:** the package is branded **Solcis** and its environment variables use the `SOLCIS_` prefix.
> The credentials it handles are minted under the `solcis_` prefix too: a channel pre-shared key (PSK)
> reads `solcis_pep_…` and an operating credential reads `solcis_ac_…`. An older credential may still
> carry the legacy `rails_` prefix, and both brands are accepted, so a `rails_pep_`- or `rails_ac_`-prefixed
> value is valid too, not a mismatch.

## Use

```python
import solcis_pep

result = solcis_pep.decide_and_enforce(
    {
        "action": "export",
        "enforcement_point_id": "agent-1",
        "user_id": "user-1",
        "source": "secret-db",
        "destination": "public-bucket",
        "payload": {"rows": 3},
    },
    policy_bin="/path/to/policy",  # or set SOLCIS_POLICY_BIN
    bundle="/path/to/bundle", trust="/path/to/trust",  # or SOLCIS_BUNDLE / SOLCIS_TRUST
)

if result["outcome"] == solcis_pep.PROCEED:
    ...  # let the action happen
elif result["outcome"] == solcis_pep.HOLD:
    ...  # pause; a human must decide first
else:  # solcis_pep.BLOCK
    ...  # stop the action
```

`policy_bin` defaults to the `SOLCIS_POLICY_BIN` environment variable (an explicit path — never a
bare name found on `PATH`). **Prefer `decide_and_enforce`** — it decides and maps in one call, so the
two steps can't be mis-wired.

With `policy_bin` set but **neither `bundle` nor `trust` configured**, `decide()` fails closed
(`no_bundle_configured`) rather than silently falling back to the engine's built-in demo policy — see
[the reason-code table](../README.md#the-process-exec-variables). To try the SDK with no bundle to
hand, opt into the demo engine explicitly: pass `demo_engine=True`, or set
`SOLCIS_PEP_DEMO_ENGINE=true`. **Not for production or a shared environment** — the demo engine
decides from a permissive sample policy, not one you authored. `demo_engine` is **tri-state**: the
default `None` defers to the env; `True` turns it on; anything else (including `False`) turns it off,
even with the env set — precedence is **per-call > env > off**.

The same opt-in also covers the demo engine's **receipt anchor** on the serve transport. `policy serve
--demo` signs receipts with a key anyone can reproduce, so without the opt-in the client refuses that key.
Configured as `SOLCIS_PDP_TRUST_ANCHOR`, a serve decide fails closed with `receipt_untrusted` and
`report_outcome` returns `outcome_receipt_unverifiable`, both before any connection; as a decide's
trust-on-first-use pin, the decide fails closed before any connection; and first seen on first contact, it
is never pinned and that decide fails closed once the response is read. Pass `demo_engine=True` to
`decide_via_selected_transport`, `enforce_action` or `report_outcome`, or set
`SOLCIS_PEP_DEMO_ENGINE=true`, only when rehearsing against the demo engine: the same setting turns on the
process-exec demo policy, so set it only in a rehearsal shell — see
[the demo receipt anchor](../README.md#the-serve-transport-variables).

### `enforce()` accepts a string **or** a result mapping

`enforce()` maps the engine's 5-outcome decision to `PROCEED` / `BLOCK` / `HOLD`. It accepts **either**
the decision string **or** a whole `decide()` result mapping (it reads `result["decision"]`), so passing
the whole result enforces the decision it carries:

```python
result = solcis_pep.decide(action, policy_bin="/path/to/policy")
solcis_pep.enforce(result["decision"])  # the decision string
solcis_pep.enforce(result)  # the whole result mapping — same outcome
solcis_pep.result_to_outcome(result)  # the self-documenting named form
```

A valid but unrecognised token still maps to `BLOCK` (fail closed). Genuine misuse — `None`, a number,
a list, or a mapping with no string `"decision"` — **raises** a descriptive, secret-free `TypeError` in
development and **fails closed to `BLOCK`** in production (`SOLCIS_PEP_ENV=production` or `python -O`);
pass `strict=` to force either mode. `decide_and_enforce` only ever passes the decision string, so it
never raises.

### Per-call enforcement mode

Set the enforcement mode for a single call with the `enforcement_mode=` keyword — threaded verbatim into
the process-exec argv as `--enforcement-mode <value>`:

```python
solcis_pep.decide_and_enforce(action, policy_bin="/path/to/policy", enforcement_mode="shadow")
```

**Precedence:** the per-call `enforcement_mode` (when non-empty) **overrides** `SOLCIS_ENFORCEMENT_MODE`,
which overrides the engine default. It writes **no** `os.environ`, is **never** sourced from the
request/action/payload, and governs the **process-exec transport only** — on serve the daemon owns the
mode (no effect, no error). It never softens a real `deny`: routing keys solely on `decision`.

### Non-blocking async API

`decide_async` and `decide_and_enforce_async` are **non-blocking** async siblings of `decide` /
`decide_and_enforce`. Each thread-offloads the audited sync core **verbatim** via `asyncio.to_thread`, so
it frees the event loop while the `policy` child is in flight and **resolves** to the same result mapping
the sync path returns for the same input:

```python
import asyncio
import solcis_pep


async def main():
    r = await solcis_pep.decide_async(action, policy_bin="/path/to/policy")
    result = await solcis_pep.decide_and_enforce_async(action, policy_bin="/path/to/policy")
    if result["outcome"] == solcis_pep.PROCEED:
        ...  # allowed — carry on


asyncio.run(main())
```

They are **additive** — the sync `decide` / `decide_and_enforce` are byte-for-byte unchanged — and govern
the **process-exec transport only** (the serve transport is already async network I/O; for the
config-selected front door use the sync `enforce_action` / `decide_via_selected_transport`). Because they
reuse the sync core verbatim, they inherit its **exact** fail-closed semantics (every failure resolves to
the identical coded `reason_code`) and the same decided-request / `bound_fields` binding, so
`assert_executed_request` behaves identically on an async result. They **never raise** — every failure,
including an async-plumbing fault, resolves to a fail-closed `BLOCK` (an unobserved raise could be
swallowed upstream and fail **open**).

**Threading — thread-safety matters.** The offload runs on the event loop's **default
`ThreadPoolExecutor`**, so the optional `on_decision` observer fires on a **worker thread** — it **must be
thread-safe** — and many concurrent `decide_async` calls **queue** on that bounded pool (each still spawns
its own `policy` child; the async path lifts the sync path's implicit spawn serialisation, so the host is
responsible for bounding in-flight decides).

When a bundle is configured and no skip-verify handle is passed, the offloaded core runs `policy verify`
then `policy decide` as **two sequential spawns**, so an un-skipped call costs up to **~2× the per-spawn
timeout budget**. Verify once at boot with `verify_at_boot` (below) and pass its handle as `verified=` to
collapse this to a single spawn.

### Bundle identity, skip-verify, provenance, and the executed→decided binding

Every decide result carries the engine's **native-identity block** — four additive, tolerant-parse
string fields (absent ⇒ `""`, never fabricated), surfaced on **both** transports:

- **`policy_id`** — the `id:` of the **policy that decided**: the policy whose rule decided, or whose own
  policy-level check did (such as its large-input refusal). For a deployment the console publishes, it is
  the policy id the console shows. It is `""` when no single policy decided — an allow traced to no
  rule, an empty set of policies, or an engine-wide check such as intent alignment (on a deny, the rule id
  says which). When several policies objected, it names the one whose outcome was enforced; the decision
  log names them all. **Identity is not an authz anchor**: it is a non-authenticated author label for
  observability only; never grant or route on `policy_id` alone.
- **`version_hash`** — `blake3(policy.yaml)`, the revision of the **combined set of policies enforced** — not
  the version of the policy `policy_id` names (distinct from the skip-verify `bundle_hash` and the serve
  Health `bundle_manifest_hash`).
- **`action`** — the decided action-subject the engine evaluated (e.g. `bank_transfer_external`).
- **`verification`** — `"verified"` (the signature was checked this call) or `"assumed"` (skip-verify).

```python
r = solcis_pep.decide(action, policy_bin="/path/to/policy", bundle=bundle, trust=trust)
# policy_id is "" when no single policy decided.
print("decided by", r["policy_id"] or "(no single policy)", r["action"], r["verification"])
```

Read the block in this order: if `ok` is false, ignore it; an empty `version_hash` means the demo engine or no
bundle; an empty `policy_id` with a `version_hash` means no single policy decided.

#### Rules the engine could not evaluate

When the policy engine cannot evaluate a rule (an AI model it could not reach, a fact the request did
not supply, a policy module that failed), it fails closed, usually to a hold. `reason_code` alone cannot
tell that hold from one the policy asked for. **`engine_exceptions`** can: one record for each rule, or eval
within a rule, that could not be evaluated, on both transports. It is `[]` when the engine
evaluated every rule, when the engine predates the field, and when `ok` is false.
**`engine_exceptions_omitted`** counts any further records the engine left out: an integer from 0 to
2147483647, and 0 when there are none or when the engine sent anything else.

| key | meaning |
| --- | --- |
| `rule_id` | the rule that could not be evaluated |
| `eval_id` | the eval within the rule, or `None` |
| `cause` | why it could not be evaluated (below) |
| `refusal` | when model routing refused, which check refused it: a `reason` token from the serve Health report's `escalating_rules`; otherwise `None` |
| `capability` | the capability name the policy's author gave the rule's AI model, never an endpoint; otherwise `None` |
| `policy_id` | the `id:` of the policy the rule belongs to; `""` when no single policy owns it; `None` on the demo engine |
| `deciding` | `True` on the one record, if any, whose failure is the outcome this decision took |
| `observe_only` | `True` when the rule is observe-only, so its failure never decides |

`cause` is currently one of `model_disabled`, `model_not_configured`, `model_unavailable`, `model_no_capacity`,
`decision_deadline_passed`, `model_timed_out`, `model_error`, `model_input_missing`,
`judge_misconfigured`, `predicate_unevaluable`, `wasm_module_failed`, `rule_has_no_evaluator` or
`required_registry_empty`. The list can grow: treat a value you do not recognise as a rule
that could not be evaluated, for a reason this version cannot name. A malformed record is skipped
rather than failing the decision.

```python
r = solcis_pep.decide_and_enforce(action, policy_bin=policy_bin, bundle=bundle, trust=trust)
for e in r["engine_exceptions"]:
    if e["deciding"]:
        logging.warning("%s could not be evaluated (%s)", e["rule_id"], e["cause"])
```

These records explain an outcome and never change it: route on `outcome` (or `decision`) as always. The
`on_decision` record does not carry them, so read them from the result.

#### The mandatory executed→decided binding

`decide` reports the `action` it evaluated; a PEP **must** bind the tool call it is about to execute to
that decided action. `assert_executed_action(result, executed_action)` is that binding, and it is the
**mandatory** PEP pattern: decide the action you are about to execute, then assert the executed action
equals `result["action"]` **before** acting. It returns `PROCEED` **only** when the decision routed to
PROCEED, the result is a real engine decision, and the decided action equals the executed one — every
other case (including a matched-action `deny`) returns `BLOCK`. A caller without this binding is outside
the trust envelope.

```python
r = solcis_pep.decide_and_enforce(action, policy_bin=policy_bin, bundle=bundle, trust=trust)
if (
    r["outcome"] == solcis_pep.PROCEED
    and solcis_pep.assert_executed_action(r, action["action"]) == solcis_pep.PROCEED
):
    ...  # safe to execute exactly action["action"]
```

When a signed policy denies on a **request field** (not just the action) — e.g. `destination` or
`payload.amount` — the decide result lists those fields in `result["bound_fields"]`, and the PEP must
bind **every** one. `assert_executed_request(result, executed_request)` is the generalisation of
`assert_executed_action`: it binds the action **and** every `bound_fields` entry, comparing each
executed value against the decided one **byte-for-byte** (no trim / case-fold / unicode / numeric
normalisation), so a decide-benign / execute-malicious dodge (decide `destination="internal"`, execute
`"external"`) fails closed to `BLOCK`. Prefer it whenever your policies branch on request parameters.
(It needs the decided request the SDK captured, so it applies to the process-exec `decide` result.)

```python
r = solcis_pep.decide_and_enforce(action, policy_bin=policy_bin, bundle=bundle, trust=trust)
if (
    r["outcome"] == solcis_pep.PROCEED
    and solcis_pep.assert_executed_request(r, action) == solcis_pep.PROCEED
):
    ...  # safe to execute action: its action AND every bound field matched the decided request
```

#### Skip per-call re-verify (`verify_at_boot`)

Verify a bundle **once at boot** and skip the per-call signature re-verify (two `policy` spawns → one):

```python
handle = solcis_pep.verify_at_boot(
    policy_bin, bundle, trust
)  # runs the real anchor verify, captures bundle_hash
r = solcis_pep.decide(action, policy_bin=policy_bin, bundle=bundle, trust=trust, verified=handle)
```

`verify_at_boot` runs the real Ed25519/anchor verify and, on success, captures the engine-reported
`bundle_hash` (`h`) into an opaque handle; `decide(verified=…)` then appends `--assume-verified
--expect-bundle-hash <h>`. **The exact security trade:** it **skips the Ed25519 signature check** but the
engine still recomputes every file's blake3 **and** the full-signed-set `h`, refusing fail-closed on any
mismatch. It **needs the operator-pinned hash** — a skip requested without a successfully-minted handle
**fails closed** (`assume_verified_requires_verify`), and with no bundle configured
(`assume_verified_requires_bundle`). A hand-built handle never enables skip; on the serve transport it is
a no-op. **Two-hash discipline:** `version_hash` (`blake3(policy.yaml)`, identity) ≠ `h` (the signed-set
`bundle_hash`, integrity) — never pin `version_hash` as the skip hash.

#### The decision-provenance hook

Pass `on_decision=callable` to observe the finalized decision — `outcome`, `decision`, `reason_code`,
`rule_id`, `policy_id`, `version_hash`, `action`, `verification`, `shadow`, `transport`, `ok` — without a
bespoke telemetry ring buffer. It fires exactly once, **after** the decision is finalized, **never**
influences routing, and a raise is swallowed (the observer must be non-blocking):

```python
solcis_pep.decide_and_enforce(action, policy_bin=policy_bin, on_decision=lambda record: log(record))
```

## The four security rules

1. **argv, never a shell string** — action data is passed as a plain argument, never evaluated by a
   shell. No command injection.
2. **decision from the JSON, never the exit code** — the engine exits `0` even when it denies. The
   outcome is read from `decision.decision`; the exit status is never trusted.
3. **fail closed** — any problem (non-zero exit, empty/garbled output, missing decision, timeout,
   an exception) maps to `BLOCK`. A broken check must never accidentally allow.
4. **no signing key** — the client verifies signatures and never produces them; it needs only the
   **public** trust anchor and the bundle. It does hold two secrets you must protect if you use those
   features: the serve channel's pre-shared key, and the agent credential you supply to enrolment.

The full frozen decide contract ships with the SDK sources rather than inside this package. Ask your
Solcis contact for it if you are writing a client of your own.

## The two transports (one API)

The distribution ships three top-level modules — `solcis_pep` (the process-exec transport + the unified
front door), `serve` (the framed-TCP serve transport), and `receipt` (the signed-receipt verifier). The
example above uses the process-exec entry point (`decide_and_enforce`). For the **unified front door**
that auto-selects serve-vs-process-exec by config, use `solcis_pep.enforce_action(action)`: it enforces
over the **serve** transport when `SOLCIS_PDP_ADDR` is set (a loopback `host:port`), else over
process-exec. `SOLCIS_PDP_ADDR` is the only selector — **not** `SOLCIS_PDP` (the testbed `stub|api` mode
enum) or `SOLCIS_PDP_URL` (an HTTP address). The serve transport verifies a signed receipt before
honouring any proceed-able decision.

> **Required — `SOLCIS_CHANNEL_AGENT_ID`.** The daemon channel-overwrites `request.enforcement_point_id` with the
> channel id its PSK maps to, so the signed receipt is bound to that channel id. Set
> `SOLCIS_CHANNEL_AGENT_ID` to that channel id: the agent's id from the console, shown under the
> environment's name on the agent's page. **There is no default.** Unset or empty, every decide
> BLOCKs with `channel_agent_id_missing` before any connection is opened, and `report_outcome` is refused
> with the same code. A **wrong** value fails closed too: a genuine allow fails the content-bind
> (`receipt_tampered`), and a legitimate outcome report surfaces as `outcome_receipt_unverifiable`
> (recorded, but not provably the reporter's).

### Serve env vocabulary (canonical names)

The serve transport is configured **from the environment only** (never from a request field). Each value
resolves with the precedence **explicit call option > canonical `SOLCIS_PDP_*` env**, read presence-based
(*not* truthy: the resolver tests `is not None`, never `or`, so a set-but-**empty** canonical stays empty
and still fails closed).

| Concept | Canonical |
|---|---|
| Pre-shared key (channel credential) | `SOLCIS_PDP_PSK` |
| Receipt-signing trust anchor | `SOLCIS_PDP_TRUST_ANCHOR` |
| Connect+read timeout (ms) | `SOLCIS_PDP_TIMEOUT_MS` |
| Daemon loopback `host:port` (selects serve) | `SOLCIS_PDP_ADDR` |

`SOLCIS_PDP_TRUST_ANCHOR` is the **receipt-signing** anchor (it verifies the signed serve receipt) — a
**distinct** name from `SOLCIS_TRUST`, the bundle-verify (`policy verify --trust`) anchor on the
process-exec path. No env name or value ever appears in a fail-closed diagnostic line (coded reason
tokens only).

The full env surface and the serve wire are in
[`../README.md`](../README.md#environment-configuration-surface) and
[`../SERVE_PROTOCOL.md`](../SERVE_PROTOCOL.md).

### What is this daemon enforcing? (`serve.health`)

To render "this engine is currently enforcing X" without making a decision, read the enforced-identity
block off a serve **Health** report. `serve.health()` returns a parsed `HealthReport` — or `None` on any
fault — and, on a bundle-serving daemon, that report carries `policy_set_id` — the id of the policy document enforced, `composed:` followed by your
enforcement point id for a policy set — and the same `version_hash` / `verification` a decision carries:

```python
from solcis_pep import serve

h = serve.health()  # None on any fault (unreachable / refused / malformed)
if h is None:
    ...  # could not reach the daemon — render "unknown"
elif h.policy_set_id is None:
    ...  # no bundle yet (demo engine, or awaiting first pull)
else:
    print(f"enforcing {h.policy_set_id} @ {h.version_hash} ({h.verification})")
    # on serve, h.verification is always serve.VERIFICATION_VERIFIED today
```

Three rules keep this honest:

- **Provenance, not permission.** `policy_set_id` / `version_hash` / `verification` ride **outside** the
  signed receipt, so they are labels to display and log — never an authorization anchor, and never a
  substitute for the pinned receipt trust anchor the serve decide path verifies.
- **Identity present is not fresh, unstale, or non-empty.** A present identity is what the engine decides
  with now; it does not say the bundle is current, that a reload did not fail, or that anything is
  enforced (a signed zero-member bundle has an identity and enforces nothing). Freshness is the
  operator-only `bundle_manifest_hash` / `bundle_stale`, absent (`None`) from a Pep report.
- **`None` means unknown.** `serve.health()` collapses every fault to `None`. Render "unknown"; never
  fall back to a negative claim about enforcement.

## Enrol — bind your agent's identity at startup (`enrol`)

A **cold-path** helper, separate from the decide/enforce core, wraps the control-plane
`POST /api/agent-environments/enrol`: a running agent presents its `solcis_ac_…` operating credential and
its runtime `did`, and its declared instance flips `declared -> active`.

```python
from solcis_pep.enrol import enrol, EnrolError

try:
    # Each keyword falls back to its env var, so a fully-configured agent can call enrol() with no args.
    instance = enrol(
        base_url=base_url, credential=credential, did=did
    )  # EnrolResult(id, did, first_seen_at, status="active")
except EnrolError as e:
    if e.retryable:
        ...  # transient (control-plane down / rate-limited / 5xx) — safe to retry: a repeat enrol is idempotent
    else:
        raise  # a config error (wrong credential, did already claimed) — fail startup loudly
```

| Env var | Meaning |
|---|---|
| `SOLCIS_CONTROL_PLANE_URL` | The control-plane **origin** (management plane — **not** the PDP; unrelated to `SOLCIS_PDP_ADDR`/`SOLCIS_PDP_URL`). Must be `https` unless loopback. |
| `SOLCIS_AGENT_CREDENTIAL` | The `solcis_ac_…` operating credential (a bearer secret; never logged). |
| `SOLCIS_AGENT_DID` | The runtime did (**not** `SOLCIS_CHANNEL_AGENT_ID`, the serve channel id). |

**Idempotent** on the same `did` (a 200 no-op, `first_seen_at` preserved) — call it every boot; retry when
`e.retryable`. It **raises** (it does not fail closed to BLOCK): `EnrolUsageError` on misuse (missing config
or a non-https base), `EnrolError` (with a closed `.code`, a `.retryable` flag, and the HTTP `.status`) on an
HTTP/transport fault. It uses only the stdlib (`urllib`) — no new dependency — and **transits** your
credential to the control-plane, storing none. TLS trust uses the system OpenSSL trust store.

## Migration: vendored file → installed package

If your tree still vendors this client **by path** — e.g.
`sys.path.insert(0, ".../oss/pep-sdk/python")` then `import solcis_pep`, or a `SOLCIS_PEP_PYTHON`
directory override — depend on the installed package instead, as described in [Install](#install)
above:

```sh
pip install solcis-sdk
```

Everything installs under the one name `solcis_pep`: `import solcis_pep` is the main entry point, and
the helpers are its submodules — `from solcis_pep import serve, receipt` and
`from solcis_pep.enrol import enrol`. Replace the `sys.path` insertion (or the `SOLCIS_PEP_PYTHON`
override) with a normal dependency on `solcis-sdk`; the call sites (`solcis_pep.decide_and_enforce(...)`,
or `solcis_pep.enforce_action(...)` for transport auto-selection) are unchanged.

**Breaking change from 1.0.0a3 and earlier:** those releases installed `serve`, `receipt`, `tofu`,
`enrol` and `env_names` as separate top-level modules, so code wrote `import serve` or
`from enrol import enrol`. Those names were generic enough to clash with any other installed package
that ships one, so they now live inside `solcis_pep`. Change `import serve` to
`from solcis_pep import serve`, `import receipt` to `from solcis_pep import receipt`, and
`from enrol import …` to `from solcis_pep.enrol import …`. `import solcis_pep` and the enforcement
result are unchanged. The only other additions are the
minimal crypto runtime dependencies (`blake3` + `PyNaCl`, pulled transitively, used only by the
serve/receipt path — the process-exec path is unaffected) and the new serve transport + unified front
door. The `SOLCIS_PEP_PYTHON` override remains only as an escape hatch for containers that mount the
client at a fixed path.

## Licence

MIT.
