Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Publishing, Licensing and Versioning

Thalweg is a workspace, not a monolith. Most crates are published independently so they can be used on their own — a pure-Rust Kafka client, a sans-io SASL state machine, an Avro-to-Arrow decoder — without taking the rest of the stack.

Licence: MIT OR Apache-2.0

Dual-licensed, at the user’s option. This is the Rust ecosystem convention and it is deliberate:

  • Both require attribution. MIT requires the copyright notice and licence text be retained. Apache-2.0 requires retained notices, a statement of changes made, and propagation of any NOTICE file. Either satisfies “attribution back to us”.
  • Offering the choice maximises adoption. Apache-2.0 carries an express patent grant, which some organisations require; MIT is simpler and compatible with GPL-2.0-only projects, which Apache-2.0 is not. Dual licensing means a downstream user’s compliance regime is never a reason not to use the crate.

Neither licence compels visible attribution — no “powered by” notice, no advertising clause (the old BSD-4-clause advertising requirement is deprecated and we do not reintroduce it). If visible credit is ever wanted, that is a courtesy request in a README or a trademark policy, not a licence term.

Copyright holder. Currently The Thalweg Authors, which is a placeholder. The legal entity to name is a decision for the project owners; it appears in both licence files and should be settled before the first publish.

Every published crate is a standalone product

A crate that is published carries obligations beyond compiling:

  • Its own version, and its own release cadence. version is deliberately not a workspace-inherited field — a consumer taking one crate should not be forced to track the others.
  • Its own README, rendered on crates.io. It must say what the crate does without assuming the reader knows what Thalweg is.
  • Its own tests, meeting the standard in testing/TESTING.md — and for sans-io crates, tests that run without a container or a runtime, which is a large part of what makes them reusable at all.
  • Semver discipline. Below 1.0.0, a minor bump may break; from 1.0.0, breaking changes need a major. cargo-public-api snapshots guard the surface.
  • Documented MSRV, inherited from the workspace, raised only in a minor bump.
  • Complete crates.io metadata: description, keywords, categories, repository, licence, readme.

Four crates are not published, and say so in their manifests: the twg-cli binary, the two *-arrow composition crates, and the twg-e2e harness. They are glue and have no standalone use.

API reference is self-hosted, not on docs.rs

docs.rs only builds published crates from crates.io, so it carries nothing while the workspace is unpublished, and never carries the four internal crates. The API reference is therefore self-hosted: cargo doc is rendered locally and committed under docs/site/api/, served at docs.thalweg.dev/api/ alongside the prose reference at /reference/. It regenerates on commit via the pre-commit hook. See ADR-0059 and operations/DOCS-DEPLOY.md — contributors need the Rust toolchain and mdbook installed for local regeneration. Once a crate is published, docs.rs builds it too; the self-hosted copy remains the one place the whole workspace (internal crates included) is documented together.

Three tensions this creates

Independent publishing is not free. These are real and are stated here rather than discovered later.

1. The Arrow version chokepoint becomes a public contract

Internally, twg-codec-core holds the only arrow re-export and every codec crate depends on it, so the workspace pins exactly one Arrow version. That discipline still holds between our crates — but a downstream consumer mixing twg-codec-avro with their own arrow dependency can hit a version mismatch, and Arrow’s frequent breaking releases make this likely rather than theoretical.

Therefore: arrow is a public dependency of the codec crates. Its supported version must be stated in each crate’s README and treated as part of the API — an Arrow major bump is a breaking change for us, requiring a major version bump, not a patch. This is more constraining than it would be for an internal-only crate.

2. Release ordering and cascade

Forty-seven crates in a dependency graph means a change to twg-codec-core cascades: every codec crate needs a dependency bump and a release. Doing that by hand does not scale and will be got wrong.

Therefore: release-plz, configured in ../release-plz.toml and run by the Release workflow. It works out which crates actually changed, bumps them and their dependents, updates per-crate changelogs, and publishes in topological order. It honours publish = false, so the four internal crates are excluded without being listed.

Two jobs: release-pr opens a reviewable PR with the proposed bumps, and release publishes on merge. Releases are therefore reviewed rather than automatic.

Publish-by-design is eventual, not immediate. Most publish = true crates are still unimplemented stubs, so release-plz is currently gated to auto-publish only the five implemented, publish-ready cratestwg-proto-flatten, twg-proto-schema, twg-proto-decode, twg-type-map, and twg-udf. The rest stay gated in ../release-plz.toml until their code lands; “published independently” describes the design intent for all of them, not what releases today.

The codec family shares a version group (codec-core, type-map, the five codec crates, plus twg-proto-schema and twg-proto-decode). They share arrow as a public dependency through the single re-export, so an Arrow major bump breaks all of them simultaneously and a change to the core traits forces a coordinated release anyway — the grouping reflects a real coupling and collapses a six-crate bump cascade into one. The cost is that a consumer of twg-codec-json takes a bump when twg-codec-avro changes; that is acceptable precisely because the shared Arrow contract means they were never independent. True leaves — twg-wire-sasl, twg-wire-tls, twg-wire-compression, twg-resource-loader — release independently.

cargo-semver-checks is enabled and blocks a non-major bump that breaks the public API. That matters more here than in most workspaces: because arrow is a public dependency of the codec crates, an Arrow type in a signature makes an Arrow major bump our breaking change, and catching that mechanically beats catching it in someone else’s build.

Commit messages become load-bearing. release-plz derives bumps and changelogs from Conventional Commits, with the crate name (minus the twg- prefix) as the scope. See AGENTS.md. Commits predating the convention are plain prose and are not retrofitted.

Secrets required

SecretPurpose
CARGO_REGISTRY_TOKENcrates.io API token, scoped to publish-update and publish-new
RELEASE_PLZ_TOKENPAT or GitHub App token with contents + pull-requests write. The default GITHUB_TOKEN cannot trigger CI on a PR it created, so the release PR would sit untested

3. Discoverability versus the prefix

twg- is compact and signals provenance, which is right inside the workspace. But someone searching crates.io for a pure-Rust Kafka client will not search “twg”. The prefix trades discoverability for consistency and for not squatting generic names.

This is accepted, with one mitigation: each crate’s README and its keywords must carry the descriptive terms someone would actually search for, since keyword search is how these crates will be found rather than by name.

Before the first publish

  • Docs-visibility coupling resolved: the documentation site is public, so crate READMEs linking to docs.thalweg.dev resolve for anyone arriving from crates.io (see operations/DOCS-DEPLOY.md)
  • Settle the copyright holder in both licence files
  • Add the two release secrets, then prove release-plz with a dry run before the first real publish
  • State the supported arrow version in each codec crate’s README (Arrow 58, per ADR-0003)
  • Verify the twg- prefix is unclaimed for every crate name to be published
  • cargo-public-api baselines committed for crates with a stability promise
  • Confirm every published crate’s tests pass standalone, outside the workspace