Read the initial identity document from the blob addressed by the
repository ID and include its founder in the set of namespaces to
be fetched.
This lets clones resolve the identity COB root even if the founder
is not a delegate anymore.
Add a test that demonstrates erroneous behavior in case the founder of a
repository is not a delegate anymore.
The change in commit 388d985578 changed
the semantics of loading the identity COB so that the founder is now
crucial.
Adding the "public seeds" of the network to the `preferred_seeds`
configuration on all nodes causes all nodes to connect to the preferred
seeds and to have them attempt to keep the connection open at all times.
Assuming some growth in the network, and no growth in the number of
"public seeds", this means that the number of inbound connections to the
public seeds grows as well. Once the public seeds are at capacity,
the network as a whole is in some sense bottlenecked by the capacity of
the public seeds: As the network grows further, more and more outbound
connections to the public seeds will fail.
To avoid such issues, do not populate `preferred_seeds`.
The functions `PayloadId::{project,canonical_refs}` have two
weaknesses:
1. They always execute the checks in `from_str` for the same
`str` over and over again.
2. They do not return a reference, thus, callers that only
need a reference make a copy that they then discard.
For improved performance, switch to `LazyLocks`. Keep the less efficient
functions, but deprecate them.
Many codepaths error if the `xyz.radicle.project` payload is not present
in the identity document. Change these to fall back gracefully.
Loading of payloads is unified in `GetPayload`.
An important detail is that `PayloadError::NotFound` is removed, and
`Option` is used instead.
When executing programs on Windows, we suppress creating a new window,
and, if the process should continue to run in the background, we
explicitly create a new process group and detach.
Spawning `rad diff` works, but prints a deprecation notice, because
`rad diff` itself is deprecated. `rad patch diff` however is not
deprecated and should just take a shortcut.
Make `Identity::new` private, to have more control over the initial
revision of the COB. Also assert that the author of the revision is the
solve delegate of the initial revision, i.e., the "founder".
Do not iterate over all user namespaces to find the ID of the identity
COB, but load from the founder's namespace specifically.
If the namespace of the founder is gone, attempt to recover by
walking back from `rad/id`.
Plenty conservative `debug_assert!`s, cause panics as soon as two
actions that should be added to the timeline appear within the same
operation. But there is no code in place to prevent this.
It also does not seem to be problematic to visit the same operation
multiple times (once per contained action).
A set that retains iteration order does the job.
Analysis of this bug was done over Zulip, see
<https://radicle.zulipchat.com/#narrow/channel/369873-Support/topic/radicle-node.201.2E10.20segfaults/with/616991470>.
`Update::Reviewed::review` is boxed due to:
error: large size difference between variants
--> crates/radicle-cli/src/terminal/patch/timeline.rs:158:1
|
158 | / enum Update<'a> {
159 | | /// A revision of the patch was reviewed.
160 | | Reviewed { review: Review },
| | --------------------------- the largest variant contains at least 352 bytes
161 | | /// A revision of the patch was merged.
162 | |/ Merged {
163 | || author: Author<'a>,
164 | || /// If the merge is none, this means that it was a fast-forward merge.
165 | || merge: Option<Merge>,
166 | || },
| ||_____- the second-largest variant contains at least 120 bytes
167 | | }
| |__^ the entire enum is at least 352 bytes
The tests `rad-warn-ipv6.md` and `rad-warn-old-nodes.md` are meant to
primarily test the output of warnings. They test the output twice, via
`rad debug` and via `rad node status`.
Testing the output via one command should be sufficient.
Similar to `rad [issue|patch] cache`, this will re-compute cached state
about the identity document. The identity COB does not cache to SQLite,
but there is `refs/rad/id`, which can become stale.
This is the result of
cp -r \
../radicle-git/radicle-git-ext/git-ref-format/core/src/{{cbor,check,deriv,lit,name,refspec,serde}.rs,{name,refspec}} \
crates/radicle-git-ref-format/src/
with the radicle-git workspace at commit
8c5df5ecbd3f2507d323608b52e1fbf7b177d864.
In the radicle-git workspace, crates brought their own testing crates
along with them, usually in a subdirectory named "t". In the heartwood
workspace we use tests within the same crate.
This commit only moves files, so that it is easier to follow changes to
them in the upcoming commit.
In `/Cargo.toml` and all `/crates/*/Cargo.toml`, remove homepage package
metadata. This field is inteded to carry the *homepage of the crate*. In
our case, no crate has an actual homepage on its own.
In all `/crates/*/Cargo.toml`, split package metadata into two or three
blocks:
1. name, version, and description (in this order)
2. Further metadata defined per crate.
3. References to the workspace.
Lines in blocks (2.) and (3.) are sorted lexicographically, and blocks
are separated by empty lines.
In `radicle-cli`, which is a binary crate (!) a few `pub use`s can be
removed. This forces `radicle-remote-helper` to depend on the library
crate `radicle-term` to access some of the types that were re-exported
from `radicle-cli`, making the dependency more obvious, and minimizing
the public symbols on `radicle-cli`.
The dependency on `radicle-cli` cannot be removed from
`radicle-remote-helper` easily, so is kept.
Running `cargo semver-checks` errors with
error: unsupported rustdoc format v57 for file:
…/heartwood/target/semver-checks/local-radicle-…/target/doc/radicle.json
(supported formats are v53, v55, v56)
Support for rustdoc format v57 is part of cargo-semver-checks 0.46,
which is not part of NixOS 25.11, which has reached EOL.
Re-point the "nixpkgs-stable" input to NixOS 26.05 to reach a newer
cargo-semver-checks and a NixOS version that is supported.
Update the flake.
In `df6524e7e21027c4eb128dd2eaab0a6d70a6d76d`, default features for
`zeroize` were disabled for the workspace in `/Cargo.toml`, accidentally
affecting `radicle-term`, since `radicle-term` depends on
`impl Zeroize for String` which is available on feature "alloc" only.
The Rust version used for development is tracked in
`/rust-toolchain.toml`, and the value specified there is used to set
`RUST_VERSION` in the `build/build` script. Thus, when using
`build/build`, the default value is unimportant.
However, ensuring that the default value remains consistent with
`/rust-toolchain.toml` is a maintenance burden.
Remove the default value. This increases friction of building without
`build/build`, but makes maintenance easier.
The re-exports from other crates got quite confusing.
Remove all `pub use` from `radicle-node`, and rewrite all `use`s
accordingly.
The result is a slight increase in the number of `use` lines, but with
improved clarity on which type is actually imported.
It appears that `radicle_node::PROTOCOL_VERSION` (which is a re-export
of `radicle::node::PROTOCOL_VERSION` is the only symbol exported from
`radicle-node` that is used in `radicle-cli` (except tests).
Since `radicle-cli` depends on `radicle` anyway, it is questionable how
much sense that re-export and use from a binary crate (!) makes.
Remove the re-export.
Reasons for the rewrite:
- Migrate away from `ec25519` as our implementation of Ed25519,
because that crate is unmaintained.
Instead of `ec25519` we use `ed25519-dalek` which is the most popular
implementation of Ed25519 in Rust.
- Migrate away from `amplify`, because we make very little use of its
features. Here, we only use `amplify::Bytes32`, which is just a thin
wrapper of `[u8; 32]`.
Instead of `amplify::Bytes32` we use `type PublicKeyBytes = [u8; 32]`.
The realization that `ExtendedSignature` is not really tied to SSH, but
is merely a representation of a key together with a signature, allows to
relax the bounds on traits in `radicle-cob`.
Separating out uses of "public key" (a rather simple model of a key,
just 32 bytes) and "verifying key" (an actual point on the Edwards
curve, ready to verify signatures, using more memory than just 32 bytes)
allows a more sensible definition of `crypto::Signer`, which propagates
to `radicle-cob`, `radicle`, `radicle-cli`, `radicle-node` and
simplifies trait bounds.
In `radicle-node`, make the fact clearer that actually the secret key
itself is required (for our implementation of Diffie-Hellman) not
"just" signing. This results in removal of the generic argument
(usually called `G`) of multiple types.
Concretizing the types in `radicle-node` this way, i.e. by (morally)
setting `G = radicle_crypto::SigningKey`, leads to removal of
`MemorySigner` and `MockSigner`, as they really just wrap a signing
key.
Re-exporting `ed25519::Signature` (via re-export in `ed25519-dalek`)
is hopefully more future-proof than using our own type for signatures.
However, the fact that `ed25519::Signature` is not serializable and
does not implement `std::hash::Hash` means that we have to resort to
ad-hoc solutions in `radicle`:
1. The Identity COB requires serialization of signatures. These are
generally questionable, since the security of the Identity COB
should rely on the security of Signed References. In the medium
term we might even want to consider ignoring signatures in actions.
2. When reading Signed References, detection of Replay Attacks uses
a `HashMap` keyed by signatures. This detection only exists for
backwards compatibility.
These two use cases are exotic enough to warrant ad-hoc implementations.
By not wrapping `ed25519::Signature`, we encourage writing more widely
compatible code in the future.
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
In preparation of a rewrite of `radicle-crypto`, move the
responsibility for generating a cryptographically secure
seed from `radicle` (transitively via dependency on
`radicle-crypto` and `ec25519`) to the dependent of `radicle`.
Applications should decide how to obtain randomness, not `radicle` via
some far-removed dependency.
Within the workspace, the only dependent using `Profile::seed` is
`radicle-cli`, in particular for the `rad auth` subcommand. To obtain
random bytes, `radicle-cli` depends on `getrandom` and uses
`getrandom::fill`.
Clippy reports dead code on all variants except `Hours` on `Expiry`,
since the type is defined in a binary crate.
Allow dead code here, just in case we want to use the other variants in
the future. They are not a maintenance burden, as the implementation is
simple and does not see much change.
Slightly refactor:
- `impl std::fmt::Display for Expiry` to look cleaner.
- `fn collect` to borrow `RepoId`, since it only needs
a borrow, and a a borrow of `Expiry` to spare a copy
at the only call site.
- The constant for the default value of `Expiry` to be
associated with the type `Expiry`.
Remove a remark about the default value of `Expiry` from `FetchConfig`,
as the default value is not a concern there, but rather wherever
`Expiry` is initialized.
For most use-cases, i.e., except some in `radicle-node`, the channels
in `std::sync::mpsc` suffice, allowing us to get rid of the dependency
`crossbeam-channel`.
Crossbeam is sometimes mentioned to offer higher performance compared
to `std::sync::mpsc`.
For usage in `radicle-signals` (and the counterparts in `radicle-node`
and `radicle-term`) performance is irrelevant, as these channels are
used to send a singular message (to the effect of shutting down the
node or stopping the spinner). These could potentially use
`std::sync::oneshot` in the future, which is nightly-only as of
2026-06-17, see <https://github.com/rust-lang/rust/issues/143674>.
Usage in `radicle-protocol` and `radicle`, is a heavier duty.
The channels pass commands to the service (arriving e.g. via the
control socket in `radicle-node`), events from the service to other
listeners (e.g. the control socket), and fetch results from workers to
the service. For user interaction, performance is irrelevant, since
the messages are in the tens or hundreds per second. Even for delivering
events and programmatic usage of commands, thousands to hundreds of
thousands of messages per second are plenty.
Note that these channel implementations are benchmarked in the millions
of operations per second, see e.g.:
- <https://github.com/crossbeam-rs/crossbeam/tree/b23b7e8eca2efdad9bdc1ceb1aee1207a852c03b/crossbeam-channel/benchmarks#results>
- <https://github.com/fereidani/rust-channel-benchmarks/blob/b4c979a94482e5012734657c2ee153b528bc56d6/README.md>
Also, `crossbeam-channel` was merged into `std` in 2022-11, and we do
not believe that performance of the implementations has siginifcantly
drifted since then, see <https://github.com/rust-lang/rust/pull/93563>.
One minor downside is that the channels in the standard library do not
expose the number of pending messages.
The update to `flake.lock` is a simple
nix flake update rust-overlay
to be able to reach 1.97.1.
The changes to `**/*.rs` files are improvements suggested by Clippy.
The `amplify` crate is only used in a handful places to derive `impl`s
that are not very complex.
Provide the required `impl`s directly, and remove the dependency.
The tests in `crates/radicle-node/src/tests/.rs` repeat code quite a
bit, for example, construction of IP addresses. Also, `Peer` is
unnecessarily complex (too many struct members, state indicated via a
boolean, type parameter that is always instantiated for the same type).
Clean up `Peer` by removing multiple struct members, remove the
unnecessary type parameter, and provide convenience functions that make
statement of the actual tests a little less repetitive and more concise.
For test networks, five standard nodes (Amy, Bob, Cid, Dan, Eve) are all
easy to construct. Tests are refactored to make use of these nodes in
lexicgraphic oreder. The order of initialization may differ.
Modification of configuration is done by passing `FnMut`.
In the future, using the builder pattern could be explored.
The functions
- `Session::run_handshake`
- `Session::stream`
- `Session::disconnect`
are unused.
The associated type `Session::Inner` is unused.
The function `Session::display` is of limited use with little benefit.
`Transport::link_direction` is only used in implementations of
`Debug` and `Display`, but is not helpful otherwise.
Small improvements:
- Remove confusing comments in test
- Rename `has_active_sibling_accept`
to `has_accepted_active_sibling`
- Small refactoring to avoid `… } else { None }`
`inquire::PasswordDisplayMode::Masked` causes inquire to
render characters of the password text input marked as
different characters (asterisks).
Users reported that they would prefer password text input to not be
rendered at all, i.e., have no indication of input. This is inquire's
default, so remove the lines that set the display mode.
Parsing of IPv6 addresses not enclosed in square brackets causes a
warning to be logged.
This is too strict, since the user can not influence IPv6 addresses
received over the network, but only those in their configuration.
Remove the warning from parsing, and instead carry a boolean to warn the
user later.
`cargo check` fails because `human-panic` and `sysinfo` require at least
1.88.0.
+++ command cargo check --release --locked --all-targets
error: rustc 1.85.0 is not supported by the following packages:
human-panic@2.0.6 requires rustc 1.88
sysinfo@0.37.2 requires rustc 1.88
Bump MSRV to fix this.
1.88.0 introduced [let chains], which in turn has `clippy` warn about
nested if statements. All of these sites are fixed in this change.
1.87.0 introduced [`is_multiple_of`], which is a more readable version
of `x % y == 0`.
[let chains]: https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/#let-chains
[`is_multiple_of`]: https://doc.rust-lang.org/std/primitive.usize.html#method.is_multiple_of
In `cb57e6560fe7767e79b87473a11261fbb82ce84c` the loading of systemd
credentials was refactored. The idea was to only change the accepted IDs
slightly after switching from radicle.xyz to radicle.dev. Unfortunately,
this was done in a sloppy way. The two cases for passphrase and secret
actually differ in ways that was overlooked.
Rectify these mistakes and restore the old behavior:
1. The logic to read the passphrase from the file that contains the
credential is moved out of `load_credential` since it only applies
to the passphrase, not the secret.
2. The warnings printed are now generic, and will not mention the
passphrase in case reading the path for the secret fails.
Because we changed our main domain from radicle.xyz to radicle.dev, we
should also change our systemd credential identifiers.
Handling is move into a new function, which also checks the old names
for backwards compatibility.
With `connect: std::collections::HashSet`, we suffer unpredictable reordering
of values in our test cases.
Use `connect: indexmap::IndexSet` instead, which preserves insertion
order for iteration.