Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Working here as an agent

The active naming decision is [0012](docs/decisions/0012-explicit-data-and-wire-trees.md):
`Wire` is addressless, `WireTree` is the complete Deixis structure, and
`AddressedWire` is the existing carrier access. Preserve this distinction.

Read [COLLABORATION.md](COLLABORATION.md), the ownership decisions
([0001](docs/decisions/0001-shared-wire-contract.md),
[0007](docs/decisions/0007-using-bitwire-never-requires-nightseam.md) and
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

- Accept decision 0012 and prepare breaking 0.3.0 declarations in all eight
languages: `Wire.send(message)` is addressless; `WireTree = DeixisNode<Wire>`
provides complete byte-keyed structure; the former addressed interface becomes
`AddressedWire`. Align the model with Bitstore Data/DataTree, preserve Endpoint
and return-capability semantics under unchanged bitwire/1, add migration
guidance and independent structural reference cases. Runtime adoption and
registry publication are separate delivery steps.

- Document the family component-first layout with two-letter language directories,
command paths and explicit adoption notes for existing source.

Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.21)
project(Bitwire VERSION 0.2.0 LANGUAGES CXX)
project(Bitwire VERSION 0.3.0 LANGUAGES CXX)

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
Expand Down
4 changes: 4 additions & 0 deletions COLLABORATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Collaborating on Bitwire

The active naming decision is [0012](docs/decisions/0012-explicit-data-and-wire-trees.md):
`Wire` is addressless, `WireTree` is the complete Deixis structure, and
`AddressedWire` is the existing carrier access. Preserve this distinction.

## The boundary

Bitwire owns the shared Wire contract, language presentations and independent
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["wire/rs"]
resolver = "3"

[workspace.package]
version = "0.2.0"
version = "0.3.0"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"
Expand Down
100 changes: 56 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,63 @@
[![ci](https://github.com/Bitspark/bitwire/actions/workflows/ci.yml/badge.svg)](https://github.com/Bitspark/bitwire/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

One contract for access through relative paths.

Bitwire defines the interface between a model's generated adapters and the
runtime that carries its interactions. A wire gives access to an origin;
selecting a path or mounting several origins must preserve that same interface.
The contract is shared across languages, generators and runtime implementations.

**Status: [0.2.0 released](https://github.com/Bitspark/bitwire/releases/tag/v0.2.0).**
All eight bindings separate send access from receive attachment and closure.
Nightseam v0.6.0 has adopted the Go/TypeScript contract. The
[current baseline](conformance/current/README.md) checks production composition
locally and over WebSockets, plus scoped lifecycle observations. The test-only
reference and ten historical 0.1.0 cases remain distinct. The
[language matrix](docs/languages.md) records publication and adoption separately;
full lifecycle acceptance review remains open in
[#20](https://github.com/Bitspark/bitwire/issues/20).
No production endpoint runtime is included; implementations live in bitruntime ([decision 0010](docs/decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md)).
Addressless interaction and complete, byte-keyed interaction trees.

Bitwire defines `Wire`, the primitive that sends one message, and
`WireTree = DeixisNode<Wire>`, the full structure that gives primitives
addresses. Bitstore uses the same construction: `Data.read(): Promise<Bytes>`
and `DataTree = DeixisNode<Data>`. [Decision 0012](docs/decisions/0012-explicit-data-and-wire-trees.md)
records the shared contract and the intentional breaking rename.

**Source status: 0.3.0 declarations; publication pending.** The last published
release is [0.2.0](https://github.com/Bitspark/bitwire/releases/tag/v0.2.0).
All eight source bindings distinguish the primitive, full tree and addressed
carrier. Historical evidence remains versioned separately; compiling these
interfaces does not prove runtime structural conformance. The
[language matrix](docs/languages.md) records each delivery boundary.
Production implementations belong to bitruntime under
[decision 0010](docs/decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md).

## The interface

```typescript
interface Wire {
send(message: Message): void;
}

interface DeixisNode<T> {
own(): T;
children(): ReadonlyArray<readonly [Uint8Array, DeixisNode<T>]>;
at(path: readonly Uint8Array[]): DeixisNode<T> | undefined;
decompose(): Readonly<{
own: T;
children: ReadonlyArray<readonly [Uint8Array, DeixisNode<T>]>;
}>;
}

type WireTree = DeixisNode<Wire>;
```

Trees are finite and acyclic, with an own value and a complete child map at
every node. Keys are exact arbitrary bytes. Empty path selects self; missing
selection differs from a Wire that refuses. Decomposition and reconstruction
preserve the complete structure and primitive identities.

For an existing path, the two lanes differ only in their own operation:

```text
send(tree, path, message) = select(tree, path).own().send(message)
read(tree, path) = select(tree, path).own().read()
```

The old addressed surface has an explicit separate name:

```typescript
interface AddressedWire {
send(path: Path, message: Message): void;
}

interface Endpoint extends Wire {
interface Endpoint extends AddressedWire {
receive(receiver: Receiver): () => void;
close(code?: number, reason?: string): void;
}
Expand All @@ -39,37 +70,18 @@ interface Receiver {
}
```

Paths are sequences of opaque strings, relative to the wire's origin. `Message`
carries a request, response, event or cancellation and may hold a local return
capability. An Endpoint accepts one active receiver and returns its detach
function. Path registration and matching belong to a composed dispatcher;
selected receiving views share that owner. The [design decision](docs/decisions/0002-delivery-dispatch-and-ownership.md)
explains why access and ownership are separate capabilities.
The [contract](docs/wire/contract.md) gives these names their shared meaning.

Selection and mounting are governed by laws, not by the choice of carrier:

```text
at(at(w, a), b) ≃ at(w, a ++ b)
at(w, []) ≃ w
```

These are contract laws. Runtime implementations supply `at` and `mount`;
Bitwire's independent cases check their observable behavior. A declared
composite adds an origin, its own behavior at `[]`, beside complete named
children; [decision 0006](docs/decisions/0006-declared-composites-realize-deixis-nodes.md)
relates this to Deixis's node model.
The [composition guide](docs/composition.md) explains what this enables across
consumers and which additional agreements make their integration meaningful.
The [runnable use-case catalogue](examples/README.md) shows it in working Go and
TypeScript programs: a shopping cart retains state and guards when its parent
is rebuilt, and a pending request still reaches its original invocation.
`Endpoint` and `ReturnAddress.wire` retain addressed delivery and existing
string paths. The `bitwire/1` envelope, invocation paths, admission, attachment
and closure semantics are unchanged. An opaque addressed router cannot supply
the complete structure required of a `WireTree`. See the
[contract](docs/wire/contract.md), [migration guide](docs/migration-0.3.md)
and [composition guide](docs/composition.md).

## Who owns what

| Project | Responsibility |
| --- | --- |
| **Bitwire** | Shared access contract, language declarations, protocol and carrier specifications, and independent conformance criteria. |
| **Bitwire** | Primitive and full tree contracts, language declarations, protocol and carrier specifications, and independent conformance criteria. |
| [**bitruntime**](https://github.com/Bitspark/bitruntime) | The Go and TypeScript implementations: operators, carriers, protocol engine, dispatch, live references, tunnels ([decision 0010](docs/decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md)). Until it delivers, Nightseam v0.6.0, now frozen, is the implementation in use. |
| **Bitlink** | Its planned protocol projections and generated adapters. |
| **Bitsystem** | Typed spaces and the kernel/system operations exposed through them. |
Expand Down
17 changes: 10 additions & 7 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Releases

The current release is `0.2.0`; `0.1.0` remains immutable. A release identifies the shared contract revision,
The last published release is `0.2.0`; source declarations target `0.3.0` under
[decision 0012](docs/decisions/0012-explicit-data-and-wire-trees.md). Publication
is pending until the release process and clean registry checks succeed.
`0.1.0` and `0.2.0` remain immutable. A release identifies the shared contract revision,
native bindings and independent cases. The [language matrix](docs/languages.md)
records implementation, package validation, registry publication and consumer
adoption separately. A source tag does not claim an upload to every registry.
Expand All @@ -26,16 +29,16 @@ a `v*` tag push. Publication requires a successful public provenance rehearsal
of that exact commit and version.

1. Run `pnpm install --frozen-lockfile`, `node scripts/check.mjs`,
`node scripts/conformance.mjs`, `node scripts/release-prepare.mjs v0.2.0`,
`node scripts/conformance.mjs`, `node scripts/release-prepare.mjs v0.3.0`,
`node scripts/smoke-packed.mjs` and `node wire/rs/check-package.mjs`.
2. Optionally rehearse the merged commit privately:
`gh workflow run release.yml --ref main -f tag=v0.2.0 -f provenance=false`.
`gh workflow run release.yml --ref main -f tag=v0.3.0 -f provenance=false`.
3. For the public launch, make the repository public and enable immutable
GitHub releases. The organization's release-tag rule already protects `v*`.
Run `gh workflow run release.yml --ref main -f tag=v0.2.0 -f provenance=true`.
Run `gh workflow run release.yml --ref main -f tag=v0.3.0 -f provenance=true`.
Verify the successful run's SHA and stored rehearsal receipt. A source change
requires a new rehearsal; an earlier run does not validate a later commit.
4. Tag that exact merged commit as `v0.2.0` and push the tag once. The workflow
4. Tag that exact merged commit as `v0.3.0` and push the tag once. The workflow
repeats checks, publishes `@bitspark/bitwire` with provenance and the Rust
crate when present, verifies public npm/Go/Rust installation and creates the
GitHub release. Go's module `github.com/Bitspark/bitwire` is distributed by
Expand All @@ -55,13 +58,13 @@ Swift consumes the root SwiftPM package through the public Git URL and tag.
C++ consumes tagged source and the installed CMake package. Haskell consumes the
public Git release using Cabal's `source-repository-package`; see the
[installation instructions](wire/hs/README.md#install-from-git). Run
`node wire/hs/check-git.mjs --tag v0.2.0 --version 0.2.0` after publication to
`node wire/hs/check-git.mjs --tag v0.3.0 --version 0.3.0` after publication to
verify this release independently of the local library (prefer its full immutable
commit SHA in place of the tag). Without arguments the command intentionally
checks historical 0.1.0, which is not acceptance evidence for a new release.
Hackage publication is deferred until uploader approval.
After the immutable release exists, run
`gh workflow run verify-source.yml --ref main -f tag=v0.2.0` to verify SwiftPM,
`gh workflow run verify-source.yml --ref main -f tag=v0.3.0` to verify SwiftPM,
C++ installed-package and Haskell Git consumers against the exact public release
SHA. This workflow verifies only; it neither uploads nor changes a release.
Additional registry
Expand Down
6 changes: 6 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Conformance

**Historical evidence:** the observations and names below refer to the stated
0.1/0.2 addressed contract. In 0.3 that surface is `AddressedWire`; `Wire` is
addressless and `WireTree` is complete byte-keyed structure. These results do
not establish the new structural contract. See
[decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md).

**Status: current released 0.2 composition and scoped lifecycle evidence, a
test-only reference, and a preserved historical 0.1.0 runtime baseline.**

Expand Down
6 changes: 6 additions & 0 deletions conformance/current/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Current released composition baseline

**Historical evidence:** the observations and names below refer to the stated
0.1/0.2 addressed contract. In 0.3 that surface is `AddressedWire`; `Wire` is
addressless and `WireTree` is complete byte-keyed structure. These results do
not establish the new structural contract. See
[decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md).

Run from the repository root:

```sh
Expand Down
6 changes: 6 additions & 0 deletions conformance/declared/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Declared-composite evidence

**Historical evidence:** the observations and names below refer to the stated
0.1/0.2 addressed contract. In 0.3 that surface is `AddressedWire`; `Wire` is
addressless and `WireTree` is complete byte-keyed structure. These results do
not establish the new structural contract. See
[decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md).

[Decision 0006](../../docs/decisions/0006-declared-composites-realize-deixis-nodes.md)
defines the realization: an origin at every node, complete named children, and
construction parts retained by their owner. Run `node scripts/conformance-current.mjs`
Expand Down
6 changes: 6 additions & 0 deletions conformance/production/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Production acceptance for declared composites

**Historical evidence:** the observations and names below refer to the stated
0.1/0.2 addressed contract. In 0.3 that surface is `AddressedWire`; `Wire` is
addressless and `WireTree` is complete byte-keyed structure. These results do
not establish the new structural contract. See
[decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md).

Run `node scripts/conformance-production.mjs` to replay the 39
[decision 0006 cases](../declared/cases.json) through Nightseam's production Go
and TypeScript declared-composition API: `ComposeDeclared` /
Expand Down
6 changes: 6 additions & 0 deletions conformance/reference/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Receive ownership and composition reference

**Historical evidence:** the observations and names below refer to the stated
0.1/0.2 addressed contract. In 0.3 that surface is `AddressedWire`; `Wire` is
addressless and `WireTree` is complete byte-keyed structure. These results do
not establish the new structural contract. See
[decision 0012](https://github.com/Bitspark/bitwire/blob/main/docs/decisions/0012-explicit-data-and-wire-trees.md).

Run `node scripts/composition.mjs` after installing the repository's pinned
dependencies. This compiles and executes independent Go and TypeScript
implementations against the current Bitwire declarations. Both must produce the
Expand Down
10 changes: 5 additions & 5 deletions conformance/reference/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ func (e *endpoint) Close(code wire.Code, reason string) error {
type sender func([]string, wire.Message) error

func (s sender) Send(path []string, message wire.Message) error { return s(path, message) }
func at(w wire.Wire, prefix []string) wire.Wire {
func at(w wire.AddressedWire, prefix []string) wire.AddressedWire {
origin := append([]string{}, prefix...)
return sender(func(path []string, message wire.Message) error {
return w.Send(append(append([]string{}, origin...), path...), message)
})
}
func mount(children map[string]wire.Wire) wire.Wire {
func mount(children map[string]wire.AddressedWire) wire.AddressedWire {
return sender(func(path []string, message wire.Message) error {
if len(path) == 0 || children[path[0]] == nil {
return errors.New("no mounted destination")
Expand All @@ -116,7 +116,7 @@ type route struct {
receiver wire.Receiver
}

// Explicit optional policy, not a Wire requirement: unique prefixes, longest
// Explicit optional policy, not a AddressedWire requirement: unique prefixes, longest
// prefix wins, suffix-relative callback paths. One router owns Receive.
type router struct {
routes map[string]*route
Expand Down Expand Up @@ -192,7 +192,7 @@ type selectedAttachment struct {
type selectedEndpoint struct {
router *router
prefix []string
access wire.Wire
access wire.AddressedWire
attachment *selectedAttachment
ended bool
}
Expand Down Expand Up @@ -325,7 +325,7 @@ func main() {
must(err)
detachForwarder, err := forwardServer.Receive(wire.Receiver{Message: func(path []string, message wire.Message) { must(destinationClient.Send(path, message)) }})
must(err)
composed := at(mount(map[string]wire.Wire{"": at(at(forwardClient, []string{"a"}), []string{"b"})}), []string{""})
composed := at(mount(map[string]wire.AddressedWire{"": at(at(forwardClient, []string{"a"}), []string{"b"})}), []string{""})
if _, grantsOwnership := composed.(wire.Endpoint); grantsOwnership {
panic("selected access grants endpoint ownership")
}
Expand Down
12 changes: 6 additions & 6 deletions conformance/reference/ts/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Endpoint, Message, Path, Receiver, ReturnAddress, Wire } from '../../../wire/ts/src/index.ts';
import type { Endpoint, Message, Path, Receiver, ReturnAddress, AddressedWire } from '../../../wire/ts/src/index.ts';

// Test-only admission scheduler. No code here is a shipped runtime.
class Scheduler {
Expand Down Expand Up @@ -43,12 +43,12 @@ function pair(scheduler: Scheduler): [TestEndpoint, TestEndpoint] {
return [a, b];
}

function at(wire: Wire, prefix: Path): Wire {
function at(wire: AddressedWire, prefix: Path): AddressedWire {
const origin = [...prefix];
return { send: (path, message) => wire.send([...origin, ...path], message) };
}

function mount(children: ReadonlyMap<string, Wire>): Wire {
function mount(children: ReadonlyMap<string, AddressedWire>): AddressedWire {
return { send(path, message) {
if (!path.length || !children.has(path[0]!)) throw new Error('no mounted destination');
children.get(path[0]!)!.send(path.slice(1), message);
Expand All @@ -60,7 +60,7 @@ function prefixOf(prefix: Path, path: Path): boolean {
}

// An explicit optional routing policy: unique prefixes, longest prefix wins,
// and callbacks see suffixes relative to their selected view. Wire does not
// and callbacks see suffixes relative to their selected view. AddressedWire does not
// require this policy. Exactly one router owns the endpoint attachment.
class TestRouter {
private readonly routes = new Map<string, { prefix: Path; receiver: Receiver }>();
Expand Down Expand Up @@ -108,11 +108,11 @@ class TestRouter {
// shares the same root dispatcher; even nested selection creates no root receiver.
class SelectedEndpoint implements Endpoint {
private readonly router: TestRouter;
private readonly access: Wire;
private readonly access: AddressedWire;
private readonly prefix: Path;
private attachment?: { receiver: Receiver; detachRoute: () => void };
private ended = false;
constructor(router: TestRouter, root: Wire, prefix: Path) {
constructor(router: TestRouter, root: AddressedWire, prefix: Path) {
this.router = router; this.prefix = [...prefix]; this.access = at(root, prefix);
}
select(suffix: Path): SelectedEndpoint { return this.router.select([...this.prefix, ...suffix]); }
Expand Down
Loading
Loading