From f7dec7d278f19214442f615ecae29cc85ef0ec75 Mon Sep 17 00:00:00 2001 From: Julian Matschinske Date: Sat, 26 Sep 2026 10:41:51 +0200 Subject: [PATCH 1/2] wire: Separate addressless primitives from full byte-keyed trees. --- AGENTS.md | 4 + CHANGELOG.md | 8 + CMakeLists.txt | 2 +- COLLABORATION.md | 4 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 100 +++++---- RELEASING.md | 17 +- conformance/README.md | 6 + conformance/current/README.md | 6 + conformance/declared/README.md | 6 + conformance/production/README.md | 6 + conformance/reference/README.md | 6 + conformance/reference/go/main.go | 10 +- conformance/reference/ts/main.ts | 12 +- conformance/trees/README.md | 22 ++ conformance/trees/expected.json | 23 +++ conformance/trees/go/main.go | 116 +++++++++++ conformance/trees/ts/main.ts | 50 +++++ docs/README.md | 6 +- docs/composition.md | 191 +++++++----------- docs/decisions/0001-shared-wire-contract.md | 5 + .../0002-delivery-dispatch-and-ownership.md | 5 + .../0003-public-invocation-lifecycle.md | 5 + ...04-return-origins-and-profile-revisions.md | 5 + ...declared-composition-and-subtree-policy.md | 5 + ...eclared-composites-realize-deixis-nodes.md | 5 + .../0012-explicit-data-and-wire-trees.md | 126 ++++++++++++ docs/decisions/README.md | 5 +- docs/delivery.md | 6 + docs/goals/README.md | 13 +- docs/integration.md | 21 +- docs/languages.md | 39 +++- docs/migration-0.3.md | 67 ++++++ docs/wire/carriers.md | 13 +- docs/wire/contract.md | 151 +++++++++----- docs/wire/profile.md | 14 +- examples/README.md | 6 + poster/SEAM.md | 6 + poster/index.html | 11 +- scripts/README.md | 2 + scripts/check.mjs | 1 + scripts/release-lib.mjs | 16 +- scripts/trees.mjs | 16 ++ wire/cpp/README.md | 56 ++--- wire/cpp/include/bitwire/wire.hpp | 59 +++++- wire/cpp/tests/consumer/CMakeLists.txt | 2 +- wire/cpp/tests/consumer/main.cpp | 15 +- wire/cpp/tests/contract.cpp | 72 ++++++- wire/go/README.md | 64 +++--- wire/go/wire.go | 57 +++++- wire/go/wire_test.go | 22 +- wire/hs/README.md | 82 ++++---- wire/hs/bitspark-bitwire.cabal | 8 +- wire/hs/check-git.mjs | 4 +- wire/hs/src/Bitwire.hs | 63 +++++- wire/hs/test/Main.hs | 58 +++++- wire/hs/test/consumer-020/Main.hs | 14 ++ .../test/consumer-020/bitwire-consumer.cabal | 13 ++ wire/hs/test/consumer/Main.hs | 13 +- wire/hs/test/consumer/bitwire-consumer.cabal | 2 +- wire/java/README.md | 61 +++--- wire/java/examples/consumer/pom.xml | 2 +- .../src/main/java/example/Consumer.java | 13 +- wire/java/pom.xml | 4 +- .../dev/bitspark/bitwire/AddressedWire.java | 24 +++ .../java/dev/bitspark/bitwire/DeixisNode.java | 101 +++++++++ .../java/dev/bitspark/bitwire/Endpoint.java | 2 +- .../java/dev/bitspark/bitwire/Message.java | 2 +- .../dev/bitspark/bitwire/ProfileFrame.java | 6 +- .../dev/bitspark/bitwire/ProfileKind.java | 4 +- .../dev/bitspark/bitwire/ReturnAddress.java | 8 +- .../main/java/dev/bitspark/bitwire/Wire.java | 17 +- .../java/dev/bitspark/bitwire/WireTree.java | 12 ++ .../dev/bitspark/bitwire/package-info.java | 11 +- .../dev/bitspark/bitwire/ContractTest.java | 94 ++++++++- wire/py/README.md | 58 +++--- wire/py/pyproject.toml | 2 +- wire/py/src/bitwire/__init__.py | 54 ++++- wire/py/tests/test_consumer.py | 12 +- wire/py/tests/test_tree.py | 85 ++++++++ wire/py/tests/typecheck.py | 22 +- wire/rs/README.md | 60 +++--- wire/rs/examples/consumer.rs | 25 ++- wire/rs/src/lib.rs | 51 ++++- wire/rs/tests/endpoint.rs | 8 +- wire/rs/tests/tree.rs | 119 +++++++++++ wire/swift/NOTICE | 4 +- wire/swift/README.md | 61 +++--- wire/swift/Sources/Bitwire/Wire.swift | 67 +++++- .../Tests/BitwireTests/ContractTests.swift | 18 +- .../BitwireTests/TreeContractTests.swift | 78 +++++++ wire/swift/consumer/Sources/Smoke/main.swift | 13 +- wire/ts/README.md | 66 +++--- wire/ts/package.json | 4 +- wire/ts/src/index.ts | 42 +++- wire/ts/test/contract.ts | 21 +- 97 files changed, 2344 insertions(+), 633 deletions(-) create mode 100644 conformance/trees/README.md create mode 100644 conformance/trees/expected.json create mode 100644 conformance/trees/go/main.go create mode 100644 conformance/trees/ts/main.ts create mode 100644 docs/decisions/0012-explicit-data-and-wire-trees.md create mode 100644 docs/migration-0.3.md create mode 100644 scripts/trees.mjs create mode 100644 wire/hs/test/consumer-020/Main.hs create mode 100644 wire/hs/test/consumer-020/bitwire-consumer.cabal create mode 100644 wire/java/src/main/java/dev/bitspark/bitwire/AddressedWire.java create mode 100644 wire/java/src/main/java/dev/bitspark/bitwire/DeixisNode.java create mode 100644 wire/java/src/main/java/dev/bitspark/bitwire/WireTree.java create mode 100644 wire/py/tests/test_tree.py create mode 100644 wire/rs/tests/tree.rs create mode 100644 wire/swift/Tests/BitwireTests/TreeContractTests.swift diff --git a/AGENTS.md b/AGENTS.md index 4f12764..18e1baa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b6310a4..e7563e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` + 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. diff --git a/CMakeLists.txt b/CMakeLists.txt index dc58fa5..7e27d91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/COLLABORATION.md b/COLLABORATION.md index 8b8984f..7a263b4 100644 --- a/COLLABORATION.md +++ b/COLLABORATION.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 0a8e1ae..d85a533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "bitspark-bitwire" -version = "0.2.0" +version = "0.3.0" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index c0040d5..4f34e7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 6ab6f9a..4c6be47 100644 --- a/README.md +++ b/README.md @@ -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`, the full structure that gives primitives +addresses. Bitstore uses the same construction: `Data.read(): Promise` +and `DataTree = DeixisNode`. [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 { + own(): T; + children(): ReadonlyArray]>; + at(path: readonly Uint8Array[]): DeixisNode | undefined; + decompose(): Readonly<{ + own: T; + children: ReadonlyArray]>; + }>; +} + +type WireTree = DeixisNode; +``` + +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; } @@ -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. | diff --git a/RELEASING.md b/RELEASING.md index 475adca..cefafb8 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -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. @@ -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 @@ -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 diff --git a/conformance/README.md b/conformance/README.md index 83c35f6..99f0926 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -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.** diff --git a/conformance/current/README.md b/conformance/current/README.md index 13428e5..a3dde5f 100644 --- a/conformance/current/README.md +++ b/conformance/current/README.md @@ -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 diff --git a/conformance/declared/README.md b/conformance/declared/README.md index 91e43ef..28aa2a3 100644 --- a/conformance/declared/README.md +++ b/conformance/declared/README.md @@ -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` diff --git a/conformance/production/README.md b/conformance/production/README.md index dcaac10..3ee21e3 100644 --- a/conformance/production/README.md +++ b/conformance/production/README.md @@ -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` / diff --git a/conformance/reference/README.md b/conformance/reference/README.md index 061789b..ba96d37 100644 --- a/conformance/reference/README.md +++ b/conformance/reference/README.md @@ -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 diff --git a/conformance/reference/go/main.go b/conformance/reference/go/main.go index 34d2b8b..e894986 100644 --- a/conformance/reference/go/main.go +++ b/conformance/reference/go/main.go @@ -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") @@ -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 @@ -192,7 +192,7 @@ type selectedAttachment struct { type selectedEndpoint struct { router *router prefix []string - access wire.Wire + access wire.AddressedWire attachment *selectedAttachment ended bool } @@ -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") } diff --git a/conformance/reference/ts/main.ts b/conformance/reference/ts/main.ts index 7a2df37..4fc5614 100644 --- a/conformance/reference/ts/main.ts +++ b/conformance/reference/ts/main.ts @@ -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 { @@ -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): Wire { +function mount(children: ReadonlyMap): 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); @@ -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(); @@ -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]); } diff --git a/conformance/trees/README.md b/conformance/trees/README.md new file mode 100644 index 0000000..ff1d239 --- /dev/null +++ b/conformance/trees/README.md @@ -0,0 +1,22 @@ +# Full-tree reference observations + +These independent cases exercise the 0.3 structural contract from +[decision 0012](../../docs/decisions/0012-explicit-data-and-wire-trees.md): +`WireTree = DeixisNode`, addressless own sending, exact byte keys, +complete children, partial selection and decomposition/reconstruction. + +Run from the repository root: + +```console +node scripts/trees.mjs +``` + +The runner compares Go and TypeScript test-only interpreters with an independent +oracle. The same core check also compiles the native declarations. Reference +construction is deliberately scoped test infrastructure; it is not a shipped +production tree runtime, carrier implementation or proof of downstream adoption. +bitruntime owns production construction and derived operators. + +The older [declared cases](../declared/README.md) exercise retained owner parts +and addressed forwarding. Their pinned release names and observations remain +historical evidence and do not substitute for these structural laws. diff --git a/conformance/trees/expected.json b/conformance/trees/expected.json new file mode 100644 index 0000000..1e5bcc1 --- /dev/null +++ b/conformance/trees/expected.json @@ -0,0 +1,23 @@ +{ + "self": true, + "binary": "binary", + "emptyKey": "empty", + "missing": true, + "nested": "leaf", + "nestedLaw": true, + "children": [ + "", + "61", + "612f62", + "ff" + ], + "slashIsLiteral": true, + "partsIdentity": true, + "rebuildIdentity": true, + "keyCopy": true, + "refusingExists": true, + "refusingSend": true, + "admissions": [ + "binary:event" + ] +} diff --git a/conformance/trees/go/main.go b/conformance/trees/go/main.go new file mode 100644 index 0000000..e785d4d --- /dev/null +++ b/conformance/trees/go/main.go @@ -0,0 +1,116 @@ +// This is a test-only structural interpreter, not a published implementation. +package main + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + wire "github.com/Bitspark/bitwire/wire/go" + "os" + "sort" +) + +type node[T any] struct { + own T + children []wire.Child[T] +} + +func compose[T any](own T, children []wire.Child[T]) *node[T] { + n := &node[T]{own: own} + for _, c := range children { + for _, old := range n.children { + if bytes.Equal(old.Key, c.Key) { + panic("duplicate key") + } + } + n.children = append(n.children, wire.Child[T]{Key: bytes.Clone(c.Key), Tree: c.Tree}) + } + return n +} +func (n *node[T]) Own() T { return n.own } +func (n *node[T]) Children() []wire.Child[T] { + result := make([]wire.Child[T], len(n.children)) + for i, c := range n.children { + result[i] = wire.Child[T]{Key: bytes.Clone(c.Key), Tree: c.Tree} + } + return result +} +func (n *node[T]) At(path wire.TreePath) (wire.DeixisNode[T], bool) { + var current wire.DeixisNode[T] = n + for _, key := range path { + var found wire.DeixisNode[T] + for _, c := range current.Children() { + if bytes.Equal(key, c.Key) { + found = c.Tree + break + } + } + if found == nil { + return nil, false + } + current = found + } + return current, true +} +func (n *node[T]) Decompose() (T, []wire.Child[T]) { return n.own, n.Children() } + +type primitive struct { + name string + admissions *[]string + refuse bool +} + +func (p *primitive) Send(message wire.Message) error { + if p.refuse { + return errors.New("refused") + } + *p.admissions = append(*p.admissions, p.name+":"+string(message.Frame.Kind)) + return nil +} +func main() { + admissions := []string{} + makeNode := func(name string, children []wire.Child[wire.Wire]) *node[wire.Wire] { + return compose[wire.Wire](&primitive{name: name, admissions: &admissions}, children) + } + leaf := makeNode("leaf", nil) + refusing := compose[wire.Wire](&primitive{refuse: true}, nil) + tree := makeNode("root", []wire.Child[wire.Wire]{ + {Key: []byte{}, Tree: makeNode("empty", nil)}, {Key: []byte{255}, Tree: makeNode("binary", nil)}, + {Key: []byte("a/b"), Tree: refusing}, {Key: []byte("a"), Tree: makeNode("branch", []wire.Child[wire.Wire]{{Key: []byte("b"), Tree: leaf}})}, + }) + at := func(path wire.TreePath) wire.WireTree { + n, ok := tree.At(path) + if !ok { + panic("missing") + } + return n + } + label := func(path wire.TreePath) string { return at(path).Own().(*primitive).name } + own, children := tree.Decompose() + rebuilt := compose(own, children) + exposed := tree.Children() + exposed[1].Key[0] = 0 + _ = at(wire.TreePath{{255}}).Own().Send(wire.Message{Frame: wire.ProfileFrame{Version: 1, Kind: wire.ProfileEvent}}) + refusal := at(wire.TreePath{[]byte("a/b")}).Own().Send(wire.Message{}) + self, _ := tree.At(nil) + _, missing := tree.At(wire.TreePath{{0}}) + nested, _ := at(wire.TreePath{[]byte("a")}).At(wire.TreePath{[]byte("b")}) + reconstructed, _ := rebuilt.At(wire.TreePath{[]byte("a"), []byte("b")}) + keys := []string{} + for _, c := range tree.Children() { + keys = append(keys, hex.EncodeToString(c.Key)) + } + sort.Strings(keys) + _, exists := tree.At(wire.TreePath{[]byte("a/b")}) + result := map[string]any{ + "self": self == tree, "binary": label(wire.TreePath{{255}}), "emptyKey": label(wire.TreePath{{}}), "missing": !missing, + "nested": label(wire.TreePath{[]byte("a"), []byte("b")}), "nestedLaw": nested == at(wire.TreePath{[]byte("a"), []byte("b")}), + "children": keys, "slashIsLiteral": at(wire.TreePath{[]byte("a/b")}) != at(wire.TreePath{[]byte("a"), []byte("b")}), + "partsIdentity": own == tree.Own() && children[1].Tree == at(wire.TreePath{{255}}), "rebuildIdentity": reconstructed.Own() == leaf.Own(), + "keyCopy": label(wire.TreePath{{255}}) == "binary", "refusingExists": exists, "refusingSend": refusal != nil, "admissions": admissions, + } + if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { + panic(err) + } +} diff --git a/conformance/trees/ts/main.ts b/conformance/trees/ts/main.ts new file mode 100644 index 0000000..dbe856b --- /dev/null +++ b/conformance/trees/ts/main.ts @@ -0,0 +1,50 @@ +// Test-only structural interpreter. No constructor or sending runtime is shipped. +import type { Child, DeixisNode, Key, Message, Parts, TreePath, Wire, WireTree } from '../../../wire/ts/src/index.ts'; +class Node implements DeixisNode { + #own: T; + #children: Child[]; + constructor(own: T, children: readonly Child[] = []) { + this.#own = own; + this.#children = children.map(([key, child]) => [key.slice(), child]); + if (new Set(this.#children.map(([k]) => hex(k))).size !== children.length) throw new Error('duplicate key'); + } + own(): T { return this.#own; } + children(): Child[] { return this.#children.map(([key, child]) => [key.slice(), child]); } + at(path: TreePath): DeixisNode | undefined { + let current: DeixisNode = this; + for (const key of path) { + const child = current.children().find(([candidate]) => hex(candidate) === hex(key)); + if (!child) return undefined; + current = child[1]; + } + return current; + } + decompose(): Parts { return { own: this.#own, children: this.children() }; } +} +const hex = (key: Key): string => [...key].map(b => b.toString(16).padStart(2, '0')).join(''); +const key = (...bytes: number[]): Key => new Uint8Array(bytes); +const admissions: string[] = []; +const primitive = (name: string): Wire => ({ send(message: Message) { admissions.push(`${name}:${message.frame.kind}`); } }); +const names = new Map(); +const node = (name: string, children: Child[] = []): WireTree => { + const own = primitive(name); names.set(own, name); return new Node(own, children); +}; +const refusing = new Node({ send() { throw new Error('refused'); } }); +const leaf = node('leaf'); +const tree = node('root', [[key(), node('empty')], [key(255), node('binary')], [key(97,47,98), refusing], [key(97), node('branch', [[key(98), leaf]])]]); +const label = (path: TreePath) => names.get(tree.at(path)!.own()); +const parts = tree.decompose(); +const rebuilt = new Node(parts.own, parts.children); +const exposed = tree.children(); exposed[1]![0][0] = 0; +const message: Message = { frame: { version: 1, kind: 'event', data: null } }; +tree.at([key(255)])!.own().send(message); +let refusingSend = false; +try { tree.at([key(97,47,98)])!.own().send(message); } catch { refusingSend = true; } +console.log(JSON.stringify({ + self: tree.at([]) === tree, binary: label([key(255)]), emptyKey: label([key()]), missing: tree.at([key(0)]) === undefined, + nested: label([key(97),key(98)]), nestedLaw: tree.at([key(97)])!.at([key(98)]) === tree.at([key(97),key(98)]), + children: tree.children().map(([k]) => hex(k)).sort(), slashIsLiteral: tree.at([key(97,47,98)]) !== tree.at([key(97),key(98)]), + partsIdentity: parts.own === tree.own() && parts.children[1]![1] === tree.at([key(255)]), + rebuildIdentity: rebuilt.at([key(97),key(98)])!.own() === leaf.own(), keyCopy: label([key(255)]) === 'binary', + refusingExists: tree.at([key(97,47,98)]) !== undefined, refusingSend, admissions, +})); diff --git a/docs/README.md b/docs/README.md index 678f437..c47239d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,14 @@ # Documentation -Bitwire is the shared contract for access through relative paths. These pages +Bitwire specifies addressless Wire primitives, complete byte-keyed WireTree +structures and explicitly named AddressedWire carrier access. These pages separate the shared boundary, executable checks and consumer adoption. | Read this | To understand | | --- | --- | +| [0.3 migration](migration-0.3.md) | Primitive, tree and addressed-carrier renames and preserved lifecycle obligations. | | [Goals](goals/README.md) | What the contract is meant to make possible. | -| [Composition](composition.md) | How access composes within Wire and what consumers must agree on. | +| [Composition](composition.md) | Full tree composition, addressed access and their separate guarantees. | | [Runnable examples](../examples/README.md) | Service trees, remounting, retained cart state and guards, cancellation and cross-language calls. | | [Wire contract](wire/contract.md) | The surface, paths, receiver and composition laws. | | [Message profile](wire/profile.md) | The existing Nightseam profile and the limits of interface compatibility. | diff --git a/docs/composition.md b/docs/composition.md index dbe69eb..3ba3682 100644 --- a/docs/composition.md +++ b/docs/composition.md @@ -1,133 +1,82 @@ -# What composition through Wire means +# Composition of full trees and addressed access -For programs you can run, start with the [use-case catalogue](../examples/README.md). -It connects these laws to a documented bookshop model and complete Go and -TypeScript consumers. +The full structural contract is `WireTree = DeixisNode`, exactly parallel +to Bitstore's `DataTree = DeixisNode`. The primitives differ only in their +operation: `Wire.send(message)` admits interaction; `Data.read()` retrieves +fixed bytes. [Decision 0012](decisions/0012-explicit-data-and-wire-trees.md) +replaces the earlier practice of calling addressed access the full structure. -The useful property is that composing access produces access that can be -composed again. A component can participate in a larger assembly without knowing -where the assembly placed it. The common contract states what must survive that -change of surroundings; conformance checks observe whether an implementation -keeps those promises. +## Full tree composition -## Within Wire +Each node contains its own primitive and a complete map of byte-keyed child +trees. Keys are arbitrary exact bytes; empty and non-UTF-8 keys remain distinct. +A tree is finite and acyclic. The same child instance may be shared under more +than one name. Construction rejects duplicate byte keys and protects the +structure from mutation through caller-owned collections. -Suppose storage, a worker and a catalog each expose Wire access. An assembly can -mount them under three names: +The common methods are `own`, `children`, `at` and `decompose`. Runtime +construction preserves primitive/child identities and supports both directions: ```text -storage ── ["storage"] ──┐ -worker ── ["worker"] ──┼── system Wire -catalog ── ["catalog"] ──┘ +decompose(compose(own, children)) ≅ { own, children } +compose(decompose(tree)) ≅ tree +select(tree, []) ≅ tree +select(select(tree, a), b) ≅ select(tree, a ++ b) ``` -Selecting `["worker"]` from the system yields access to the worker. That access -can itself be mounted inside another system or selected further. The worker -receives paths relative to its own origin, so changing the assembly's outer -prefix does not require changing the worker's operation names. - -For valid paths, equivalent dispatch policies and the required attachments: - -```text -at(w, []) ≃ w -at(at(w, a), b) ≃ at(w, a ++ b) -at(mount({"worker": w}), ["worker"]) ≃ w -``` - -The equivalence concerns routing and message observations, including the -documented admission/refusal behavior. It does not make the objects identical -or transfer closure ownership. Closing the mount leaves its borrowed worker -usable. Wire itself grants only send access; receiving views require a shared -dispatcher that owns the endpoint's one attachment. Matching and overlap policy -are explicit above that attachment. - -Pure selection, mounting and forwarding preserve the original local return -capability and established context. They do not create a new carrier or inspect -and translate hidden references in payloads. A physical hop is a different -boundary: its profile maps correlation and establishes receiving context, and -its value adapters manage references crossing scopes. - -## A composite's own behavior - -A mount has nothing of its own at `[]`. A declared composite can: its value is -an **origin** that handles messages sent to the composite itself, beside its -named children. This is Deixis's `Node[T]` with an origin at every node: +The selection equation applies when the first selection exists. Missing paths +return no node; they do not invoke an ancestor's own primitive as fallback. +For existing paths the operation is entirely determined by structure: ```text -system { origin: describeSystem } - ├─ counter { origin: increment } - └─ report (an existing endpoint, used whole) +send(tree, path, message) = select(tree, path).own().send(message) +read(tree, path) = select(tree, path).own().read() ``` -Sending to `[]` reaches `describeSystem`; sending to `["counter"]` reaches -`increment`; sending to `["report", "daily"]` reaches the report endpoint at -`["daily"]`. Selecting `["counter"]` gives exactly the counter's access, with -nothing of the system in between. A missing name refuses and never falls back to -`describeSystem`. A mount is the same construction with a refusing origin. - -The assembler that built the system keeps its parts: the origin and the complete -child map. Rebuilding from those parts keeps the same counter, the same origin -and any child shared under two names, so their state continues. Rebuilding from -the children alone loses `describeSystem`. Replacing the counter with a fresh -copy resets it and breaks sharing. Those parts are held by the assembler, not -exposed through the Wire it hands out; a caller with send access learns nothing -about the structure it reaches. - -Interception, such as a budget over the whole system, is access composed around -the system: `guard(budget, system)`. It is not part of any node's value. -Selecting through the guard checks the budget once per send; rebuilding the -system inside the same guard keeps the budget's state. The -[decision](decisions/0006-declared-composites-realize-deixis-nodes.md) states -the key mapping, laws, equivalence and ownership. The -[evidence](../conformance/declared/README.md) separates the test-only interpreter -from released runtime behavior and its recorded gaps. - -## Across consumers - -The common boundary allows an implementation authored in one repository to be -used by a caller, generated adapter or assembly authored in another. Each keeps -its own responsibility: - -| Layer | What it contributes | -| --- | --- | -| Bitwire | Addressed access, preservation and ownership laws, native declarations, the protocol and carrier specifications, and independent expectations. | -| Runtime, such as bitruntime | Operators, transport, correlation, the invocation lifecycle, dispatch and reference machinery ([decision 0010](decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md)). Until [bitruntime](https://github.com/Bitspark/bitruntime) delivers, frozen Nightseam v0.6.0 supplies them. | -| Generated adapter | The declared methods, events and value conversions exposed to application code. | -| Domain consumer | What operations mean, how components attach and which actions are authorized. | - -Using the same Wire signature alone is insufficient. Participants must agree on -the operation contract and identity, value encoding, profile revision, reference -scope and authority. A storage API is not made into a worker API by mounting it -at `["worker"]`. Different meanings need an explicit domain adapter. Likewise, -the same profile name does not negotiate compatible pre-1.0 releases. - -## Why lifetime is part of composition - -A request admitted to worker A must retain its captured route even if that -registration is subsequently replaced by worker B. Its delayed response and -cancellation belong to A's invocation. A caller timeout does not prove that A's -body finished, and a completed call does not automatically release a live -capability returned by that call. Each boundary must retain enough state to -honor these obligations and reclaim it when they actually end. - -A callable return capability is itself Wire access at an origin. Its profile -may define lifecycle operations there, allowing independently implemented -participants to coordinate through public messages. Bitwire does not prescribe -Nightseam's vocabulary to every Wire or make that vocabulary an authority proof. - -## What this enables, and how we establish it - -A component can be tested locally, mounted into a larger assembly and accessed -over a compatible remote carrier while keeping the same domain-facing -interface. A call can return another callable component; the caller can continue -using or composing that access under the live-reference profile. A system can -therefore discover a capability, invoke it, receive a child space and continue -operating on that space through the same access foundation. - -That is an architectural possibility with explicit conditions, not proof that -every consumer already implements it. The [current baseline](../conformance/current/README.md) -checks actual production composition and scoped lifecycle observations. The -remaining generated/live/authority and downstream attachment requirements stay -visible there. The next domain-level demonstration must compare a real declared -component locally and remotely, including returned child access, under the -consumer's actual attachment and policy rules. +A subtree can be inserted beneath another key without changing the primitive. +A retained primitive's state survives decomposition and reconstruction because +reconstruction preserves that capability, rather than copying its hidden state. +Complete parts also expose all the capabilities they hold. Delegate restricted +access separately if the caller must not have the complete structure. + +## Addressed access and carriers + +`AddressedWire.send(path, message)` preserves the former addressed interface. +It can be derived from a tree with an explicit key mapping, but an arbitrary +AddressedWire is not itself a tree. It may hide routes, dispatch dynamically or +route through cycles; none can satisfy the complete finite structural contract +by changing a type annotation. + +Binding a prefix of addressed access is therefore a separate operation from +structural `at`. A prefix can be bound without knowing whether a route exists; +structural selection answers whether a node is present. Similarly, refusal to +send does not prove that a child is absent. + +Existing carrier paths contain Unicode-scalar strings. Tree keys contain any +bytes. The unchanged `bitwire/1` carrier supports the exact UTF-8 image only; +an adapter must reject other keys or specify an additional encoding/profile. +No implicit normalization or lossy conversion is permitted. + +`Endpoint` extends AddressedWire with one receive attachment and closure. +Selected receiving views share a dispatcher's attachment. Pure routing keeps +local return identity and received context. A physical hop has additional +profile-defined correlation, context and live-reference obligations. +`ReturnAddress.wire` remains addressed because its profile has response and +invocation-lifecycle paths. Replacing it with a primitive Wire would erase +those operations and requires a separate explicit lifecycle design. + +## Ownership and evidence + +Bitwire owns the declarations, laws, protocol and independent expectations. +bitruntime owns production tree construction, derived operators, carriers and +profile execution. Consumers own application meaning, interpretation identity +and authority policy. Sharing a tree interface does not translate incompatible +payloads or make an access handle proof of authorization. + +The [tree reference cases](../conformance/trees/README.md) exercise the new +structural contract using test-only interpreters. The +[declared composition evidence](../conformance/declared/README.md) and +[runnable example catalogue](../examples/README.md) retain the older addressed +interpretation and its versioned runtime observations. Historical green cases +are not full WireTree runtime adoption. The [language matrix](languages.md) +records source declarations, publication and consumer adoption separately. diff --git a/docs/decisions/0001-shared-wire-contract.md b/docs/decisions/0001-shared-wire-contract.md index d98a8b2..6096c37 100644 --- a/docs/decisions/0001-shared-wire-contract.md +++ b/docs/decisions/0001-shared-wire-contract.md @@ -1,5 +1,10 @@ # 0001: The shared Wire contract has an independent home +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Partly superseded** by [decision 0007](0007-using-bitwire-never-requires-nightseam.md) on 2026-09-25. Using Bitwire must never require Nightseam, so the operators, carriers and network profile left in Nightseam below move to Bitwire. The record diff --git a/docs/decisions/0002-delivery-dispatch-and-ownership.md b/docs/decisions/0002-delivery-dispatch-and-ownership.md index 0b657ee..1a23aa1 100644 --- a/docs/decisions/0002-delivery-dispatch-and-ownership.md +++ b/docs/decisions/0002-delivery-dispatch-and-ownership.md @@ -1,5 +1,10 @@ # 0002: Separate addressed delivery, dispatch and endpoint ownership +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Status:** accepted, 2026-09-21. **Contract version:** 0.2.0, breaking 0.1.0. [Decision 0003](0003-public-invocation-lifecycle.md) sharpens the required public diff --git a/docs/decisions/0003-public-invocation-lifecycle.md b/docs/decisions/0003-public-invocation-lifecycle.md index 836d1f3..a0ea812 100644 --- a/docs/decisions/0003-public-invocation-lifecycle.md +++ b/docs/decisions/0003-public-invocation-lifecycle.md @@ -1,5 +1,10 @@ # 0003: Invocation-aware composition has a public lifecycle contract +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Status:** accepted design requirements, 2026-09-21. Exact profile APIs and implementation evidence remain required in [Nightseam #439](https://github.com/Bitspark/nightseam/issues/439) and diff --git a/docs/decisions/0004-return-origins-and-profile-revisions.md b/docs/decisions/0004-return-origins-and-profile-revisions.md index 1fecaa8..8a7deb5 100644 --- a/docs/decisions/0004-return-origins-and-profile-revisions.md +++ b/docs/decisions/0004-return-origins-and-profile-revisions.md @@ -1,5 +1,10 @@ # 0004: Return origins and profile revisions make composition explicit +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Status:** accepted contract clarification, 2026-09-22. Native declarations are unchanged. Executable evidence and outstanding acceptance are recorded in the [current baseline](../../conformance/current/README.md). diff --git a/docs/decisions/0005-declared-composition-and-subtree-policy.md b/docs/decisions/0005-declared-composition-and-subtree-policy.md index 4949a3c..5be2e04 100644 --- a/docs/decisions/0005-declared-composition-and-subtree-policy.md +++ b/docs/decisions/0005-declared-composition-and-subtree-policy.md @@ -1,5 +1,10 @@ # 0005: Declared composition retains own behavior and subtree policy +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Superseded** by [decision 0006](0006-declared-composites-realize-deixis-nodes.md) on 2026-09-23, before any release. A node's value is now its origin alone, children keep their complete Wire access, and admission policy becomes an diff --git a/docs/decisions/0006-declared-composites-realize-deixis-nodes.md b/docs/decisions/0006-declared-composites-realize-deixis-nodes.md index e28541b..317670f 100644 --- a/docs/decisions/0006-declared-composites-realize-deixis-nodes.md +++ b/docs/decisions/0006-declared-composites-realize-deixis-nodes.md @@ -1,5 +1,10 @@ # 0006: Declared composites realize Deixis nodes over origin behavior +**Native names and structural claim superseded:** [decision 0012](0012-explicit-data-and-wire-trees.md) +reclaims `Wire` for addressless sending and names the full structure `WireTree`. +The addressed interface below is now `AddressedWire`; its carrier and lifecycle +obligations remain. This record preserves the historical names. + **Status:** accepted, 2026-09-23, following the user's direction for [#29](https://github.com/Bitspark/bitwire/issues/29): the own value is the behavior at the composite's origin, and named children keep their complete Wire diff --git a/docs/decisions/0012-explicit-data-and-wire-trees.md b/docs/decisions/0012-explicit-data-and-wire-trees.md new file mode 100644 index 0000000..2173dae --- /dev/null +++ b/docs/decisions/0012-explicit-data-and-wire-trees.md @@ -0,0 +1,126 @@ +# 0012: Data and Wire are primitives; their trees share the Deixis contract + +**Status:** accepted, 2026-09-26, on the user's explicit direction to make the +names and structure symmetric, accepting the breaking rename before wider +adoption. Native declarations target **0.3.0**. Runtime adoption, behavioral +acceptance and registry publication are separate delivery steps. + +This supersedes the addressed meaning of `Wire` in decisions 0001–0006 and the +incomplete structural claim in decision 0006. It also supersedes proposed +decision 0011, `Bitwire = Deixis[End]`, in +[draft PR #49](https://github.com/Bitspark/bitwire/pull/49); that draft was not +accepted or merged. Ownership under decisions 0007 and 0010 is unchanged. + +## Decision + +The family uses the following names and equations: + +```typescript +interface Data { + read(): Promise; +} + +interface Wire { + send(message: Message): void; +} + +type DataTree = DeixisNode; +type WireTree = DeixisNode; +``` + +Bitstore owns `Data` and `DataTree`. Bitwire owns `Wire` and `WireTree`. `Bytes` +remains the raw value representation, not a reader or a tree. `End`, +`ByteSource`, and addressed `Wire` are not the names of the new primitives. +Language-native error and asynchronous representations may differ without +changing the meaning. A successful `Data.read` yields that primitive's fixed +content. A successful `Wire.send` admits a message; it does not await an +application result. + +Both trees expose the same full structural contract: + +```typescript +type Key = Uint8Array; +type TreePath = readonly Key[]; +type Children = ReadonlyArray]>; + +interface DeixisNode { + own(): T; + children(): Children; + at(path: TreePath): DeixisNode | undefined; + decompose(): Readonly<{ own: T; children: Children }>; +} +``` + +This is the Deixis model `T × FiniteMap[Bytes, DeixisNode]`, not merely a +path-prefix wrapper. Every node has an own value and a complete child map. +Keys are arbitrary exact bytes, including empty and non-UTF-8 keys. Trees are +finite and acyclic. Empty path selects self; an absent edge returns no node. +Construction rejects duplicate byte keys and preserves the identity of own +capabilities and retained children. Implementations prevent mutation of keys +or child maps from changing the represented structure. + +Decomposition is complete. Recomposition from it preserves structure and own +capability identity; decomposition after construction recovers the same own +value and exact child map. Shared child instances may be preserved under +multiple names, but a child cannot introduce a structural cycle. + +## Derived operations + +For an existing path, the only dispatch rule is: + +```text +read(tree, path) = select(tree, path).own().read() +send(tree, path, message) = select(tree, path).own().send(message) + +select(tree, []) = tree +select(select(tree, a), b) = select(tree, a ++ b) when selection succeeds +``` + +Missing selection is distinct from selecting a primitive that refuses. No own +value serves as fallback for a missing descendant. The implementations of tree +construction and derived sending belong in bitruntime; this repository owns +the declarations, laws and independent criteria. A data capability itself is +not serialized bytes: materialization reads it to obtain a byte-valued tree. + +## Existing addressed carriers + +The old `Wire.send(path, message)` is explicitly named `AddressedWire`. It is +an addressed carrier/access surface, not a `WireTree`. `Endpoint` extends +`AddressedWire`, and `ReturnAddress.wire` remains an `AddressedWire`. They +retain the existing string `Path`, admission, received-context, correlation, +receive attachment and closure contracts of `bitwire/1`. + +This distinction is necessary: an arbitrary addressed router can hide children, +have routes that vary on use, or route through cycles. It does not provide a +complete finite tree. Giving it a new type name cannot manufacture structural +guarantees. Selecting and binding a prefix of such a router likewise does not +become structural `WireTree.at`. + +The unchanged carrier uses Unicode-scalar string segments. A bridge from tree +keys to that carrier accepts only exact UTF-8 keys or specifies an additional +encoding/profile explicitly; it never silently normalizes, decodes arbitrary +bytes lossily, or claims byte-path support in the unchanged protocol. + +A return capability's existing profile-defined relative path space includes +response and lifecycle operations. Replacing that `AddressedWire` with an +addressless `Wire` would erase those operations. Moving return delivery to a +different primitive requires its own explicit lifecycle mapping and evidence; +this rename does not make that change. + +## Migration and delivery + +1. Update all eight native declarations and their independent package consumers + together. Existing addressed implementations use `AddressedWire`; new + primitives use `Wire`, and full structures use `WireTree`. +2. Keep immutable release and historical conformance evidence labeled with the + version it tested. Old evidence is not structural conformance evidence. +3. Migrate bitruntime's constructors, operators and carriers to the explicit + distinction, then update its consumers against a verified dependency revision. +4. Publish only through the established release process. Source declarations, + runtime behavior and registry availability are recorded independently in the + [language matrix](../languages.md). + +There is no compatibility alias that keeps the old addressed meaning under +`Wire`; compilation failures identify call sites that must choose the correct +capability. The spelling is intentionally changed now, before more consumers +build against the ambiguous contract. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 35cf048..89f43e6 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -6,11 +6,12 @@ choice. An accepted repository boundary does not imply a completed implementatio | Decision | Status | | --- | --- | -| [0010: Bitwire holds the contract, and bitruntime implements it](0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md) | Accepted; supersedes where 0007 put the implementations and the part of 0009 that says Bitwire provides carriers. bitruntime, bittype and bittheory were created with charters on 2026-09-26 and hold no code yet. | +| [0012: Data and Wire are primitives; their trees share the Deixis contract](0012-explicit-data-and-wire-trees.md) | Accepted, 2026-09-26. Breaking 0.3 declarations in eight languages; supersedes the addressed Wire name, incomplete structural claim and unmerged draft 0011/PR49. Runtime adoption and publication are separate. | +| [0010: Bitwire holds the contract, and bitruntime implements it](0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md) | Accepted; supersedes where 0007 put the implementations and the part of 0009 that says Bitwire provides carriers. bitruntime, bittype and bittheory opened with charters on 2026-09-26; subsequent delivery is tracked in each repository. | | [0009: Which carriers Bitwire provides, and how byte streams carry frames](0009-carriers-bitwire-provides-and-byte-stream-framing.md) | Accepted; carrier groups and the `bitwire-stream/1` framing. The [carrier specification](../wire/carriers.md) is a draft; nothing is implemented. Implemented by bitruntime under 0010. | | [0008: A protocol revision has its own identity](0008-a-protocol-revision-has-its-own-identity.md) | Accepted; supersedes 0004's compatibility section. Takes effect when 0007 publishes the protocol. | | [0007: Using Bitwire never requires Nightseam](0007-using-bitwire-never-requires-nightseam.md) | Accepted and amended the same day. Its rule and independence check stand; where it put the implementations is superseded by 0010, which moves them to bitruntime. | -| [0006: Declared composites realize Deixis nodes over origin behavior](0006-declared-composites-realize-deixis-nodes.md) | Accepted; Go/TypeScript reference and unreleased production construction pass all cases. Released Nightseam child-only specialization retains recorded gaps. | +| [0006: Declared composites realize Deixis nodes over origin behavior](0006-declared-composites-realize-deixis-nodes.md) | Structural claim superseded by 0012: complete structure is WireTree, separate from AddressedWire. Historical reference and unreleased production construction pass their cases; released Nightseam retains recorded gaps. | | [0005: Declared composition retains own behavior and subtree policy](0005-declared-composition-and-subtree-policy.md) | Superseded by 0006 before release; its policy is re-expressed as a guard around access. | | [0004: Return origins and profile revisions make composition explicit](0004-return-origins-and-profile-revisions.md) | Accepted clarification; current Go/TypeScript baseline and remaining acceptance recorded. Compatibility section superseded by 0008. | | [0003: Invocation-aware composition has a public lifecycle contract](0003-public-invocation-lifecycle.md) | Accepted requirements; Nightseam implementation landed, scoped evidence and remaining review recorded. The state machine moves to Bitwire under 0007. | diff --git a/docs/delivery.md b/docs/delivery.md index 8ffa9d8..fda8374 100644 --- a/docs/delivery.md +++ b/docs/delivery.md @@ -1,5 +1,11 @@ # First delivery +**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). + ## Independence from Nightseam [Decision 0007](decisions/0007-using-bitwire-never-requires-nightseam.md) made diff --git a/docs/goals/README.md b/docs/goals/README.md index 04c4e40..a73b815 100644 --- a/docs/goals/README.md +++ b/docs/goals/README.md @@ -9,9 +9,16 @@ meaning at that boundary. ## Composition preserves access -Selecting an origin, mounting origins and forwarding access retain the same Wire -interface. Their laws hold independently of the physical carrier. Introducing a -wire boundary preserves the behavior of the model presented through it. +`Wire` is addressless sending; `WireTree = DeixisNode` gives it complete +structure. The same `own`, `children`, partial `at` and `decompose` contract +applies to Bitstore's `DataTree = DeixisNode`. Exact byte keys and primitive +identity survive decomposition and reconstruction. Selecting a subtree keeps +that same structural interface. + +`AddressedWire` is the separate carrier/access interface. Prefix selection and +forwarding preserve its addressed behavior, but an opaque router is not a full +tree. [Decision 0012](../decisions/0012-explicit-data-and-wire-trees.md) requires +this distinction throughout the family. Wire composition and type composition are distinct obligations. A generic adapter's construction must also preserve substitution, including arguments and diff --git a/docs/integration.md b/docs/integration.md index 3071f1f..0610b2f 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -1,6 +1,21 @@ # Integration and current status -## Current state +## Current source contract + +Decision 0012 names the primitive `Wire`, its complete structure +`WireTree = DeixisNode`, and existing addressed access `AddressedWire`. +Bitstore follows the same structure with `Data.read()` and `DataTree`. +All eight source bindings target 0.3.0; publication and runtime adoption are +separate. Follow the [migration guide](migration-0.3.md) and +[language matrix](languages.md) rather than inferring adoption from old tests. + +## Released baseline (0.2 names) + +`Wire` below is the historical addressed interface, now named `AddressedWire`. +Its return/lifecycle path space remains intact. Earlier declared composition +observations do not establish the complete byte-keyed WireTree interface; +[decision 0012](decisions/0012-explicit-data-and-wire-trees.md) supersedes that +incomplete structural interpretation. Bitwire **0.2.0** is released in all eight native presentations. Wire provides send access; Endpoint adds one receive attachment and closure. Dispatchers own @@ -77,6 +92,10 @@ is not proof of authority. ## Next steps +Adopt the explicit primitive/tree/carrier distinction in bitruntime, validate +structural laws and preserve return/lifecycle semantics, then migrate consumers +against verified dependency revisions. No network encoding change is implicit. + 1. Review and link the remaining exact-release runtime/generated acceptance against #20; do not infer completion from a passing subset. 2. Use a real downstream scenario that discovers a capability, invokes it, diff --git a/docs/languages.md b/docs/languages.md index fd79049..584fe6d 100644 --- a/docs/languages.md +++ b/docs/languages.md @@ -22,7 +22,29 @@ a repository-root manifest so a Git dependency can resolve the public package. C++ initially uses tagged source and an installable CMake package; registry recipes can be added without changing the access contract. -## Current delivery +## Source revision 0.3.0 + +[Decision 0012](decisions/0012-explicit-data-and-wire-trees.md) is accepted. +All eight source bindings distinguish `Wire.send(message)`, the complete +`WireTree = DeixisNode`, and `AddressedWire.send(path, message)`. +Each tree supplies own, complete byte-keyed children, partial selection and +decomposition, matching Bitstore's DataTree. Endpoint and return-address +surfaces remain addressed. See the [migration guide](migration-0.3.md). + +| Delivery boundary | Status | +| --- | --- | +| Shared contract and eight native source declarations | Updated for 0.3.0. | +| Source/package checks | Run by core and native CI jobs; compilation is not runtime conformance. | +| Independent full-tree observations | Go/TypeScript test-only interpreters; see [tree cases](../conformance/trees/README.md). | +| Production full-tree construction and derived operators | Owned and delivered separately by bitruntime. | +| 0.3.0 registry publication and clean installed consumers | Pending; changing version fields is not publication. | +| Downstream adoption | Verify separately against migrated runtime and dependency versions. | + +## Published 0.2.0 delivery (historical names) + +This evidence preserves 0.2.0's names: its addressed type was `Wire`. In 0.3 +that surface is `AddressedWire`. No 0.2 artifact exports the new primitive or +full tree interface. **0.2.0 is released**, a breaking minor revision. Wire provides Send; Endpoint adds a single Receive attachment and Close. All eight native bindings and package @@ -65,15 +87,14 @@ The immutable [0.1.0 release](https://github.com/Bitspark/bitwire/releases/tag/v and its API remain available. Hackage publication is deferred by operator decision; Git is the supported Haskell delivery route. -## Declared composites +## Historical declared composites [Decision 0006](decisions/0006-declared-composites-realize-deixis-nodes.md) makes declared composites a realization of Deixis nodes. Each node's value is an origin, and named children stay complete Wire access. It supersedes the -unreleased decision 0005. All eight binding documents carry the same -obligations and state which native strings are in the key image; no native -declaration changes. This is recorded under Unreleased and ships with the next -contract release. +unreleased decision 0005. Decision 0012 supersedes this structural interpretation: +the historical UTF-8 key image and owner-retained parts are addressed behavior, +not the complete byte-keyed tree interface now required in all source bindings. | Language | Contract | Reference evidence | Released runtime evidence | Production construction with origins | | --- | --- | --- | --- | --- | @@ -96,7 +117,8 @@ both languages. Adoption by a released runtime package is still pending. that using Bitwire never requires Nightseam. `node scripts/check.mjs` fails if any published package below depends on or imports Nightseam or bitruntime. [Decision 0010](decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md) places the -implementations in [bitruntime](https://github.com/Bitspark/bitruntime), which has no code yet: +implementations in [bitruntime](https://github.com/Bitspark/bitruntime). Its +production rollout must be verified separately; the original delivery split was: | Language | Contract (Bitwire) | Operators (bitruntime) | In-process pair and transports (bitruntime) | Protocol engine (bitruntime) | | --- | --- | --- | --- | --- | @@ -111,7 +133,8 @@ from frozen Nightseam v0.6.0. ## Native representations The [contract](wire/contract.md) is normative. Representations must preserve exact -scalar-string paths, frame values and absence versus JSON null, local return +arbitrary byte tree keys, complete own/child structure, structural absence, +scalar-string carrier paths, frame values and absence versus JSON null, local return identity, received context and lifetime observations. Callback and error shapes follow each language, with their mapping documented. Local context and return capabilities are not serialized into profile envelopes. diff --git a/docs/migration-0.3.md b/docs/migration-0.3.md new file mode 100644 index 0000000..a2cc961 --- /dev/null +++ b/docs/migration-0.3.md @@ -0,0 +1,67 @@ +# Migrating to explicit primitives and trees in 0.3 + +The names and structural contract change intentionally before 1.0. +[Decision 0012](decisions/0012-explicit-data-and-wire-trees.md) is accepted; +the [language matrix](languages.md) separates source declarations, publication +and runtime adoption. + +| Previous surface or proposal | Current name and meaning | +| --- | --- | +| `Wire.send(path, message)` | `AddressedWire.send(path, message)`: existing addressed carrier access. | +| Proposed `End.send(message)` | `Wire.send(message)`: addressless interaction. | +| Addressed access described as `Deixis[End]` | `WireTree = DeixisNode`: complete finite structure with exact byte keys. | +| Bitstore's materialized `Data` tree | `DataTree = DeixisNode`; own `Data.read()` retrieves fixed bytes. Materialized byte trees and persistence machinery remain distinct. | +| Proposed `ByteSource` | `Data`: the storage primitive. | + +## Existing endpoints and return capabilities + +Rename existing addressed `Wire` implementations, parameters and imports to +`AddressedWire`. Keep their `send(path, message)` signature and behavior. +`Endpoint` now extends `AddressedWire`; the receiver still sees a complete +message and a string path, and owns one attachment. Keep `ReturnAddress.wire` +addressed: the existing profile's response and lifecycle operations depend on +its relative path space and identity. A primitive Wire is not a drop-in return +capability for that profile. + +There is no alias preserving the old meaning under `Wire`. New compile errors +are deliberate opportunities to distinguish primitive, tree and carrier. +Do not change wire envelopes, request IDs, admission/refusal, received context +or endpoint ownership as part of this rename. + +## Complete structure + +Implement `WireTree` only where the implementation can supply the complete +`DeixisNode` contract: `own`, `children`, partial `at` and `decompose`. +An arbitrary opaque router is not such a tree. It cannot gain enumeration, +finite structure or reconstruction by adding a nominal interface. + +Use exact byte keys, preserve empty and non-UTF-8 keys, reject duplicate keys, +and preserve own/child identity through reconstruction. Empty path selects +self; a missing path returns no tree and must not fall back to an ancestor's +own Wire. Sharing children is valid; structural cycles are not. + +For existing paths: + +```text +send(tree, path, message) = select(tree, path).own().send(message) +read(tree, path) = select(tree, path).own().read() +``` + +`at` is structural selection, not a deferred prefix binding that always succeeds. +A bound view of `AddressedWire` remains addressed access. Full tree parts carry +authority; access restricted to a subset of operations should be delegated +through an explicit restricted facade, not by claiming a partial enumeration is +the complete tree. + +## Paths and deployment + +`TreePath` contains arbitrary byte keys. The unchanged carrier's `Path` contains +Unicode-scalar strings. An adapter uses the exact UTF-8 image or documents a +new encoding/profile; it must not normalize or decode arbitrary bytes lossily. +The native API revision does not establish a binary path encoding in `bitwire/1`. + +Bitwire publishes declarations and criteria. bitruntime supplies constructors, +selection, derived sending and carrier implementations. Source package checks +and historical addressed conformance are not evidence that every runtime now +provides structural trees. Consumer adoption and any registry publication must +be verified against their actual versions before they are reported complete. diff --git a/docs/wire/carriers.md b/docs/wire/carriers.md index 07af148..295e168 100644 --- a/docs/wire/carriers.md +++ b/docs/wire/carriers.md @@ -8,11 +8,16 @@ Under [decision 0010](../decisions/0010-bitwire-holds-the-contract-and-bitruntim Bitwire specifies carriers and bitruntime implements them. Nothing here is implemented yet; the work is tracked in [#39](https://github.com/Bitspark/bitwire/issues/39). +Under [decision 0012](../decisions/0012-explicit-data-and-wire-trees.md), carriers +retain `AddressedWire`/`Endpoint` semantics. The new addressless `Wire` and full +byte-keyed `WireTree` are separate contracts; this draft does not make an opaque +carrier a complete tree or change its path encoding. + ## Transport and carrier - A **transport** moves frames between two places. It knows nothing of requests, ids or return capabilities. -- A **carrier** provides Wire endpoints. It mints request ids, correlates +- A **carrier** provides AddressedWire endpoints. It mints request ids, correlates responses and cancels, gives each received request a return capability, and creates each accepted request's invocation. @@ -68,8 +73,8 @@ Notes on the groups: - **Native multiplexing.** QUIC, HTTP/2 and SSH carry many streams natively. A tunnel over them can map its channels onto those streams instead of reimplementing flow control. -- **Relays splice frames below the Wire.** bitwire-svc passes opaque frames - between two outbound connections. It is part of a transport path, not a Wire +- **Relays splice frames below the AddressedWire.** bitwire-svc passes opaque frames + between two outbound connections. It is part of a transport path, not a AddressedWire forwarder, and it preserves no end-to-end authentication. - **Brokers fit return capabilities well**, because NATS reply inboxes and MQTT 5 response topics are return addresses. What they lack is a connection: @@ -80,7 +85,7 @@ Notes on the groups: They need a contract of their own before Bitwire supports any. - **A durable log is not a carrier.** Return capabilities and live scopes cannot - be replayed from stored envelopes. Recording and following a Wire is a + be replayed from stored envelopes. Recording and following a AddressedWire is a composition over one, not a way to carry it. ## The carrier contract (draft) diff --git a/docs/wire/contract.md b/docs/wire/contract.md index b9aa914..1603d43 100644 --- a/docs/wire/contract.md +++ b/docs/wire/contract.md @@ -1,6 +1,8 @@ -# Wire contract +# Wire and WireTree contract -This page specifies the shared access contract for Bitwire 0.2. The +This page specifies the shared contract for Bitwire 0.3.0, currently unreleased. +[Decision 0012](../decisions/0012-explicit-data-and-wire-trees.md) separates +addressless interaction, its full structure and addressed carriers. The [conformance work](../../conformance/README.md) records executable evidence separately; declarations compiling does not establish behavioral conformance. The 0.1 baseline was reviewed against Nightseam at @@ -8,17 +10,46 @@ The 0.1 baseline was reviewed against Nightseam at ## Surface and scope +```typescript +type Key = Uint8Array; +type TreePath = readonly Key[]; +type Children = ReadonlyArray]>; + +interface DeixisNode { + own(): T; + children(): Children; + at(path: TreePath): DeixisNode | undefined; + decompose(): Readonly<{ own: T; children: Children }>; +} + +interface Wire { + send(message: Message): void; +} + +type WireTree = DeixisNode; +``` + +`Wire` is addressless sending. It grants neither receive attachment nor closure. +`WireTree` is the complete Deixis structure over those capabilities. It has the +same structural operations as Bitstore's `DataTree = DeixisNode`, whose +own primitive has `read(): Promise`. Both families answer to the same +model without imposing a dependency on a private Deixis checkout. + +The following table describes the retained addressed carrier surface. Its +explicit name is `AddressedWire`; it is not a full tree: + | Operation | Go | TypeScript | | --- | --- | --- | | Send at a relative path | `Send(path []string, message Message) error` | `send(path: Path, message: Message): void` | | Attach an Endpoint receiver | `Receive(receiver Receiver) (detach func(), err error)` | `receive(receiver: Receiver): () => void` | | End an Endpoint | `Close(code Code, reason string) error` | `close(code?: number, reason?: string): void` | -`Wire` contains only Send. `Endpoint` extends Wire with Receive and Close. -Passing Wire access does not require receiver or closure authority. A runtime +`AddressedWire` contains only addressed Send. `Endpoint` extends AddressedWire +with Receive and Close. Passing AddressedWire access does not require receiver +or closure authority. A runtime requiring enforced attenuation exposes a send-only facade; a static type alone does not hide extra operations on an underlying object. A return address holds -Wire access. The [decision](../decisions/0002-delivery-dispatch-and-ownership.md) +AddressedWire access. The [decision](../decisions/0002-delivery-dispatch-and-ownership.md) explains this separation and the breaking migration from 0.1. The supporting declarations are in [Go](../../wire/go/wire.go) and @@ -26,15 +57,53 @@ The supporting declarations are in [Go](../../wire/go/wire.go) and the same observable behavior; native spelling, ownership and error mechanisms need not be identical. -A Wire is access to an origin, not a serialized address. `Wire[A]` in a model -description means this access interpreted through contract `A`; the base -interface itself is type-erased. Bitwire 0.2 carries the four structured frame +An AddressedWire provides access at an origin, not a serialized address. The +base message interface is type-erased. Bitwire 0.3 carries the four structured frame kinds defined in the [profile boundary](profile.md). It is independent of the carrier, runtime and generator, but is not an arbitrary-payload or profile-polymorphic interface. +## Full tree structure + +Every node has an own capability and a complete finite map of child trees. +Keys are arbitrary exact bytes, including empty and non-UTF-8 keys. `[]` +selects self; one empty key selects the empty-key child. Missing selection +returns no tree, distinctly from a present node whose Wire refuses every send. + +The represented structure is immutable, finite and acyclic. Construction +rejects duplicate byte keys and prevents mutation of caller-owned keys or +child lists from changing the tree. Sharing a child under multiple names is +valid and preserves identity. Structural cycles are invalid. + +`children()` returns the complete immediate child map. `decompose()` returns +both own and children. Runtime construction and derived operations obey: + +```text +decompose(compose(own, children)) ≅ { own, children } +compose(decompose(tree)) ≅ tree +tree.at([]) ≅ tree +tree.at(a).at(b) ≅ tree.at(a ++ b) when selection succeeds + +send(tree, path, message) = tree.at(path).own().send(message) +read(tree, path) = tree.at(path).own().read() +``` + +The final equations require an existing path; read is the sibling DataTree +operation. Missing selection invokes no primitive. Equivalence preserves exact +keys, complete structure, own/child capability identities and shared instances; +it does not copy primitive state. Constructors and derived sending belong to +bitruntime, not this declarations package. + +Full structural access exposes the capabilities in its parts. Restricted +addressed access may omit structure, but must not be advertised as a WireTree. + ## Paths +These `Path` rules apply to `AddressedWire`, `Endpoint` and the unchanged +`bitwire/1` carrier. They do not restrict WireTree's byte keys. A bridge must +reject keys outside the exact UTF-8 image or specify an additional encoding +and profile; arbitrary bytes must never be converted lossily. + A path is a sequence of opaque Unicode scalar strings. There is no separator parsing, normalization or permission inheritance. `[]`, `[""]`, `["a/b"]` and `["a", "b"]` are different paths. Canonically equivalent Unicode spellings remain @@ -58,7 +127,8 @@ into a nonempty root path. ## Sending and receiving -Send completes on admission or refusal; it does not await a response or execute +Both Wire and AddressedWire sending complete on admission or refusal; neither +awaits a response or executes destination application code on the sender's stack. The endpoint implementation owns asynchronous dispatch. Successful admission says nothing about completion of an application effect. Bounds, request correlation and termination policy are @@ -78,10 +148,10 @@ receiver receives no later closure notification from that attachment. A dispatcher may own that attachment and provide many routed receiving views or handler registrations. Exact/prefix matching, precedence and duplicate-path rules -belong to its explicit policy, not to Wire or Endpoint. Sibling selected views +belong to its explicit policy, not to AddressedWire or Endpoint. Sibling selected views share that dispatcher; they cannot each attach an independent root receiver. Overlapping views require a stated selection policy. The dispatcher can expose -Wire access and Endpoint views without exposing its routing table to callers. +AddressedWire access and Endpoint views without exposing its routing table to callers. The returned detach action is idempotent. It prevents new dispatch through that attachment; a later attachment may be installed. Already admitted requests retain @@ -99,7 +169,7 @@ or rebind must not retarget that invocation to a new receiver. A pure router cannot infer this lifetime from callback return or observe it by wrapping the return capability; the latter would violate identity preservation. Bounds and retirement belong to that explicit runtime integration, not an unbounded table -silently introduced by Wire selection. The reference composition experiment +silently introduced by AddressedWire selection. The reference composition experiment checks retained replies; full cancellation/retirement acceptance remains with the implementing profile. @@ -125,7 +195,7 @@ profile lifecycle integration to be public and testable by independent endpoint implementations. It separates already admitted stale controls from newly arriving ambiguous controls, specifies per-traversal capture obligations, and requires bounded accounting beyond the request count. The exact lifecycle facility is a -profile API, not another method silently added to Wire. +profile API, not another method silently added to AddressedWire. ## Preservation laws @@ -157,54 +227,31 @@ hidden in payloads. Detaching a forwarder leaves its borrowed endpoints usable. ## Declared composites -A declared composite realizes a Deixis node `Node[T] = T × FinMap[Bytes, Node[T]]` -with Wire access, as specified in [decision 0006](../decisions/0006-declared-composites-realize-deixis-nodes.md). -Its own value is its **origin**: the behavior for a message sent at `[]`, which -never sees a path. A refusing origin is a value. Each named child is complete -Wire access, retained and delegated unchanged: +Full declared composition returns `WireTree` and exposes its own Wire and +complete children under the laws above. This supersedes decision 0006's +construction-owner-only parts model as the full structural contract. -```text -send(compose(o, m), [], x) = o(x) -send(compose(o, m), k : p, x) = send(m[k], p, x) when k ∈ dom m, else refused - -at(compose(o, m), [k]) ≃ m[k] -compose(parts(c)) ≃ c -parts(compose(o, m)) ≅ (o, m) -mount(m) ≃ compose(refuse, m) -``` +A runtime can derive `AddressedWire` access from a tree with an explicit key +mapping. The reverse is not generally possible: sending alone cannot enumerate +a router's complete structure or distinguish a missing child from a refusing +one. Prefix binding of an opaque router remains addressed access, not structural +selection. -A segment maps to a Deixis key by exact UTF-8 encoding; the empty segment is the -empty key and `[]` is the empty path. Construction refuses a missing origin or -child, a segment outside that image and duplicate segments. It copies its -inputs. The origin is never a fallback for a missing child. Rebuilding from the -retained parts at any complete cut preserves origin behavior, child state and -aliases, exact keys, empty branches, replies, return identity, context, -captured invocations and borrowed ownership. - -`parts` belongs to the construction owner, not to the Wire it hands out. A -send-only Wire gains no enumeration, unwrapping or new method, and an arbitrary -Wire is not decomposable. Missing children and childless refusing children -refuse alike; only retained parts distinguish them. Behavior can still reveal -which routes respond, and parts carry the capabilities they hold, so delegate -bound access rather than parts. Interception is explicit access composed around -a node, not part of its value. It attenuates routes; it is not a membrane over -capabilities in payloads, callbacks or replies. A composite of `at(w, [k])` views -restricts by first segment; origin-only leaves give an exact operation set. See -the decision's [clarifications](../decisions/0006-declared-composites-realize-deixis-nodes.md#clarifications-24-september-2026). These laws are -obligations on compositions, not a claim that a runtime already offers such a -constructor. The [declared evidence](../../conformance/declared/README.md) -separates the test-only interpreter, released Nightseam behavior and recorded gaps. +The historical [declared evidence](../../conformance/declared/README.md) tests +decision 0006's addressed interpretation and retained owner parts. It does not +establish the new byte-keyed structural contract. An opaque child in that older +composition cannot become a complete child tree merely by renaming its type. ## Local capabilities and context A local Message comprises a structured frame and optional local delivery capability/context. A request's return capability supports its response; correlation uses both that capability's identity and the request identifier. -The Wire in a callable return capability has its own origin and relative path +The AddressedWire in a callable return capability has its own origin and relative path space. The profile defines its supported paths, frame kinds and lifetime, and may reserve that origin's paths for invocation operations. This reserves no application or peer-root namespace and grants no receive or closure authority. -It does not make every Wire a lifecycle participant. Unsupported invocation-aware +It does not make every AddressedWire a lifecycle participant. Unsupported invocation-aware use is explicitly refused by the implementing profile. See [decision 0004](../decisions/0004-return-origins-and-profile-revisions.md). Composition must retain capability identity, not construct a new wrapper merely @@ -238,14 +285,14 @@ live-reference and publication obligations. ## Lifetime -A root Endpoint owns its closure. Wire access does not imply that ownership. +A root Endpoint owns its closure. AddressedWire access does not imply that ownership. A dispatcher owns its root attachment and routes, not a borrowed root's closure. A mount owns its attachments and routing, not its borrowed children. Closing a mount detaches its attachments and notifies its receivers without closing those children. Closing or detaching twice has no additional effect on ownership. Closing an Endpoint is not release of a live binding. Scope nonces, checked reference -import, owner ledgers and release barriers belong to the live profile. A Wire +import, owner ledgers and release barriers belong to the live profile. A AddressedWire implementation claiming that profile must preserve them when presenting access through this contract. Moving a type declaration does not transfer those runtime responsibilities or establish consumer adoption. diff --git a/docs/wire/profile.md b/docs/wire/profile.md index a5d726f..725b335 100644 --- a/docs/wire/profile.md +++ b/docs/wire/profile.md @@ -1,10 +1,14 @@ # Message profile and interoperability -Bitwire 0.2 defines shared relative-path access and its structured message +Bitwire 0.3 retains the addressed carrier and its structured message vocabulary. The network profile is **`nightseam.duplex/1`**, which [decision 0007](../decisions/0007-using-bitwire-never-requires-nightseam.md) moves here as `bitwire/1`, unchanged on the wire. Bitwire specifies it; bitruntime implements it ([decision 0010](../decisions/0010-bitwire-holds-the-contract-and-bitruntime-implements-it.md)), so importing Bitwire alone never does. +The addressless `Wire` and full `WireTree` are defined in the [contract](contract.md). +The existing carrier/profile keeps string paths and addressed return capabilities; +this API rename introduces no binary path encoding or network revision. + ## Structured messages The shared frame vocabulary is: @@ -17,7 +21,7 @@ The shared frame vocabulary is: | Cancel | request identifier | trace fields | A public error contains its code, message and optional JSON data. Requests and -events take their operation name from the Wire path, so a structured frame has +events take their operation name from the AddressedWire path, so a structured frame has no competing method or event name. All frames belong to version 1. A native presentation can represent that fixed version implicitly; an encoder must emit 1 and a decoder must reject unsupported versions. @@ -52,7 +56,7 @@ preservation and explains how native presentations can carry them. | Structured frame vocabulary and preservation of represented data | Complete envelope validation, id minting/correlation, cancellation and configured bounds; specified by Bitwire under decision 0007, implemented by bitruntime under 0010 | | Stable local capability identity and received-context preservation | Creation, validation and recognition of invocation context, tracing and observation | | Selection/mount/forwarding observations and borrowed endpoint lifetime | Declaration identity checks, preparation, live-value conversion, scopes and release barriers | -| Declared-composite realization, segment-to-key mapping and reconstruction laws ([0006](../decisions/0006-declared-composites-realize-deixis-nodes.md)) | Any production construction facility with origins, its retained parts and their runtime integration | +| Complete byte-keyed WireTree structure and reconstruction laws ([0012](../decisions/0012-explicit-data-and-wire-trees.md)) | Production tree construction, primitive sending and explicit mapping into addressed carriers | | Native binding types and independent conformance expectations | Concrete runtimes, generators and any optional authority profile | The current profile baseline is Nightseam **v0.6.0**, at immutable revision @@ -89,7 +93,7 @@ less restrictive revision is not assumed compatible. A callable return capability has an origin distinct from the destination of the request. Nightseam uses `[]` for its outcome and `invocation.*` paths for local lifecycle participation, refusing unsupported operations. Those reservations -belong to this profile's return origin, not to every Wire or a peer root. +belong to this profile's return origin, not to every AddressedWire or a peer root. Physical bridges establish their own correlation/lifecycle mapping; they do not serialize local return objects or blindly export the local control vocabulary. @@ -113,7 +117,7 @@ The marker is absent from serialized profile errors. ## What two adapters must agree on -| Agreement | Why the Wire signature alone is insufficient | +| Agreement | Why the AddressedWire signature alone is insufficient | | --- | --- | | Operation paths and frame grammar | A receiver must understand the operation and its arguments. | | Profile revision | A shared profile name does not establish compatibility between pre-1.0 releases. | diff --git a/examples/README.md b/examples/README.md index d044d5f..2ba25d2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,5 +1,11 @@ # Runnable use cases +**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). + Bitwire defines the access contract. Runnable programs live with the runtime or domain interface they exercise. This catalogue connects a use case to its implementation, model, command and observable result. diff --git a/poster/SEAM.md b/poster/SEAM.md index b24372f..35dfff2 100644 --- a/poster/SEAM.md +++ b/poster/SEAM.md @@ -1,5 +1,11 @@ # Bitwire's public seam +**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). + The evidence base for [the poster](index.html). Every technical claim cites `path:line` at commit `02846d5` (main, 2026-09-24). A refinement run re-derives these facts before changing the page. diff --git a/poster/index.html b/poster/index.html index eeb3a20..d80330b 100644 --- a/poster/index.html +++ b/poster/index.html @@ -4,7 +4,7 @@ -Bitwire +Bitwire 0.2 historical overview