Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

terva-sdk-rust

A Rust SDK for terva external processes: the protocol crates and author-side runtimes for terva connectors (connproto) and extensions (extproto), so a Rust connector or extension starts from a conformant wire instead of hand-rolling one.

Extracted from the two Rust implementations that proved the patterns — terva-conn-matrix (the Matrix connector) and terva-ext-index (the tree-sitter outline extension) — and grown by demand, not speculation.

Status: P1-P9 landed — the wire, env, and connproto/connsdk crates are extracted with their conformance suites (terva's published golden corpus and the byte-exact host-conversation replay), terva-conn-matrix runs on them (verified by its hermetic suite and eight-scenario live harness), terva-extproto covers the full protocol 1..6 vocabulary on the same published-corpus model, terva-extsdk serves the extension MVP (tools, standing context, cwd-following, jail) with a replay-style run-loop suite, terva-ext-index runs on it, the protocol-5 connector carrier composes the two halves (Extension::connector hosts the standalone connsdk engine over the chat envelope tunnel, proven end-to-end by a duplex live-conversation suite), secrets are sealed at rest (connsdk's SealedState for a standalone connector's own config, the protocol-6 brokered secret verbs for extensions — verified against the Go SDK in both directions), and the release model below is in effect: consumers pin by tag, and the connector and bridge templates seed new projects.

Crates

crates/
  terva-wire       — LF-JSON framing: 4 MiB cap, CRLF tolerance, bounded
                     oversize drain; byte-parity with terva's lineframe
  terva-env        — terva home/state-dir resolution, 0600/0700 atomic
                     persistence, the stderr panic hook
  terva-connproto  — connproto v1..2 frame types, golden-tested against
                     the host corpus (byte-exact encode / tolerant
                     decode), plus the additive hello `secrets`
                     declaration
  terva-connsdk    — the connector serve loop: Service trait, Responder
                     (exactly one result per command, enforced by Drop),
                     FrameSink single-writer discipline, strict handshake,
                     malformed-command envelope recovery — and
                     SealedState: per-value at-rest sealing of the
                     connector's own config, dual-recipient, each value
                     bound to its path (`load`/`save` for bytes,
                     `load_as`/`save_as` for a config type)
  terva-extproto   — extproto frame types (protocol 1..6), golden-tested;
                     the chat-tunnel envelope carries connproto frames
                     verbatim (RawValue — provably unperturbed bytes)
  terva-extsdk     — the extension runtime: Extension builder (tools,
                     standing context, cwd-following, min-protocol),
                     Host dirs, the self-Jail discipline, ToolResult,
                     panic-isolated dispatch, oversize degradation,
                     the protocol-6 brokered secret verbs (an extension
                     stores through the host, not a key of its own) —
                     and, behind the off-by-default `carrier` feature,
                     the protocol-5 connector carrier: one process that
                     is also a chat connector, hosting the connsdk
                     engine over the `chat` envelope tunnel
templates/
  bridge           — a ready-to-copy extension+connector project (one
                     tool, standing context, a stub connector service)
  connector        — a ready-to-copy STANDALONE connector: the four
                     verbs, and credentials sealed at rest and declared
                     in the handshake

One protocol family, one wire, composed: Extension::connector hosts the SAME terva_connsdk::Service a standalone connector implements, so a connector moves between the two packagings by swapping a few lines of main. Planned: terva-ctrlproto (later).

Protocol compatibility

Each crate states its contract in its own docs; the summary:

crate speaks oracle
terva-wire protocol-agnostic framing (4 MiB cap) packages/lineframe
terva-env host env conventions, no wire protocol envcompat / privfs
terva-connproto connproto v1..2, plus the additive hello secrets declaration terva's published golden.jsonl, vendored and drift-checked
terva-connsdk serves connproto v1..2 (range via ServeConfig); SealedState seals the connector's own state (enc:age:v2, dual-recipient), bytes via load/save or a config type via load_as/save_as the host-conversation replay + a Go-sealed interop fixture (both directions verified)
terva-extproto extension protocol 1..6 vocabulary terva's published golden.jsonl (89 cases, all 49 types), vendored and drift-checked
terva-extsdk extension protocol ≥ 1 (declare a floor with min_protocol; cwd-following needs ≥ 2; the connector carrier needs the carrier feature and a host ≥ 5; the brokered secret verbs need ≥ 6 and fail fast below it) replay + duplex carrier and secrets suites, run both ways round carrier

Features

terva-extsdk has one: carrier, off by default, enabling the protocol-5 connector role (Extension::connector). It links the whole connector SDK, and connsdk's at-rest sealing links age behind it — a 129-package dependency graph against 14 without. A tools-only extension should not pay for a role it cannot use, so the cost is opt-in.

terva-extsdk = { git = "https://github.com/terva-sh/terva-sdk-rust", tag = "v0.3.4", features = ["carrier"] }

When terva bumps a protocol, the matching proto crate re-mirrors before any consumer declares the new version.

Documentation

  • docs/design.md — what the SDK does and how the parts fit, with diagrams and almost no code. Start here to decide whether to use it.
  • docs/architecture.md — how each crate is built, which invariants hold it together, and where to add code. For building on it, extending it, or fixing it.
  • CHANGELOG.md — what changed between two tags, and what it means for code already written against the older one.

Consuming the SDK

Pin for release, path for development. Releases are annotated vX.Y.Z tags; all crates version in lockstep. Pin the tag and commit your Cargo.lock so --locked builds are reproducible:

[dependencies]
terva-extsdk = { git = "https://github.com/terva-sh/terva-sdk-rust", tag = "v0.3.4" }

What changed between two tags — and what it means for code already written against the older one — is in CHANGELOG.md.

For an SSH remote, cargo needs the git CLI for fetching — put this in your project's .cargo/config.toml:

[net]
git-fetch-with-cli = true

To co-develop against a local checkout without touching the pin, add a temporary [patch] (drop it — and the Cargo.lock churn — before committing):

[patch."https://github.com/terva-sh/terva-sdk-rust"]
terva-extsdk = { path = "/path/to/terva-sdk-rust/crates/terva-extsdk" }

New projects: copy a template — templates/connector for a standalone chat connector (four verbs, sealed credentials), or templates/bridge for an extension, with or without the connector role. Both are built and linted by CI against the crates they demonstrate, so neither can rot.

Development

Needs Rust (stable) and just.

just test    # cargo test --workspace (+ the template build)
just ci      # fmt --check + clippy -D warnings + locked tests + template

Day-to-day history lives on an internal main; the phase plan (PLAN.md) and release plumbing are maintainer-only and are not part of the public tree. Only curated release branches reach this mirror.

Conformance

terva's golden corpora are the oracles. Frames this SDK produces are asserted byte-exact against them; frames the host produces are asserted on decode.

connproto reads terva's published corpus. golden.jsonl is vendored into crates/terva-connproto/tests/testdata/ and driven case by case — not transcribed into Rust literals, which is a copy that drifts in silence. A completeness test requires every case to be exercised in exactly one direction, so a frame type added upstream fails the suite instead of sitting unnoticed. Byte-equality with Go's HTML-escaping encoder is now guaranteed rather than lucky: terva guards its corpus against acquiring a <, > or &, and this side re-checks it.

extproto reads terva's published corpus too, on the same model, with one addition: the extension fixture reports a direction per frame (ext_to_host / host_to_ext / both — the chat tunnel envelope is emitted by each side), so the classification is read from the fixture rather than guessed here. Its 89 cases cover all 49 frame types, against the 41-case sample the old hand-mirror was copied from — the protocol-6 secret verbs and the entire widget vocabulary had no coverage at all. That new coverage immediately earned itself: it caught this SDK emitting "value":3.0 where terva emits "value":3 for a whole-numbered meter widget.

At-rest sealing has an oracle of its own, since a format read from the source is not the same as one that interoperates: a tree sealed by the real Go connsdk.SealedState is committed as a fixture this SDK opens, and the reverse direction — Go's Load opening a tree this SDK sealed — was verified against the Go SDK directly.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages