Commit Graph

29 Commits

Author SHA1 Message Date
Lorenz Leutgeb f223afd9d7 radicle: Refactor COB Storage Access
Instead of passing the signer as an argument to many methods on `Store`,
scope the `Store` itself to a signer.

This further allows to differentiate two different access modes on the
store in `radicle::cob::store::access`: `WriteAs` (which requires signer)
and `ReadOnly` (which does not require a signer).

The caches for issues and patches in `radicle::cob::{issue,patch}::Cache`
are concretised by removing the first type parameter, since it was
specific to issues and patches anyway. This was done in this commit as
it touches very similar usage sites.

Make `Device` less prominent, and instead lean more heavily towards
traits from the `signature` crate, such as `Keypair` and `Verifier`
in addition to `Signer`. Trait bounds regarding `Signer` could be
simplified, but this is left for the future.

In `radicle-cli`, the function `term::cob::patches_mut`, which generates
errors with a hint is used instead of the lower-level `Profile::patches_mut`.

Commands `rad issue cache` and `rad patch cache` now construct a
writeable cache on top of a read-only store.

Many knock-on changes are handled as well, to arrive at a clean state.
2026-03-31 17:57:14 +01:00
Lorenz Leutgeb 8bac24d6ab
rust/edition/fmt: 2021 → 2024
Reformat code with `rustfmt`, now that the edition changed from
Rust 2021 to Rust 2024.
2026-03-30 16:57:51 +02:00
Lorenz Leutgeb 6d771abfcc radicle/storage/refs: Remove `RefAt::load`
This is a very thin wrapper around `SignedRefs::load_at` which only
has a single usage in all of the workspace. Remove it.
2026-03-25 08:19:36 +00:00
Lorenz Leutgeb ec36d1c3ae radicle/sigrefs: Merge `SignedRefs{,At}`
Since now all callers expect `SignedRefsAt`, merge the two structs
and name the result just `SignedRefs`.
2026-03-25 08:07:49 +00:00
Lorenz Leutgeb 07011233eb radicle/sigrefs: Use `SignedRefsAt` everywhere
To make more information available to callers, prefer returning
`SignedRefsAt` everywhere.
2026-03-25 08:07:49 +00:00
Fintan Halpenny 07f3d56559 protocol: Allow configuration of `radicle_fetch::Config`
The configuration of the fetch process was only achieved at the setup
of the of the node.
This limits the ability to pass configuration options to the fetch
worker while the node service is running.

This change introduces `radicle_protocol::fetcher::state::FetchConfig`
to allow configuration options to be kept track of. This includes the
previous `timeout` option, but now also includes the
`radicle_fetch::Config`.

The service now produces the `FetchConfig` and passes it through to
the fetch worker.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb e245e3115b fetch: Configure Minimum Feature Level
Add a configuration knob `node.fetch.signedReferences.featureLevel.minimum` to
allow node operators to enforce more strict verification when fetching
Signed References.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 47063057a8 radicle/sigrefs: Automatically Migrate
In order to allow automatic migration to the feature level 'parent',
introduce a way to force writing signed references, even if the
user-controlled refs are unchanged.

To detect the special case of a Signed References history with a single
and root commit (which would otherwise be detected as feature level
'parent', even though the `refs/rad/sigrefs-parent` ref cannot be
written), keep information about the parent commit in `SignedRefs`.

The output of `rad inspect --sigrefs` is changed to match the more
strict interpretation of the feature level for histories with a single
and root commit.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 8bc3ffc0b0 radicle/sigrefs: Detect features when writing
In order to respond to the caller with the correct feature level in
case sigrefs are to be saved but unchanged, the write side needs to
care about feature levels. This is done by sharing more code with the
read side.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 372a78754a radicle/sigrefs: Implement feature detection
To be able to reason about backwards compatibility, downgrade attacks,
and future configurable enforcement of minimum required (security)
features, introduce the concept of a "feature level" in Signed
References.

In this implementation feature levels are assumed to be monotonic, i.e.,
that higher feature levels behave like lower ones, and only add
semantics on top.

Implementing this as an `enum` that is `Ord` flows nicely.

Treatment of the write-side is left for the next commit. Currently,
the write side "promises" `FeatureLevel::Parent`, but is not able
to "keep the promise", since it does not actually inspect the
feature level of the head in case it is unchanged.

CHANGELOG
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb c824d317e4
radicle: Fix `storage::refs::Error::is_not_found`
Detection of errors of the "not found" kind does not account for the
respective variant in `sigrefs::read::error::Read`. Fix this by adding
it.
2026-03-18 18:55:28 +01:00
Lorenz Leutgeb 304a66311a
radicle: Remove generics for verification markers
`SignedRefs` are now always verified.
2026-03-17 21:31:42 +01:00
Fintan Halpenny d40fa9a347 radicle/sigrefs: Switch to new implementation 2026-03-17 21:31:42 +01:00
Fintan Halpenny d3bc868e84 radicle/sigrefs: Rewrite Signed References
Split up signed references into its read and write components.

On the write side:
 - Preserve the old behavior of writing references to the blob `/refs`
   and sign over the blob.
 - Ensure `refs/rad/root` is contained in the `/refs` blob.
 - Ensure `refs/rad/sigrefs` is *not* contained in the `/refs` blob.
 - Introduce a new (internal) reference `refs/rad/sigrefs-parent`
   so that no two `/refs` blob are equal, even if they contain
   the same set of (non-internal) refs.

On the read side:
 - Preserve the verification of the signature in `/signature` and
   the reference `refs/rad/root` (if present).
 - Fail verification of `refs/rad/root` is not present.
 - Protect against replay attacks by walking the history of the
   head of `refs/rad/sigrefs`, skipping interpretation of `/refs`
   blobs in case they are identical to a previous `/refs` blob.
   This is achieved by searching for repeated contents of the
   `/signature` blob.

The reference `refs/rad/sigrefs-parent` is never read from or written to
the Git repository in storage.

The pre-existing implementation of signed references did not include
a nonce, thus duplicate but legitimate sets of references could not
be distinguished from maliciously replayed sets of references.

The new implementation uses `radicle-git-metadata` which is moved from
`dev-dependencies` to `dependencies`.
2026-03-17 21:31:42 +01:00
Fintan Halpenny 0c47d06f2d radicle/storage/refs: Strengthen Encapsulation
`radicle::storage::Refs` carries a map from reference names to OIDs, and
there are some invariants.
2026-03-16 14:30:48 +01:00
Fintan Halpenny ba9c09facb radicle/refs: Better `SignedRefs` Encapsulation
Ensure that the public interface of signed references does not leak its
implementation details.

This allows the evolution of the interface in a safer manner, and does
not leak implementation details to the rest of the crate or any
dependents.
2026-03-16 14:30:48 +01:00
Fintan Halpenny f7ff4d8f4e radicle/arbitrary: Move `impl Arbitrary` of refs
Avoid leaking the types of the signed reference types by moving their
`Arbitrary` implementations to a `refs::arbitrary` sub-module.

Since `SignedRefsAt` require a correctly signed payload, a helper
constructor is added: `signed_refs_at`.
In turn, a function, `arbitrary::with_gen` is introduced so that this
constructor can be easily called with a `Gen` value.
2026-03-16 14:30:48 +01:00
Lorenz Leutgeb faeee9f370 crypto: Optionally provide JSON Schema
Providing the schema in `radicle-crypto` saves a lot of repetition
in `radicle`, and can be done via an optional dependency.
2026-02-10 09:39:34 +00:00
Fintan Halpenny 7cf73300f6 radicle/storage/refs: Derive `Hash` for `RefsAt` 2026-02-05 20:04:28 +01:00
Yaroslav Halchenko 32d77641ab chore: Fix spelling errors with codespell 2025-11-01 12:11:02 +01:00
Lorenz Leutgeb b0beef4391
workspace/rust/clippy: Fix all warnings
Fix clippy warnings following the upgrade of the Rust toolchain to
version 1.90. Almost all are "mismatched_lifetime_syntaxes".
2025-10-17 12:56:43 +02:00
Lorenz Leutgeb b7cfcfff75
workspace: A Little Less `git2`
Make `git2` an *optional* dependency of `radicle-cob`, and refactor
`radicle` to depend on crates that in turn do not depend on `git2`
*non-optionally*

The main offending dependency of `radicle-cob` is `radicle-git-ext`
from the `radicle-git` workspace in repository
(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on
`git2`.

So, to achieve removal of this dependency:

 1. The crate is refactored to depend on the new crates
    `radicle-git-ref-format` `radicle-git-metadata`, and
    `radicle-oid` introduced in the previous commits, instead of
    `radicle-git-ext`.
 2. Some code from the `radicle-git-ext` crate in the `radicle-git`
    workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
    copied. See `crates/radicle-cob/src/backend/git/commit.rs`.

This cascades to `radicle` and its dependents.

  Firstly, the there is an
`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made
it very convenient to just deref to obtain the wrapped `git2::Oid`,
so there are many expressions of the shape `*oid` in `radicle` and
its dependents. However `radicle-oid` does not provide
`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,
`Target` is an associated type, and not a type parameter, so an
implementation of `Deref` would tie the new `Oid` too tightly to a
particular implementation.
Instead, work with `impl From<radicle_oid::Oid> for git2::Oid`
(which can be enabled using the feature flag `radicle-oid/git2`).
This explains the changes from `*oid` to `oid.into()` at every
transition to "`git2` land".

  Secondly, `radicle` and its dependents are refactored to also depend
on `radicle-git-ref-format` and (much less prominently)
`radicle-git-metadata` instead of `radicle-git-ext`
This is to avoid pulling in `git2` via these dependencies.

  Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,
they are at the same time also cleaned up. Notably, the types from
`radicle-git-ref-format` are re-exported under `fmt` only, not "twice".

While overall this obviously is very much a breaking change, these
changes should mostly amount to changing from `Deref` to `Into`, i.e.,
`*oid` → `oid.into()` and rewriting imports for `radicle::git::fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
2025-10-13 16:14:23 +02:00
Lorenz Leutgeb ea562215ea
radicle/git/raw: Introduce `trait ErrorExt`
This extension trait is more ergonomic than what `radicle-git-ext`
provides.
2025-10-10 11:29:24 +02:00
Lorenz Leutgeb 880634acd4
radicle/git/raw: Capture all `git2` re-exports
A refactoring internal to the `radicle` crate, with the goal of
making dependency on `git2` clearer and more controlled.
`radicle::git::raw` is changed from a complete re-export of `git2`
to a module that selectively re-exports (many) members of `git2`.
required to build the workspace (potentially breaking dependents
outside the workspace, but given just how many types are
re-exported, this seems unlikely).

In the future, no more `use git2::…` statements should be added
outside of `crates/radicle/src/git/raw.rs`. This is in an effort to
decrease dependence on `git2` in the future.
2025-10-10 11:29:24 +02:00
Lorenz Leutgeb 9689de9af0
node: Allow announcing refs for given public keys
The "announce references" command does only take a repository ID, and
carries the implicit assumption that the namespace with the same public
key as the Node ID of the receiving `radicle-node` process should be
announced, but no other namespace.

In the spirit of separating user and node identity, relax this, so that
the command also carries the public keys for which the announcement
should be made. This allows to announce arbitrary namespaces out of
storage via command.

For now, this feature is not exposed to the CLI, but rather:
 - `rad sync` always announce for the public key of the active profile.
 - `git-remote-rad` ditto, as it calls the same codepath.
2025-10-09 07:55:10 +02:00
Lorenz Leutgeb 766e281d39 radicle: Allow creating bare clones 2025-09-15 10:21:09 +01:00
Sebastian Martinez 1d7478cd90 radicle: introduce `cob::common::Title`
This patch adds the `cob::common::Title` struct, this allows instead of
using `std::str::String` or generics and traits for it, to define more
precisely what a title should be.
It trims the provided string and makes sure it contains no carriage
return or new line characters.

Where a `std::str::String` makes more sense so it's easier to mutate it,
we keep the code as is.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-08-19 17:05:33 +02:00
Lorenz Leutgeb 9068033789 chore: Automated fixes generated by clippy 2025-07-25 11:25:05 +02:00
Lorenz Leutgeb 5bc2dc677b repo: Move workspace crates into `crates` subdirectory
This keeps the top level of the repo cleaner, e.g.
<https://github.com/radicle-dev/heartwood> scrolls less and
<https://app.radicle.xyz/nodes/rosa.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/>
might show more files at the top level.

This way we also don't have to explicitly name all crates in
`Cargo.toml`.
2025-06-09 15:09:21 +02:00
Renamed from radicle/src/storage/refs.rs (Browse further)