# capOS SDK Crate And Dual Transport Backlog

Detailed decomposition for the front-door `capos` SDK crate: one published
crate whose typed capability clients run **unchanged** against two transports --
the in-process capability ring (an application running inside capOS) and a
remote connection (a host-side RPC client). This extends the
[Crate publication](../roadmap.md) roadmap track with a concrete architecture
and publication ordering, and consumes the remote transport planned in
[Remote Session CapSet Client](remote-session-capset-client.md). `docs/tasks/README.md`
should point here when selecting slices; it should not inline the details.

## Visible Outcome

- A Rust author writes against typed capability clients (`Console`, `Timer`,
  `EntropySource`, `VirtualMemory`, and future caps) once. `cargo add capos`
  builds an in-system `no_std` application that reaches the kernel through the
  ring. `cargo add capos --no-default-features --features remote` builds a host
  program that reaches a capOS instance through the remote session transport,
  using the same client API.
- The crates.io names `capos` (front-door facade) and the `capos-*` family
  (`capos-abi`, `capos-lib`, `capos-config`, `capos-rt`) are published with
  real content, stable versioning, rendered docs, and license/metadata.

## Why This Is High Priority

Two reasons, one architectural and one external:

- **Architecture.** The Cap'n Proto-first design already treats each process as
  a capnp-rpc vat and the per-process ring as that vat's connection to the
  kernel (design principle 5). "In-system app" and "remote client" differ only
  in the transport under the typed clients, so a single SDK with a transport
  seam is the natural shape rather than two parallel client stacks.
- **Namespace contention.** The `capos`/`capos-*` crate names overlap with an
  unrelated capability-OS effort already publishing under the same prefix on
  crates.io. crates.io is a flat, first-come namespace, so publishing the real
  reusable crates early (and reserving the bare `capos` facade) both prevents
  name contention and establishes a dated public-use record. Publish crates
  with real content; do not register empty placeholder names, which the
  crates.io policy can reclaim.

## Transport Seam

The seam is a `Transport` trait (working name) that the typed capability
clients depend on instead of the concrete ring. It must express the existing
ring opcode semantics faithfully rather than collapse them:

- `call(cap, interface_id, method_id, request_bytes) -> CallHandle` -- maps to
  the ring `CALL` SQE.
- completion retrieval (`poll` / `wait`) -- maps to consuming a `CQE` after
  `cap_enter`.
- `release(cap)` -- maps to the local `RELEASE` SQE.
- server-side `recv` / `return_(call, response_bytes, result_caps)` -- maps to
  `RECV` / `RETURN` for endpoint-owning servers. The first SDK slice may scope
  to the client side (`CALL`/`RELEASE` + completions) and add the server side
  when an endpoint-owning userspace service consumes the SDK.

Two implementations:

- `RingTransport` (`no_std`, default `ring` feature): wraps the existing
  capos-rt single-owner ring client and `cap_enter`. This is current behavior
  moved behind the trait, not new behavior.
- `RemoteTransport` (`std`, `remote` feature): connects over the remote session
  transport, authenticates through `SessionManager`/`AuthorityBroker`, holds a
  forwarded `RemoteCapSet`, and dispatches typed calls over the same
  length-prefixed DTO gateway used by `tools/remote-session-client/`.

## Crate Layout

| crate | role | std/no_std |
| --- | --- | --- |
| `capos` | front-door facade: prelude, re-exports runtime + typed clients, transport selection by feature | `no_std` core; `std` only behind `remote` |
| `capos-rt` | ring runtime (`_start`, syscalls, ring client); provides `RingTransport` | `no_std` |
| `capos-abi` | ABI/policy constants | `no_std` |
| `capos-lib` | host-testable pure logic (ELF, CapTable, ring/SQE validation) | `no_std + alloc` |
| `capos-config` | manifest/CUE loader, ring structs | `no_std + alloc` |

Feature flags on `capos`: `ring` (default, in-system, `no_std`), `remote`
(host client, pulls `std` + the remote transport deps). The shared core --
typed capability clients plus capnp encode/decode (`capnp` 0.25 is
`no_std + alloc`) -- must stay `no_std`; `std` is confined to the `remote`
transport. Open decision: whether `RemoteTransport` lives in `capos` behind
`remote` or in a separate `capos-remote` crate the facade re-exports.

## Honesty / Caveats

- The remote transport is **transitional**. The remote session path is
  currently length-prefixed schema-framed DTOs, not standard `capnp-rpc` with
  live object proxies; the rewrite is gated on a reviewed capOS userspace async
  runtime or a sync-friendly Cap'n Proto RPC adapter (see
  [Remote Session CapSet Client](remote-session-capset-client.md), Gate 1 /
  Task 1). So the first `remote` form proxies through the trusted host backend
  boundary; it is not arbitrary remote capability invocation with promise
  pipelining. Do not document it as live guest-wire `capnp-rpc`.
- `capnp-rpc` 0.25 is `std`-only and needs a futures executor, which is why the
  `remote` transport is `std`-only and host-side by necessity. This matches the
  in-system/`no_std` versus host/`std` split rather than fighting it.
- The trust models differ but the client API does not: in-system the kernel
  hands an unforgeable bootstrap CapSet; remote, the client only ever sees caps
  explicitly forwarded into its authenticated session. Keep the API identical
  and the authority boundary explicit.

## Publication Decision

Decision recorded `2026-05-22 23:41 UTC`: the SDK track publishes real crates
only, never empty placeholder packages. This follows the
[Cargo publishing model](https://doc.rust-lang.org/cargo/reference/publishing.html)
where crate names are first-come, first-served, published versions are
permanent, and publishers should fill out license, repository, readme,
description, keyword, and category metadata before upload. It also follows the
[crates.io usage policy](https://rust-lang.github.io/rfcs/3463-crates-io-policy-update.html#terms-of-use)
against packages that exist only to reserve a name for a prolonged period
without genuine functionality or development activity. The exact planned
crates.io names (`capos`, `capos-abi`, `capos-capnp-build`, `capos-config`,
`capos-lib`, and `capos-rt`) were not present in the crates.io API or sparse
index when checked on `2026-05-22 23:39 UTC`, and were re-confirmed unclaimed on
`2026-06-02 16:10 UTC`; the adjacent `capos-bitstruct` crate exists under the unrelated
`cap-os/rust-tools` repository and is the visible namespace contention signal.
`libcapos` and `libcapos-posix` are **not** crates.io crates (they ship as
release artifacts, see item 7), so their names are deliberately left unclaimed
on crates.io -- an accepted residual risk. Re-check the registry immediately
before any real publish.

The publish set and order are:

1. `capos-abi` `0.1.0` -- shared `no_std` ABI and policy constants.
2. `capos-capnp-build` `0.1.0` -- build-time schema generation helper used by
   `capos-config`; it is a real package because `capos-config` cannot publish
   with an unpublished path-only build dependency.
3. `capos-config` `0.1.0` -- manifest/config/ring structs and generated
   schema module built from packaged schema source; depends on `capos-abi` and
   `capos-capnp-build`.
4. `capos-lib` `0.1.0` -- reusable `no_std + alloc` pure logic; depends on
   `capos-abi` and `capos-config`.
5. `capos-rt` `0.1.0` -- in-system `no_std` runtime and ring transport,
   published with the bare facade slice after the transport seam lands.
6. `capos` `0.1.0` -- front-door facade with `ring` as the default
   `no_std` feature and `remote` reserved as a `std` feature until the remote
   transport slice closes.
7. `libcapos` / `libcapos-posix` -- C-substrate distribution. Decision
   `2026-06-02 16:10 UTC`: these ship as **release artifacts only** (prebuilt
   `libcapos.a` / `libcapos_posix.a` plus the `capos/*.h` headers attached to a
   GitHub release/tarball), **not** crates.io crates, because their consumers
   are C programs that link the archive, not Rust crates run through
   `cargo add`, and they build only for the custom `x86_64-unknown-capos`
   target. The artifact build is bundled into the same explicit operator
   publish wave as the Rust crates above (built via `make libcapos` /
   `make libcapos-posix`, verified through the existing C smokes). Their
   crates.io names are intentionally not reserved (accepted residual risk).

Do not reserve `capos-remote` yet. If slice 4a proves the remote backend should
live outside the facade crate, publish `capos-remote` with real host transport
code in that slice.

The MSRV target for Rust crates was `1.85.0` (the Rust 2024 edition floor) until
the first-public-release slice verified the package set against stable. The
verified MSRV is stable Rust **`1.88.0`**: `capos-config` uses `let` chains in
`if`/`while` conditions (`&& let Some(..) = ..`), stabilized in `1.88.0`, so the
set does not build on `1.85.0`. `rust-version = "1.88.0"` is set on all four
published crates. The current repository still builds the OS with the
date-pinned `nightly-2026-04-20` toolchain. `capos-rt` must document the capOS
target toolchain requirement separately if its package cannot be built on stable
for the custom userspace target.

The license gate is satisfied: the repository carries `LICENSE-APACHE`,
`LICENSE-MIT`, and `LICENSING.md`, and per `LICENSING.md` the published SDK
crates use `MIT OR Apache-2.0` (the kernel/system is Apache-2.0-only and is not
in the publish set). The publish metadata uses that SPDX expression in the
`license` field of `capos-abi`, `capos-capnp-build`, `capos-config`,
`capos-lib`, `capos-rt`, and `capos`. Each carries
repository/readme/description/keywords/categories metadata and docs.rs settings.

Generated Cap'n Proto bindings do not ship as a separate published crate in the
first release set. `capos-config` ships the schema source needed to build its
own generated module, and `capos-capnp-build` remains the single no-std patch
helper for that generation path. The publish slice must make the
`capos-config` package self-contained instead of relying on repository-sibling
paths such as `../schema/capos.capnp`. A separate generated-bindings crate is
deferred until an external consumer needs schema bindings without the
manifest/config crate.

## Versioning Policy

The published crates -- `capos`, `capos-rt`, `capos-abi`, `capos-lib`,
`capos-config`, and `capos-capnp-build` -- follow one policy:

- **Pre-1.0 SemVer.** The set starts at `0.1.0`. Per Cargo's SemVer rules a
  `0.y.z` release treats the **minor** field as the breaking-change field: a
  breaking API/ABI change bumps the minor (`0.1 -> 0.2`); a backward-compatible
  addition or fix bumps the patch (`0.1.0 -> 0.1.1`). Treat the whole `0.x`
  series as unstable and do not promise API stability until a `1.0.0` is
  deliberately cut.
- **Schema/ABI contract maps to a breaking bump.** These crates encode the
  capOS wire contract: `capos-config` carries the generated Cap'n Proto
  bindings and the ring `CapSqe`/`CapCqe` structs, `capos-abi` carries the
  policy/quota constants, and the typed clients in `capos-rt`/`capos` encode
  the SQE CALL/RELEASE wire format. A change to `schema/capos.capnp`, the
  generated bindings, the ring wire layout, or a typed-client method's wire
  encoding is a breaking change and bumps the minor field (pre-1.0). The SDK is
  a schema *consumer*; the schema change lands under its owning plan, and this
  set's version bump follows it.
- **Lockstep versioning across the set.** Because the crates share the wire
  contract and depend on each other by exact path/version, publish them at the
  same version and bump them together rather than independently. A consumer
  pinning `capos = "0.1"` then gets a coherent `capos-rt`/`capos-config`/
  `capos-abi` graph. Independent per-crate versioning is deferred until a crate
  has external consumers and a stability story of its own; revisit at `1.0`.
- **MSRV is stable Rust `1.88.0`.** Verified by the slice-2 publish dry-run:
  `capos-config` uses `let` chains (`&& let Some(..) = ..`) stabilized in
  `1.88.0`, so the set does not build on the Rust 2024 edition floor `1.85.0`.
  `rust-version = "1.88.0"` is set on the four published workspace crates. The
  OS itself still builds only on the date-pinned nightly; the MSRV applies to
  the host-target build of the reusable crates, not the kernel.

### Publish Dry-Run Gate

`make sdk-publish-dry-run` is the repeatable, one-command reproduction of the
slice-2 publish verification. It runs, on the host target:

1. A coordinated multi-package `cargo publish --dry-run` over
   `capos-abi -> capos-capnp-build -> capos-config -> capos-lib` in dependency
   order. Cargo packages each crate, unpacks the earlier ones into a temporary
   registry, and verify-builds the later ones against them, so it fails loudly
   if publish metadata, packaging (including the `capos-config` packaged
   `schema/capos.capnp`), or the dependency order regresses. Coordinated
   multi-package dry-run is nightly-only, so this step runs on the repo-pinned
   nightly.
2. An MSRV-floor `cargo +1.88.0 build` of the same set, catching a regression
   that would need a newer toolchain than the recorded MSRV (the nightly
   dry-run alone would not).

The gate is **prep-only**: every dry-run upload aborts, and no real
`cargo publish` runs. Like `dependency-policy-check`, it is a focused target,
not part of `make check`, because the verify-builds are slow.

`capos-rt` and `capos` are part of the publish set but are **not** in this host
gate: they build only on the custom userspace target/code model, so a
host-target `cargo publish --dry-run` verify-build does not apply. The release
gate verifies their capOS-target builds via `make capos-sdk-check` rather than
a host dry-run. The initial publication used the local Cargo API-token path
after the final crates.io name re-check; subsequent releases can use
`.github/workflows/publish-crates.yml` after each crate's trusted publisher is
configured on crates.io for this repository, workflow, `refs/heads/main`, and
the `crates-io-publish` GitHub environment.

## Ordered Slices

The near-term high-priority slices (1-3, 5) do **not** depend on the `capnp-rpc`
transport rewrite and have landed. Slice 4 is split: slice **4a** (a
transitional `RemoteTransport` over the existing host DTO backend) can ship now,
while slice **4b** (the live-proxy `capnp-rpc` upgrade) is gated on the
remote-session async-runtime decision.

1. **Publish-set + reservation decision (`docs-status`, closed
   `2026-05-22 23:41 UTC`).** The decision above pins the publish order, exact
   crate names, MSRV target, feature-flag story, license gate, metadata
   requirements, and generated-binding packaging decision.
2. **First public release of existing layers (`behavior`, prep landed
   `2026-05-23`; `0.1.0` published `2026-06-05`).** Publish metadata
   (description, `MIT OR Apache-2.0` license,
   repository, keywords, categories, `rust-version = 1.88.0`, README, docs.rs
   config) added to `capos-abi`, `capos-capnp-build`, `capos-config`, and
   `capos-lib`. `capos-config` is now self-contained: it ships `schema/capos.capnp`
   (an in-repo symlink to the single repo-root schema, materialized into the
   package archive) and `capos-capnp-build` resolves it from the crate's own
   manifest dir via `generate_packaged_schema_bindings()`. Verified with a
   coordinated `cargo publish --dry-run` of the set in dependency order plus a
   stable-`1.88.0` build and a local docs render. The initial `0.1.0` versions
   were published from `origin/main` on `2026-06-05` through the local Cargo
   API-token path after the final crates.io name re-check. The `capos-config`
   docs.rs build is accommodated by a packaged generated-binding fallback used
   only when `DOCS_RS` is set, so the docs.rs sandbox no longer needs the
   external pinned `capnp` binary. The repository-URL rewrite is **no longer a
   blocker**: decision `2026-06-02 16:10 UTC` keeps
   `repository = "https://github.com/ei-grad/capos"` for the first wave
   (publishing many crates from one repo is standard; the `repository` field can
   be updated later at repo-migration time without republishing). This claims
   the `capos-*` prefix with shipped code.
3. **Reserve bare `capos` + transport seam (`behavior`, closed
   `2026-05-23 23:07 UTC`).** `capos-rt` now defines the `Transport` trait
   (`src/transport.rs`): the client-side seam of `submit_call` /
   `submit_call_with_copy_transfers` / `submit_call_borrowed_wait_forever`
   (`CALL`), `wait` / `try_complete` (completion after `cap_enter`), and
   `release_wait` (local `RELEASE`). `RingTransport` is the existing
   single-owner `RingClient` viewed through the seam (current behavior, not new
   behavior); both `RingClient` and `RuntimeRingClient` implement `Transport`.
   The 189 client-side typed-client methods take `&mut impl Transport`; the
   result-cap-adopting methods stay on the concrete `RuntimeRingClient` because
   generalizing result-cap adoption across transports is later
   server-side/promise work. The new standalone `capos/` facade crate
   re-exports the runtime, typed clients, the `entry_point!` macro, and a
   `prelude` behind the default `ring` feature; the later 4a slice made
   `remote` a host-only feature over the transitional DTO backend. QEMU proof:
   `make run-spawn` boots `demos/timer-smoke`, whose typed-client code now imports
   from `capos` instead of `capos-rt`, and asserts
   `[timer-smoke] Timer now/sleep ok.`. `capos-rt` and `capos` `0.1.0` were
   published with the slice-2 set after the final name re-check and
   `make capos-sdk-check` custom userspace target verification; the
   repository-URL rewrite is no longer a blocker -- see slice 2.
4. **`remote` transport backend, split into 4a/4b.**
   - **4a -- transitional `RemoteTransport` (`behavior`, closed
     `2026-06-06`).** The `capos` facade's `remote` feature now builds on the
     host target without the default `ring` feature, enables `capos-rt/host-test`
     for the shared typed clients, and provides `RemoteTransport` over the
     **existing** host DTO backend boundary used by `tools/remote-session-client`.
     `RemoteTransport` authenticates through the same DTO gateway, obtains
     forwarded caps through `CapSetGet`, assigns synthetic host-side cap ids,
     and proves `SystemInfo.motd` through `SystemInfoClient::motd_wait` over the
     current length-prefixed DTO wire without making the unpublished host tool
     crate part of the published `capos` package graph. Unsupported calls fail
     closed with ring-style transport completions. Negative-path hardening now
     covers wrong-interface and missing-cap denials, released local cap ids,
     remote denied calls, malformed and mismatched DTO responses, and
     disconnects during synchronous DTO calls. This is **not** blocked on the
     async-runtime decision and remains transitional host-backend proxying, not
     guest-wire `capnp-rpc` with promise pipelining.
   - **4b -- live-proxy `capnp-rpc` upgrade (`behavior`, blocked).** Replace the
     DTO wire under `RemoteTransport` with standard `capnp-rpc` framing and live
     object proxies. Gated on a reviewed capOS userspace async runtime or a
     sync-friendly Cap'n Proto RPC adapter, tracked by remote-session Gate 1
     (`docs/backlog/remote-session-capset-client.md`). Do not block 4a on it.
5. **Versioning + publish CI (`harness-hardening`, closed
   `2026-05-24`).** The "Versioning Policy" section above pins pre-1.0 SemVer,
   the schema/ABI-to-breaking-bump mapping, lockstep versioning, and the
   `1.88.0` MSRV. `make sdk-publish-dry-run` reproduces the slice-2 publish
   verification in one command (coordinated multi-package
   `cargo publish --dry-run` over the four host-buildable crates in dependency
   order + an MSRV-floor build); see "Publish Dry-Run Gate". The `capos`
   facade `README.md` documents the working `ring` default and the transitional
   `remote` feature. `.github/workflows/publish-crates.yml` runs the same
   release gates, obtains a short-lived crates.io token through trusted
   publishing only from `refs/heads/main`, skips versions already present on
   crates.io, and publishes the six crates in dependency order when its manual
   `publish` input is enabled with the current explicit user release
   instruction recorded in the dispatch input. Non-main `publish=true`
   dispatches and publish dispatches without that current instruction fail
   before any crates.io token is requested. The initial six-crate `0.1.0`
   release is complete; future releases use the workflow only after explicit
   user authorization for that release and once crates.io trusted publishers are
   configured for the six crates.

## Conflict Surface

- **Owns:** NEW `capos/` facade crate, this backlog file, the roadmap
  "Crate publication" section, `[package.metadata]`/publish metadata on the
  published crates, and any new `docs/proposals/capos-sdk-proposal.md`.
- **Coordinates (do not run blindly in parallel):**
  - `capos-rt/` -- the `Transport`-trait refactor of typed clients. Serial with
    other capos-rt client changes.
  - `tools/remote-session-client/` and the Remote Session CapSet Client plan --
    the `remote` transport reuses that host transport. The `capnp-rpc` rewrite
    is owned there, not here.
- **Must NOT touch:** `schema/capos.capnp` or `tools/generated/` (the SDK is a
  schema consumer, not a producer) and kernel behavior. If a slice needs a
  schema change, it queues on the shared schema serial surface under the owning
  plan, not this one.

## Grounding Files

- `docs/roadmap.md` "Crate publication" track.
- `docs/backlog/remote-session-capset-client.md` (remote transport, gating).
- `docs/proposals/remote-session-capset-client-proposal.md`.
- `docs/proposals/userspace-binaries-proposal.md` (the C substrate layer under the same
  SDK family).
- `docs/capability-model.md`, README "Core Idea" (design principle 5: each
  process is a capnp-rpc vat; the ring is its connection).
