# TypeScript interop boundary

This document records the surviving TypeScript and Python package boundary for
the Rust takeover. It is the package-disposition source of truth for OSS
packages during the final runtime cutover.

## Current boundary after takeover

Rust is canonical for trusted local runtime and execution: local skill and
graph execution, harness and dogfood execution, receipt sealing and
verification, history, policy and registry configuration, generic authority and
effect admission, sandbox admission/metadata, built-in adapter execution, and
external execution-adapter supervision (defined as a contract; see the
shipped-vs-defined note below for what the CLI actually enables). OS sandbox
enforcement is implemented in the Rust runtime for the local sandbox profile
(bubblewrap on Linux, sandbox-exec/seatbelt on macOS); TypeScript is not a
fallback confinement layer.
TypeScript remains for generated contracts, CLI/client wrappers,
cloud/product integrations, host adapters, authoring tooling, docs,
and helper SDKs over language-neutral external protocols. TypeScript does not
own a local executor-package fallback for trusted local behavior.

MCP follows the same rule. `rmcp` is an adapter-tier Rust dependency for MCP
protocol behavior, while runx keeps graph state, policy, authority, approvals,
and receipts in the Rust kernel. TypeScript MCP code may survive only as
generated contracts, hosted cloud code, helper SDKs over documented protocol
surfaces, or contract fixtures. It must not execute trusted local fallback
behavior.

The package CLI is a distribution and UX shim, not a second runtime. A usable
installation must be able to execute the Rust `runx` binary without TypeScript
source, tsx, or a local workspace. If the wrapper cannot find a supported
native binary, it should fail closed with installation guidance rather than
falling back to a TypeScript implementation of local behavior. No wrapper may
import deleted executor packages as a hidden execution fallback.

Four crossing families exist between the surviving TypeScript surface and the
Rust runtime. Each crossing has a contract surface that owns the wire shape.

1. **CLI JSON.** Anything that shells `runx`, including package launchers, the
   LangChain bridge, `runx-py`, user scripts, and CI workflows. The Rust CLI
   owns behavior; the contract is each command's JSON output shape, exit
   codes, and human-output stability.
2. **Published TypeScript contracts.** Anything that imports from
   `@runxhq/contracts`, including host adapters, cloud packages, and external
   TypeScript consumers. The contract is the TypeScript shape that mirrors
   `runx-contracts` Rust types, with fixture cross-validation.
3. **Cloud HTTP contracts.** Anything where the Rust runtime calls cloud, such
   as approval routing, connect/auth, registry, and receipts-store. The
   contract is versioned, documented HTTP endpoints.
4. **Language-neutral extension protocols.** Anything the Rust runtime launches
   or calls outside the trusted local runtime. This is a family of protocols, not
   one catch-all plugin API: skill subprocess ABI, external execution adapter,
   source-event ingress, hosted/embedded runtime binding, tool catalog/read
   model access, and thread/outbox provider adapters are distinct lanes. The
   contract is the lane-specific language-neutral protocol and manifest shape,
   not a TypeScript package API or an in-process Rust trait. External authors can
   implement these protocols in TypeScript, Python, Rust, or another language
   without a core fork. `external-adapter-plugin-protocol-v1` is the external
   execution-adapter lane only; it must not be used as the umbrella answer for
   source ingress, hosted runtime binding, catalog/read-model, or outbox queues.
   Thread/outbox provider mutation is owned by
   `thread-outbox-provider-protocol-v1`; provider adapters that need tokens must
   consume Rust-supervised `CredentialDelivery`, while the existing file-thread
   helper remains a credential-free local persistence path.

No fifth boundary is added without updating this document. No published
TypeScript package is silently broken: each package disposition is named here.
Stable-boundary edits to consume new `runx-contracts` versions are normal
maintenance. Sunset means deletion or rename through the S-tier runtime
cutover; old executor package names must not survive as aliases.

### Shipped vs defined, and current boundary debt

The crossing families above describe the intended boundary. The shipped CLI is
narrower than the contracts suggest, and that gap is itself part of the boundary
truth, so it is recorded here rather than implied.

- **Family-4 lanes are contract-defined but not all shipped.** The `a2a` runtime
  supervisor is `#[cfg(feature = ...)]`-gated and is NOT enabled in `runx-cli`.
  The shipped binary enables the local `external-adapter` lane; future integration
  work should land there instead of adding provider HTTP clients to the kernel.
- **Effects: authority is Rust, domain interpretation stays outside the generic
  kernel.** Admission, capability binding, proof validation, and receipt sealing
  are Rust and stay Rust. Domain network legs such as payment settlement
  provider calls or external-signer calls are not kernel work: they are
  non-deterministic, secret-bearing or network-bearing, and offline-impossible,
  so they belong on family-4 external-adapter lanes behind the generic effect
  kernel.
- **Provider clients belong outside the kernel.** GitHub PR creation, provider
  outcome observation, and source-thread publication are owned by adapters or
  product workflows, not by in-kernel provider clients. When provider calls are
  wired for real, they belong on family-4 provider/external-adapter lanes rather
  than as token-bearing kernel `reqwest` clients. Payment's inert rail dispatcher
  and HTTP clients were also removed; real rails are rebuilt as generic effect-family
  adapters behind the kernel. The unbuilt GitHub post-merge publisher (mutation
  half) likewise belongs on the `thread-outbox-provider` lane, not a new
  in-kernel client. The deterministic halves these feed are pure and correctly
  stay in the kernel; only the network legs move.
- **Coded once, on the binary (in progress).** The agent loop now lives on the
  binary: the Rust managed-agent loop ships behind the enabled `agent` feature as the
  opt-in governance path (default stays host-drives). What remains is the MCP server,
  still implemented twice (Rust `serve_mcp_json_rpc` plus the TypeScript
  `cloud/packages/mcp-hosted`), and `cloud/packages/agent-runner`, still single-shot.
  The target boundary is one of each, on the binary: `cloud/packages/mcp-hosted` and
  `cloud/packages/agent-runner` shrink to a thin transport/auth bridge and a
  provider resolver respectively, neither owning a second MCP server or a second
  agent loop. The identity this boundary serves (runx as the governed execution
  layer: one governed core, protocol fronts, TypeScript as transport plus ecosystem
  adapters plus authoring) is recorded in the superproject plan
  `plans/governed-execution-layer.md`.

## OSS package dispositions

| Package | Disposition |
| --- | --- |
| `@runxhq/authoring` | Stays as authoring tooling for skills, manifests, protocol fixtures, and generated artifacts until the authoring DX plan decides whether any piece moves to Rust or scafld. It does not own trusted local execution. |
| `@runxhq/cli` | Stays as a platform-aware npm launcher that resolves and execs the Rust binary. It must remain useful from an installed package without TypeScript sources and must fail closed instead of falling back to TypeScript local execution. It also carries the drift-free cold-start: `npx @runxhq/cli new <name>` downloads the launcher and runs the same native `runx new` scaffold without a prior runx install. |
| `@runxhq/contracts` | Stays as the published generated TypeScript view of `runx-contracts`, maintained with fixture cross-validation. |
| `@runxhq/core` | Deleted. Its registry/config/parser remnants were not a shipped execution boundary; live OSS code uses Rust crates, generated contracts, tool-local modules, or explicit protocol packages instead. Cloud imports the promoted `@runx/protocol` package. |
| `@runxhq/host-adapters` | Stays as thin host response adapters over the runx host protocol, retargeted to `@runxhq/contracts` types. It can shape host/client responses, not execute trusted local runtime behavior. |
| `@runxhq/langchain` | Stays as an optional LangChain bridge that shells the `runx` CLI or uses documented external protocols for governed skill and tool invocation. |
| `runx-py` | Stays as a thin Python client over `runx` CLI JSON output. |

The deleted trusted executor packages and their npm deprecation text are
tracked in `docs/runtime-cutover-inventory.json`. They are intentionally absent
from the surviving package table because they no longer have a TypeScript
runtime surface, alias, path mapping, workspace dependency, or API export.

Cloud packages remain TypeScript. The Rust runtime consumes cloud through the
cloud HTTP contracts. Local registry and policy configuration remains
Rust-owned when exercised by the native CLI. Cloud/product integrations, host
adapters, authoring tooling, and helper SDKs can remain TypeScript as long as
they stay on one of the contract surfaces above or the cloud-owned
`@runx/protocol` helper package. External integration authors target the correct
language-neutral protocol lane; they must not need Rust, `runx-core`,
`runx-runtime`, or a fork of the core repository to ship an extension.

## Test ownership

Language-owned unit tests stay with their implementation. Contract and parity
tests use durable fixtures under `fixtures/`. End-to-end tests should spawn the
`runx` binary and assert stdout, exit codes, and JSON instead of importing
TypeScript internals. Trusted local graph, harness, receipt, authority,
registry/policy config, and payment behavior needs Rust coverage or a TS-free
Rust CLI fixture before wrapper tests can count as proof. External
execution-adapter tests prove protocol conformance by spawning or simulating the
external process through the documented wire contract, not by importing
TypeScript runtime-local internals. Other extension-lane tests must use their
own wire contract and must not borrow the execution-adapter protocol as a
stand-in.

The TypeScript oracle is temporary. Once a TypeScript domain sunsets, no new
fixtures should be derived from that domain's TypeScript implementation.
