diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10a314c6..c511e0ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: # a test too, and here for the same reason as the two above. - run: cargo run -p xtask -- platforms # And the table on the end of the release: what a tag publishes, - # which eight repositories fetch by version. release.yml assembles + # which twelve repositories fetch by version. release.yml assembles # from it and verifies the directory back against it, so an # artifact that stopped being produced fails the release that # dropped it rather than the eight that wanted it. @@ -123,7 +123,7 @@ jobs: # floor catches is the order of magnitude, since a rewrite that # allocates per block turns a release into a minute of hashing. - run: ZU_GATE=1 cargo bench -p xtask --bench sha256 - # And the table under all of it: the nine repositories the split + # And the table under all of it: the thirteen repositories the split # created. The conductor dispatches to that list, the README # publishes it, and the contract above names its consumers from # it, so a repository joining or leaving the project is one row @@ -559,7 +559,7 @@ jobs: # them. It is not allowed on a branch heading for a release, so # the gate lives here rather than in the runner's defaults. - run: cargo run -p zu-cli --release -- corpus conformance/cases --strict - # The reader is read by nine repositories on every CI run of each + # The reader is read by thirteen repositories on every CI run of each # of them, so its cost per case is asserted to stay linear as the # corpus grows. The bench fails rather than reporting when it # does not. diff --git a/.github/workflows/conductor.yml b/.github/workflows/conductor.yml index fbc5c830..36fe9efb 100644 --- a/.github/workflows/conductor.yml +++ b/.github/workflows/conductor.yml @@ -2,7 +2,7 @@ name: conductor # The dispatching half of the release train (dx/14 section 6). The tag # on this repository builds and publishes the artifacts; this drives the -# eight repositories that build against them, collects what each reports +# twelve repositories that build against them, collects what each reports # back, and fails the release if any of them fails a gate. # # Every dispatch here is a no-op, because none of the eight has a @@ -40,7 +40,7 @@ on: concurrency: # One conductor per version. Two runs of one version dispatching to - # eight repositories is eight repositories building the same tag twice + # twelve repositories is twelve repositories building the same tag twice # and reporting back in whichever order they finish. group: conductor-${{ inputs.version }} cancel-in-progress: false @@ -74,6 +74,18 @@ jobs: - repo: zu-dotnet workflow: release.yml reports: scorecard api-map corpus perf sizes + - repo: zu-kotlin + workflow: release.yml + reports: scorecard api-map corpus perf sizes + - repo: zu-scala + workflow: release.yml + reports: scorecard api-map corpus perf sizes + - repo: zu-swift + workflow: release.yml + reports: scorecard api-map corpus perf sizes + - repo: zu-dart + workflow: release.yml + reports: scorecard api-map corpus perf sizes - repo: zu-kit workflow: release.yml reports: scorecard corpus diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5403fafd..cc6c7478 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ name: release # The release train of dx/14 section 6: one version number, one day, -# one orchestrated run across nine repositories. This is the skeleton of +# one orchestrated run across thirteen repositories. This is the skeleton of # it, and what is real here is deliberate. The build is the same matrix # every pull request runs, the assemble step gathers exactly the rows of # artifacts.toml, and the verify step reads the directory back against @@ -88,7 +88,7 @@ jobs: if-no-files-found: error path: packaging - # The eight repositories, driven rather than releasing on their own + # The twelve repositories, driven rather than releasing on their own # schedule, which is what keeps one version number meaning one thing. # It runs after the artifacts exist because every one of them builds # against those artifacts, and before the registries because a binding @@ -141,5 +141,5 @@ jobs: echo "no-op: deploy zu-web against this version, publish the notes, push the rendered Homebrew formula and Scoop manifest to the tap and the bucket, and bump AUR" - name: What this run did not do run: | - echo "The dispatches to the eight repositories ran and did nothing, because none of them has a release workflow yet." + echo "The dispatches to the twelve repositories ran and did nothing, because none of them has a release workflow yet." echo "Every publish above is idempotent when it is real, so a partial release is resumed and not restarted." diff --git a/README.md b/README.md index e2fca690..c1c43455 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,12 @@ This repository holds the engine, the Rust SDK, the CLI, the C ABI and its gener | [zu-python](https://github.com/tamnd/zu-python) | `zudb` on PyPI. PyO3, three wheels per platform | 1 | | [zu-node](https://github.com/tamnd/zu-node) | `zudb` on npm. napi-rs, plus the WASM build, for Node, Bun, Deno, and the browser | 1 | | [zu-go](https://github.com/tamnd/zu-go) | `github.com/tamnd/zu-go`. cgo, with a `purego` path | 1 | -| [zu-java](https://github.com/tamnd/zu-java) | `dev.zudb` on Maven Central. Panama, with a JNI fallback, plus Kotlin and Scala layers | 1 | +| [zu-java](https://github.com/tamnd/zu-java) | `dev.zudb` on Maven Central. Panama, with a JNI fallback | 1 | | [zu-dotnet](https://github.com/tamnd/zu-dotnet) | `ZuDb` on NuGet. Source-generated P/Invoke, NativeAOT-clean | 2 | +| [zu-kotlin](https://github.com/tamnd/zu-kotlin) | `dev.zudb:zu-kotlin` on Maven Central. Kotlin/JVM over the Panama layer, coroutines and `Flow` | 2 | +| [zu-scala](https://github.com/tamnd/zu-scala) | `dev.zudb::zu-scala` on Maven Central. Scala 3 and 2.13, with Cats Effect and ZIO modules kept apart | 2 | +| [zu-swift](https://github.com/tamnd/zu-swift) | Swift Package Manager. The C ABI through the clang importer, `AsyncSequence` over rows | 2 | +| [zu-dart](https://github.com/tamnd/zu-dart) | `zudb` on pub.dev. `dart:ffi`, with the declarations generated from `zu.h` | 2 | | [zu-kit](https://github.com/tamnd/zu-kit) | The binding kit: generated FFI declarations, corpus runners, a reference binding, the scorecard tool | 3 | | [zu-web](https://github.com/tamnd/zu-web) | The documentation site. Two thirds of it is generated from this repository's release artifacts | | diff --git a/artifacts.toml b/artifacts.toml index c76dc11b..0061a478 100644 --- a/artifacts.toml +++ b/artifacts.toml @@ -1,5 +1,5 @@ schema = 1 -doc = "What a release of zu publishes, one table for nine repositories." +doc = "What a release of zu publishes, one table for thirteen repositories." audited = "2026-08-17" # A release is a tag on this repository and a run that drives the eight @@ -13,7 +13,7 @@ audited = "2026-08-17" # own. `cargo xtask artifacts --assemble` gathers exactly these rows and # `--verify` reads the directory back, which means an artifact that # stopped being produced fails the release that dropped it rather than -# the eight repositories that wanted it. +# the twelve repositories that wanted it. # # `made` says where a row comes from and there are five answers. `file` # is a path this tree already holds and the release copies. `corpus` is @@ -23,39 +23,39 @@ audited = "2026-08-17" # is over the rest of the release. `later` is an artifact the contract # names and nothing makes yet, with the milestone that makes it: writing # it down early is the point, since a consumer needs to know what a -# release will eventually carry and the alternative is eight +# release will eventually carry and the alternative is twelve # repositories each guessing. [[artifact]] name = "libzu-.tar.zst" made = "platform" -consumers = ["zu-c", "zu-go", "zu-java", "zu-dotnet", "zu-kit"] -doc = "An install prefix for one tier 1 target, as the platform's job built it: the header, both library forms, the CLI, a pkg-config file, a CMake package config, the export list in each linker's syntax and the license. The five repositories here reach the engine through the C ABI rather than compiling against it, so this archive is the whole of what they link, and it unpacks as a prefix because that is the only shape pkg-config and find_package(zu) can both resolve their paths against (dx/09 C-4, C-5)." +consumers = ["zu-c", "zu-go", "zu-java", "zu-dotnet", "zu-kotlin", "zu-scala", "zu-swift", "zu-dart", "zu-kit"] +doc = "An install prefix for one tier 1 target, as the platform's job built it: the header, both library forms, the CLI, a pkg-config file, a CMake package config, the export list in each linker's syntax and the license. The nine repositories here reach the engine through the C ABI rather than compiling against it, so this archive is the whole of what they link, and it unpacks as a prefix because that is the only shape pkg-config and find_package(zu) can both resolve their paths against (dx/09 C-4, C-5)." [[artifact]] name = "zu.h" made = "file" from = "crates/zu-capi/include/zu.h" -consumers = ["zu-c", "zu-go", "zu-java", "zu-dotnet", "zu-kit", "zu-web"] +consumers = ["zu-c", "zu-go", "zu-java", "zu-dotnet", "zu-kotlin", "zu-scala", "zu-swift", "zu-dart", "zu-kit", "zu-web"] doc = "The C ABI, published beside the libraries as well as inside each of them, because a consumer generating bindings needs the header without downloading a platform it does not build for. tamnd/zu-c deliberately does not hold a copy (dx/18 section 2)." [[artifact]] name = "model.json" made = "file" from = "docs/api/model.json" -consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kit", "zu-web"] +consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kotlin", "zu-scala", "zu-swift", "zu-dart", "zu-kit", "zu-web"] doc = "The public Rust surface as data. Every binding checks its api-map.toml against the model of the version it builds against, and the site renders the reference pages from it, so it is fetched by version rather than read from this repository's main branch." [[artifact]] name = "conformance-.tar.zst" made = "corpus" -consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kit"] +consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kotlin", "zu-scala", "zu-swift", "zu-dart", "zu-kit"] doc = "The cross-client conformance corpus for this exact version. A client pins an engine version and needs the cases that shipped with it, not the cases on this branch, which are the cases for a version it has not adopted (dx/15 section 2)." [[artifact]] name = "SHA256SUMS" made = "sums" -consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kit", "zu-web"] +consumers = ["zu-c", "zu-python", "zu-node", "zu-go", "zu-java", "zu-dotnet", "zu-kotlin", "zu-scala", "zu-swift", "zu-dart", "zu-kit", "zu-web"] doc = "The digest of every other file of the release, one `sha256sum -c` line each, written last because it is over the rest of them. The install one-liners of dx/12 section 6 fetch this before they fetch anything else, since a `curl | sh` that pipes an unverified download into a shell is the install story every audit stops at, and the packaging manifests carry the same numbers so Homebrew and Scoop check what they install too." [[artifact]] diff --git a/clients.toml b/clients.toml index 1a849450..54c48f14 100644 --- a/clients.toml +++ b/clients.toml @@ -1,6 +1,6 @@ schema = 1 doc = "The clients of this engine, who maintains each one, and what its tier promises." -audited = "2026-08-22" +audited = "2026-08-25" # The scorecard of dx/01 section 5. A tier is a promise, and a promise # with nobody's name on it is a commitment nobody made, so every client @@ -157,7 +157,7 @@ registry = "Maven Central" maintainer = "Tam Nguyen Duc <@tamnd>" tier = 1 holds = ["quickstart", "reference", "idiom", "conditions", "misuse", "leaks", "install", "stability"] -doc = "Panama with a JNI fallback, and the Kotlin and Scala layers on top of it." +doc = "Panama with a JNI fallback. The Kotlin and Scala layers were on top of it until DX5 and are their own clients now, because a dependency is taken by name and the name a Kotlin project wants is a Kotlin one." [[client]] repository = "https://github.com/tamnd/zu-c" @@ -179,6 +179,46 @@ tier = 2 holds = [] doc = "Source-generated P/Invoke, NativeAOT-clean." +[[client]] +repository = "https://github.com/tamnd/zu-kotlin" +language = "Kotlin" +package = "dev.zudb:zu-kotlin" +registry = "Maven Central" +maintainer = "Tam Nguyen Duc <@tamnd>" +tier = 2 +holds = [] +doc = "Kotlin/JVM over the Panama layer. use for closing, Sequence for rows, a suspend query and a Flow, and cancellation that reaches the interrupt rather than being dropped." + +[[client]] +repository = "https://github.com/tamnd/zu-scala" +language = "Scala" +package = "dev.zudb::zu-scala" +registry = "Maven Central" +maintainer = "Tam Nguyen Duc <@tamnd>" +tier = 2 +holds = [] +doc = "Scala 3 first, cross published for 2.13. Using for resources, a sealed value type, Either at the edges, and the Cats Effect and ZIO modules kept apart." + +[[client]] +repository = "https://github.com/tamnd/zu-swift" +language = "Swift" +package = "zu-swift" +registry = "Swift Package Manager" +maintainer = "Tam Nguyen Duc <@tamnd>" +tier = 2 +holds = [] +doc = "The C ABI through the clang importer, AsyncSequence over rows, a non-copyable handle that closes in its deinit, and the one-thread rule said in Sendable rather than in a comment." + +[[client]] +repository = "https://github.com/tamnd/zu-dart" +language = "Dart" +package = "zudb" +registry = "pub.dev" +maintainer = "Tam Nguyen Duc <@tamnd>" +tier = 2 +holds = [] +doc = "dart:ffi with the declarations generated from zu.h and regenerated in CI, so a drift in the header is a diff in a pull request." + [[client]] repository = "https://github.com/tamnd/zu-kit" language = "Rust" diff --git a/crates/xtask/src/artifacts.rs b/crates/xtask/src/artifacts.rs index 0e82d3e5..69cecb28 100644 --- a/crates/xtask/src/artifacts.rs +++ b/crates/xtask/src/artifacts.rs @@ -24,7 +24,7 @@ //! a `later` row is an artifact the contract names that nothing makes //! yet, carrying the milestone that will make it: naming it early is //! the point, since a consumer needs to know what a release will -//! eventually carry, and the alternative is eight repositories each +//! eventually carry, and the alternative is twelve repositories each //! guessing. use std::collections::BTreeMap; diff --git a/crates/xtask/src/clients.rs b/crates/xtask/src/clients.rs index 923f3f8c..0bef907f 100644 --- a/crates/xtask/src/clients.rs +++ b/crates/xtask/src/clients.rs @@ -1245,11 +1245,15 @@ mod tests { let notes = table.check(&root, false).expect("the tree is readable"); assert!(notes.is_empty(), "{notes:#?}"); - // Seven clients: the five tier 1 SDKs of DX4, the tier 2 one, - // and the kit. Every one of them owes a scorecard back, which is - // the list in repos.toml, and every one of them has a name on - // it, which is what dx/01 section 5 asks for. - assert_eq!(table.clients.len(), 7); + // Eleven clients: the five tier 1 SDKs of DX4, the five tier 2 + // ones DX5 adds, and the kit. Every one of them owes a + // scorecard back, which is the list in repos.toml, and every one + // of them has a name on it, which is what dx/01 section 5 asks + // for. The five new ones start at tier 2 rather than at tier 1, + // because tier 1 asks for 100 on conformance and 90 on practice + // and a binding earns that by having shipped rather than by + // having been written. + assert_eq!(table.clients.len(), 11); let tier1: Vec<&str> = table .clients .iter() diff --git a/crates/xtask/src/corpus.rs b/crates/xtask/src/corpus.rs index f70b9140..8e2dd5bd 100644 --- a/crates/xtask/src/corpus.rs +++ b/crates/xtask/src/corpus.rs @@ -20,7 +20,7 @@ //! reproducible, which is the shared tar writer's doing and the reason //! a mirror can be compared against a release rather than trusted. And //! the packer parses every case before it ships one, so a corpus that -//! does not load cannot become an artifact that eight repositories fail +//! does not load cannot become an artifact that twelve repositories fail //! on. use std::path::Path; diff --git a/crates/xtask/src/matrix.rs b/crates/xtask/src/matrix.rs index e9279ada..f247270e 100644 --- a/crates/xtask/src/matrix.rs +++ b/crates/xtask/src/matrix.rs @@ -9,7 +9,7 @@ //! Reading the block by its indentation rather than parsing YAML: what //! is wanted is a list of `key: value` under one key, the files are //! written here, and a YAML library to read six keys out of them would -//! be a dependency in the build tooling of nine repositories. A +//! be a dependency in the build tooling of thirteen repositories. A //! structure this does not understand is an error with a line number //! rather than a row silently dropped, which is the rule the TOML //! reader beside it follows too. diff --git a/crates/xtask/src/repos.rs b/crates/xtask/src/repos.rs index 49d37298..d9ed6a71 100644 --- a/crates/xtask/src/repos.rs +++ b/crates/xtask/src/repos.rs @@ -1,7 +1,7 @@ //! The repository table, and the three places that have to agree with //! it. //! -//! The split of dx/18 section 2 put eight repositories outside this one, +//! The split of dx/18 section 2 put twelve repositories outside this one, //! and a split is only a decision once: after it, the list of what was //! split out is a thing every part of the project quotes. The release //! train dispatches to it, the README publishes it, the artifact @@ -921,12 +921,16 @@ mod tests { let notes = table.check(&root).expect("the tree is readable"); assert!(notes.is_empty(), "{notes:#?}"); - // dx/18 section 2 is nine repositories, one of them this one, - // and the split is a decision that was made once. A row that - // quietly appeared or went is that decision changing without - // anybody saying so. - assert_eq!(table.repos.len(), 9); - assert_eq!(table.dispatched().count(), 8); + // dx/18 section 2 was nine repositories, one of them this one, + // and DX5 made it thirteen: zu-kotlin and zu-scala came out of + // zu-java, because a dependency is taken by name and the name a + // Kotlin project wants is a Kotlin one, and zu-swift and + // zu-dart were added on the reasoning that gave .NET its own + // row. The split is still a decision made deliberately, which + // is what this number is for: a row that quietly appeared or + // went is that decision changing without anybody saying so. + assert_eq!(table.repos.len(), 13); + assert_eq!(table.dispatched().count(), 12); let tier1 = table .repos .iter() diff --git a/crates/xtask/src/terms.rs b/crates/xtask/src/terms.rs index ae9d0903..bd25eae3 100644 --- a/crates/xtask/src/terms.rs +++ b/crates/xtask/src/terms.rs @@ -9,7 +9,7 @@ //! not. The table is `style/zu/terms.yml` in `tamnd/zu-web`, because //! the site is where the program's prose is published from; the reader //! is here, because the prose with the most readers is here and because -//! a table checked by nine repositories against nine readers would be +//! a table checked by thirteen repositories against thirteen readers would be //! nine tables. //! //! What is checked is prose, and only prose. Markdown, and the doc @@ -43,7 +43,7 @@ use zu_corpus::yaml::{self, Node}; pub const SCHEMA: i64 = 1; /// The floor on a definition. It is here rather than in a test because -/// the table is read by nine repositories and a definition nobody wrote +/// the table is read by thirteen repositories and a definition nobody wrote /// is worse than a term nobody defined: the first looks like an answer. const DOC_MIN: usize = 24; diff --git a/crates/xtask/src/toml.rs b/crates/xtask/src/toml.rs index 6e1ef674..69ab9233 100644 --- a/crates/xtask/src/toml.rs +++ b/crates/xtask/src/toml.rs @@ -1,7 +1,7 @@ //! The subset of TOML `api-map.toml` is written in. //! //! A general TOML parser is not what this needs. The map is a -//! hand-written file that has to exist in nine repositories and be +//! hand-written file that has to exist in thirteen repositories and be //! read by people who did not write it, so the reader's job is to //! refuse anything it does not understand rather than to accept as //! much as possible. A key it silently ignored would be a mapping diff --git a/crates/xtask/tests/ledger.rs b/crates/xtask/tests/ledger.rs index 95389921..5f3d9ac0 100644 --- a/crates/xtask/tests/ledger.rs +++ b/crates/xtask/tests/ledger.rs @@ -112,7 +112,7 @@ fn the_surface_a_binding_owes_is_the_one_a_user_touches() { #[test] fn a_binding_map_written_against_this_ledger_is_checked_the_other_way() { - // The nine repositories have no maps yet, so what this asserts is + // The thirteen repositories have no maps yet, so what this asserts is // that the release direction works against the real ledger and not // only against a fixture: a map naming one tier-1 entity is short // by the rest, and naming a tier-3 entity is a disagreement. diff --git a/crates/zu-corpus/src/yaml.rs b/crates/zu-corpus/src/yaml.rs index 09fddf2d..7f883cbd 100644 --- a/crates/zu-corpus/src/yaml.rs +++ b/crates/zu-corpus/src/yaml.rs @@ -4,7 +4,7 @@ //! block mappings, block sequences, and scalars. Everything else is //! refused with a line number, for the reason the map reader gives. //! These files are hand written, they have to be read by people in -//! nine repositories who did not write them, and a construct the +//! thirteen repositories who did not write them, and a construct the //! reader quietly reinterpreted would be a case that says one thing to //! a reviewer and another to the runner. //! diff --git a/docs/10-api-and-tooling.md b/docs/10-api-and-tooling.md index 3399b152..285becea 100644 --- a/docs/10-api-and-tooling.md +++ b/docs/10-api-and-tooling.md @@ -172,7 +172,7 @@ The exit codes are the half of the interface a script reads: 0 success, 1 query ## 4. The C ABI, and the bindings on it -Python first (PyO3, Arrow-native `.to_arrow()/.to_pandas()`), then Node (napi-rs), WASM (browser demo, zu1 read-only over HTTP range requests), Swift last (post-v1). All bindings sit on the C ABI crate `zu-capi` (stable, versioned, generated header). Nine repositories compile against that header and nothing else, which is ADR 0005, so its shape is the one decision here that cannot be taken back once it is frozen: dx/02 R9 makes the surface additive after v1, and a signature that was wrong stays wrong in six languages. +Python first (PyO3, Arrow-native `.to_arrow()/.to_pandas()`), then Node (napi-rs), WASM (browser demo, zu1 read-only over HTTP range requests), Swift last (post-v1). All bindings sit on the C ABI crate `zu-capi` (stable, versioned, generated header). Thirteen repositories compile against that header and nothing else, which is ADR 0005, so its shape is the one decision here that cannot be taken back once it is frozen: dx/02 R9 makes the surface additive after v1, and a signature that was wrong stays wrong in six languages. The header carries the revision of that surface as `ZU_ABI_VERSION`, so a build system can compile one way against 0.9 and another against what comes after it without inferring the answer from a crate version that moves for unrelated reasons. The same string is a constant in the workspace, is what `zu version` reports, and is checked by `cargo xtask package` against the `#define`, so a header and a binary that disagree are a failed check rather than an afternoon spent in a debugger. @@ -272,7 +272,7 @@ Not to be confused with `conformance.toml` at the repository root, which shares Nine repositories in seven languages write prose about one database. An element that is a `vertex` on one page and a `node` on the next is two data models to a reader who does not already know it is one, and the fix is one table rather than nine style guides that agree until they do not. -`style/zu/terms.yml` in `tamnd/zu-web` is that table: every term, the group it belongs to, a one sentence definition somebody wrote, and the forms that must give way to it. It is in the site's repository because the site is where this program's prose is published from and because the glossary it renders is the table. `cargo xtask terms` is the reader, and it is here, because the prose with the most readers is here and because a table checked by nine repositories against nine readers would be nine tables. `style/README.md` has the schema and the rule for adding a term. +`style/zu/terms.yml` in `tamnd/zu-web` is that table: every term, the group it belongs to, a one sentence definition somebody wrote, and the forms that must give way to it. It is in the site's repository because the site is where this program's prose is published from and because the glossary it renders is the table. `cargo xtask terms` is the reader, and it is here, because the prose with the most readers is here and because a table checked by thirteen repositories against thirteen readers would be thirteen tables. `style/README.md` has the schema and the rule for adding a term. What is checked is prose, and only prose: markdown, and the `//!` and `///` comments that become reference pages. Not identifiers, which answer to the language they are written in; not code spans or fenced blocks, because a check that fires on `Vertex` in a signature teaches people to ignore it; not link targets, which are addresses. A form matches whole words only, the last word of a form may pick up a plural `s`, the words of a multi-word form have to be one phrase, and case is ignored except where a form differs from its own term only in case, which is what lets `zu` refuse `Zu` without refusing every other capitalised word. @@ -320,7 +320,7 @@ What installs a pinned toolchain in a job is `.github/actions/rust`, a local com Seven targets are tier 1: linux x86_64 and aarch64 against glibc, the same two against musl, both Macs, and Windows on x86_64. Tier 1 means a prebuilt binary for every SDK, a full matrix per release, and a release that stops when one of them fails, which is a promise worth exactly what CI proves of it. So all seven build on every pull request rather than for the first time on the day of a release. -`platforms.toml` is the table, and it is the tiers of dx/14 §2 as data rather than as a paragraph nine repositories each read differently. A row is a target, its tier, the runner that is that machine, the image it builds in where that matters, what the three artifacts are called there, and whether the runner can run what it built. Three, because a platform builds three: the shared library a package manager installs, the static archive a binding links into itself so that its own users install one file, and the CLI. dx/09 C-5 ships both library forms and they are not the same promise, so a table that named one of them would be a release that quietly shipped one of them. `cargo xtask platforms` holds `.github/workflows/libzu.yml` to it in both directions: a tier-1 target the matrix does not build is a promise nothing keeps, and a matrix row for a target the table does not have is a platform being shipped by nobody's decision. The check runs as a test, so it fires on the machine of whoever edited one of the two files. +`platforms.toml` is the table, and it is the tiers of dx/14 §2 as data rather than as a paragraph thirteen repositories each read differently. A row is a target, its tier, the runner that is that machine, the image it builds in where that matters, what the three artifacts are called there, and whether the runner can run what it built. Three, because a platform builds three: the shared library a package manager installs, the static archive a binding links into itself so that its own users install one file, and the CLI. dx/09 C-5 ships both library forms and they are not the same promise, so a table that named one of them would be a release that quietly shipped one of them. `cargo xtask platforms` holds `.github/workflows/libzu.yml` to it in both directions: a tier-1 target the matrix does not build is a promise nothing keeps, and a matrix row for a target the table does not have is a platform being shipped by nobody's decision. The check runs as a test, so it fires on the machine of whoever edited one of the two files. The glibc floor is 2.28, which is manylinux_2_28 and covers RHEL 8 and everything newer, so the two gnu rows build inside that image rather than against the runner's own glibc. A library linked against a newer glibc loads on the machine that built it and dies on the user's, which arrives as a bug report saying the install is broken. The musl rows are what makes a container work, and they are built inside Alpine rather than on the runner: a musl shared library links musl's libc and the unwinder beside it, which an Ubuntu machine does not have, so the row would fail to link here long before it failed to load there. Building where the artifact runs also makes the smoke test the real one. Rows a hosted runner cannot run at all, freebsd and riscv64, are recorded as tier 2 with no runner rather than left out, since the table is the promise and the promise is smaller there. @@ -340,7 +340,7 @@ The same table carries the size ceilings of dx/14 §4, and `cargo xtask platform A release is a tag on this repository and a run that drives the eight others (dx/14 §6). Every one of them builds against what this one published, which makes the list of what gets published a contract rather than a step in a workflow. A binding that fetches `model.json` for the version it pins and finds nothing cannot tell a release that dropped the artifact from a version that never had it, and the failure surfaces in somebody else's CI a day later, which is the worst place for it. -`artifacts.toml` is that list, and the release workflow has none of its own: it assembles from the table and reads the directory back against it. A row is a name, where it comes from, the repositories that fetch it, and a sentence saying why they do. There are five ways a row comes to exist, because there are five ways an artifact does. A `file` is a path this tree already holds, `zu.h` and `model.json` being the two. A `corpus` is packed by the packer of §7. A `platform` row is one artifact per tier-1 target, expanded against `platforms.toml`, so the seven move with that table rather than with this one. A `sums` row is `SHA256SUMS`, the digest of every other file of the release, written last because it is over the rest of them and read first by anything that installs from them. And a `later` row is an artifact the contract names that nothing makes yet, carrying the milestone that will make it: `cli.json` with D1, `gql.json` and `errors.json` with D2. Naming those early is the point rather than an oversight, since a consumer needs to know what a release will eventually carry and the alternative is eight repositories each guessing. +`artifacts.toml` is that list, and the release workflow has none of its own: it assembles from the table and reads the directory back against it. A row is a name, where it comes from, the repositories that fetch it, and a sentence saying why they do. There are five ways a row comes to exist, because there are five ways an artifact does. A `file` is a path this tree already holds, `zu.h` and `model.json` being the two. A `corpus` is packed by the packer of §7. A `platform` row is one artifact per tier-1 target, expanded against `platforms.toml`, so the seven move with that table rather than with this one. A `sums` row is `SHA256SUMS`, the digest of every other file of the release, written last because it is over the rest of them and read first by anything that installs from them. And a `later` row is an artifact the contract names that nothing makes yet, carrying the milestone that will make it: `cli.json` with D1, `gql.json` and `errors.json` with D2. Naming those early is the point rather than an oversight, since a consumer needs to know what a release will eventually carry and the alternative is twelve repositories each guessing. The digests are recomputed by `--verify` rather than read, which is the only version of that check worth having: a list the release wrote and then trusted proves that the file was hashed once, not that the file beside it now is that file. So a rebuilt artifact copied in after the list was written fails the release that would have published it, and a published file with no line in the list fails too, since an installer cannot check it and this one refuses to install what it cannot check. The hasher is a hundred lines in `xtask` rather than a dependency, for the same reason the tar writer is: a build tool that pulls a crate in to hash ten files has added a supply chain to the thing whose whole job is to be the trusted end of one. It runs at about 230 MiB/s on a laptop, so the digests of a whole release cost under a second. @@ -372,13 +372,13 @@ Unpacking is where the platforms disagree, and the fallback is the half worth ex ## 14. The repository table and the conductor -The split of ADR 0005 put eight repositories outside this one, and a split is a decision made once: after it, the list of what was split out is a thing every part of the project quotes. The release train dispatches to it, the README publishes it to everybody arriving through a package manager, and the artifact contract of §12 names its consumers from it. Three copies of one list is how a repository ends up on the train and off the README, which is a release publishing to a registry that no page tells anybody about. +The split of ADR 0005 put twelve repositories outside this one, and a split is a decision made once: after it, the list of what was split out is a thing every part of the project quotes. The release train dispatches to it, the README publishes it to everybody arriving through a package manager, and the artifact contract of §12 names its consumers from it. Three copies of one list is how a repository ends up on the train and off the README, which is a release publishing to a registry that no page tells anybody about. `repos.toml` is the list. A row is a name, what the repository is to the train, its support tier, the milestone it appears at, the workflow the train dispatches there, and what it reports back. The last column is the one that makes the collect step of dx/14 §6 checkable rather than aspirational: a release collects scorecards, map completeness in both directions, perf and sizes, and which repository owes which is written down before any of them can quietly owe nothing. The roles are the reason a row can be checked at all: the engine is the one repository the train does not dispatch to, because the train is its own tag, and the site is the one without a tier, because a tier is a promise about an SDK. `cargo xtask repos` holds three files to it, each in both directions. The conductor's matrix, so a repository the table drives and nothing dispatches to is caught along with a dispatch to a repository nobody decided to drive. The README's client table, including the tier column, since the tier is the promise a user reads before they depend on something. And the artifact contract's consumers, since a consumer that is not a repository is a fetch nobody will ever make and a repository that fetches nothing is either a gap in the contract or a repository that should not have been split out. Nine rows will not be nine forever, so the cost of holding all three is measured per repository and is about two microseconds of it. -`.github/workflows/conductor.yml` is the dispatching half of the train, and every dispatch in it is a no-op, because none of the eight has a release workflow to dispatch to until DX1 through DX5. What is real is the shape, which is the expensive part to get wrong late: two stages, the seven bindings and the kit first and the site after them, because a site deployed against a release whose wheels failed their corpus run documents something nobody can install. Each dispatch collects a file per report it owes, every one of them saying `pending`, and the collect step prints the eight repositories and their thirty-one empty results. When the dispatches become real, the gate is already there and the only change is what the files say. +`.github/workflows/conductor.yml` is the dispatching half of the train, and every dispatch in it is a no-op, because none of the twelve has a release workflow to dispatch to until DX1 through DX5. What is real is the shape, which is the expensive part to get wrong late: two stages, the ten bindings and the kit first and the site after them, because a site deployed against a release whose wheels failed their corpus run documents something nobody can install. Each dispatch collects a file per report it owes, every one of them saying `pending`, and the collect step prints the twelve repositories and their fifty-one empty results. When the dispatches become real, the gate is already there and the only change is what the files say. ## 15. The client table and the scorecard diff --git a/docs/clients/overview.md b/docs/clients/overview.md index 841d9c01..b110ec38 100644 --- a/docs/clients/overview.md +++ b/docs/clients/overview.md @@ -4,7 +4,7 @@ The clients of this engine, who maintains each one, and what its tier promises. -Every client is a repository of its own, with a person who answers for it and a tier that says what it promises. The tier is the same one the engine's README publishes, because a client cannot be tier 1 on one page and tier 2 on another. Audited 2026-08-22. +Every client is a repository of its own, with a person who answers for it and a tier that says what it promises. The tier is the same one the engine's README publishes, because a client cannot be tier 1 on one page and tier 2 on another. Audited 2026-08-25. | Client | Language | Package | Registry | Maintainer | Tier | |---|---|---|---|---|---| @@ -14,6 +14,10 @@ Every client is a repository of its own, with a person who answers for it and a | [zu-java](https://github.com/tamnd/zu-java) | Java | `dev.zudb` | Maven Central | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 1 | | [zu-c](https://github.com/tamnd/zu-c) | C and C++ | `zu` | vcpkg and Conan | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 1 | | [zu-dotnet](https://github.com/tamnd/zu-dotnet) | C# | `ZuDb` | NuGet | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 2 | +| [zu-kotlin](https://github.com/tamnd/zu-kotlin) | Kotlin | `dev.zudb:zu-kotlin` | Maven Central | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 2 | +| [zu-scala](https://github.com/tamnd/zu-scala) | Scala | `dev.zudb::zu-scala` | Maven Central | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 2 | +| [zu-swift](https://github.com/tamnd/zu-swift) | Swift | `zu-swift` | Swift Package Manager | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 2 | +| [zu-dart](https://github.com/tamnd/zu-dart) | Dart | `zudb` | pub.dev | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 2 | | [zu-kit](https://github.com/tamnd/zu-kit) | Rust | `zu-kit` | crates.io | Tam Nguyen Duc ([@tamnd](https://github.com/tamnd)) | 3 | ## What a tier promises @@ -55,6 +59,10 @@ The practice score, today, out of what the client's tier asks for. What is missi | [zu-java](https://github.com/tamnd/zu-java) | 1 | 90 | 90 | `api-map`, `perf` | | [zu-c](https://github.com/tamnd/zu-c) | 1 | 100 | 90 | nothing | | [zu-dotnet](https://github.com/tamnd/zu-dotnet) | 2 | 0 | 75 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability`, `api-map`, `perf` | +| [zu-kotlin](https://github.com/tamnd/zu-kotlin) | 2 | 0 | 75 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability`, `api-map`, `perf` | +| [zu-scala](https://github.com/tamnd/zu-scala) | 2 | 0 | 75 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability`, `api-map`, `perf` | +| [zu-swift](https://github.com/tamnd/zu-swift) | 2 | 0 | 75 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability`, `api-map`, `perf` | +| [zu-dart](https://github.com/tamnd/zu-dart) | 2 | 0 | 75 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability`, `api-map`, `perf` | | [zu-kit](https://github.com/tamnd/zu-kit) | 3 | 0 | 50 | `quickstart`, `reference`, `idiom`, `conditions`, `misuse`, `leaks`, `install`, `stability` | A client under its tier is a client with work left rather than a client that broke: the apparatus arrives one milestone at a time, and this page is what says which milestone is owed what. `cargo xtask clients --gate` is the run that fails on a row below its threshold, and it is the release's, which is where a promise has to hold. diff --git a/platforms.toml b/platforms.toml index f6c11610..3a81120f 100644 --- a/platforms.toml +++ b/platforms.toml @@ -1,5 +1,5 @@ schema = 2 -doc = "The platforms zu builds for, one table for nine repositories." +doc = "The platforms zu builds for, one table for thirteen repositories." audited = "2026-08-16" # The tiers of dx/14 section 2, as data rather than as a paragraph nine diff --git a/repos.toml b/repos.toml index 466e71d5..d9670a74 100644 --- a/repos.toml +++ b/repos.toml @@ -1,6 +1,6 @@ schema = 1 -doc = "The nine repositories of the split, and what the release train asks of each." -audited = "2026-08-16" +doc = "The thirteen repositories of the split, and what the release train asks of each." +audited = "2026-08-25" # One row per repository of dx/18 section 2. The engine is the first row # and the only one the train does not dispatch to, because the train is @@ -61,7 +61,7 @@ tier = 1 created = "DX4" workflow = "release.yml" reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] -doc = "dev.zudb on Maven Central, Panama with a JNI fallback, plus the Kotlin and Scala layers." +doc = "dev.zudb on Maven Central, Panama with a JNI fallback. The Kotlin and Scala layers moved to repositories of their own at DX5, because a Kotlin project takes a Kotlin artifact and a Scala project takes one built for its binary version." [[repo]] name = "zu-c" @@ -81,6 +81,42 @@ workflow = "release.yml" reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] doc = "ZuDb on NuGet, source-generated P/Invoke, NativeAOT-clean." +[[repo]] +name = "zu-kotlin" +role = "binding" +tier = 2 +created = "DX5" +workflow = "release.yml" +reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] +doc = "dev.zudb:zu-kotlin on Maven Central, Kotlin/JVM over the Panama layer, coroutines and Flow. Its own artifact rather than a package inside zu-java's jar." + +[[repo]] +name = "zu-scala" +role = "binding" +tier = 2 +created = "DX5" +workflow = "release.yml" +reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] +doc = "dev.zudb::zu-scala on Maven Central, Scala 3 first and cross published for 2.13, with the Cats Effect and ZIO modules separate so neither is the other's dependency." + +[[repo]] +name = "zu-swift" +role = "binding" +tier = 2 +created = "DX5" +workflow = "release.yml" +reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] +doc = "Swift Package Manager, a binary target per platform, AsyncSequence over rows. Its publish is a git tag, which it shares with zu-go as the one that cannot be taken back." + +[[repo]] +name = "zu-dart" +role = "binding" +tier = 2 +created = "DX5" +workflow = "release.yml" +reports = ["scorecard", "api-map", "corpus", "perf", "sizes"] +doc = "zudb on pub.dev, dart:ffi with the declarations generated from zu.h, the library shipped as a native asset rather than found on the path." + [[repo]] name = "zu-kit" role = "kit" diff --git a/toolchains.toml b/toolchains.toml index 0cc9a74b..9bfecb06 100644 --- a/toolchains.toml +++ b/toolchains.toml @@ -1,5 +1,5 @@ schema = 1 -doc = "The versions zu builds against, one table for nine repositories." +doc = "The versions zu builds against, one table for thirteen repositories." audited = "2026-08-21" # One table, referenced rather than restated, so that "which version do @@ -10,7 +10,7 @@ audited = "2026-08-21" # well as the pin, because a library that only ever builds against the # newest release finds out its floor is broken from a bug report. # -# `repos` says which of the nine repositories build against a component. +# `repos` says which of the thirteen repositories build against a component. # A component this one builds against says where the version is written, # in a `[[site]]` below it, and `cargo xtask pins` holds the file to the # table. Bumping a version is a pull request that touches this table and