diff --git a/rfcs/0028-openclaw-owned-rust-node-runtime.md b/rfcs/0028-openclaw-owned-rust-node-runtime.md new file mode 100644 index 00000000..560c8865 --- /dev/null +++ b/rfcs/0028-openclaw-owned-rust-node-runtime.md @@ -0,0 +1,477 @@ +--- +title: OpenClaw-owned Rust node runtime +authors: + - Gio Della-Libera +created: 2026-07-29 +last_updated: 2026-07-31 +status: draft +issue: +rfc_pr: https://github.com/openclaw/rfcs/pull/54 +--- + +# Proposal: OpenClaw-owned Rust node runtime + +## Summary + +OpenClaw should own a reusable, headless Rust node runtime in the OpenClaw +repository. Its first proposed product adopter is the new Copilot app system +tray in Microsoft's Edge/Chromium repository. Separately, Windows Companion +and `openclaw-windows-node` can align on the same crates to simplify their +native stack. Each product embeds or launches the runtime through a narrow +adapter while retaining ownership of its user experience, native tools, +packaging, and deployment. + +This RFC records the requested technical ownership split. Microsoft product +stakeholders want the Windows shell to remain with the Edge/Chromium product +while the reusable, product-neutral runtime lives in OpenClaw. That is a +proposal for OpenClaw maintainer agreement, not a claim that the architectural +decision has already been accepted. + +The acceptance discussion and decision record for that proposal is +[RFC PR #54](https://github.com/openclaw/rfcs/pull/54). Acceptance of this RFC +means accepting the bounded, unpublished monorepo foundation described below; +it does not by itself publish crates, ship the proof binary, promise platform +support, or accept broader TypeScript node-host parity. A maintainer approval on +the RFC PR records that bounded technical decision. It does not assign +individual maintainers or require a staffing decision as a condition of +acceptance. Normal OpenClaw maintainership, review, and CODEOWNERS processes can +evolve with the implementation. Later release or scope expansion requires the +separate gates in this RFC and its companion specifications. + +## Motivation + +OpenClaw already has a TypeScript node host, and its Tauri application has Rust +code that connects to the Gateway. Neither is the right reusable boundary for a +Windows-native product that intends to run without a Node.js or Tauri runtime: + +- `src/node-host` combines the canonical node behavior with a TypeScript/Node.js + implementation and process assumptions. +- Tauri is an application shell and consumer of Gateway connectivity, not a + complete headless node runtime. +- A Rust-to-TypeScript wrapper would retain the Node.js dependency and split + lifecycle, cancellation, and security ownership across two runtimes. +- Implementing the node directly in Edge/Chromium would make protocol and policy + drift likely and would not give Scout Cloud or other native hosts a reusable + OpenClaw implementation. + +The desired architecture is materially simpler when a product shell delegates +the portable node execution plane while all remote management remains behind +Gateway authentication and authorization: + +![Proposed OpenClaw Rust node runtime topology](0028/rust-node-runtime-topology.svg) + +The Copilot app system tray is the first proposed host. Windows Companion is a +separate adopter: it can reuse the same crates for its native node work and can +also act as an authorized Gateway controller for the tray node. Scout Cloud and +other authorized OpenClaw controllers use the same Gateway-mediated path; none +connect directly to the local runtime. + +One implementation and conformance suite can then serve native desktop shells, +headless deployments, and managed-node scenarios without transferring ownership +of those products into OpenClaw. + +## Goals + +- Establish OpenClaw as the owner of the portable Rust node runtime, its Gateway + contract, security invariants, conformance tests, and release compatibility. +- Keep Microsoft-specific tray, settings, Windows UX, native-tool adapters, + signing, packaging, and deployment in Microsoft-owned repositories. +- Provide a headless runtime that can be embedded in-process or launched as a + sidecar without requiring Tauri or Node.js. +- Allow Windows Companion and Scout Cloud to manage the same node identity and + lifecycle through canonical Gateway APIs. +- Reuse OpenClaw's command, approval, cancellation, and policy semantics rather + than defining a Microsoft-specific wire profile. +- Preserve incremental value: the Gateway client can be used independently of + the complete node host. +- Let OpenClaw maintainers accept the Rust commitment incrementally—from the + shared client, through the bounded node-role subset, up to broader native + node-mode parity—without making later layers prerequisites for earlier ones. +- Keep every accepted runtime and capability API customer-neutral so native, + embedded, appliance, and headless adopters can reuse it without Microsoft + product dependencies or a product-specific protocol profile. + +## Non-Goals + +- Moving Windows Companion, the system tray, or Edge/Chromium product code into + the OpenClaw repository. +- Replacing Tauri or requiring native consumers to use Tauri. +- Making OpenClaw responsible for Microsoft product UX, native tool + implementations, IPC, signing, packaging, deployment, or support policy. +- Sandboxing product-native handlers. Product adapters are trusted components + inside the embedding product's security boundary; untrusted capability code + must not be registered with this runtime. +- Adding a second protocol or Microsoft-specific node role. +- Shipping unrestricted `system.run`, PTY, or process execution without the + canonical OpenClaw approval and policy boundary. +- Reimplementing the model-facing agent loop inside the node runtime. This RFC + covers the node invocation loop; any broader agent-loop ownership requires a + separate decision. +- Stabilizing or publishing the Rust crate APIs before conformance and adoption + evidence are sufficient. + +## Proposal + +The normative candidate contracts and evidence are split into companion +documents so this RFC can stay focused on ownership and architectural choice: + +- [Rust Gateway client v1 specification](0028/gateway-client-v1-spec.md) +- [Rust node runtime v1 specification](0028/node-runtime-v1-spec.md) +- [Rust node sidecar hosting v1 specification](0028/sidecar-hosting-v1-spec.md) +- [Rust node sidecar Gateway connection v1 specification](0028/sidecar-gateway-connection-v1-spec.md) +- [Conformance and adoption plan](0028/conformance-and-adoption-plan.md) +- [Implementation and evidence inventory](0028/implementation-and-evidence-inventory.md) + +The first two describe the reusable OpenClaw layers. The hosting specification +defines product-neutral process and IPC invariants. The connection specification +defines how a sidecar reacquires endpoint/auth material, requests external +signatures, returns issued tokens, and retires authority without selecting a +product IPC transport or secure-store API. The plan and inventory distinguish +implemented evidence from future adoption and release gates. + +### Repository and crate boundary + +The runtime lives in `openclaw/openclaw` as two workspace crates: + +1. `openclaw-gateway-client` owns transport, authentication, request/response + correlation, event delivery, reconnect classification/backoff primitives, + and trust policy. Reconnect supervision, per-attempt material, and final + retry decisions remain with `NodeLifecycle` or the embedding. The client is + useful to applications—including Tauri applications—that need Gateway + connectivity but do not host a node. +2. `openclaw-node-host` builds on the client and owns node identity, pairing, + capability declaration, lifecycle, invocation dispatch, duplex input and + progress, cancellation, and a bounded adapter boundary for commands/tools. + +The split is deliberate: it prevents every Gateway consumer from inheriting an +execution runtime, while ensuring node hosts do not invent transport or +authentication behavior. + +The crates remain in the OpenClaw monorepo while the contract is evolving. This +keeps TypeScript and Rust conformance changes reviewable together and avoids +repository, version, and release skew. A later RFC or maintainer decision may +publish them once the public API and support policy are stable. + +During incubation, `pub` means available to reviewed workspace consumers, not +a stable or supported public Rust API. The crate names, module layout, Rust +types, configuration, and proof-binary interface may change through ordinary +OpenClaw review without SemVer compatibility. In-tree consumers must change in +the same commit or stacked series, and the shared wire fixtures and canonical +Gateway behavior—not the current Rust API shape—remain the compatibility +authority. The supported-release gate must define an API/versioning policy +before publication or compatibility claims. This RFC does not select or commit +to a cross-repository distribution mechanism. + +### Ownership boundary + +OpenClaw maintainers own: + +- the Gateway Rust client and node-host implementation; +- protocol compatibility and role-safe session behavior; +- identity, pairing, reconnect, cancellation, input, and progress semantics; +- bounded invocation supervision and the generic tool adapter contract; +- integration with canonical OpenClaw approval and command/tool policy; +- cross-language fixtures, conformance tests, compatibility, and releases. + +Microsoft product teams own: + +- the Copilot app system-tray shell in Edge/Chromium; +- the Windows Companion and `openclaw-windows-node` product integration; +- settings and Windows-native user experiences; +- Windows-native tools and their runtime adapters; +- the IPC shape between the product shell and an out-of-process runtime, when + that deployment mode is selected; +- product policy selection, signing, packaging, deployment, and servicing. + +Scout Cloud owns its management and orchestration experience. It discovers and +manages the node through supported Gateway surfaces; it does not fork the Rust +runtime or introduce a separate node protocol. + +Microsoft is prepared to act as the anchor adopter and contribute implementation, +conformance, and maintenance for the Rust capability layers, including broader +node-mode parity if OpenClaw accepts that scope. That sponsorship does +not make the runtime Microsoft-specific: accepted APIs, fixtures, and policy +integration must serve other OpenClaw customers and contributors equally. +OpenClaw maintainers retain authority over the canonical contract, security and +policy boundaries, repository acceptance, and release requirements. The +maintainer group can therefore choose the product scope that is right for +OpenClaw and its broader customer base without making individual staffing part +of this architecture decision. + +Product-specific shells and adapters remain outside the generic runtime. Other +customers can embed the crates, supervise the headless host, and contribute +portable capability layers under the same conformance and ownership rules. + +### Embedding contract + +The runtime must support both of these topologies without changing its semantic +contract: + +- **In-process:** a native shell links the crates and supplies platform adapters. +- **Sidecar:** a shell supervises an OpenClaw-owned executable over a narrow, + authenticated local IPC adapter. + +The selection is a product and deployment concern. Runtime APIs must not expose +Tauri types, Chromium types, Windows handles, or Scout-specific management +objects. Platform tools are registered through bounded adapters and execute only +after canonical admission and approval decisions. + +The adapter is trusted product code, not a sandbox boundary. The Rust runtime +authenticates, authorizes, bounds, correlates, and cancels dispatch, but it does +not isolate a handler from the product account or operating system. The product +owns least privilege, OS permissions, containment, and any stronger isolation +required for native capability execution. + +### Hosting and management are separate relationships + +A product may have either or both of these relationships with a Rust node: + +1. **Local hosting and supervision:** the product shell embeds the runtime or + starts it as a sidecar, supplies credentials and native-tool adapters, and + owns local start, stop, health, and recovery behavior. The first proposed + host is the Copilot app system tray in Edge/Chromium. +2. **Gateway-mediated management:** Windows Companion, Scout Cloud, or another + authorized OpenClaw controller observes and manages a node through canonical + Gateway APIs. + +The first relationship is local process composition. The second is a control- +plane relationship and does not require another runtime or a product-specific +management implementation inside `openclaw-node-host`. The Copilot system-tray +node can be locally hosted by its product shell while authorized controllers +manage it through the Gateway. + +Gateway remains authoritative for controller authentication, node session +state, authorization, conflict behavior, audit attribution, and revocation. +The Rust runtime receives canonical authorized events, executes or cancels the +corresponding work, preserves attribution, and reports status and results. It +must not contain separate Windows Companion and Scout Cloud control planes. +These authority rules must be complete before the multi-manager topology is +considered production-ready. + +Sidecar deployments also require an explicit operational contract: local-only +authenticated IPC, bounded startup and memory, truthful readiness, supervised +crash recovery, idempotent reconnect, and graceful shutdown of active work. +These are release gates for sidecar mode, not details delegated implicitly to +each shell. + +### Conformance and security + +Until the Rust implementation reaches parity, `src/node-host` is the executable +behavioral reference. Shared fixtures should replace implementation-by- +implementation interpretation for: + +- connect metadata, identity, authentication, and pairing; +- command declaration and invocation envelopes; +- input/progress ordering and byte limits; +- cancellation, deadlines, saturation, shutdown, and reconnect behavior; +- structured failures and redaction; +- approval and command/tool policy decisions. + +The runtime fails closed when policy, approval, identity, or controller authority +is unknown. A native tool adapter cannot broaden permissions granted by the +Gateway or product policy. Every admission, approval, denial, cancellation, +controller action, and approved-but-failed execution must retain distinct, +exportable audit attribution. Revocation must stop new work promptly and cancel +affected in-flight work; a disconnected management surface cannot silently +retain authority. + +### Delivery plan + +The proposed review shape is three logically stacked OpenClaw implementation +PRs plus one initial Windows adopter PR: + +1. **OpenClaw foundation:** add the two crates, a role-safe Gateway session, a + minimal bounded node host, and a Tauri consumer that proves the client is + reusable ([openclaw/openclaw#116050](https://github.com/openclaw/openclaw/pull/116050)). +2. **OpenClaw embeddable runtime:** add external credential/signing hooks, + issued-token delivery, the supervised lifecycle, bounded duplex invocation, + local fail-closed admission, connection-scoped command manifests, and the + shared TypeScript/Rust lifecycle corpus. Draft + [openclaw/openclaw#116450](https://github.com/openclaw/openclaw/pull/116450) + is the second upstream PR, explicitly dependent on the foundation landing + first. +3. **OpenClaw authenticated sidecar bridge:** add transport-neutral framing, + negotiation, immutable configuration, an ordinary-command runtime bridge, + exact cross-language corpora, and a real child-process authenticated IPC test + ([openclaw/openclaw#116863](https://github.com/openclaw/openclaw/pull/116863)). + This draft is logically stacked on #116450. It defines the shared primitive + and proves it across a real process/transport boundary, but deliberately + stops before choosing product IPC, protected credential bootstrap, process + supervision, or rollout policy. +4. **Windows adopter:** keep the existing C# runtime as the production default + while adding one replaceable runtime boundary and one Windows-owned shared + capability dispatcher, plus an independent non-selectable C# consumer of + the sidecar contracts + ([openclaw-windows-node#1068](https://github.com/openclaw/openclaw-windows-node/pull/1068)). + +Fork-only follow-up evidence now launches the real Rust test child over +anonymous pipes, verifies an exact SHA-256 artifact pin while locking the +artifact path against substitution through launch, delivers the fresh +session key through a bounded inherited private pipe, and requires the +authenticated runtime offer to match that verified identity +([Rust #12](https://github.com/giodl73-repo/openclaw-rust-node/pull/12), +[Windows #4](https://github.com/giodl73-repo/openclaw-windows-node/pull/4)). +It remains non-selectable source evidence rather than another upstream +implementation PR or a packaged production runtime. + +The next proposed OpenClaw slice implements the product-neutral sidecar Gateway +connection-control contract defined here: per-attempt material acquisition, +external signing, issued-token acknowledgement, and authoritative retirement. +It does not move secure storage, endpoint authorization, process supervision, +or product policy into Rust. A matching adopter slice can then replace the +current environment-based live-Gateway proof with the protected sidecar path. + +The earlier fork drafts #186-#191 remain detailed evidence history for #116450, +and closed fork drafts #193-#195 remain detailed evidence history for #116863. +Their commits remain intact in the consolidated branches, so the review shape +does not hide the native-signing, lifecycle, duplex, authority, manifest, +sidecar-protocol, negotiation, or runtime-bridge boundaries. + +After those reviews, adoption proof must demonstrate the authenticated, +versioned Windows adapter and a Scout Cloud management flow against a real +Gateway, including cancellation, revocation, reconnect, crash recovery, +readiness, rollback, and audit evidence. Sidecar proof also measures startup +and steady-state resource cost. API stability, artifacts, SBOM/signing, +compatibility windows, servicing, and support ownership remain explicit release +decisions before the crates are declared generally supported. + +The existing official C# Windows node now has one consolidated draft adopter +([openclaw-windows-node#1068](https://github.com/openclaw/openclaw-windows-node/pull/1068)). +It introduces an injectable node-runtime contract and extracts a single +Windows-owned capability dispatcher shared by the current C# transport and the +new non-selectable sidecar adapter, while keeping the C# client as the +production default. The public OpenClaw draft now proves authenticated framing +across a real child process. Fork evidence extends that proof with a verified, +privately bootstrapped process over authenticated, versioned +anonymous pipes. A future product adoption slice can wire that launcher into +an opt-in runtime and add signing, packaging, supervision, and rollback: +Rust owns Gateway transport, registration, invocation, cancellation, reconnect, +and runtime lifecycle, while the Windows app retains WinUI, the operator role, +MCP, approvals, and native capability handlers. This proves adoption can be +incremental without duplicating Windows command routing, replacing the product +shell, or introducing a Tauri dependency. + +Each earlier layer remains useful if maintainers defer a later layer. In +particular, adopting only the Gateway client still removes duplicated transport, +authentication, and reconnect logic from Rust consumers. + +## Rationale + +### Existing multi-language node precedent + +OpenClaw node mode already has several language- and platform-specific +implementations: the generic TypeScript headless host, shared Swift sessions +used by Apple nodes, the Kotlin Android node, the official C# Windows node, and +the official C ESP-IDF component. They do not expose identical tools. Each +advertises the capabilities and commands its platform can currently serve. + +These are not competing protocols. They implement one node role: protocol +admission, signed device identity and pairing, a connection-scoped capability +manifest, invocation/result behavior, bounded lifecycle, and Gateway plus local +policy enforcement. Transport adapters, credential storage, native capability +handlers, and application lifecycle remain implementation-specific. + +The Rust work is therefore not a new architectural exception. It fills the +reusable native/headless Rust slot and prevents every Rust adopter from +implementing that common machinery independently. Its additional commitment is +to strengthen the shared cross-language fixtures and conformance corpus where +input, progress, cancellation, lifecycle, and error behavior are currently +distributed across schemas and implementation tests. + +### Why OpenClaw owns the runtime + +The runtime implements an OpenClaw protocol role and security boundary. Keeping +it beside the canonical TypeScript implementation makes behavior changes, +fixtures, and review atomic. It also makes the implementation available to +non-Microsoft native consumers. + +### Why the product shell stays with Microsoft + +Tray behavior, settings, Windows UX, native tools, packaging, and deployment are +product concerns with different release and platform constraints. Moving them +into OpenClaw would blur responsibility and make the portable runtime less +reusable. + +### Why this is not a Tauri decision + +Tauri and the Rust runtime occupy different layers. A Tauri app can consume the +Gateway client or node host, but a headless service, Chromium component, or +Windows-native shell should not need an application framework to reuse protocol +and lifecycle behavior. + +### Why this is not two competing OpenClaw runtimes + +Node mode is a protocol role, not a requirement that every node run in the same +language or application framework. OpenClaw keeps one canonical node contract, +policy boundary, and conformance corpus. The TypeScript `src/node-host` remains +the executable behavioral reference while the Rust implementation reaches the +approved native/headless subset. + +The minimum Rust commitment is the proper node-role subset needed by native and +headless hosts; it does not absorb the Gateway's model-facing agent loop. +Additional node-mode capabilities—including MCP, skills, plugins, and richer +tool execution where they truly belong to the node role—can be added when +OpenClaw accepts the scope and the change carries implementation, conformance, +and a credible maintenance path. Microsoft is willing to seed that work, but +the resulting surface must remain useful to non-Microsoft adopters. Shared +fixtures must prove equivalent behavior for every capability implemented in +both languages, and Rust must reuse the canonical policy rather than create a +parallel one. + +This lets OpenClaw maintainers choose among a shared Gateway client, a bounded +native node host, or broader native node-mode parity. In every +case it eliminates product-owned Rust node forks. Whether a future native or +headless deployment replaces its TypeScript bridge is an adoption decision made +only after conformance is proven; this RFC does not require removing the +TypeScript host from Node.js deployments. + +### Why not keep a separate Rust repository + +A separate repository would make protocol changes, security fixes, tests, and +releases easier to skew while the implementation is young. The monorepo provides +the strongest path to conformance. Extraction remains possible after the API and +release contract stabilize. + +### Why not wrap the TypeScript node host + +A wrapper is the shortest path for products already carrying Node.js. It does +not meet the all-Rust runtime goal, does not simplify the Windows process model, +and preserves two runtimes across lifecycle and security-sensitive paths. + +## Acceptance record and follow-on decisions + +One technical decision is required to accept this RFC: + +- Should the bounded, unpublished Rust Gateway-client and node-runtime + foundation be an OpenClaw core surface in the monorepo, with Gateway/security + authority and cross-language conformance maintained beside the TypeScript + reference? + +The recommended bounded answer is **yes**. It permits the two `publish = false` +workspace crates and the Tauri reuse slice to incubate in `openclaw/openclaw`. +It does not make the foreground proof host an official binary, promise a public +crate API, select supported platforms, or authorize broader tool execution. +Public Rust visibility during incubation is an internal workspace seam, carries +no SemVer promise, and may change with its reviewed in-tree consumers. +It also does not assign individual maintainers; that is an implementation and +project-maintenance concern rather than an architectural prerequisite. + +The following are explicit follow-on adoption or release decisions, not hidden +prerequisites for reviewing the bounded foundation: + +- Select the canonical command/tool and approval adapters before enabling each + native capability beyond the bounded custom-handler surface. +- Choose in-process embedding or an authenticated signed sidecar per adopter; + both must satisfy the same runtime contract. +- Define controller authority, conflicts, revocation, and audit attribution + before Windows Companion and Scout Cloud jointly manage one node. +- Name release owners, platforms, artifact identities, compatibility windows, + and servicing policy before publishing crates or shipping an official binary. +- Which identity and token material is stored by the runtime versus a + Windows-secure-storage adapter? +- What is explicitly in scope for the node invocation loop, and what remains in + the Gateway's model-facing agent loop? +- When should the crates be published, and who owns artifact signing, SBOMs, + compatibility windows, servicing, and security response? +- What end-to-end evidence is required before the TypeScript node host is no + longer the behavioral reference for a capability? diff --git a/rfcs/0028/conformance-and-adoption-plan.md b/rfcs/0028/conformance-and-adoption-plan.md new file mode 100644 index 00000000..7cc53f8a --- /dev/null +++ b/rfcs/0028/conformance-and-adoption-plan.md @@ -0,0 +1,183 @@ +# Rust runtime conformance and adoption plan + +This plan turns RFC 0028 into independently reviewable acceptance gates. Code, +fixtures, live proof, and deletion must agree before a layer is called +supported. + +## Evidence principles + +- The OpenClaw Gateway protocol is the wire authority. +- TypeScript `src/node-host` is the node behavioral reference until a shared + fixture replaces an implementation-specific interpretation. +- Tauri, Swift, Kotlin, C#, and ESP implementations are analogues and evidence, + not independent schema authorities. +- A source harness proves runtime mechanics; a real Gateway proves integration; + a packaged adopter proves deployment. +- Every proof records exact repository, head, OS/toolchain, command, result, + and known gap. +- A layer may land without later layers. No proof for a higher layer rewrites + the ownership or safety bar of a lower layer. + +## Acceptance layers + +| Layer | Review surface | Required proof | Deletion unlocked | +| --- | --- | --- | --- | +| G1 Gateway client | OpenClaw #116050 | Unit/socket tests, Linux Tauri tests, current-head static gates | Tauri app-local duplicate transport | +| N1 bounded node foundation | OpenClaw #116050 | Real loopback Gateway invocation plus health/readiness/shutdown | Per-adopter basic node session/runtime scaffolding | +| N2 embeddable lifecycle | OpenClaw #116450 | Shared fixtures and real socket lifecycle, signing, token, reconnect, duplex, manifest, admission tests | Per-adopter signing/reconnect/invocation lifecycle | +| A1 adopter seam | Windows #1068 | Existing C# default, full unit suites, real Gateway MXC path | Duplicate Windows routing when Rust adapter arrives | +| A2 sidecar adapter | OpenClaw #116863 plus Windows #1068 | Independent authenticated/versioned framing, handshake/configuration, typed ordinary-command bridge, dispatcher routing, exact cross-language corpora, and a real OS child exchanging authenticated frames over TCP | No deletion; product bootstrap/supervision and production adoption gates remain | +| A3a launch/bootstrap evidence | Rust fork #12 plus Windows fork #4 | Exact artifact SHA-256 and reparse-path rejection, path locks through launch, self-identity handshake binding, bounded private-pipe bootstrap, real process admission/invocation | No deletion; signing, packaging, Gateway parity and operational adoption remain | +| A3b Gateway connection control | RFC connection fixture plus future OpenClaw and adopter PRs | Negotiated feature gate, per-attempt material acquisition, external signing, issued-token acknowledgement, generation retirement, redaction, and protected-IPC live Gateway proof | Environment/file credential proof paths only; no incumbent production deletion | +| A3 sidecar adoption | Future Windows adopter PR | Verified artifact/launch, protected bootstrap, concrete IPC, Gateway/pairing/token parity, crash, revocation, audit, resource, rollout and rollback proof | Incumbent product-owned Gateway transport after observation window | +| R1 supported release | future OpenClaw release decision | Package acceptance, signing/provenance, SBOM, compatibility, servicing and security runbooks | Experimental-only packaging and private distribution | + +## Shared fixture families + +The conformance corpus must cover: + +| Family | Minimum cases | +| --- | --- | +| Connect | challenge ordering, canonical signed metadata, accepted protocol, structured rejection | +| Identity/auth | configured credential, issued token, rejected issued token, pairing and re-pairing | +| Manifest | deterministic order, empty surface, widening/reapproval, retired generation | +| Invocation | success, structured handler failure, unsupported command, duplicate ID, saturation | +| Duplex | ordered input, oversized input, UTF-8 progress, heartbeat, late/duplicate frames | +| Cancellation | before dispatch, during admission, active handler, input wait, disconnect, shutdown | +| Authority | Gateway permits delivery; local admission narrows; neither side broadens the other | +| Recovery | retryable transport/TLS, terminal auth/config/protocol, backoff reset, token fallback | +| Observability | stable state/reason codes, redaction, approved-but-failed distinct from denial | +| Sidecar connection control | material request/deny, endpoint trust, external signing, token acknowledgement, mismatch fallback, retirement, stale-generation rejection | + +Fixtures must identify their canonical source and version. Unknown additive +fields must be exercised where the protocol permits them. A fixture must fail +in at least one deliberately nonconforming implementation or mutation so it is +evidence rather than a happy-path snapshot. + +The current seed corpus is +`test/fixtures/node-invoke-lifecycle-contract.json`, consumed by the published +Gateway-protocol tests and Rust node tests, plus +`test/fixtures/node-runtime-integration-contract.json`, consumed by the +TypeScript integration-contract test and Rust authority/admission test. These +are seeds, not a claim that the complete node contract is already projected. + +RFC 0028 also carries draft semantic and cryptographic vectors in +`sidecar-gateway-connection-v1.json`. Its first implementation PR must move or +mirror that fixture into the OpenClaw test corpus, add byte-exact canonical +encodings, and prove independent Rust and adopter consumers before the contract +is considered implemented. + +## Validation ladder + +### Per-commit checks + +- formatting and diff hygiene; +- focused unit tests for the changed contract; +- strict lint and rustdoc warnings; +- schema/fixture drift validation when shared artifacts change. + +### Per-PR checks + +- complete affected Rust workspace tests; +- TypeScript validation for shared fixtures; +- real loopback socket tests for transport/lifecycle behavior; +- current-head independent review with findings mapped to fixes; +- an exact Real behavior proof section in the PR body. + +### Adoption checks + +- actual packaged supervisor and runtime, not only a library harness; +- live Gateway pairing, invocation, cancellation, reconnect, and revocation; +- allowed and denied platform capability paths; +- sidecar crash, supervisor crash, IPC loss, version mismatch, and rollback; +- finite bootstrap-stage deadlines, pre-negotiation ceilings, and negotiated + limits that cannot exceed either peer's local policy; +- duplicate authenticated-sequence and retired-session replay rejection without + native redispatch; +- per-attempt credential reacquisition, external signing without private-key + export, durable issued-token acknowledgement, and authoritative generation + retirement through the protected sidecar transport; +- startup/steady-state resource measurements; and +- audit correlation through the product's real audit/export path without + credential or raw-error disclosure. + +## Compatibility matrix + +Before a supported release, test the candidate against: + +- the exact current supported Gateway release; +- the declared predecessor release (N-1) where OpenClaw promises compatibility; +- OpenClaw `main` as a non-gating drift canary; and +- each supported target OS/artifact architecture. + +Compatibility is a declared window, not best effort. A failing predecessor lane +either blocks the release or causes an explicit support-window change reviewed +with migration guidance. + +## Security gates + +The following are blocking: + +- insecure endpoint or certificate-pin fallback; +- unbounded request, event, invocation, input, output, or restart state; +- authority broadening by local admission or product IPC; +- secret-bearing logs, command lines, or public errors; +- cancellation gaps that leave approved work running after revocation, + disconnect, session retirement, or shutdown; +- runtime selection without authenticated version negotiation; and +- deletion of the incumbent path before rollback is proved. + +## Release gates + +An official crate or binary requires named OpenClaw owners for protocol, +security, release, and incident response. Candidate artifacts require exact +checksums, repository-bound provenance, dependency audit, SBOM, code signing +where the platform requires it, fresh-machine install smoke, upgrade and +rollback instructions, and a stated compatibility/support window. + +Workspace tests alone do not authorize publication. + +## Evidence already available + +The current drafts provide: + +- #116050: reusable Gateway client, bounded host, Tauri migration, real + loopback node/health proof, and 56 Rust workspace tests at `1aaec0c5a56`; +- #116450: lifecycle/signing/token, duplex/admission/manifest conformance and + 73 stacked Rust tests plus current generated native models at `bfd5c4d14e2`; +- #116863: consolidated authenticated sidecar framing, handshake, immutable + configuration, ordinary-command bridge, three exact corpora, a real + cross-process authenticated configuration, manifest, admission, invocation, + and result test, and 124 stacked Rust tests at `71c1c8cb23c`; +- Windows #1068: replaceable runtime boundary, shared dispatcher, independent + C# sidecar consumer, 3,701 Shared and 22 focused Connection tests at + `711fe095`; the prior head also passed its larger focused, Tray, and full + Connection suites, and the unchanged C# default path has 2/2 live Gateway + MXC proof; +- fork-only Rust #11/#12 and Windows #3/#4: real child-process pipes, exact + artifact hash verification with reparse-safe path locks through launch, bounded secret bootstrap outside arguments, + environment and files, required handshake artifact binding, 122 shared Rust + plus 64 focused Windows tests, and three exact fixture blobs; and +- earlier experimental repository package, SBOM, dependency, and provenance + evidence, which remains evidence history rather than an official release. + +Known gaps are a production/deployed Gateway run for the latest Rust heads, +platform signing/provenance and packaged artifact discovery/update, +implementation of the specified Gateway connection-control messages and their +Rust/adopter fixture consumers, live Gateway/pairing/token integration through +the sidecar, crash/circuit-breaker/ +resource/rollback proof, an embedding/product audit adapter, explicit +authorization for the reserved `system.*` namespace, supported artifact +publication, and maintainer ownership acceptance. + +## Promotion and deletion ledger + +Every adopter PR must name: + +1. the duplicate implementation or process it intends to remove; +2. the owner behavior that remains in place; +3. the conformance evidence covering the replacement; +4. the rollout and rollback control; and +5. the observation window before deletion. + +No deletion is credited merely because a Rust alternative compiles. diff --git a/rfcs/0028/gateway-client-v1-spec.md b/rfcs/0028/gateway-client-v1-spec.md new file mode 100644 index 00000000..20cbf377 --- /dev/null +++ b/rfcs/0028/gateway-client-v1-spec.md @@ -0,0 +1,180 @@ +# Rust Gateway client v1 specification + +This document defines the proposed v1 behavioral contract for the +`openclaw-gateway-client` crate described by RFC 0028. It specifies a reusable, +role-neutral Gateway session. It does not define node behavior, credential +storage, a product shell, or a second Gateway protocol. + +Status: draft. The canonical Gateway protocol and server behavior remain +authoritative. The Rust API remains unstable until the RFC ownership and +release decisions are accepted. + +## Scope + +A conforming client provides: + +- secure WebSocket establishment; +- challenge-before-connect ordering; +- caller-supplied connect parameters; +- correlated requests and responses; +- bounded event delivery and request concurrency; +- typed closure, protocol, transport, TLS, timeout, and Gateway failures; and +- enough structured recovery metadata for an embedding to decide whether to + retry, pause, repair configuration, or replace a rejected credential. + +The client must remain independent of Tauri, the node role, product IPC, +platform key stores, command execution, approvals, and management-plane policy. + +## Authority and compatibility + +The published OpenClaw Gateway protocol is the wire authority. The Rust client +must not create Rust-only methods, fields, error codes, or authentication +semantics. It may expose a narrower typed projection and preserve unknown JSON +payloads where the Gateway contract allows additive fields. + +Protocol compatibility is an explicit tested window. A server-reported +protocol version is evidence of the accepted session; it is not permission for +the client to silently emulate an undocumented older dialect. + +## Endpoint and trust policy + +The client must reject unsupported URL schemes before network activity. + +- Public or otherwise untrusted remote endpoints require `wss://`. +- `ws://` is limited to the reviewed trusted-host policy: loopback, private or + link-local IP addresses, `localhost`, `.local`, and `.ts.net` names. +- IPv4-mapped IPv6 addresses must be evaluated by their mapped IPv4 address so + an address cannot bypass the plaintext policy through representation alone. +- Deployments should prefer TLS except for same-host loopback development. + +TLS trust is either platform system roots or one exact SHA-256 leaf-certificate +fingerprint. A configured pin is valid only with `wss://`. Pin comparison must +be constant-time, and the TLS stack must still verify that the peer owns the +certificate private key. TLS validation/configuration failures must remain +distinguishable from transient transport or handshake failures so reconnect +policy does not permanently pause on a recoverable outage. + +## Connect sequence + +A conforming session must: + +1. establish the WebSocket with bounded frame and message sizes; +2. wait for `connect.challenge` within the configured challenge timeout; +3. reject a missing or empty nonce; +4. invoke the embedding exactly once to build connect parameters from that + nonce; +5. send one correlated `connect` request; +6. require a successful response within the request timeout; and +7. expose the accepted hello payload without interpreting product-specific + fields. + +Frames unrelated to the challenge may be ignored only where the canonical +protocol permits them before connection. A malformed frame or invalid connect +response must not activate a session. + +## Requests, events, and closure + +Request IDs must be unique within the session. The session must correlate only +an exact response ID with its pending request and must remove pending state on +response, timeout, cancellation, or session closure. + +A dropped or timed-out request must be durably marked independently of bounded +queue capacity. If it has not yet been written, it must not be transmitted; in +all states it must release its pending entry and concurrency permit. A queued +cancellation notification may wake the session loop sooner, but it must remain +a best-effort optimization rather than the source of cancellation truth. + +The default candidate limits are: + +| Limit | Candidate default | +| --- | ---: | +| Challenge timeout | 15 seconds | +| Connection establishment timeout | 10 seconds | +| WebSocket write timeout | 10 seconds | +| Request timeout | 30 seconds | +| Maximum WebSocket message/frame | 16 MiB | +| Retained event capacity | 256 events | +| Retained raw-event bytes | 64 MiB | +| Queued plus pending requests | 64 | + +Embeddings may choose smaller or larger finite limits. Zero-valued capacities +must normalize to at least one or fail validation; they must not create an +unbounded queue. + +The current candidate bounds retained events by both exact event count and +aggregate raw-frame bytes. It evicts the oldest retained frames until both +limits hold. A single frame larger than the complete byte budget advances the +stream position and produces explicit lag for affected subscribers without +closing the transport; later bounded events remain deliverable. The count, +aggregate-byte, oversized-frame, and lag paths have focused current-head tests, +including preservation of the default 256-event small-event burst. + +Event subscribers must receive explicit lag rather than silent loss. The +single-consumer convenience API must deliver already-buffered events before a +terminal closure error, including when the final event and close arrive in the +same scheduling interval. Closing a session must wake event and request +waiters, and all pending requests must receive a terminal error. + +Ping/pong traffic counts as transport activity but not as an application +event. The client must answer WebSocket pings without blocking the receive loop. + +## Failure and recovery metadata + +Public error classes must separate at least: + +- invalid URL or header; +- insecure plaintext endpoint; +- transient transport failure; +- TLS failure; +- challenge timeout or invalid challenge; +- connect-parameter failure; +- structured Gateway rejection; +- request timeout; +- clean or abnormal closure; +- invalid frame; and +- event lag. + +Gateway rejection details are untrusted input. The client may expose only +bounded, normalized recovery text and typed booleans/codes needed by reconnect +policy. It must preserve `retryable`, `retryAfterMs`, `pauseReconnect`, and the +canonical detail code when present. An explicit terminal hint must never be +discarded because a new Gateway error code is unknown to the client. + +## Security and privacy + +Connect callbacks and errors must not cause credentials, tokens, signatures, +private keys, raw headers, or arbitrary server details to enter public logs. +The crate must not persist credentials. The embedding owns secret acquisition, +storage, rotation, and deletion. + +Certificate pins, endpoint trust, frame bounds, and request bounds are security +controls. Their failure behavior must be covered by negative tests and must not +silently fall back to weaker behavior. + +## Required conformance evidence + +Before v1 support is claimed, the exact candidate head must prove: + +- trusted and rejected plaintext endpoint cases, including mapped addresses; +- system-root and exact-pin TLS paths plus pin mismatch; +- challenge success, timeout, and malformed challenge; +- correlated request success, rejection, timeout, and close cleanup; +- bounded concurrency when callers abandon futures; +- measured or mechanically enforced worst-case event-retention memory for the + supported configuration; +- durable timeout and caller-abandon cancellation under queue saturation; +- event delivery, lag, final-event-before-close, and idle-close wakeup; +- ping/pong transport activity; and +- terminal versus retryable Gateway recovery metadata. + +The current implementation evidence is inventoried in +[implementation-and-evidence-inventory.md](implementation-and-evidence-inventory.md). + +## Out of scope for v1 + +- automatic persistent credential storage; +- node identity or command manifests; +- reconnect supervision owned by a product lifecycle; +- proxy discovery beyond existing OpenClaw policy; +- an HTTP/REST compatibility layer; and +- crate publication or a stable Rust API guarantee. diff --git a/rfcs/0028/implementation-and-evidence-inventory.md b/rfcs/0028/implementation-and-evidence-inventory.md new file mode 100644 index 00000000..a2413694 --- /dev/null +++ b/rfcs/0028/implementation-and-evidence-inventory.md @@ -0,0 +1,224 @@ +# Rust runtime implementation and evidence inventory + +This inventory separates the canonical OpenClaw node contract, existing +language implementations, the proposed Rust layers, and the proof available at +the RFC review heads. It is descriptive evidence, not a source of new wire +semantics. + +## Reviewed heads + +| Repository / surface | Reviewed head | Status | +| --- | --- | --- | +| `openclaw/openclaw` foundation PR #116050 | `1aaec0c5a56` | Draft; rebased Rust client/host plus Linux Tauri consumer | +| `openclaw/openclaw` follow-up PR #116450 | `bfd5c4d14e2` | Draft; logically stacked on #116050; generated native models preserve the established nullable-string invocation API | +| `openclaw/openclaw` sidecar PR #116863 | `71c1c8cb23c` | Draft; logically stacked on #116450; includes complete configured cross-process authenticated IPC proof | +| `openclaw/openclaw` cancellation contract PR #115390 | `3cac3e4a3aa` | Draft; required `{ invokeId, nodeId }` schema consumed by TypeScript and Swift | +| `openclaw/openclaw-windows-node` PR #1068 | `711fe095` | Draft; rebased seam plus independent sidecar adapter; C# remains production default | +| Experimental Rust launch proof #12 | `3d1357f` | Draft, fork-only; protected bootstrap plus deterministic pinned aggregate evidence | +| Experimental Windows launch proof #4 | `199eaa1fcba` | Draft, fork-only; path-locked verified launcher and mandatory artifact identity | +| `openclaw/rfcs` RFC #54 | Current PR head | Draft ownership decision; this inventory is refreshed with each evidence change | +| Experimental `openclaw-rust-node` evidence repository | merged PRs #1-#5; draft #6 | Evidence history, not official distribution | + +Exact heads must be refreshed before approval or release. + +## Canonical contract owners + +| Surface | Authority | Rust disposition | +| --- | --- | --- | +| Gateway frames, connect, hello, errors | Published Gateway protocol and Gateway server | Consume; do not fork | +| Node invocation/result/input/progress/cancel | Published node schemas plus shared fixtures | Implement only published shapes | +| Pairing and approved command delivery | Gateway device/node pairing behavior | Preserve two distinct authority layers | +| Built-in command semantics and execution policy | TypeScript `src/node-host` and OpenClaw approval/policy owners | Reference; do not infer parity | +| Platform capabilities | Apple, Android, Windows, ESP and product owners | Adapter-owned and manifest-scoped | +| Product process/IPC/UX | Product repository | Remains outside generic Rust crates | + +## Implementation comparison + +| Implementation | Reusable evidence | Product-specific ownership retained | Do not copy into Rust v1 | +| --- | --- | --- | --- | +| TypeScript `src/node-host` | Generic node role, manifests, invocation lifecycle, reconnect classification, built-in semantics | Node.js process, dynamic plugin/skill inventory, full execution policy | Transliteration of every command or Node.js assumption | +| Linux Tauri Rust Gateway client | Rust TLS pinning, signed identity, issued-token replacement, stale-token clearing, reconnect, heartbeat, correlation | Tauri UI/operator role, identity/token persistence and desktop lifecycle | Treating an operator client as the node semantic authority | +| Apple Swift nodes | Connection-scoped route/capability snapshots and reconnect on authority change | TCC, app/worker IPC, Apple UI and native tools | Apple lifecycle or permission APIs | +| Android Kotlin node | Role-separated sessions, role-keyed tokens, bounded token retry, permission-driven inventory | Android service/UI/permission lifecycle | Android storage and foreground-service policy | +| Windows C# node | Backpressure, cancellation, shared capability dispatcher, real Gateway/MXC execution | WinUI, operator role, MCP, approvals, MXC/native tools | Reimplementing Windows routing or policy in Rust | +| ESP C node | Small fixed registry, bounded queues, persisted identity/session behavior | NVS, FreeRTOS and device-specific tool surface | Embedded constraints as universal desktop defaults | +| Proposed Rust stack | Role-neutral client plus bounded node lifecycle/runtime and headless host | Embedding credentials, policy composition, product IPC and handlers | `system.*`, MCP, skills, plugins, or product fields without accepted contracts | +| Moltis Rust node host | Tokio/tungstenite task ownership and a Rust crate layout are feasible | Moltis protocol extensions and execution policy | Its different v4/result shapes or unrestricted `system.run` surface | + +Third-party Rust clients may inform dependency or task-ownership choices, but +they are not OpenClaw compatibility authorities. + +### Earlier comparison audit pins + +The source comparison that shaped the proposal recorded these immutable heads: + +| Repository | Audit head | Use | +| --- | --- | --- | +| `openclaw/openclaw` | `98591fda13112737aa73cc23974c68a304e8212d` | TypeScript, Apple, Android, and Tauri analogues | +| `openclaw/openclaw-windows-node` | `fe43b157b56183ed0746ac53315f39fee41aa3c7` | C# node bounds and capability ownership | +| `openclaw/esp-openclaw-node` | `321604b329516712af5e1dc391f526116b2ca414` | Fixed registry, queues, identity and persisted session | +| `moltis-org/moltis` | `9a8d7dd70a97bb119727fe4e66f15fd61f8f44f4` | External Rust feasibility and negative protocol precedent | + +Those pins support the comparison only. The PR heads above are the current +Rust/Windows implementation evidence under review. + +## Rust surface inventory + +### `openclaw-gateway-client` in #116050 + +- `GatewayClientConfig`, `GatewayClient`, and `GatewaySession`; +- system-root or exact leaf-pin TLS; +- reviewed plaintext-host policy; +- challenge/connect callback; +- correlated bounded requests; +- retained/live events and transport activity; +- typed errors and normalized connect-recovery details; and +- deterministic socket tests for connect, timeout, ping, close, trust, and + final-event behavior. + +It does not persist credentials, supervise reconnect, implement a node role, or +execute commands. + +### `openclaw-node-host` foundation in #116050 + +- Ed25519 `NodeIdentity` and canonical node connect options; +- node activation, invocation/result, cancellation, and reconnect policy; +- bounded `CommandRuntime` and exact handler registration; +- foreground `openclaw-node` proof host; +- loopback health/readiness; and +- one configurable namespaced status command. + +### Embeddable follow-up in #116450 + +- external signing requests and verification; +- typed issued-device-token delivery and rejected-token fallback; +- supervised `NodeLifecycle` with fresh per-attempt material; +- duplex input/progress/heartbeat and UTF-8-safe chunks; +- embedding-owned fail-closed admission; +- connection-scoped command manifests and retired-session cleanup; and +- shared TypeScript/Rust lifecycle and Gateway-authority fixtures. + +### Authenticated sidecar bridge in #116863 + +- authenticated directional framing with replay/session/generation rejection; +- independently negotiated offer/accept handshake and limit lowering; +- immutable configuration and exact manifest acknowledgement; +- bounded admission, invocation, result, cancellation, and status messages; +- `CommandRuntime`/`NodeLifecycle` adapter for ordinary product-native commands; +- a real child-process test exchanging authenticated frames over TCP; and +- three byte-exact language-neutral corpora consumed independently by Windows. + +It does not yet carry Gateway endpoint/auth material, external signing +requests/results, issued-token acknowledgement, or connection retirement. +Those are a distinct live control-plane message family, not fields on the +immutable capability configuration. + +### Proposed sidecar Gateway connection control + +`sidecar-gateway-connection-v1-spec.md` and its draft fixture adapt existing +behavior rather than inventing a new credential model: + +- Tauri supplies the custody precedent for endpoint trust, identity signing, + issued-token persistence, and stale-token clearing; +- `NodeLifecycle` supplies per-attempt reacquisition, external signing, typed + issued-token delivery, reconnect classification, and secret-free status; and +- Windows supplies endpoint authorization, generation fencing, token recovery, + and product-owned capability dispatch. + +The shared contract adds only the missing protected process boundary. Private +keys remain supervisor-owned, credentials are attempt-scoped, issued tokens +require explicit durable acknowledgement, and revocation retires one exact +connection generation. No implementation PR exists yet. + +### Windows adopter seam in #1068 + +- `INodeRuntimeClient` replaceable client boundary; +- injectable runtime factory; +- transport-independent `NodeCapabilityDispatcher`; and +- one shared C# path for indexing, bounded execution, duplicate tracking, + cancellation, telemetry, and completion; +- byte-exact C# consumers for the Rust protocol, handshake, and runtime corpora; +- independently recomputed negotiation and immutable manifest acknowledgement; +- bounded admission/outbound queues, cancellation, result/work bounds; and +- ordinary native invocation routing only through `NodeCapabilityDispatcher`. + +The adapter remains an in-process, non-selectable proof. It contains no Rust +binary or runtime-selection change and does not implement the verified +Rust artifact/process owner, protected bootstrap, concrete IPC, Gateway +connection/pairing/token lifecycle, health/crash supervision, audit export, +resource proof, rollout, or rollback. The current Rust runtime also rejects the +reserved `system.*` namespace, so Windows `system.run` remains on C# pending an +explicit OpenClaw authorization mechanism. + +Fork-only follow-ups #3/#4 launch a real Rust test child over anonymous pipes. +They verify an exact SHA-256 pin while holding native handles on every parent +directory and the artifact through launch, reject reparse-point path components, +deliver the fresh session secret in a bounded private-pipe bootstrap record, +and require the authenticated runtime offer to present the same self-computed +artifact identity. These follow-ups remain non-selectable source evidence; they +do not add package discovery, platform signing, Gateway credentials, restart +policy, or rollout controls to the product. + +## Evidence inventory + +| Evidence | Environment | Result | Limitation | +| --- | --- | --- | --- | +| Foundation Rust workspace | Ubuntu 24.04 WSL2, Rust 1.93, `1aaec0c5a56` | 56 tests; strict Clippy/format/diff pass; Gateway challenge timestamp shared by Tauri and node-host | No deployed production Gateway at latest head | +| Foundation host/socket proof | Ubuntu 24.04 WSL2, Rust 1.93, `13242763f3d` | Health 200, pre-ready 503, clean shutdown, real loopback invocation | Earlier proof head; in-process Gateway harness | +| Linux Tauri suite | Linux proof head | 98/98 passed | Not a packaged desktop UI build | +| Embeddable stack | Ubuntu 24.04 WSL2, Rust 1.93, `bfd5c4d14e2` | 73 workspace tests; strict Clippy/format/diff and Swift/Kotlin protocol-generation checks pass; native `paramsJSON` remains nullable string | Android generator output is verified, but the local Android unit test is pending a Java-enabled hosted lane | +| Shared fixtures | Rust current-head consumer plus canonical TypeScript validators | Rust lifecycle and Gateway-authority consumers plus focused TypeScript schema/integration tests pass at `bfd5c4d14e2` | Live production-Gateway compatibility remains a release gate | +| OpenClaw sidecar bridge | Rust workspace, `71c1c8cb23c` | 124 workspace tests; strict Clippy/format/diff pass; exact three-corpus proof plus a separate OS child completing authenticated configuration/manifest acknowledgement, admission, invocation, and result over real TCP IPC | Test process/transport; no protected bootstrap, artifact verification, product supervisor, or live Gateway | +| Windows adopter | Windows 11, `711fe095` | 3,701 Shared tests and 22 focused Connection tests pass after rebase; the prior head additionally passed the full build, exact three-corpus reproduction, 60 focused, 2,023 Tray, and 519 Connection tests; Codex and three-model reviews were clean | C# remains selected; adapter is in-process and non-selectable; the larger suites are prior-head evidence pending CI at the rebased head | +| Protected process launch | Windows 11, Rust `3d1357f`, Windows `199eaa1fcba` | 122 shared Rust and 64 focused Windows tests; three exact fixture blobs; deterministic junction-path, hash, and handshake-identity rejection; private bootstrap and real invocation | Test artifact and source harness; no platform signature, package/update or production selection | +| Windows live MXC | Windows host + isolated Ubuntu WSL + live loopback Gateway | 2/2 allowed/denied `system.run` cases pass | Proves shared dispatcher on C# default path | +| Experimental package acceptance | Linux x64, Windows x64, macOS ARM64 | Build/checksum/extract/execute evidence | Separate experimental repository | +| Dependency/SBOM/provenance | Experimental repository PRs #5/#6 | RustSec, CycloneDX, repository-bound attestations | Not an OpenClaw-supported release | + +The shared fixture files are +`test/fixtures/node-invoke-lifecycle-contract.json` and +`test/fixtures/node-runtime-integration-contract.json`; both have TypeScript and +Rust consumers at the reviewed stacked head. The Rust consumer passed at the +current head. The final TypeScript validator execution remains pending after a +repeated package-registry TLS failure; hosted TypeScript lanes passed before the +final UTF-8 validator correction. + +## Capability gap inventory + +| Capability | Rust state | Gate before support | +| --- | --- | --- | +| Gateway session | Implemented draft | Ownership, compatibility and release acceptance | +| Basic node invocation | Implemented draft | Shared canonical fixtures and current-head live Gateway proof | +| Duplex input/progress/cancel | Implemented draft | Complete published node-event corpus and cross-language proof | +| Sidecar IPC | OpenClaw #116863 now proves authenticated framing across a real OS child and TCP transport; Windows #1068 implements the independent adapter; fork #12/#4 additionally proves anonymous-pipe transport, exact hash launch, protected bootstrap, and handshake artifact binding; RFC connection-control contract is drafted but unimplemented | Implement both consumers of `sidecar-gateway-connection-v1-spec.md`; prove platform signing/package delivery plus live Gateway, audit, crash, resource, rollout and rollback behavior | +| Persistent secure identity/token storage | Embedding seam only | Platform adapter and rotation/revocation proof | +| Product audit/export adapter | Not implemented | Stable event contract, correlation/redaction proof, real product audit sink | +| Aggregate retained-event byte budget | Implemented draft: exact count plus aggregate raw-frame bytes; 256 events and 64 MiB by default | Current-head compatibility and load proof before support | +| Windows runtime selection | Not implemented | Opt-in adapter, parity, rollout and rollback | +| `system.which` | Not implemented | Admin-sensitive canonical policy/result corpus | +| `system.run` / PTY | Not implemented | OpenClaw-owned preparation, approval, execution, audit and emergency-disable contracts | +| MCP, skills, plugins | Not implemented | Concrete adopter, owner API, lifecycle and conformance | +| Pending/offline work | Not implemented | Canonical replay/idempotency contract and concrete adopter | +| Supported artifacts | Not published | Named owners, package acceptance, signing, SBOM, servicing and incident response | + +## Deletion inventory + +| Candidate deletion | Earliest gate | Retained owner surface | +| --- | --- | --- | +| Linux Tauri app-local Gateway transport | #116050 accepted and Tauri proof green | Tauri shell, UI, persistence and operator behavior | +| Per-adopter Rust Gateway/session implementations | Gateway client accepted and supported | Product lifecycle, credentials and adapters | +| Per-adopter node lifecycle/correlation machinery | #116450 conformance accepted | Product policy, approval UX and native handlers | +| Windows C# Gateway transport | Sidecar adapter rollout and rollback complete | WinUI, operator, MCP, approvals, dispatcher and native tools | +| Experimental standalone repository as source of truth | In-tree ownership and release path accepted | Historical evidence and archived provenance | + +No deletion is authorized by RFC acceptance alone. + +## Open decisions + +- maintainer ownership and code owners; +- embed versus sidecar default; +- whether anonymous pipes remain the adopter transport or another concrete IPC is selected; +- supported compatibility window and platforms; +- secure-store adapter ownership; +- canonical approval/tool integration APIs; and +- artifact naming, publication, servicing, and security response. diff --git a/rfcs/0028/node-runtime-v1-spec.md b/rfcs/0028/node-runtime-v1-spec.md new file mode 100644 index 00000000..8ebe9c92 --- /dev/null +++ b/rfcs/0028/node-runtime-v1-spec.md @@ -0,0 +1,197 @@ +# Rust node runtime v1 specification + +This document defines the proposed v1 contract for `openclaw-node-host`, the +node-role layer built on `openclaw-gateway-client`. The TypeScript +`src/node-host` remains the executable behavioral reference until OpenClaw +accepts and proves a Rust capability as conforming. + +Status: draft. This contract defines the bounded native/headless subset; it +does not claim parity with every TypeScript command, plugin, skill, or execution +policy. + +## Scope + +The runtime owns portable node mechanics: + +- canonical node connect metadata and challenge-bound device identity; +- device authentication and typed issued-device-token delivery; +- capability and command advertisement for one connection; +- invocation, ordered input, progress, heartbeat, result, and cancellation; +- bounded handler admission and execution; +- reconnect and shutdown lifecycle; and +- transport-neutral adapter APIs for embedding-owned handlers. + +The Gateway remains authoritative for pairing, approved command delivery, +controller authentication, and revocation. An embedding-owned admission policy +may narrow delivered work but must never broaden Gateway authority. + +## Node connection and identity + +A node session must advertise `mode=node`, `role=node`, the supported protocol +range, one stable device identity, and a deterministic snapshot of commands and +capabilities. The current candidate advertises protocol maximum 4 and minimum +node compatibility 3; that range must track the canonical OpenClaw contract. + +Device identity uses Ed25519. The signature payload must match the canonical +OpenClaw v3 field order and values exactly. An embedding may sign externally so +private key material can remain in platform secure storage. The runtime must +verify that an externally supplied signature matches the exact requested bytes +before connecting. + +Authentication material and newly issued device tokens are distinct outputs. +The runtime must deliver an issued token through a typed callback, not a +general lifecycle event or log. Persistence is embedding-owned and should be +keyed by Gateway, device, and role. If an adopted issued token receives the +canonical device-token-mismatch response, the runtime may clear it and retry +configured authentication once; it must not loop or discard unrelated auth +failures. + +In sidecar deployments the supervisor, not the runtime, selects and persists +credentials. The runtime reports the mismatch and retires that connection +generation; any bounded fallback is explicitly reauthorized through +[`sidecar-gateway-connection-v1-spec.md`](sidecar-gateway-connection-v1-spec.md). + +## Connection-scoped manifest + +Commands and capabilities form an immutable lease for one connection attempt. +They must be sorted or otherwise deterministic before signing and advertising. +Changing the registered surface requires a new connection; a retired session +must not execute commands under its old manifest. + +Registration must reject empty capability or command names, duplicate command +names, and the OpenClaw-owned `system` / `system.*` namespace unless a separate +OpenClaw-owned capability specification explicitly authorizes an implementation. +An empty manifest is valid and grants no implied capability. + +## Invocation admission + +An invocation is eligible only when all of these conditions hold: + +1. it was delivered on the active node session; +2. its command is present in that session's manifest; +3. its invocation identifier is not already active; +4. bounded concurrency has capacity; +5. input and parameter size limits are satisfied; and +6. the embedding-owned admission callback succeeds within the invocation + deadline. + +Admission rejection, panic, cancellation, or timeout must fail closed before +the handler runs. Admission receives a typed invocation and cooperative +cancellation token; it must not receive authority to mutate the advertised +manifest or synthesize Gateway approval. + +## Bounded execution + +The embedding-configurable candidate defaults are: + +| Limit | Candidate default | +| --- | ---: | +| Concurrent handlers | 8 | +| Invocation input/parameter budget | 256 KiB | +| Result/output budget | 256 KiB | +| Handler deadline when omitted | 30 seconds | +| Maximum handler deadline | 5 minutes | +| Result-delivery reserve | 100 milliseconds | + +The current fixed protocol/runtime constants are: + +| Limit | Candidate constant | +| --- | ---: | +| Pending duplex input | 64 KiB | +| One input frame | 16 KiB UTF-8 | +| One progress chunk | 16 KiB UTF-8 | +| Duplex heartbeat interval | 5 seconds | + +All embedding-configurable limits must be finite and configurable downward. +Fixed constants must remain finite and require a runtime/protocol revision to +change; embedders may narrow them in their own adapters but cannot advertise a +larger shared contract. Saturation must reject immediately with a stable +structured failure; it must not create an unbounded work queue. A handler panic +must become a structured failure and must not terminate the host process. + +`timeoutMs: 0` follows the canonical Gateway meaning and disables the handler +deadline; embeddings that cannot safely permit that behavior must narrow it in +their admission policy or product contract rather than silently reinterpret the +wire value. + +## Duplex input, progress, and cancellation + +Only commands registered as duplex may consume invocation input or emit +progress. Input must remain ordered. Oversized, duplicate, late, or +out-of-scope input must be rejected or ignored according to the canonical +lifecycle fixture without crossing invocation/session boundaries. + +Progress strings must be split only at UTF-8 boundaries. Heartbeats must stop +when the invocation completes, is cancelled, or the session retires. +Cancellation must reach the admission wait, handler, duplex input receiver, and +child work through the cooperative token. Disconnect cleanup must cancel all +work owned by the retired session. + +The runtime must not automatically replay an invocation after reconnect. + +## Results and public failures + +Every admitted invocation produces at most one final result. Success and +handler failure use the canonical node result envelope. Public failures must +use stable bounded codes/messages. Default runtime and proof-host diagnostic +sinks must emit only bounded redacted classes and explicitly safe context. +Generic library error values may retain source detail for programmatic +classification; embedders must not log arbitrary `Display`, `Debug`, or source +text without an explicit redaction policy. Canonical signing payloads and +credentials must never be formatted. + +At minimum, distinct outcomes must exist for unsupported command, duplicate +invocation, saturation, invalid/oversized input, admission rejection, +admission failure, handler timeout, handler panic/failure, cancellation, +result-delivery failure, and session retirement. + +Approved-but-failed execution must remain distinguishable from policy or +approval denial in audit and telemetry. + +## Lifecycle and readiness + +The reusable lifecycle must expose connecting, connected, ready, disconnected, +backoff, paused, and stopped states. It must acquire fresh connect material for +every attempt. Successful connection resets exponential backoff. Pairing, +authentication, protocol, local-identity, and permanent configuration failures +pause with a typed reason; transient transport and TLS-handshake failures retry. + +The current candidate uses a 1-second initial reconnect delay capped at 30 +seconds, a 1-second runtime-restart delay, and a 5-second shutdown grace. +Shutdown must cancel a connection attempt or active runtime, drain within the +grace period where possible, and report whether draining completed. + +Readiness is true only while the current session is activated and its runtime +is serving the advertised manifest. A listening process, paired identity, or +healthy event loop alone is not readiness. + +## Ownership boundary + +The runtime does not own: + +- product UI or approval presentation; +- product IPC and runtime selection; +- sandboxing, OS isolation, or least-privilege policy for product-native + handlers, which are trusted embedding components; +- credential persistence or OS key-store policy; +- Windows, Apple, Android, ESP, Scout, or Lobster capability handlers; +- `system.run`, PTY, MCP, skills, or plugins without separate accepted + OpenClaw contracts; or +- the Gateway's model-facing agent loop or controller policy. + +## Required conformance evidence + +V1 evidence must cover canonical signing, issued-token adoption and rejection, +pairing/reconnect classification, deterministic manifests, retired-session +denial, admission fail-closed behavior, duplicate/saturation bounds, timeout +and panic containment, ordered input/progress, UTF-8 chunking, cancellation at +each wait window, disconnect cleanup, result correlation, lifecycle shutdown, +and readiness transitions. + +Sidecar conformance additionally covers material reacquisition, external +signing, issued-token acknowledgement, generation retirement, and redaction +through the shared sidecar Gateway connection fixture. + +Every behavior shared with TypeScript must use a shared fixture or an explicit +documented comparison. See +[conformance-and-adoption-plan.md](conformance-and-adoption-plan.md). diff --git a/rfcs/0028/rust-node-runtime-topology.svg b/rfcs/0028/rust-node-runtime-topology.svg new file mode 100644 index 00000000..5ddf503e --- /dev/null +++ b/rfcs/0028/rust-node-runtime-topology.svg @@ -0,0 +1,58 @@ + + Proposed OpenClaw Rust node runtime topology + Authorized controllers communicate with the Rust node only through the OpenClaw Gateway. A product shell hosts the runtime through a product-owned adapter and retains native tools, approvals, process supervision, packaging, and deployment. + + + + + + + + + Proposed OpenClaw Rust node runtime topology + + + Authorized controllers + Scout Cloud + Windows Companion + Cowork / other clients + No direct runtime access + + + OpenClaw Gateway + authentication · authorization + session state · audit · revocation + + + OpenClaw Rust runtime + + openclaw-gateway-client + + openclaw-node-host + + + Product-owned adapter + in-process or authenticated sidecar + + + Product shell + first adopter: Copilot system tray + native tools · approvals · IPC + process supervision · audit + packaging · deployment + + + Gateway APIs + + node session + + bounded runtime contract + + diff --git a/rfcs/0028/sidecar-gateway-connection-role-review-2026-07-31.md b/rfcs/0028/sidecar-gateway-connection-role-review-2026-07-31.md new file mode 100644 index 00000000..415eca6c --- /dev/null +++ b/rfcs/0028/sidecar-gateway-connection-role-review-2026-07-31.md @@ -0,0 +1,84 @@ +# RFC 0028 sidecar Gateway connection role review + +Date: 2026-07-31 + +Scope: the proposed connection-control contract between a product supervisor +and an out-of-process Rust node runtime. + +## Source analogues + +- Linux Tauri owns endpoint trust, device identity, canonical signing, + issued-token persistence, stale-token clearing, and desktop lifecycle. +- `openclaw-node-host` already exposes external signing requests, typed + issued-token delivery, per-attempt material acquisition, reconnect + classification, and secret-free lifecycle state. +- `openclaw-windows-node` owns endpoint authorization, generation fencing, + token recovery, and the native capability dispatcher. +- The authenticated sidecar bridge owns framing, replay resistance, + negotiation, immutable capability configuration, and bounded invocation. + +The review therefore treats the missing work as a process-boundary contract, +not a new Gateway authentication design. + +## Gateway engineer — cluster B + +Accepted findings: + +1. Reconnect acquisition must stay between the sidecar and supervisor; it must + not add a Gateway hook or per-invocation round trip. +2. One material request per connection attempt composes directly with + `NodeLifecycle` and keeps Gateway transport logic in the Rust crate. +3. Signing and token acknowledgement are handshake-time operations with finite + deadlines. Ordinary invocation remains on the existing bounded fast path. +4. A denied or unavailable supervisor must pause/back off with typed state + rather than crash the Gateway or spin a reconnect loop. + +## Platform engineer — cluster D + +Accepted findings: + +1. Endpoint, trust, account, credential, and rollout authorization must be + reacquired for every attempt so fleet changes cannot leave stale authority. +2. Every authorization allocates a fresh, never-reused connection generation; + delayed responses and retired generations fail closed. +3. Issued tokens are not considered adopted until the supervisor reports an + atomic durable store keyed by Gateway, device, and role. +4. Runtime status needs manifest, attempt, and connection-generation + correlation without endpoint or credential disclosure. +5. Credential replacement and runtime rollback are explicit retirement plus a + new attempt, not mutation of a live generation. + +## CISO — cluster A + +Accepted findings: + +1. The private key never crosses IPC. The runtime sends exact canonical bytes + for external Ed25519 signing and verifies the returned signature locally. +2. Authenticated HMAC frames do not provide confidentiality. Direct credential + messages require a peer-confidential protected IPC transport or an + equivalent secret-delivery mechanism. +3. Signing requests/results bind generation, attempt, request ID, and payload + digest; substitution or replay retires the generation. +4. Revocation stops admission and generation-owned work before acknowledgement. + IPC loss has the same fail-closed result. +5. Logs, status, public errors, crash metadata, and audit contain only stable + reason classes and correlation IDs, never credentials, signing payloads, + signatures, or issued tokens. + +## Resulting contract line + +The immutable sidecar configuration remains capability-only. Live Gateway +authority is a separate authenticated connection-control exchange with five +paired operations: material request/decision, signing request/result, +issued-token delivery/acknowledgement, supervisor retirement/acknowledgement, +and runtime closure/acknowledgement. Product storage and policy remain outside +Rust; Gateway wire behavior and node lifecycle remain inside Rust. + +## Deferred implementation evidence + +- Rust and independent adopter consumers of the shared fixture; +- byte-exact canonical JSON encodings and negative mutations; +- protected-IPC live Gateway pairing, token reconnect, mismatch recovery, and + revocation; +- crash, resource, rollout, rollback, and audit-sink proof; and +- packaged secure-store and artifact-signing validation. diff --git a/rfcs/0028/sidecar-gateway-connection-v1-spec.md b/rfcs/0028/sidecar-gateway-connection-v1-spec.md new file mode 100644 index 00000000..e56544fe --- /dev/null +++ b/rfcs/0028/sidecar-gateway-connection-v1-spec.md @@ -0,0 +1,551 @@ +# Rust node sidecar Gateway connection v1 specification + +This document defines the proposed connection-control messages between a +product supervisor and an out-of-process `openclaw-node-host`. It specializes +the authenticated channel and hosting rules in +[`sidecar-hosting-v1-spec.md`](sidecar-hosting-v1-spec.md); it does not create a +second Gateway protocol or a product-specific bootstrap API. + +Status: draft. The existing sidecar implementation does not yet implement this +message family. + +## Purpose and ownership + +The Rust runtime owns Gateway transport, canonical connect construction, +challenge handling, node lifecycle, and secret-free status classification. The +product supervisor owns endpoint authorization, credential and identity +custody, secure persistence, runtime selection, and local revocation policy. + +The boundary must let the runtime: + +- reacquire endpoint, trust, authentication, and identity metadata for every + connection attempt; +- ask the supervisor to sign the exact canonical Gateway challenge payload + without receiving a private key; +- deliver a Gateway-issued device token for atomic supervisor-owned storage; +- stop promptly when the supervisor retires or replaces authority; and +- report lifecycle state without placing secrets in status or audit events. + +This is a hosting relationship. Remote controllers continue to authenticate +and manage the node only through canonical Gateway APIs. + +## Prerequisites + +Connection-control messages are invalid until sidecar authentication, +negotiation, and immutable runtime configuration have completed. They travel +inside the existing generation-bound, strictly sequenced sidecar frames and +inherit their deadlines and size ceilings. + +V1 reserves negotiated sidecar feature bit `1` as +`gateway-connection-control-v1`. Both peers must offer and select that bit +before configuration may activate Gateway sidecar mode. If the bit is absent, +neither peer sends this message family; the supervisor receives a typed +unsupported-feature result and may keep or select its incumbent runtime. It +must not probe support by sending an unknown message. Later required message or +field semantics require another negotiated bit or a sidecar protocol major +revision. + +HMAC authentication provides integrity and peer authentication, not payload +encryption. Activating `gateway-connection-control-v1` therefore requires an IPC +transport whose handles, endpoint, and access controls make the entire exchange +confidential to the two peers. This requirement covers every message, including +authentication material, canonical signing payloads, signatures, and issued +tokens. A deployment that cannot provide peer confidentiality must not negotiate +this feature or send this message family. + +Credentials, canonical signing payloads, signatures, and issued tokens are +secret-bearing data. They must not appear in arguments, broad environment +state, files used as IPC, logs, status, crash annotations, or public errors. + +## Identifiers and generations + +Every connection-control message is bound to the authenticated sidecar session. +Three generation scopes remain distinct: + +- the sidecar frame generation identifies the authenticated process session; +- `manifestGeneration` identifies one immutable capability/command snapshot and + may span multiple reconnect attempts; and +- `connectionGeneration` identifies exactly one authorized Gateway connection + attempt under that manifest. + +The following additional identifiers are mandatory: + +- `requestId`: unique within the sidecar session for one material request; +- `connectionGeneration`: a positive JSON-safe integer selected by the + supervisor and never reused in that sidecar session; +- `attempt`: the runtime lifecycle attempt that requested the material; +- `signingRequestId`: unique within one connection generation; and +- `deliveryId`: unique within one connection generation for an issued token. + +`manifestGeneration` in a material request must equal the immutable configured +manifest. Responses with a different attempt, request, connection generation, +or manifest are terminal for the connection exchange. A retired generation can +never be reopened or reused, even if a delayed response later arrives. + +## State model + +```text +configured + | + v +material-requested -> authorized -> signing -> connecting -> token-pending -> ready + | | | | | | + +--------------------+-------------+-----------+--------------+----------+ + deny, failure, retirement, or shutdown +``` + +The runtime initiates one material request per lifecycle attempt. It may have at +most one unresolved material request and one live connection generation. The +supervisor may deny an attempt without allocating a generation. Authorization +allocates a fresh generation and replaces no earlier authority implicitly; an +earlier live generation must first be retired by the supervisor or reported +ended by the runtime and acknowledged by the supervisor. + +If the material deadline expires, the runtime does not silently abandon the +request. It sends `connection-material-cancel` and waits for +`connection-material-cancelled` before starting another attempt. An +authorization racing with cancellation is quarantined: the runtime must not use +its material or start a Gateway connection, and the supervisor retires any +generation it allocated before acknowledging cancellation. IPC loss remains a +fail-closed terminal outcome for the whole sidecar session. + +The runtime must not report `ready` until the Gateway session is activated and, +when the Gateway issued a device token, the supervisor has acknowledged durable +adoption of that token. An issued-token storage rejection retires the Gateway +session and produces a typed pause rather than silently continuing with +unrecoverable pairing state. + +The activated Gateway transport must not make the command manifest eligible +for admission while token acknowledgement is pending. Any invocation received +in that window remains within the runtime's existing bounded event budget and +is dispatched only after `outcome: "stored"`; otherwise it is rejected or +discarded when the generation retires. No product-native side effect may begin +before durable token adoption. + +## Message family + +All messages use compact UTF-8 JSON, camel-case fields, and a kebab-case `type`. +Strings and collections must have finite local limits before allocation. A peer +must ignore bounded unknown fields on a known message so additive minor-version +compatibility remains possible. Senders must not attach authority, security, or +required processing semantics to an unnegotiated field; such a change requires +a negotiated feature bit or a new message type. Unknown message types and +unnegotiated required extensions fail closed. The shared fixture defines +representative values and negative mutations. + +### Material request + +The runtime sends: + +```json +{ + "type": "connection-material-request", + "requestId": "connect-1", + "attempt": 1, + "manifestGeneration": 3 +} +``` + +This message contains no endpoint or credential hint. It asks the supervisor to +authorize the exact current attempt after applying product lifecycle, endpoint, +account, and rollout policy. A bounded mismatch fallback adds: + +```json +{ + "type": "connection-material-request", + "requestId": "connect-fallback-1", + "attempt": 2, + "manifestGeneration": 3, + "recovery": { + "kind": "device-token-mismatch", + "priorConnectionGeneration": 7 + } +} +``` + +Only the canonical device-token-mismatch path may set this v1 recovery object. +The referenced generation must have completed its runtime-ended +acknowledgement, and the runtime may emit it only once for that rejected token. + +### Material cancellation + +When its bounded material deadline expires, the runtime sends: + +```json +{ + "type": "connection-material-cancel", + "requestId": "connect-timeout-1", + "attempt": 6, + "manifestGeneration": 3, + "reason": "deadline" +} +``` + +The runtime keeps the request reserved and starts no replacement attempt until +the supervisor acknowledges: + +```json +{ + "type": "connection-material-cancelled", + "requestId": "connect-timeout-1", + "attempt": 6, + "manifestGeneration": 3, + "connectionGeneration": 11 +} +``` + +`connectionGeneration` is present only when authorization raced with the +cancellation and the supervisor had already allocated that generation. Before +sending the acknowledgement, the supervisor must make the request terminal, +retire any such generation, release its material, and ensure it can never become +live. When no generation was allocated, the field is absent. The acknowledgement +therefore establishes a common terminal point in both orderings. + +After sending cancellation, the runtime rejects all nonterminal messages for +that request except a racing `connection-authorized`, which it records only for +correlation and never activates. A matching cancellation acknowledgement must +name the same generation if authorization was observed. Mismatched, reused, or +post-ack responses are terminal protocol errors. Duplicate cancellation and +acknowledgement messages are idempotent only when their complete correlation +fields match. + +A racing `connection-denied` commutes with cancellation and completes the +runtime's wait immediately. The runtime applies the denial's typed recovery; it +does not wait for a separate cancellation acknowledgement before following that +recovery. When the supervisor later receives the cancellation, it still returns +a matching `connection-material-cancelled` without `connectionGeneration` so +both histories converge. The runtime accepts that later acknowledgement as an +idempotent terminal confirmation and performs no second recovery action. Because +denial allocates no generation, this ordering cannot create authority. + +### Authorization or denial + +The supervisor either denies the request with a stable secret-free code: + +```json +{ + "type": "connection-denied", + "requestId": "connect-1", + "attempt": 1, + "manifestGeneration": 3, + "code": "credential-unavailable", + "recovery": "pause" +} +``` + +`connection-denied` is the authoritative request-scoped cancellation before a +connection generation exists. The supervisor may send it at any time while the +request is unresolved, including when credentials are revoked, rollout is +disabled, the attempt is superseded, or shutdown begins. The runtime cancels +the pending acquisition and applies `recovery` without waiting for its normal +material deadline. Exactly one of `connection-denied` or +`connection-authorized` may resolve a request before cancellation. Once +cancellation is sent, the commuting rules above govern a racing resolution; any +other conflicting response is rejected. + +V1 `recovery` values are: + +- `pause`: publish the typed pause and emit no further material request in this + process lifecycle; +- `retry-after-backoff`: apply the normal bounded reconnect backoff/circuit + breaker, then emit a fresh material request with the next attempt; and +- `stop`: stop the runtime lifecycle cleanly with readiness false. + +An unknown recovery value is an unnegotiated required semantic. It invalidates +the response and retires the authenticated sidecar channel fail-closed. New +recovery behavior requires a negotiated feature bit or protocol-major revision. + +or supplies attempt-scoped material: + +```json +{ + "type": "connection-authorized", + "requestId": "connect-1", + "connectionGeneration": 7, + "attempt": 1, + "manifestGeneration": 3, + "endpoint": { + "url": "wss://gateway.example", + "tlsTrust": { + "mode": "pinned-leaf-sha256", + "sha256": "abababababababababababababababababababababababababababababababab" + } + }, + "auth": { + "kind": "device-token", + "secret": "fixture-device-token" + }, + "identity": { + "deviceId": "fe812c12f3ab4ce6ac5db69ac352f906cb1b11ef43fb33e252ef7ff552263889", + "publicKeyBase64Url": "6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", + "platform": "windows", + "deviceFamily": "desktop" + } +} +``` + +V1 trust modes are `system-roots` and `pinned-leaf-sha256`. The pin is the +SHA-256 digest of the exact DER bytes of the TLS end-entity certificate, +encoded as 64 lowercase hexadecimal characters. Comparison is constant-time. +A pin requires `wss://`; there is no fallback from a configured pin. Matching +the existing Rust Gateway client and OpenClawKit behavior, a configured pin +replaces CA-chain and hostname trust, while the TLS stack must still verify the +handshake signature proving that the peer possesses that certificate's private +key. V1 authentication kinds are `none`, `shared-token`, `shared-password`, and +`device-token`; `secret` is omitted only for `none`. Arbitrary headers, product +cookies, private keys, and product-specific discovery objects are not part of +v1. + +The runtime validates the URL, trust policy, authentication shape, public key, +derived device ID, normalized platform metadata, limits, and generation before +opening a socket. It retains secret material only for the authorized attempt and +zeroizes or releases it when the generation retires. + +### External signing + +After receiving the Gateway nonce, the runtime constructs the canonical +OpenClaw device-auth payload and sends its exact bytes to the supervisor: + +```json +{ + "type": "signing-request", + "connectionGeneration": 7, + "attempt": 1, + "signingRequestId": "sign-1", + "algorithm": "ed25519", + "payloadBase64": "djN8ZmU4MTJjMTJmM2FiNGNlNmFjNWRiNjlhYzM1MmY5MDZjYjFiMTFlZjQzZmIzM2UyNTJlZjdmZjU1MjI2Mzg4OXxub2RlLWhvc3R8bm9kZXxub2RlfHwxODAwMDAwMDAwMDAwfGZpeHR1cmUtZGV2aWNlLXRva2VufGZpeHR1cmUtbm9uY2V8d2luZG93c3xkZXNrdG9w", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b" +} +``` + +The supervisor verifies that the request belongs to the current authorized +identity and connection before invoking its secure-store signer. It returns +either: + +```json +{ + "type": "signing-result", + "connectionGeneration": 7, + "attempt": 1, + "signingRequestId": "sign-1", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b", + "outcome": "signed", + "signatureBase64Url": "gBSx1Nxb5fqo-srP1MNU1oMA4Le5sLiaMxkR_a8VlvzYM3PxrtERef6JwXJOuhDprnynuk8OQI7WrcocRRYYCQ" +} +``` + +or `outcome: "denied"` with a stable `code` and no signature. The runtime +checks the generation, attempt, request identifier, digest, Ed25519 encoding, +and signature against the authorized public key and exact payload before +sending connect parameters to the Gateway. A mismatch retires the connection +generation and pauses for identity repair. + +Only one signing request may be unresolved for a generation. Signing has a +finite deadline and cancellation propagates on retirement or shutdown. + +### Issued-device-token delivery + +When `hello-ok` contains a device token, the runtime sends: + +```json +{ + "type": "issued-device-token", + "connectionGeneration": 7, + "attempt": 1, + "deliveryId": "token-1", + "token": "fixture-issued-device-token" +} +``` + +The supervisor atomically stores the token keyed by the authorized Gateway, +derived device ID, and `node` role, then responds: + +```json +{ + "type": "issued-device-token-ack", + "connectionGeneration": 7, + "attempt": 1, + "deliveryId": "token-1", + "outcome": "stored" +} +``` + +`outcome: "rejected"` includes only a stable secret-free `code`. The runtime +does not persist the token, log it, place it in status, or assume it was adopted +before the acknowledgement. Duplicate delivery IDs are idempotent only when +their token digest matches the first delivery; a conflicting duplicate is +terminal. + +The token acknowledgement wait has a finite locally enforced deadline no longer +than the negotiated sidecar request timeout. On expiry the runtime keeps +readiness false, closes the Gateway session, releases the issued token and other +generation secrets, and begins the runtime-originated `connection-ended` +exchange with reason `token-adoption-timeout`. If the supervisor also fails to +acknowledge that terminal message within the bounded control-message deadline, +the runtime terminates the authenticated sidecar channel. It must not retry or +redeliver the token under the same generation. + +### Retirement + +The supervisor may retire current authority at any time: + +```json +{ + "type": "connection-retire", + "connectionGeneration": 7, + "reason": "credential-revoked" +} +``` + +The runtime immediately stops new admission for that generation, cancels the +Gateway connection and all generation-owned waits/work, releases secrets, and +then acknowledges: + +```json +{ + "type": "connection-retired", + "connectionGeneration": 7, + "reason": "credential-revoked" +} +``` + +The acknowledgement means the session is closed and generation-owned work can +no longer publish results. It is not merely receipt of the request. IPC loss +has the same fail-closed effect and requires no acknowledgement. + +When the runtime detects Gateway closure or another terminal generation failure +without a supervisor retirement request, it first stops admission, cancels +generation-owned work, and releases secrets. It then sends: + +```json +{ + "type": "connection-ended", + "connectionGeneration": 7, + "attempt": 1, + "reason": "transport" +} +``` + +The supervisor records the terminal transition and responds: + +```json +{ + "type": "connection-ended-ack", + "connectionGeneration": 7, + "attempt": 1 +} +``` + +The runtime does not request new material until this acknowledgement arrives. +The supervisor does not authorize a replacement generation until it has +received `connection-ended` or `connection-retired` for the prior generation. +Both waits are bounded; timeout pauses the runtime and makes readiness false +rather than permitting overlapping authority. Duplicate identical +`connection-ended` notifications are idempotent; a conflicting terminal reason +or any later nonterminal message for that generation is rejected. + +Supervisor retirement and runtime closure may cross in flight. Their terminal +messages commute: + +- after sending `connection-ended`, the runtime accepts either + `connection-ended-ack` or `connection-retire` as proof that the supervisor has + observed terminal state; on `connection-retire` it still replies with + `connection-retired`; +- after sending `connection-retire`, the supervisor accepts either + `connection-retired` or `connection-ended` as proof that runtime authority is + already closed; on `connection-ended` it still replies with + `connection-ended-ack`; and +- late terminal acknowledgements for that same generation are idempotent, but + every later nonterminal message remains invalid. + +The audit record preserves the supervisor retirement reason and runtime closure +reason separately when both exist; one is not rewritten as the other. This race +never reopens authority or delays replacement-generation eligibility after both +peers have observed a terminal message. + +Suggested runtime-end reasons are `transport`, `gateway`, `authentication`, +`device-token-mismatch`, `token-adoption-timeout`, `protocol`, `identity`, +`runtime`, and `shutdown`. They are secret-free classifications, not raw +exceptions. + +Suggested v1 reasons are `credential-revoked`, `credential-replaced`, +`endpoint-changed`, `manifest-replaced`, `rollout-disabled`, `shutdown`, and +`superseded`. Unknown additive reasons map to a conservative generic retirement +class, never to continued authority. + +## Reconnect and token replacement + +The runtime never chooses a stored credential or silently falls back between +credentials. Each reconnect attempt emits a new material request. The +supervisor reauthorizes the endpoint and returns its currently selected +credential under a fresh connection generation. + +When a Gateway returns the canonical device-token-mismatch classification, the +runtime closes that generation, completes the runtime-originated ended exchange, +and reports a secret-free authentication recovery state. After the ended +acknowledgement, the runtime emits exactly one new material request carrying the +bounded recovery object above. The supervisor may atomically remove the +rejected token and authorize that attempt with a configured bootstrap/shared +credential, or deny it. A denial, unavailable fallback, or second mismatch +enters the typed authentication pause; it must not loop. + +Credential replacement or Gateway removal uses `connection-retire`; changing +memory or storage alone does not revoke an already authorized sidecar session. + +## Status and audit + +Sidecar lifecycle status adds nullable `connectionGeneration` to the existing +manifest generation, attempt, state, and reason. It contains no endpoint query, +credential kind, public key, signing payload, signature, or token. Before +authorization the field is null; after retirement it remains available only as +bounded correlation for the terminal transition. + +The product and Gateway audit paths must be able to correlate sidecar session, +manifest generation, connection generation, lifecycle attempt, signing +decision class, token-delivery outcome, invocation, cancellation, and +retirement reason. Raw secret-bearing messages are never audit records. + +## Required conformance + +Cross-language tests must consume the shared fixture and prove: + +- exact happy-path request, authorization, signing, token acknowledgement, and + retirement shapes; +- additive unknown-field tolerance, unknown-message/unnegotiated-extension + rejection, and malformed-auth rejection; +- all denial recovery values plus fail-closed unknown recovery; +- system-root and exact end-entity-DER pin behavior, private-key-possession + proof, and no pin fallback; +- stale request, attempt, manifest, generation, signature, digest, delivery, + and retirement rejection; +- signing denial, timeout, cancellation, and invalid-signature pause; +- token storage rejection before readiness and idempotent duplicate delivery; +- bounded token-acknowledgement timeout, generation closure, secret release, + and sidecar-channel termination when the terminal acknowledgement also stalls; +- no invocation admission or native dispatch before issued-token + acknowledgement; +- request-scoped denial during a pending material wait, plus prompt generation + retirement during signing, connect, token, admission, and active invocation + waits; +- material-deadline cancellation before resolution and while authorization or + denial is in flight, with no orphaned or activated generation; +- runtime-originated closure acknowledgement before a replacement generation; +- concurrent supervisor retirement/runtime closure without deadlock or + duplicate authority; +- one bounded fallback after device-token mismatch; and +- redaction scans covering logs, status, diagnostics, crash metadata, and + public errors. + +A source harness must then run this exchange through the actual protected IPC +transport and a real isolated Gateway. A packaged adopter must additionally +prove secure-store behavior, process crash/restart, credential revocation, +resource bounds, rollout, and rollback. + +## Deliberate exclusions + +V1 does not define product discovery, account UI, secure-store APIs, arbitrary +Gateway headers, controller management, remote secret retrieval, platform +handles inside JSON, capability-specific credentials, or secret persistence in +the Rust runtime. Those concerns may adapt this contract at the supervisor +boundary but cannot weaken generation fencing, external signing, explicit token +adoption, retirement, confidentiality, or redaction. diff --git a/rfcs/0028/sidecar-gateway-connection-v1.json b/rfcs/0028/sidecar-gateway-connection-v1.json new file mode 100644 index 00000000..10e6bda8 --- /dev/null +++ b/rfcs/0028/sidecar-gateway-connection-v1.json @@ -0,0 +1,260 @@ +{ + "schemaVersion": 1, + "requiredFeatureBit": 1, + "status": "draft-conformance-fixture", + "notes": [ + "All credentials, payloads, signatures, and tokens are test-only sentinel values.", + "The accepted signing path uses deterministic Ed25519 seed bytes 0x07 repeated 32 times." + ], + "messages": [ + { + "type": "connection-material-request", + "requestId": "connect-1", + "attempt": 1, + "manifestGeneration": 3 + }, + { + "type": "connection-authorized", + "requestId": "connect-1", + "connectionGeneration": 7, + "attempt": 1, + "manifestGeneration": 3, + "endpoint": { + "url": "wss://gateway.example", + "tlsTrust": { + "mode": "pinned-leaf-sha256", + "sha256": "abababababababababababababababababababababababababababababababab" + } + }, + "auth": { + "kind": "device-token", + "secret": "fixture-device-token" + }, + "identity": { + "deviceId": "fe812c12f3ab4ce6ac5db69ac352f906cb1b11ef43fb33e252ef7ff552263889", + "publicKeyBase64Url": "6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", + "platform": "windows", + "deviceFamily": "desktop" + } + }, + { + "type": "signing-request", + "connectionGeneration": 7, + "attempt": 1, + "signingRequestId": "sign-1", + "algorithm": "ed25519", + "payloadBase64": "djN8ZmU4MTJjMTJmM2FiNGNlNmFjNWRiNjlhYzM1MmY5MDZjYjFiMTFlZjQzZmIzM2UyNTJlZjdmZjU1MjI2Mzg4OXxub2RlLWhvc3R8bm9kZXxub2RlfHwxODAwMDAwMDAwMDAwfGZpeHR1cmUtZGV2aWNlLXRva2VufGZpeHR1cmUtbm9uY2V8d2luZG93c3xkZXNrdG9w", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b" + }, + { + "type": "signing-result", + "connectionGeneration": 7, + "attempt": 1, + "signingRequestId": "sign-1", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b", + "outcome": "signed", + "signatureBase64Url": "gBSx1Nxb5fqo-srP1MNU1oMA4Le5sLiaMxkR_a8VlvzYM3PxrtERef6JwXJOuhDprnynuk8OQI7WrcocRRYYCQ" + }, + { + "type": "issued-device-token", + "connectionGeneration": 7, + "attempt": 1, + "deliveryId": "token-1", + "token": "fixture-issued-device-token" + }, + { + "type": "issued-device-token-ack", + "connectionGeneration": 7, + "attempt": 1, + "deliveryId": "token-1", + "outcome": "stored" + }, + { + "type": "connection-retire", + "connectionGeneration": 7, + "reason": "credential-revoked" + }, + { + "type": "connection-retired", + "connectionGeneration": 7, + "reason": "credential-revoked" + } + ], + "alternateMessages": [ + { + "type": "connection-denied", + "requestId": "connect-2", + "attempt": 2, + "manifestGeneration": 3, + "code": "credential-unavailable", + "recovery": "pause" + }, + { + "type": "signing-request", + "connectionGeneration": 8, + "attempt": 2, + "signingRequestId": "sign-2", + "algorithm": "ed25519", + "payloadBase64": "djN8ZmU4MTJjMTJmM2FiNGNlNmFjNWRiNjlhYzM1MmY5MDZjYjFiMTFlZjQzZmIzM2UyNTJlZjdmZjU1MjI2Mzg4OXxub2RlLWhvc3R8bm9kZXxub2RlfHwxODAwMDAwMDAwMDAwfGZpeHR1cmUtZGV2aWNlLXRva2VufGZpeHR1cmUtbm9uY2V8d2luZG93c3xkZXNrdG9w", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b" + }, + { + "type": "signing-result", + "connectionGeneration": 8, + "attempt": 2, + "signingRequestId": "sign-2", + "payloadSha256": "adff2da6601b1d8fc00c5e95c01bda27f29cbd50c85bef72d9be64af29b7651b", + "outcome": "denied", + "code": "identity-unavailable" + }, + { + "type": "issued-device-token-ack", + "connectionGeneration": 8, + "attempt": 2, + "deliveryId": "token-2", + "outcome": "rejected", + "code": "secure-storage-unavailable" + }, + { + "type": "connection-ended", + "connectionGeneration": 9, + "attempt": 3, + "reason": "device-token-mismatch" + }, + { + "type": "connection-ended-ack", + "connectionGeneration": 9, + "attempt": 3 + }, + { + "type": "connection-material-request", + "requestId": "connect-fallback-1", + "attempt": 4, + "manifestGeneration": 3, + "recovery": { + "kind": "device-token-mismatch", + "priorConnectionGeneration": 9 + } + } + ], + "concurrentTermination": [ + { + "type": "connection-ended", + "connectionGeneration": 10, + "attempt": 5, + "reason": "transport" + }, + { + "type": "connection-retire", + "connectionGeneration": 10, + "reason": "credential-revoked" + }, + { + "type": "connection-ended-ack", + "connectionGeneration": 10, + "attempt": 5 + }, + { + "type": "connection-retired", + "connectionGeneration": 10, + "reason": "credential-revoked" + } + ], + "materialCancellationRace": [ + { + "type": "connection-material-request", + "requestId": "connect-timeout-1", + "attempt": 6, + "manifestGeneration": 3 + }, + { + "type": "connection-material-cancel", + "requestId": "connect-timeout-1", + "attempt": 6, + "manifestGeneration": 3, + "reason": "deadline" + }, + { + "type": "connection-authorized", + "requestId": "connect-timeout-1", + "connectionGeneration": 11, + "attempt": 6, + "manifestGeneration": 3, + "endpoint": { + "url": "wss://gateway.example", + "tlsTrust": { + "mode": "system-roots" + } + }, + "auth": { + "kind": "shared-token", + "secret": "fixture-racing-token" + }, + "identity": { + "deviceId": "fe812c12f3ab4ce6ac5db69ac352f906cb1b11ef43fb33e252ef7ff552263889", + "publicKeyBase64Url": "6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", + "platform": "windows", + "deviceFamily": "desktop" + } + }, + { + "type": "connection-material-cancelled", + "requestId": "connect-timeout-1", + "attempt": 6, + "manifestGeneration": 3, + "connectionGeneration": 11 + } + ], + "materialDenialCancellationRace": [ + { + "type": "connection-material-request", + "requestId": "connect-timeout-denied-1", + "attempt": 7, + "manifestGeneration": 3 + }, + { + "type": "connection-material-cancel", + "requestId": "connect-timeout-denied-1", + "attempt": 7, + "manifestGeneration": 3, + "reason": "deadline" + }, + { + "type": "connection-denied", + "requestId": "connect-timeout-denied-1", + "attempt": 7, + "manifestGeneration": 3, + "code": "credential-unavailable", + "recovery": "retry-after-backoff" + }, + { + "type": "connection-material-cancelled", + "requestId": "connect-timeout-denied-1", + "attempt": 7, + "manifestGeneration": 3 + } + ], + "requiredNegativeMutations": [ + "unknown-message-type", + "unnegotiated-required-extension", + "missing-required-feature-bit", + "unknown-denial-recovery", + "malformed-auth-shape", + "pin-with-plaintext-url", + "stale-request-id", + "cancel-ack-generation-mismatch", + "response-after-material-cancel-ack", + "stale-attempt", + "manifest-generation-mismatch", + "reused-connection-generation", + "stale-signing-request-id", + "payload-digest-mismatch", + "invalid-ed25519-signature", + "conflicting-token-delivery", + "issued-token-ack-timeout", + "ready-before-token-ack", + "replacement-before-ended-ack", + "response-after-retirement", + "credential-in-status-or-log" + ] +} diff --git a/rfcs/0028/sidecar-hosting-v1-spec.md b/rfcs/0028/sidecar-hosting-v1-spec.md new file mode 100644 index 00000000..b346a5ea --- /dev/null +++ b/rfcs/0028/sidecar-hosting-v1-spec.md @@ -0,0 +1,247 @@ +# Rust node sidecar hosting v1 specification + +This document defines the product-neutral hosting contract for running the +OpenClaw Rust node runtime out of process. It specifies process, authority, +security, and lifecycle invariants. It intentionally does not select Windows +named pipes, Unix sockets, loopback transport, Cap'n Proto, JSON, protobuf, or +another concrete IPC encoding. + +Status: draft and not implemented by upstream OpenClaw PRs #116050 or #116450. +Upstream draft OpenClaw PR #116863 consolidates the reviewed fork evidence from +#193-#195 and implements the transport-neutral authenticated framing, handshake, +immutable configuration, ordinary-command runtime bridge, and a real OS +child-process IPC test for review. It is a shared primitive and proof harness; +it does not implement a production sidecar. Windows PR #1068 now combines the replaceable client, +capability-dispatch seam, and independent C# reproduction of all three Rust +corpora while keeping C# as the production runtime. Its adapter routes ordinary +native invocations through the shared dispatcher. It is a non-selectable +in-process proof, not the verified process, protected bootstrap, concrete IPC, +or production lifecycle required below. + +Subsequent fork-only evidence +([Rust #11](https://github.com/giodl73-repo/openclaw-rust-node/pull/11), +[Rust #12](https://github.com/giodl73-repo/openclaw-rust-node/pull/12), and +[Windows #3](https://github.com/giodl73-repo/openclaw-windows-node/pull/3)/ +[Windows #4](https://github.com/giodl73-repo/openclaw-windows-node/pull/4)) +now demonstrates real anonymous-pipe framing, an exact SHA-256 launch pin held +against reparse/path substitution through process creation, a bounded private- +pipe bootstrap, and handshake binding to the verified artifact. +That is source-harness evidence, not packaged production adoption. + +The current `openclaw-node` executable is a foreground proof host, not this +production sidecar. Its environment-indirected identity/auth loading does not +satisfy the authenticated launch and scoped secret-delivery requirements below. + +## Roles + +| Role | Responsibility | +| --- | --- | +| OpenClaw Rust runtime | Gateway connection, node manifest, invocation lifecycle, bounds, cancellation, and results | +| Product supervisor | Process creation, exact binary selection, IPC endpoint creation, credentials, restart/rollback policy, and product lifecycle | +| Platform adapter | Product-native capability execution and local policy/approval composition | +| Gateway | Node/controller authentication, pairing, approved command delivery, revocation, and canonical audit authority | + +The sidecar must not become a second product control plane. Cloud or local +controllers manage the node through Gateway APIs; local IPC exists only to host +the runtime and execute product-owned capabilities. + +The authenticated sidecar boundary is not a sandbox for product-native +handlers. The platform adapter is trusted product code. Authentication, +admission, message bounds, and cancellation prevent unauthorized or confused +dispatch, but they do not isolate a handler from the product account or OS. +The product owns least privilege, native permissions, containment, and any +stronger process isolation required by its capability surface. + +## Launch and trust bootstrap + +The supervisor must select an exact runtime artifact, prevent path or reparse- +point substitution between verification and process creation, and verify the +platform's accepted code-signing/provenance policy before launch. It must create a fresh, +local-only IPC endpoint with an unguessable session credential or equivalent +mutual authentication. The credential must not appear in command-line +arguments, inherited broad environment state, logs, crash reports, or world- +readable files. + +The sidecar must authenticate the supervisor before accepting configuration, +credentials, capability registration, or invocations. The supervisor must +authenticate the sidecar artifact and protocol version before forwarding any +native capability request. Failure on either side is terminal for that process +instance and must fail closed. + +Before reading the first protocol message, each peer must enforce local hard +ceilings for frame bytes, connections, and in-flight work. Authentication, +version negotiation, and configuration must each have a finite local deadline; +a peer that stalls or exceeds a pre-negotiation ceiling is terminated without +activation. Negotiated limits are `min(local ceiling, peer offer)` and may +never raise either peer's local ceiling. + +The supervisor owns Gateway credentials and private identity access. The +portable exchange for per-attempt endpoint/auth material, external signing, +issued-token acknowledgement, and revocation is defined in +[`sidecar-gateway-connection-v1-spec.md`](sidecar-gateway-connection-v1-spec.md). +The sidecar must not require Microsoft-, Tauri-, Chromium-, or Windows-specific +objects in that protocol. The connection-control feature requires a +peer-confidential IPC transport for its entire exchange because credentials, +signing payloads, signatures, and issued tokens are secret-bearing; +authenticated framing alone does not encrypt payloads. + +## Version negotiation + +The first IPC exchange must include: + +- protocol major and minor version; +- runtime build/version and artifact identity; +- supervisor/product identity; +- supported feature bits; +- maximum message and in-flight limits; and +- a fresh session identifier bound to the authenticated channel. + +Unknown major versions must fail before activation. Unknown optional features +must remain disabled. Minor-version compatibility must be additive and covered +by N-1 fixtures. Neither peer may infer support from product version alone. +Limit values in this exchange are offers inside the hard bootstrap envelope, +not authority to allocate or accept more than local policy allows. + +## State model + +The normative states are: + +```text +stopped -> starting -> authenticated -> configured -> connecting -> ready + ^ | | | | | + +----------+-------------+--------------+-------------+----------+ + failure / shutdown / rollback +``` + +`ready` requires all of the following: + +- authenticated local IPC; +- accepted IPC version/features; +- valid bounded runtime configuration; +- active Gateway node session; +- activated connection-scoped manifest; +- durable supervisor acknowledgement of any Gateway-issued device token; and +- a responsive product capability adapter. + +The supervisor must not route product traffic merely because the process is +alive or the IPC socket is open. Liveness and readiness are separate signals. + +## Configuration and capability registration + +Configuration must be finite, validated before activation, and free of inline +long-lived secrets. The supervisor supplies the exact command/capability +surface for one immutable `manifestGeneration`. The same manifest may be +advertised across multiple fresh Gateway connection generations during +transport reconnect. Registration must complete before any Gateway connection +advertises that surface. + +A capability update retires every active connection under the current manifest, +cancels its affected work, and requires a new manifest generation and validated +configuration/bridge before reconnect. Neither side may add a handler after +advertisement without that manifest-generation change. The authenticated +sidecar process/session generation is a third, independent scope and changes on +process restart or channel replacement. + +IPC messages must carry a sidecar session identifier and invocation identifier. +Identifiers supplied by a remote controller must not choose a local UI, +credential, or audit scope without supervisor validation. + +The authenticated channel must reject replay across process sessions. Within +one session generation, every authenticated message in each direction must +carry a strictly increasing sequence number, or use an equivalent transport +guarantee with the same property. A message at or below the accepted high-water +mark, or from a retired generation, must fail before admission or native +dispatch. The high-water mark is bounded constant state; peers must rotate the +generation before sequence exhaustion and must not reset it in place. +Invocation identifiers remain correlation and idempotency keys, but are not the +replay-security primitive. + +## Invocation flow + +The minimum sidecar flow is: + +1. Gateway delivers an invocation authorized for the active manifest. +2. The Rust runtime applies its bounds and calls the supervisor's admission + adapter. +3. The supervisor evaluates current product policy/approval state and returns + allow or a stable denial. +4. For an allowed native command, the runtime sends one typed invocation to the + platform adapter. +5. Input, progress, cancellation, and the final result remain correlated to the + same session generation and invocation. +6. The runtime returns the canonical result to the Gateway. + +The supervisor may narrow or deny work. It must not cause the sidecar to report +a command that the Gateway did not approve or a result for a different +invocation. Backpressure applies in both directions; neither peer may use an +unbounded queue. + +## Cancellation and shutdown + +Cancellation must be idempotent and cover: + +- admission/approval wait; +- queued native dispatch; +- active handler work; +- ordered input waits; +- progress/heartbeat production; and +- final-result delivery where the canonical protocol permits cancellation. + +On graceful shutdown, the supervisor stops new admissions, asks the runtime to +drain, waits a bounded grace period, and then terminates remaining work. On IPC +loss or supervisor death, the sidecar must retire the session and cancel local +work rather than continue with stale product authority. On sidecar death, the +supervisor must make readiness false immediately and must not silently switch +runtimes mid-invocation. + +## Crash recovery and restart + +Restart policy must use bounded backoff and a circuit breaker. Repeated auth, +version, signature, configuration, or policy failures require operator repair; +they must not create a restart loop. A new process creates a new IPC session and +Gateway connection generation. Pending invocations are not replayed unless a +future canonical OpenClaw contract explicitly makes them replayable. + +Health diagnostics must expose stable state/reason codes, restart count, +runtime version, Gateway connectivity, and active manifest generation without +secrets or raw remote/local exceptions. + +## Rollout and rollback + +The first adopter must keep its existing runtime as the default. Rust selection +requires an explicit rollout gate. A rollback selects the previous runtime only +for a new process/session; it must not transfer in-flight work or reuse an +authenticated IPC session. + +Promotion requires: + +- C# or other incumbent versus Rust registration parity; +- allowed, denied, timeout, cancellation, reconnect, revocation, and crash + parity against a real Gateway; +- startup and steady-state CPU/memory evidence; +- artifact verification and fresh-machine install proof; and +- a practiced rollback with observable readiness transitions. + +Deletion of incumbent Gateway transport occurs only after the rollout window +and rollback evidence succeed. Product-native dispatchers, UX, approval UI, +and capability handlers remain. + +## Audit and privacy + +The Gateway and product audit systems must be able to correlate controller, +node, local supervisor session, manifest generation, invocation, admission +decision, handler outcome, cancellation, and restart without copying secrets or +unbounded payloads. Policy denial, user denial, cancellation, and +approved-but-failed execution are distinct outcomes. + +## Decisions intentionally deferred + +- in-process versus sidecar as the default topology; +- concrete IPC transport and encoding; +- platform secure-storage API; +- binary distribution and update mechanism; +- Windows service versus tray-child lifetime; and +- product-specific command schemas. + +Those decisions may specialize this contract but must not weaken its authority, +authentication, bounds, generation, cancellation, or rollback invariants.