T-20: identify interfaces by exact scope/name pairs; add optional opaque scope_ref metadata and separate shape validation from path-dependent scope compatibility. Preserve call error precedence, strict JSON, validators and transport boundaries. Mechanically migrate consumers and document API/wire compatibility and deferred lifecycle semantics. Validation: cargo fmt --all --check; git diff --check; cargo check --workspace --all-targets; cargo test -p wip-protocol -p wip-http; cargo test --workspace (221 passing tests including doctests); cargo test --workspace --all-targets; nix develop --command cargo clippy --workspace --all-targets -- -D warnings; RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps. All pass. cargo/rustc 1.95.0; development-shell Clippy/rustc 1.97.1.
335 lines
16 KiB
Markdown
335 lines
16 KiB
Markdown
# wip-rs
|
|
|
|
Rust building blocks for the Web Interface Protocol (WIP).
|
|
|
|
The repository is a resolver-3 virtual Cargo workspace whose packages all live
|
|
under `crates/*`. It provides a transport-independent protocol model, the
|
|
standard framework-independent WIP over HTTP v1 binding, a stateful
|
|
presentation-independent client runtime, a Rust interface-authoring facade, a
|
|
request-scoped host runtime, a bounded Linux filesystem projection server, an
|
|
interactive terminal client, and a private public-API conformance consumer. The
|
|
repository root is not itself a package.
|
|
|
|
## Crate graph and responsibilities
|
|
|
|
```text
|
|
Published libraries
|
|
|
|
wip-client
|
|
├── wip-http
|
|
│ └── wip-protocol
|
|
└── wip-protocol
|
|
|
|
wip-host
|
|
├── wip-interface
|
|
│ ├── wip-interface-macros
|
|
│ └── wip-protocol
|
|
└── wip-protocol
|
|
|
|
Workspace-only applications and conformance consumers
|
|
|
|
wip-tui
|
|
├── wip-client
|
|
├── wip-http
|
|
└── wip-protocol
|
|
|
|
wip-fs-server
|
|
├── wip-host
|
|
├── wip-http
|
|
├── wip-interface
|
|
└── wip-protocol
|
|
|
|
wip-public-api-example
|
|
├── wip-host
|
|
├── wip-interface
|
|
└── wip-protocol
|
|
```
|
|
|
|
| Crate | Responsibility | Does not own |
|
|
| --- | --- | --- |
|
|
| `wip-protocol` | Transport-independent descriptors, object/call/result types, logical `Value`s, validation, and stable protocol error categories. | Application DTOs, implementations, persistence, or wire encoding. |
|
|
| `wip-http` | Standard WIP over HTTP v1 endpoint/route derivation, UTF-8 JSON codecs, direct success bodies, and error/status classification. | HTTP I/O, URL scheme or TLS policy, authentication schemes, server frameworks, filesystem projection, or client presentation/cache policy. |
|
|
| `wip-client` | Endpoint/security-context-isolated Known Space, normalized object and interface observations, intent-driven object and descriptor coverage with bounded request deduplication, immutable operation call contexts, stale transitions, and bounded histories. | HTTP I/O, credentials, persistent cache, automatic operation retries, or presentation widgets/events. |
|
|
| `wip-tui` | Keyboard-only interactive Worldspace tree, selected Entry and qualified operation panes, descriptor-driven argument editing, bounded result/diagnostic rendering, terminal lifecycle, and HTTP I/O workers over `wip-client`. | Independent cache/validator semantics, automatic operation retries, Host internals, backend paths, or persistent state. |
|
|
| `wip-interface` | The public `WipType`/`interface` authoring API, Rust-to-protocol codecs, generated descriptors, and typed implementation dispatch. | Object publication, backend lifetime, or transport policy. |
|
|
| `wip-interface-macros` | Procedural-macro expansion used by `wip-interface`. It is not an application dependency. | A supported direct user API. |
|
|
| `wip-host` | Opaque interface registration, explicit object membership, validator checks, interface-qualified dispatch against one host-owned instance, and descriptor-guided argument decoding inside that same resolution boundary. | Backend atomicity, global object lookup, or network serving. |
|
|
| `wip-fs-server` | Linux directory-handle-confined request-time projection, complete depth-bounded observations and bounded file operations, standard `wip-http` dispatch, and a loopback HTTP binary. | A new wire profile, implicit symlink following, non-loopback cleartext, authentication policy, TLS termination, or filesystem-wide preregistration. |
|
|
| `wip-public-api-example` | An independently compiled consumer of the protocol, authoring, and host public crates. Its DTOs and traits are application-owned; its implementation and backend handle remain host-private. | Reusable library API or a production server. |
|
|
|
|
## Published crates
|
|
|
|
The `0.1.0` release consists of these six crates:
|
|
|
|
- `wip-protocol`
|
|
- `wip-interface-macros` — implementation dependency re-exported by `wip-interface`
|
|
- `wip-interface`
|
|
- `wip-host`
|
|
- `wip-http`
|
|
- `wip-client`
|
|
|
|
The filesystem server, terminal client, and public-API example are workspace-only packages and set `publish = false`. All workspace packages require Rust 1.88 or newer and are licensed under either Apache-2.0 or MIT at your option.
|
|
|
|
See [RELEASING.md](RELEASING.md) for the dependency-ordered crates.io procedure and [CHANGELOG.md](CHANGELOG.md) for release contents.
|
|
|
|
The boundary is intentional: protocol types describe what can cross a
|
|
transport; application DTOs opt into that model with `WipType`; a host-private
|
|
implementation owns credentials, handles, and backend state; and `wip-http`
|
|
maps standard routes/envelopes to `CallOperationRequest` and
|
|
`CallOperationResponse` without placing transport-specific types in descriptors
|
|
or application traits.
|
|
|
|
## Structured Interface references
|
|
|
|
The unreleased Protocol/HTTP API uses explicit `InterfaceReference { scope,
|
|
name }` records and optional `scope_ref` observation/precondition metadata.
|
|
Legacy opaque-string wire references are rejected. See [MIGRATION.md](MIGRATION.md)
|
|
for construction examples, validation boundaries and downstream migration status.
|
|
|
|
## End-to-end example
|
|
|
|
[`crates/wip-public-api-example/src/lib.rs`](crates/wip-public-api-example/src/lib.rs)
|
|
is the complete public-API example and
|
|
[`crates/wip-public-api-example/src/main.rs`](crates/wip-public-api-example/src/main.rs)
|
|
runs it. It demonstrates all of the following:
|
|
|
|
1. public application DTOs derived as `WipType` records, enums, and unions,
|
|
including canonical entry paths and schema-opaque JSON;
|
|
2. two public traits declared with `#[wip::interface]`;
|
|
3. a private host struct implementing both traits;
|
|
4. explicit publication of both interfaces as an ordered set on one object;
|
|
5. separate observation of structured interface references, descriptors, and
|
|
descriptor validators;
|
|
6. calls qualified by `target.path`, object validator, interface reference and
|
|
validator, operation, and schema-neutral `Value` arguments;
|
|
7. validated result values decoded back to the typed application result;
|
|
8. independent dispatch of the same `status` operation name on both interfaces;
|
|
9. distinct domain rejection, rollback-confirmed and outcome-unknown
|
|
host/backend failures, `PermissionDenied`, stale validator recovery, interface
|
|
membership, and lookup failure codes; and
|
|
10. canonical failure precedence when several request fields are invalid.
|
|
|
|
Run it inside the development shell:
|
|
|
|
```console
|
|
nix develop
|
|
cargo run --package wip-public-api-example
|
|
```
|
|
|
|
The public-only conformance suite is in
|
|
[`crates/wip-public-api-example/tests/end_to_end.rs`](crates/wip-public-api-example/tests/end_to_end.rs).
|
|
It covers canonical object/descriptor shape, ordered interfaces,
|
|
schema-neutral record/enum/union/entry/JSON values, fully qualified calls, and
|
|
the distinct domain, host, authorization, validator-recovery, and interface
|
|
failure classes. Exhaustive public type construction and matching make legacy
|
|
ObjectId, descriptor `name`, and semantic `Value` variants compile-time
|
|
regressions. Unit tests in the public crates additionally cover every host
|
|
dispatch failure, `InvalidRequest` versus descriptor-bound `InvalidArguments`,
|
|
invalid local responses, validators, same-named operations, and exhaustive
|
|
per-interaction protocol error-code sets.
|
|
|
|
## Interface authoring
|
|
|
|
Applications depend on `wip-interface`, normally imported as `wip`:
|
|
|
|
```rust
|
|
use wip_interface as wip;
|
|
|
|
#[derive(wip::WipType)]
|
|
struct Message {
|
|
/// Message body.
|
|
text: String,
|
|
}
|
|
|
|
#[wip::interface]
|
|
pub trait Messenger {
|
|
/// Sends a message.
|
|
///
|
|
/// # Returns
|
|
/// Whether the application accepted the message.
|
|
fn send(
|
|
&self,
|
|
/// Message to send.
|
|
message: Message,
|
|
) -> wip::OperationResult<bool>;
|
|
}
|
|
```
|
|
|
|
The `interface` attribute and `WipType` derive are re-exported by the facade;
|
|
users do not import `wip-interface-macros` or generated private support symbols.
|
|
The generated `MessengerDefinition` constructs an `InterfaceDescriptor`,
|
|
decodes named arguments, encodes results, validates both boundaries, and
|
|
provides typed dispatch for any `T: Messenger`.
|
|
|
|
### Supported Rust syntax
|
|
|
|
A supported interface is a public, non-generic trait containing methods with:
|
|
|
|
- exactly an immutable `&self` receiver;
|
|
- simple named, owned parameters;
|
|
- an explicit `wip::OperationResult<T>` return;
|
|
- `()` only as the application return when no value is produced; and
|
|
- direct `Option<T>` only for an omittable parameter.
|
|
|
|
`WipType` derives support named-field structs, all-unit enums, and unions whose
|
|
cases may be unit variants or variants containing exactly one unnamed payload.
|
|
The macros reject private,
|
|
generic, inherited, unsafe, auto, or associated-item traits; const, async,
|
|
unsafe, extern, variadic, generic, default-bodied, or non-`&self` methods;
|
|
borrowed/custom-lifetime values; tuple/unit/generic structs; Rust unions;
|
|
named-field/multi-field enum variants; explicit discriminants; nested `Option`; and
|
|
`Option` in returns, collections, entries, or union payloads. These diagnostics
|
|
are compile-fail tested under `crates/wip-interface/tests/ui`.
|
|
|
|
### Rust type mapping
|
|
|
|
| Rust | Protocol |
|
|
| --- | --- |
|
|
| `()` | unit / `Value::Unit` |
|
|
| `bool` | boolean |
|
|
| integer primitives | JavaScript-lossless integer |
|
|
| `f32`, `f64` | finite number |
|
|
| `String` | string |
|
|
| `wip::Bytes` | bytes |
|
|
| `wip::JsonValue` | JSON interpreted over the schema-neutral `Value` tree |
|
|
| `wip::Entry` | canonical absolute Worldspace path encoded as a string |
|
|
| `Vec<T>` | list |
|
|
| named-field `#[derive(wip::WipType)]` struct | named record |
|
|
| all-unit `#[derive(wip::WipType)]` enum | named enum encoded as a string |
|
|
| mixed unit/newtype or all-newtype enum | named union encoded as a discriminator record |
|
|
|
|
`Option<T>` means that a direct DTO field or operation parameter may be
|
|
omitted; it is not protocol null. JSON null remains available inside
|
|
`wip::JsonValue`.
|
|
|
|
#### Migration from the pre-canonical value API
|
|
|
|
The old generic `wip::Entry<K, V>` represented an application key/value pair
|
|
and is no longer available. Use `wip::Entry::new("/canonical/path")` for a
|
|
Worldspace entry path and place any associated application data in a record.
|
|
`Entry` encodes as `Value::String` and validates the canonical absolute path.
|
|
|
|
The semantic `Value::Json`, `Value::Entry`, `Value::Enum`, and `Value::Union`
|
|
variants have also been removed. JSON recursively uses `Unit`, primitive,
|
|
`List`, and `Record` values and rejects `Bytes`; enums use `String`; unions use
|
|
a `Record` containing `$case` and, for payload-bearing cases, `value`. Unit
|
|
operation results are represented explicitly as `Value::Unit`.
|
|
|
|
Rust doc comments become descriptor metadata. For an operation, a `# Returns`
|
|
section documents the return declaration; text before that section documents
|
|
the operation itself.
|
|
|
|
## Host publication and dispatch
|
|
|
|
Applications use `wip-host` to register generated definitions and explicitly
|
|
choose the ordered interface set for each object publication:
|
|
|
|
```rust,ignore
|
|
let mut interfaces = wip_host::InterfaceRegistry::new();
|
|
let messenger = interfaces.register(MessengerDefinition, None)?;
|
|
|
|
let published = wip_host::PublishedObject::new(request_scoped_object)
|
|
.expose(&messenger)?;
|
|
let protocol_object = published.protocol_object();
|
|
```
|
|
|
|
`expose` compiles only when the object implements the generated definition's
|
|
interface trait. Implementing a trait does not expose it automatically, empty
|
|
interface sets are valid, insertion order is preserved, and duplicate
|
|
references are rejected. Registry references are opaque and remain outside the
|
|
descriptor; generated descriptors contain neither those references nor Rust
|
|
trait identity.
|
|
|
|
Dispatch is qualified by interface reference and operation name and targets a
|
|
canonical Worldspace path. An `ObjectProvider` validates that path before
|
|
resolution, maps malformed paths to `InvalidRequest`, then resolves a valid path
|
|
exactly once inside `with_object`; the host checks the supplied object validator,
|
|
current interface membership, interface validator, descriptor operation,
|
|
operation-specific object-validator policy, arguments, and authorization in
|
|
canonical precedence. Path and children remain provider resolution context and
|
|
are never projected by `WipObject`; host-private fields and backend handles are
|
|
also excluded.
|
|
|
|
Application domain failures belong in a derived result union and are returned
|
|
through `Ok`. `wip::HostError` is reserved for runtime/backend failures. A
|
|
structurally malformed call is `InvalidRequest`; only argument values checked
|
|
after the current Interface Descriptor is fixed are `InvalidArguments`. A Host
|
|
must never return a wire `InvalidResult`: Clients keep invalid Object,
|
|
Descriptor, operation-result, and error responses as local `InvalidResponse`
|
|
failures, while unsupported descriptor formats remain local
|
|
`UnsupportedDescriptorFormat` failures.
|
|
|
|
For operation calls, `Internal` and `ResourceLimitExceeded` guarantee that the
|
|
operation did not run or that its effects were rolled back.
|
|
`OperationOutcomeUnknown` is the only protocol code that makes no such
|
|
guarantee, so Clients must not automatically retry it. `wip::HostError`
|
|
therefore requires both a `HostErrorKind` and an `EffectGuarantee`; implementation
|
|
panic, result encoding, and result validation default to an unknown outcome.
|
|
Host-private diagnostics are bounded locally but never copied into protocol
|
|
messages.
|
|
|
|
`ExecutionContext` carries a runtime-neutral cancellation token, monotonic
|
|
deadline, and optional Host-defined resource budget through resolution and
|
|
execution. Cancellation and deadlines are cooperative: the runtime checks them
|
|
immediately before typed invocation, while providers enforce them during backend
|
|
work. A pre-invocation limit failure is `ResourceLimitExceeded`; after invocation
|
|
it is safe to use that code only when non-commit or rollback is confirmed.
|
|
|
|
Providers remain responsible for backend atomicity. `with_object` returns a typed
|
|
`ProviderResult`, and `execute_operation` receives an `OperationAttempt` that
|
|
separates `NotStarted` from `Started`. A transactional provider holds its
|
|
transaction across the callback, commits successful attempts, and may call
|
|
`OperationAttempt::confirm_rollback` only after every operation effect has been
|
|
rolled back. Without that proof, post-invocation failures remain
|
|
`OperationOutcomeUnknown`. Mutable operations must still make validator
|
|
comparison and backend commit atomic and return a validator describing the state
|
|
that becomes committed.
|
|
|
|
## Interactive terminal client
|
|
|
|
[`crates/wip-tui`](crates/wip-tui) provides the `wip-tui` executable. It starts
|
|
from Worldspace `/`, lazily expands materialized tree nodes, displays current
|
|
object/interface/operation metadata, validates all canonical descriptor types in
|
|
a field editor, and requires a separate confirmation action before every call.
|
|
It uses the stateful `wip-client` runtime for request correlation, observed
|
|
validators, stale transitions, and unknown outcomes. One canonical `http` or
|
|
`https` endpoint is used unchanged for request construction, transport, and
|
|
cache partitioning. Scheme, TLS, certificates, authentication, and proxy policy
|
|
belong to the HTTP implementation and deployment. Full startup, key binding,
|
|
layout, validator, and filesystem-operation examples are in the
|
|
[package README](crates/wip-tui/README.md).
|
|
|
|
## Filesystem projection server
|
|
|
|
[`crates/wip-fs-server`](crates/wip-fs-server) contains the practical server
|
|
binary and its full security model, configuration, curl walkthrough, and
|
|
resource/failure semantics. It resolves every object from current filesystem
|
|
state, uses pinned directory-relative handles without following symlinks, omits
|
|
non-UTF-8 backend names, returns complete recursive observations through the
|
|
requested depth or a resource-limit failure, and publishes validator-protected
|
|
replacement only in explicit read-write mode.
|
|
The built-in HTTP listener is loopback-only. Externally visible URL schemes,
|
|
TLS termination, certificates, authentication, and proxy routing are deployment
|
|
concerns rather than separate WIP profiles.
|
|
|
|
## Validation
|
|
|
|
Enter the dev shell and run the acceptance commands exactly:
|
|
|
|
```console
|
|
nix develop
|
|
cargo fmt --all -- --check
|
|
cargo check --workspace --all-targets
|
|
cargo test --workspace --all-targets
|
|
cargo test --workspace --doc
|
|
cargo clippy --workspace --all-targets -- -D warnings
|
|
RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps
|
|
cargo run --package wip-public-api-example
|
|
git diff --check
|
|
```
|
|
|
|
`cargo test --workspace --all-targets` includes the public-only conformance
|
|
consumer, runtime failure tests, and `trybuild` compile-fail suites. The separate
|
|
doctest, rustdoc, executable, and whitespace checks are all required acceptance
|
|
steps.
|