Commit Graph
3326 Commits
Author SHA1 Message Date
Lorenz Leutgeb 5083212468 cargo/memmap2: 0.9.10 → 0.9.11
https://rustsec.org/advisories/RUSTSEC-2026-0186
2026-10-22 19:47:03 +02:00
Lorenz Leutgeb 4e89e16cb3 cargo/faster-hex: 0.10.0 → 0.10.1
https://rustsec.org/advisories/RUSTSEC-2026-0306
2026-10-22 19:46:43 +02:00
Lorenz Leutgeb 1e6d444d99 cargo/anyhow: 1.0.102 → 1.0.103
https://rustsec.org/advisories/RUSTSEC-2026-0190
2026-10-22 19:46:06 +02:00
Xeonacid 5e0f26a73f cli/tests: Give private-clone success paths more time
The unauthorized clone in `rad-init-private-clone` keeps a 1s timeout
so it still fails quickly. The success paths used 1s and 3s, which is
not enough on slower hosts: `rad sync --announce` reports "All seeds
timed out" after Bob has already fetched, and `rad seed`/`rad clone`
miss the fetch handshake.

Raise those success timeouts to 30s, matching `DEFAULT_TIMEOUT`. The
wait ends as soon as the target is met, so passing runs are not slower.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:56:30 +02:00
Xeonacid 2054b7a894 node/test: Match large-fetch timeout to payload size
`test_large_fetch` populates the repository with `scale.max(3)` but
waits only `9 * scale` seconds for `RefsFetched`. At the default scale
of 1 that is about 9MB of incompressible data with a 9s budget.

On slower hosts the wait times out before the fetch finishes. Use
`scale.max(3)` for the timeout as well, matching the payload size.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:53:41 +02:00
Xeonacid ec3a58050b cli/tests: Wait for seed discovery after clone
`git_push_canonical` clones the repository as Bob and then immediately
runs `git push` as Alice, expecting "Synced with 1 seed(s)".

`clone` announces Bob's inventory, but Alice only treats Bob as a seed
after that announcement is applied to her routing table. On a slower
host the reactor can still be busy when Alice looks up seeds, so she
reports "No seeds found".

Wait until Alice's routing table contains Bob for the repository before
pushing. Apply the same wait to the lightweight-tags variant, which
uses the same setup.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:48:26 +02:00
Xeonacid 422b7c033f cli/tests: Treat exit code 127 as "not found"
`fn program_reports_version` skips optional tools such as `jq` and
`jj` when they are missing. It currently only treats an error returned
by `std::process::Command::status` of kind
`std::io::ErrorKind::NotFound` as absence.

Rust's `std::command::Command` uses glibc's `posix_spawn` on Unix.
When `execve` fails after `spawn` has already returned, POSIX requires
the child to exit with code 127 rather than reporting `errno` to the
parent. glibc usually copies that failure back through a
`CLONE_VM|CLONE_VFORK` shared-stack channel, but not every
implementation preserves it. qemu-user, OpenBSD, and HPPA surface a
successful `spawn` and an exit code of 127 instead.

On those platforms the helper panics, so tests that are meant to skip
without `jq` or `jj` fail. Treat exit status 127 the same as
`NotFound`.

See `man:posix_spawn(3)` and POSIX.1.

Signed-off-by: Xeonacid <h.dwwwwww@gmail.com>
2026-10-20 17:40:39 +02:00
Lorenz Leutgeb 515b5884d0 master → main
The default branch of the repository changed from `master` to `main`,
so adapt scripts and documentation.
2026-10-20 16:31:17 +02:00
Lorenz Leutgeb 28f740cb8a radicle/storage/git/cob: List using a glob
Instead of iterating over *all references* and filtering for those that
look like a COB of the expected type, use a glob so that Git can do more
lifting for us.
2026-10-18 21:12:10 +02:00
Lorenz Leutgeb 0f0d64203a radicle/git/refs/storage: Tune glob helpers
Rename `cob` to `cob_with_namespace` and `cobs` to `cob` to make
room for a new `cobs` that can be used to list all COBs (by type) in a
repo.
2026-10-18 21:12:10 +02:00
Lorenz Leutgeb a9525f2173 git-ref-format: Allow composing patterns 2026-10-18 21:12:10 +02:00
Yorgos SaslisandLorenz Leutgeb f4bc34450e radicle/storage/cob: Skip non-commit refs
Valid COB refs always point to commits. However, any peer can write
an invalid COB ref that points to a blob. When the node fetches
that peer's refs and it tries to load them, it would fail the whole
`objects` or `types` call. The targeted COB could not be opened and every
listing of that type from storage failed, until the ref was removed.

Any peer could thus create invalid COB refs for existing COBs, causing
any other node that fetched those refs (e.g. a permissive node) to no
longer be able to list / display the COB in question. A malicious peer
could thus prevent permissive nodes from displaying any issues or patches,
e.g. on popular projects.

Skip the ref with a warning instead. A valid COB ref always points to a
commit, so honest data evaluates the same as before. Other conversion
errors still fail the call as before.

Tests cover opening and listing an issue next to such a ref, and
opening an issue whose only ref is such a ref.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-10-18 21:12:10 +02:00
Daniel Norman 81d79077d0 protocol: Do not panic on trivially empty duration
Any connected peer can send a `Subscribe` with `since` after `until`.
The service passes the range to `Store::filtered`, which asserts
`from <= to`. The panic stops the service thread.

Remove the assertion. The query already returns no rows when
`from > to`, so the peer gets no history.
2026-10-15 00:24:28 +02:00
Daniel NormanandLorenz Leutgeb a4aae610f4 protocol: Allow storing announcements with time 0
There is no apparent reason why announcements with a timestamp of zero
cannot be stored. Avoid panicking.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-10-15 00:16:29 +02:00
Lorenz Leutgeb 9ce88eda3f build/zig: 0.16.0 → 0.17.0
This reverts commit a87e0bd1c8, which
changed from downloading Zig to installing it via `apk`.

The reason to do this back then was because the download failed after
the version bump in 8d50b75826.
However, the reason for the download to fail was that the file name
pattern for the tarballs changed, see
<https://ziggit.dev/t/zig-0-14-1-released/10233/2> and that no
indirection via <https://ziglang.org/download/index.json> was done.

Now, as 0.17.0 is released, and the package in the Alpine repositories
is taking time to be updated, we revert the change, and rewrite the
download step to be more resilient.
2026-10-13 23:56:45 +02:00
Daniel Norman 3032ba1298 radicle/patch: Use cref rule quorum for merges
To determine whether a patch has been merged to the default branch, we
tally merges based on the default branch quorum: allow set and
threshold. With the introduction of custom branches, the tallying was
not updated to use the quorum rules for the target branch, instead
merging on the identity document threshold and delegate set.

This fix uses the `allow` set and the `threshold` of the most specific
rule that matches the target branch of patch.

Note that a reference that no rule matches never becomes canonical, so a
patch that targets one can never reach a quorum.
2026-10-13 14:39:23 +02:00
Lorenz Leutgeb ada90d12ae node: Fix tests for inventory pruning 2026-10-13 14:39:23 +02:00
Daniel NormanandLorenz Leutgeb 8820be95a4 radicle, node: Enable std feature of indexmap
The crates `radicle` and `radicle-node` need the default hasher for
`IndexMap<K, V>` and `IndexSet<T>`, which currently gets enabled
indirectly by `serde_json`, which depends on `indexmap` with `std`
enabled by default.

Make the dependency on the `std` feature of `indexmap` explicit.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-10-13 12:43:45 +02:00
Daniel Norman ceb74c9971 cli: Test explorer URL for annotated tag push
- Push an annotated tag and expect the tree URL of the tagged commit
- Currently fails: the URL uses the tag object id
2026-10-08 12:43:42 +02:00
Daniel Norman 8501e56345 remote-helper: Peel pushed tags for explorer URL
- Use the tagged commit for the tree URL, not the tag object
- Print no URL if the ref does not point to a commit
2026-10-08 12:43:42 +02:00
Lorenz Leutgeb c4cc462483 Revert "remote-helper: Compute HEAD for user namespaces"
This reverts commit 2747c52beb.

I forgot that this is only supported from Git 2.48 onwards.
2026-10-08 12:34:56 +02:00
Yorgos Saslis 736d21f606 node/db: Index routing by node
`sync_routing` calls `get_inventory(&from)` once per inbound inventory
announcement, to work out which of a peer's previously-announced repos are
no longer in its inventory:

    // crates/radicle-protocol/src/service.rs
    synced.removed.extend(
        self.db.routing().get_inventory(&from)?
            .into_iter()
            .filter(|rid| !included.contains(rid)),
    );

That is `SELECT repo FROM routing WHERE node = ?`. The table's only index is
the implicit one for the primary key `(repo, node)`, whose leading column is
`repo`, so the lookup cannot use it and scans instead. The index fixes that.
2026-10-08 12:21:20 +02:00
Yorgos Saslis 2f930e3561 node/reactor: Terminate transports whose socket is dead
A transport whose peer has gone away is never terminated, and the
reactor then spins on it at 100% of a core. On every iteration the reactor
[re-registers the socket with whatever interests the transport still
reports][1], `poll` returns it as ready immediately, and the I/O
handlers do nothing with it. The service thread stops serving the
control socket, so `rad node status` times out and the node looks
offline while its container is still up. Restarting a node only helps
until the next peer disconnect.

How the transport loses track of the socket:

1. A peer resets the connection between `poll` returning and the event
   being handled. The event carries no error flag, so the reactor
   [hands it to the transport instead of reaping it][2].
2. On the writable event the transport [first asks whether the session
   is established][3]. That answer is [derived from the session
   artifact][4], which for a TCP stream [comes from `getpeername`][5].
   On a reset socket that fails with ENOTCONN, so the transport takes
   the "handshake in progress" branch.
3. That branch [pokes the session with a zero-length write and
   discards the result][6]. The write [passes straight through to the
   socket][7], and a zero-length send is exactly what surfaces the
   pending ECONNRESET. The one signal that the peer is gone is thrown
   away, and the transport [re-arms its write interest][8] instead.
4. On the readable event the read also [passes straight through to
   the socket][9] and returns zero bytes, which the transport
   [ignores as well][10].

Nothing ever [marks the transport terminated][11], so its [interest
set][12] stays READABLE and WRITABLE on a closed socket forever.

On an affected node, strace shows thousands of failing `getpeername`
calls in 5 seconds, 5 per iteration, with `epoll_pwait` returning 1,200
times a second.

Two changes:

- Propagate a real error from the handshake-branch zero-length write
  as a termination instead of discarding it. WouldBlock, Interrupted
  and WriteZero stay non-fatal, matching the established branch. A
  socket whose error was already consumed elsewhere reports EPIPE
  here, so this also reaps transports that are already stuck.
- Treat a zero-byte read as end-of-file once the transport is
  [active][13]. From that point reads pass straight through to the
  socket. During the handshake the protocol layer [returns zero after
  consuming a handshake step][14], so that case keeps its current
  behaviour. Without this a half-closed peer also spins, until the
  service's [stale connection reaper][15] disconnects it about
  [2 minutes][16] later.

The termination event [reaches the wire layer][17], which [unregisters
the transport whether or not it has a peer entry for it][18]. That
covers inbound connections that die mid-handshake, which have no node
ID and so are invisible to the stale reaper.

Tests use real loopback sockets: a peer that resets (SO_LINGER 0), the
same after the pending error has already been taken, and a peer that
half-closes. All three failed before this change.

[1]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor.rs#L350
[2]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor.rs#L444
[3]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L206
[4]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L21
[5]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L180
[6]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L207
[7]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L109
[8]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L208
[9]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L78
[10]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L298
[11]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L144
[12]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L317
[13]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L45
[14]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L99
[15]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-protocol/src/service.rs#L2417
[16]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-protocol/src/service.rs#L91
[17]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/wire.rs#L819
[18]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/wire.rs#L356
2026-10-08 12:12:15 +02:00
Yorgos Saslis 0f71581033 protocol/node: count received pack bytes as peer liveness
`disconnect_unresponsive_peers` drops any session whose `last_active` is
older than STALE_CONNECTION_TIMEOUT (hard-coded to 2 minutes).
`last_active` is written in only two places: `to_connected`, and
`handle_message` (the gossip message handler).

Pack data never reaches `handle_message` which is what marks . It arrives as `FrameData::Git`
and goes straight to the worker thread over a channel. So a peer busy
sending us a large pack appears completely idle, and
after two minutes we hang up on it mid-transfer. gitoxide then sees a
truncated stream and the fetch fails with:

    Clone of rad:<rid> from <nid> failed with
    'gix fetch error: Failed to consume the pack sent by the remote'

This only affects large repositories. Small ones finish inside the
window. Neither `limits.fetchPackReceive` nor `limits.fetchTimeout`
bounds it, and it reproduces against every peer, since any peer sending a
big pack goes gossip-quiet for the duration.

We now treat arriving pack bytes as liveness, symmetrically with the
`FrameData::Gossip` arm immediately above, which already calls
`received_message`.

Note that a remote that spends more than STALE_CONNECTION_TIMEOUT
server-side (e.g. enumerating or compressing a large pack before sending
its first byte), is still reaped, and `limits.fetchTimeout` does not help.
This is not a change in behaviour. Gossip silence would have reaped it anyway.
2026-10-08 12:09:25 +02:00
Yorgos Saslis 73ad1de2a4 node/routing: Enforce routingMaxSize
`limits.routingMaxSize` is documented as "Number of routing table entries
before we start pruning", but the implementation diverges from this.

    DELETE FROM routing WHERE node <> ?1 AND rowid IN
      (SELECT rowid FROM routing WHERE timestamp < ?2 ORDER BY timestamp LIMIT ?3)

`?2` is `now - routingMaxAge`. So if nothing is older than routingMaxAge,
nothing is deleted, however far over routingMaxSize the table has grown. On a
seed following a busy network, entries are refreshed by gossip faster than
they age out, and the cap is never reachable.

For example, on a permissive node with routingMaxSize=1000, routingMaxAge=7d,
I observed:

    routing rows                             94,254
    older than routingMaxAge, ie. prunable       16

Dropping routingMaxAge to 1 day cleared the backlog the 7-day setting had
accumulated -- from 94,254 down to 82,400 rows -- but then stopped, with
8 rows prunable and the table still ... 82x over the cap. The `routing` table
size is therefore decided by arrival rate; the configured max size has no
effect on it.

Split the two evictions so each does what its name says:

- size eviction removes the oldest entries down to `routingMaxSize`,
  regardless of age;
- age eviction then removes anything still older than
`routingMaxAge`.

Size runs first: it takes the oldest rows, so the age pass only fires when
the table was near its limit.
2026-10-08 12:06:06 +02:00
Yorgos Saslis 3129de3b95 cli/init: Warn about Vulnerability
All released versions of Radicle are affected by vulnerabilities [1] in the
network protocol: traffic is unencrypted and peers are not authenticated.
A network observer can read the contents of private repositories, and an
attacker can impersonate a node that to fetch them. No fix has been released
yet.

Since users may rely on private repositories being confidential, `rad
init` now warns about this before the interactive "Visibility" prompt,
so the warning is printed before the choice is made. The warning is printed
again in the epilogue whenever the created repository is private. This
also covers `--private`, which skips the prompt.

Update the `rad-init-private` and `rad-init-private-no-seed` examples
to match the new output.

[1]: https://radicle.dev/2026/09/23/disclosure-of-vulnerability-in-network-protocol
2026-10-05 16:36:47 +02:00
Lorenz Leutgeb 0e5a56f25f protocol: Limit allocations when decoding frames
`VarInt` can get large enough to exhaust available memory on many
machines. Instead of allocating directly, check the size of the
allocation against some limit.

The limit chosen for frames is 128 KiB, which is roughly twice as much
as actual gossip and Git protocol frames are expected to be. However,
128 KiB is arguably short enough to not burden a standard consumer
machine too much.
2026-09-10 10:22:22 +02:00
Lorenz Leutgeb d8298a1d4c treewide: Remove test_ prefix from test fns
This redundancy does not add much. These functions are already marked
via `#[test]`, and often times they are in a separate module named
"test" or "tests".
2026-09-08 14:47:53 +02:00
Lorenz Leutgeb 7e1cb406bb build/Dockerfile: Bump cargo-zigbuild
This is required for compatiblity with Rust.
releases/1.10.3
2026-09-08 11:26:27 +02:00
Lorenz Leutgeb 3419821106 Release 1.10.3 2026-09-08 10:37:22 +02:00
Lorenz Leutgeb 3fc92f83fe radicle/identity: Remove useless @ binding 2026-09-07 21:13:06 +02:00
Lorenz Leutgeb 63ed389041 flake: Add cargo-msrv to development shell 2026-09-07 21:11:46 +02:00
Lorenz Leutgeb 6fd73f4f7b flake: Update 2026-09-07 21:11:46 +02:00
Lorenz Leutgeb d285eccb23 fetch: Include the founder namespace always
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.
2026-09-07 18:54:53 +02:00
Lorenz Leutgeb 90f448241e node/test/e2e: Clone without founder namespace
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.
2026-09-07 18:54:43 +02:00
Lorenz Leutgeb a6f938026f CHANGELOG: Fix level of headings and whitespace 2026-09-04 16:14:08 +02:00
Steven RooseandLorenz Leutgeb 919d5c780c crypto: Error on empty SSH_AUTH_SOCK
The empty string will never give a useful socket.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-09-04 13:58:57 +02:00
Lorenz Leutgeb 50129a7179 radicle/canonical: Remove dangling reference
An oversight in commit `218ba30d73adbab0c2c51c6`.
2026-09-04 13:58:53 +02:00
Steven RooseandLorenz Leutgeb ba4ca69404 Add .editorconfig
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-09-04 13:45:43 +02:00
Daniel NormanandLorenz Leutgeb 218ba30d73 radicle/canonical: Introduce RawSymbolicRefs
Serialization and deserialization of symbolic references was implemented
with assumptions on the order of pairs of key and value in JSON objects,
and further, that the enabling the `preserve_order` of `serde_json` would
always guarante that these assumptions hold. See
<https://docs.rs/crate/serde_json/1.0.149/features#preserve_order>.

This is mistaken. The identity document is serialized via `Doc::encode`
which uses `radicle::…::CanonicalFormatter`, which enforces a particular
ordering. From the point of view of the implementation of symbolic
references, this means that the ordering changes, thus violating the
ordering assumption.
The effects are that during deserialization, some symbolic references
might be considered dangling, when they are only missing other symbolic
references that were reordered to occur "later" in the JSON object.

To remedy, introduce `RawSymbolicRefs`, in analogy to `RawRules`. Just
like `RawRules` may be converted to `Rules`, `RawSymbolicRefs` may be
converted to `SymbolicRefs`. This gives the most flexibility for
handling `RawSymbolicRefs`, and one clear point at which validation
happens.

The fact that the implementation cannot depend on ordering anymore means
that all symbolic references have to be validated together, which fits
the pattern of `Raw{,*}` well.

Tests expose the issue.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-09-03 16:47:46 +02:00
Lorenz Leutgeb 0c2e8c1511 radicle/config: Remove public_seeds
This function is unused.
2026-09-02 17:14:04 +02:00
Lorenz Leutgeb 5790e319d0 radicle/config: Do not populate preferred_seeds
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`.
2026-09-02 17:14:04 +02:00
Fintan Halpenny 8677fd9ef0 treewide: Update gix-packetline to 0.21.5
[Advisory Report].

[Advisory Report]: https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-2vh6-hw4j-32ww
2026-09-02 08:17:16 +01:00
Lorenz Leutgeb c14346d93a radicle/canonical: Remove MatchedRule
This `struct` is unused.
2026-09-01 19:41:02 +02:00
Daniel NormanandLorenz Leutgeb 4867917f37 radicle/canonical: Remove RawRules::matches
Testing revealed that the implementation violated RIP-0006, which
requires the most specific rule matching the target of `HEAD`
to use an `allow` of `delegates` and the document threshold.
`validate_head_rule` took the first match from `RawRules`, which orders
patterns lexicographically, so a glob could be validated in place of the
rule that actually governs the branch.
The issue here is that the correct order for matching patterns is only
guaranteed on `Rules`, but not on `RawRules`.

To prevent such errors in the future, remove `RawRules::matches`. This
requires some refactoring, and makes synthesis of the default branch
rule slightly less efficient in the worst case, but we are willing to
take that hit for a cleaner implementation.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-09-01 19:41:02 +02:00
Lorenz Leutgeb fd4e4903d7 radicle, cli: Move Git versions
Move `struct Version` from `radicle` to `radicle-cli`, where it is used.
2026-08-30 23:53:25 +02:00
Lorenz Leutgeb 05a192daff fetch: Do not pretend to be Git
There is no good reason to fake being Git.
2026-08-30 23:53:25 +02:00
Lorenz Leutgeb 2747c52beb remote-helper: Compute HEAD for user namespaces
If a user pushes to the reference of the same name as the default branch
according to the identity document, expose this as `HEAD`, too.
2026-08-30 17:37:09 +02:00
Lorenz Leutgeb fba60164f0 radicle/identity/doc: LazyLock for PayloadId
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.
2026-08-30 17:37:09 +02:00
Lorenz Leutgeb 138404df6f radicle: Rely less on ….project payload
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.
2026-08-30 17:37:09 +02:00