superdoc-sdk 2.25.0

Beta Rust SDK for the SuperDoc document runtime
Documentation
# SuperDoc Rust SDK (Beta)

**BETA:** The Rust SDK and its documentation are in beta. Pin the crate version and use its matching native runtime.

An asynchronous, generated Rust client for SuperDoc's native document runtime.
The crate uses Tokio and requires Rust 1.85 or newer. The development toolchain is
pinned separately in `rust-toolchain.toml`.

Start with [Automate a DOCX with Rust (Beta)](https://docs.superdoc.dev/agents/automation/rust-sdk).
Use the [shared Document API reference](https://docs.superdoc.dev/document-api/reference) for operation inputs,
receipts and failures, and [docs.rs](https://docs.rs/superdoc-sdk/latest/superdoc_sdk/) for Rust types and methods.

This is a beta prerelease. Release qualification includes the packaged crate,
its pinned native bundles and both host modes on Linux x64 and arm64. Source compilation alone does not qualify a release.

```rust,no_run
use superdoc_sdk::{Client, HostOptions, Result, generated::types::*};

# async fn example() -> Result<()> {
let client = Client::connect(HostOptions::new("/installed/runtime/superdoc")).await?;
let doc = client.open(&DocOpenParams::new().with_doc("input.docx".into())).await?;
doc.api().insert(&DocInsertBoundParams::new().with_value("Hello from Rust".into())).await?;
doc.api().save(&DocSaveBoundParams::new().with_out("output.docx".into())).await?;
doc.close(&DocCloseBoundParams::new()).await?;
client.shutdown().await?;
# Ok(())
# }
```

Provisioning the native runtime is explicit. Building the crate and invoking a
document operation never download an executable. Normal native use requires
neither Node nor Python. `HostOptions` spawns the specified executable directly;
`prefix_args` supports explicit development wrappers without a shell. The CLI
host is the default. Set `HostMode::Document` for the standalone document host;
unsupported collaboration or CLI-only fields fail before transmission.

The generated `DocApi` follows the contract's namespaces using Rust snake case:
`doc.api().comments().create(...)`. Document methods bind their session ID.
`Client::describe()` and `Client::describe_command()` inspect the embedded
contract without starting a host. Core tool schemas, prompts and dispatch use
`get_tools`, `get_system_prompt` and `Document::dispatch_tool`; custom JavaScript
callback registration is outside the Rust API.

`Client::presets()` exposes the generated, typed sessionless preset getters for
catalogues, tools and prompts. An omitted preset uses the host's preset default;
the existing `get_tools` and `get_system_prompt` conveniences select core
explicitly. Preset getters also work with document hosts that advertise
`document.preset.full.v1`. Core dispatch requires the CLI host; Full dispatch
also supports document hosts advertising that capability.

For an editing agent, `Document::core_toolkit(&DocPresetGetToolsParams)` owns one
core policy for its tools, prompt and dispatch. With `enforceChangeMode: tracked`,
it reads the document's capabilities once and excludes unavailable tracked
actions while retaining conditional actions for native preflight. Pass the
returned tools and prompt to the model, and execute its calls through
`CoreToolkit::dispatch` to apply the same policy. The toolkit preserves the
provider's tool and cache metadata. Build a new toolkit when the document's
capabilities change; its capability snapshot is fixed at construction.

`Document::full_toolkit(&DocPresetGetToolsParams)` provides the experimental
Full preset's tools, prompt and dispatch under one policy. `FullToolkit::dispatch`
and `dispatch_with_options` use the shared Full dispatcher for API search,
API calls and built-in actions. See `examples/full-toolkit.rs` for the typed
parameter and return shapes.

Required request fields are constructor arguments; optional fields have
`with_*` builders. `Presence::Missing` omits a field, while
`Presence::Present(Nullable::Null)` sends explicit JSON null. Optional non-null
fields reject null. Integers are checked against the JavaScript host's exact
integer range; numbers must be finite. Overlapping unions preserve their original
JSON and expose checked variant accessors. Closed objects reject unknown fields;
open maps preserve additions. The host validates schema constraints such as
ranges, patterns and exclusive alternatives.

All handle clones share close state. Close and shutdown are explicit async
operations; dropping a document never saves it. A failed close with a known
host error preserves the handle. Cancelling a request before writer admission
sends no frame. After transmission starts, cancellation stops waiting while the
transport drains the reply. Cancelling a transmitted open retires the host to
avoid leaving an unreachable session. Cancellation or timeout can leave a sent
mutation's outcome unknown: inspect `Error::outcome_unknown` and reconcile saved content
before retrying. The SDK never replays mutations automatically. Explicit request
deadlines on structured save and replacement fail before transmission. A CLI
save can finish writing its output and updating saved metadata after a returned
`TIMEOUT`; that error has `outcome_unknown: true`. The underlying command keeps
its context lock until it finishes, but the timeout does not cancel file I/O.
The document handle can remain usable after this response. Reconcile the output
and saved metadata before another save; applications must not retry it automatically.

For local sessions using document RPC, client shutdown stops new calls, drains
admitted work and saves dirty source documents before confirming native shutdown.
A source-save failure on a live connection returns the error and permits explicit
recovery. Ordinary CLI sessions retain the host's checkpoint and cleanup
behavior; use an explicit save when source-file persistence is required.

An undecodable mutation result marks its document handle and all clones as
outcome unknown. Unknown handles reject document operations, including discard
close; client shutdown releases the remaining native sessions. A malformed
read result leaves the document handle usable. A response watchdog or host death
retires the connection and invalidates its handles; this differs from an ordinary
returned host timeout and from a typed result decode failure. Open is an exception:
any returned `TIMEOUT` from open also retires the connection, marks its outcome
unknown and invalidates every sibling handle, even if the host reports a known
failure. Reopen verified content through a new client.

Default and per-call request timeouts accept one millisecond through
2,147,483,647 milliseconds. Startup and shutdown timeouts remain limited to one day.

`Error` retains the operation ID, host error code, RPC code, CLI exit code,
original host message and structured details. CLI and document domain codes take
precedence over transport timeout codes; uncategorized host failures use
`COMMAND_FAILED`, as in Node and Python. Default Debug/Display omit host text, document content,
stdin and authentication. Access original diagnostic fields deliberately.
Read optional metadata with `error.operation_id.as_deref()` and
`error.exit_code.as_deref().copied()`. Operation IDs use boxed strings, keeping
the error compact while preserving their complete text.
Notification lag is reported explicitly; clients must resynchronize cached state.
Owned temporary state is removed after confirmed process exit. Unconfirmed exit
retains state for diagnosis. Caller-owned state directories are never removed.

`CollaborationAuth::token` is scoped to one `open_with_options` call. Credentials
are carried in the RPC sidecar, never command-line arguments or persisted client
configuration. `replace_bytes` keeps its staged file alive until the native
request finishes or the process exits, including when the caller cancels.

For a shared room, a successful mutation receipt confirms the local document
transaction. It does not acknowledge server acceptance, peer visibility or disk
persistence. A remote-change event tells a subscriber to refresh its document;
it is not a durability acknowledgement. Applications that require a durable
checkpoint must use their collaboration server's persistence contract and retain
an operation journal. After an unknown outcome, reconcile room state through a
fresh client before deciding whether another write is needed.

Provision write-capable room credentials for editing agents. On the generic
`y-websocket` token path, the SDK has no trusted permission metadata and cannot
infer write access from a token. A server read-only token protects authoritative
room state, but a local mutation can still succeed and leave that client with
unreplicated local edits. Keep read-only agents on read operations and enforce
that restriction in the agent's tool policy. A fresh authoritative join is
required when reconciling such a client; a local success receipt is insufficient.

The room checks use pinned Yrs through y-sweet, document-scoped authorization and
independent clients. Yrs version, executable hash, helper and service configuration
are retained with the qualification results. Deployment with a different server
or persistence configuration requires exercising the same checks there.

From the public SDK workspace, run `check:rust`, `test:rust-generation` and
`test:conformance`. The independent installed-consumer and native release checks
are required in addition to source tests. The code generator owns generated
modules; edit the exported contract or emitter, then regenerate.

## Runtime setup

A released crate embeds the exact runtime version, contract hash, platform
mapping, archive digest and complete file inventory. Setup is explicit and
blocking; run it before document work (or use `spawn_blocking` in an async app):

```rust,no_run
use superdoc_sdk::{installation::{RuntimeManifest, host_target}, HostMode, Result};
use std::path::Path;
# fn example() -> Result<()> {
let manifest = RuntimeManifest::pinned()?;
let cache = Path::new("/application/cache/superdoc");
let installed = manifest.install(cache, host_target()?)?;
let host = installed.host_options(HostMode::Cli)?;
# Ok(())
# }
```

For offline setup, use `manifest.install_archive(cache, target, archive_path)`.
For subsequent starts, use `manifest.resolve(cache, target)` and
`installed.host_options(mode)`. Resolution verifies installed bytes and executable
permissions. A corrupt existing installation fails closed; provision a new cache
or remove that exact, inactive version after inspecting it. Setup serializes
concurrent installers and publishes a complete installation by atomic rename.
Interrupted temporary staging directories are never treated as installed versions.
An externally supplied executable can use `HostOptions::new(path)`; its host
protocol and capabilities are checked when connecting.

The deployment mappings are `aarch64-apple-darwin`, `x86_64-apple-darwin`,
`aarch64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu`, and
`x86_64-pc-windows-msvc`. Native CI executes on Linux x64 and arm64 only. Both
Linux jobs verify and provision every pinned bundle, including Mach-O/PE target
identity and retained Mac signature data. Those checks do not execute a Mac or
Windows runtime. Linux bundles require glibc; musl, WASM and Windows ARM are
unsupported. Tested Linux environments use Ubuntu 24.04. Older OS/CPU baseline
compatibility has not been established by these checks.

The checked-in manifest is an intentionally unprepared template. Source builds
can configure a host explicitly; release preparation replaces the template in an
isolated package copy. Publishing a source checkout directly with `cargo publish`
does not produce a qualified release.

## Validation and release

From the SDK package, run `pnpm check:rust`, `pnpm test:rust-generation` and
`pnpm test:conformance` after `pnpm prepare:ci`. From Orbit root, run
`pnpm proof run --suite superdoc/rust-sdk`. The Proof adapter collects actual
Cargo test cases, checks generated bytes against the retained preparation, and
executes every reviewed case once. Installed qualification extracts the exact
`.crate` outside Orbit, resolves fresh consumer dependencies, builds with Rust
1.85 and the pinned current compiler, and removes Node/Python from its runtime
PATH. Use:

```sh
pnpm test:rust-installed CANDIDATE_DIRECTORY INPUT.docx linux-x64
```

The Orbit SDK preview CI also qualifies a controlled authenticated collaboration
room with independent Rust clients, edits in both directions, invalid auth,
rejoin, DOCX export and reopen. The 100-cycle native run checks host-process
count, state-file growth, save/close, dropped read futures, and a 256 MiB host-RSS
growth ceiling after ten warmup cycles. This is a bounded regression check,
not a production capacity claim.

The SuperDoc SDK team owns SDK changes and prerelease feedback. The Orbit release
owner owns candidate custody, native bundles and publication. Rust has its own
`0.x` prerelease sequence: `pnpm version:set-rust 0.1.0-alpha.2` changes only the
Cargo package and its lock entry. A Node/Python `2.x` release prepares Rust
artifacts but never publishes a Rust version automatically.

The release adapter requires a clean committed source and a canonical native
SDK inventory. It hashes the exact source/assets, Cargo's normalized archive,
the generation receipt, and all five deterministic runtime archives. Evaluation
candidates remain `localOnly` and publication rejects them. Keep the complete
candidate directory and the Linux qualification JSON receipts together.

From Orbit, `pnpm rust:publish:next --version 0.1.0-next.beta.1` coordinates
canonical GHA qualification and local publication using your exported
`CARGO_REGISTRY_TOKEN` or `CARGO_TOKEN`. First publication also requires
`--claim-new-crate`; if immutable runtime releases are disabled, the explicit
one-time `--enable-runtime-immutability` flag is required. Run from a clean
checkout of the current main commit. The command retains artifacts so the same
invocation can resume verification after an interruption.

The lower-level release-owner operation is
`scripts/publish-rust-sdk.mjs MODE CANDIDATE QUALIFICATION_DIRECTORY [--owner LOGIN]`:

1. `--preflight` checks all local artifacts, both exact-candidate Linux receipts,
   controlled Hocuspocus and authenticated Yrs collaboration evidence for each Linux
   architecture, registry ownership and version availability.
   First publication additionally requires `--claim-new-crate`; name availability
   and organization ownership must be confirmed by the release owner.
2. `--stage-runtime` uploads missing bundles to a GitHub release draft. Existing
   assets must have identical digests; it never overwrites them. For a new draft,
   pass `--release-target PUBLIC_COMMIT_SHA`, an existing commit in the public
   release repository. The private Orbit source SHA remains separate provenance.
   Publish the
   complete draft with GitHub release immutability enabled.
3. `--publish-crate` requires that immutable release, reads back every remote
   bundle, uploads the tested `.crate` bytes through Cargo's registry API, then
   reads back the registry checksum and downloaded crate. It writes a publication
   receipt only after they match.

Credentials come from `CARGO_REGISTRY_TOKEN` (or `CARGO_TOKEN`) and authenticated `gh`; do not put
them in argv or receipts. A timeout after upload is an ambiguous result. Preserve
the candidate and resume verification of that immutable version; do not rebuild
or blindly retry. A conflicting published checksum stops publication. Stop
promotion for a broken release, publish a corrected crate/runtime pair and use
`cargo yank` only through the release owner's normal withdrawal process. Do not
replace a bundle or edit generated modules to repair a published version. Pinning
the previous crate also pins its previous runtime.

Manual Orbit SDK preview qualification defaults to the canonical build profile.
Its optional `stress` input expands the seeded framing corpus from 2,000 to
100,000 cases and the native lifecycle run from 100 to 1,000 cycles. Ordinary
PR callers retain the explicitly marked evaluation profile; publication requires
qualification of the canonical candidate. For a stacked PR with a non-main base,
dispatch this workflow on the exact branch because automatic native preview
routing is limited to main/v2 bases. Keep that exact-head workflow URL with the
qualification receipts.