Radish alpha
r
Radicle Improvement Proposals (RIPs)
Radicle
Git (anonymous pull)
Log in to clone via SSH
RIP 4: URI Scheme
Richard Levitte committed 16 days ago
commit 43301b47e5643de823fc14c51e4826dde54db3fb
parent 7e993a9bec13c00a924cb147bca54e16d45aa934
60 files changed +1848 -3
modified .gitignore
@@ -1 +1,2 @@
+
node_modules/
result
added 0004-general-uri-scheme/data/rad-uri.abnf
@@ -0,0 +1,208 @@
+
; This grammar is written using ABNF, as defined in RFC 5234
+
; (https://datatracker.ietf.org/doc/html/rfc5234), from which ALPHA, DIGIT
+
; and HEXDIG are borrowed
+
;
+
; Furthermore, this grammar is based on and specialises the generic URI
+
; specification (https://datatracker.ietf.org/doc/html/rfc3986) for Radicle,
+
; from which 'query', 'fragment', 'host', 'port', 'unreserved', 'pchar', ...
+
; are borrowed
+

+
; tag::overview[]
+
; RFC 3986 defines the syntax for 'query' and 'fragment'
+
rad                     = rad-scheme ":" rad-auth-and-resource rad-resource-type-and-identity [ "?" query ] [ "#" fragment ]
+
                        / rad-scheme ":" rad-legacy
+

+
rad-scheme              = "rad"
+

+
; RFC 3986 allows an absolute resource path without authority, i.e. a URI
+
; that looks like this: rad:/{RID}/{NID}
+
; We don't include that sort of URI in this scheme, because it's, strictly
+
; speaking, unnecessary in this context, and may seem confusing.
+
rad-auth-and-resource   = "//" [ rad-auth ] "/" rad-resource    ; abs. resource "path" with authority
+
                        / rad-resource                          ; rel. resource "path"
+

+
; rad-legacy are URLs defined by RIP #3.  That URL doesn't quite hold up
+
; against the general URI syntax defined in RFC3986, in so far that a node
+
; (identified with NID or NID@host) is the conceptual authority, not
+
; a repository.
+
; To be noted is that RIDs and NIDs are distinct enough that there should not
+
; be any ambiguity between rad-auth-and-resource and rad-legacy.
+
; Of all current implementations, this mostly impacts the git remote helper
+
; git-remote-rad.
+
rad-legacy              = "//" rad-resource
+
; end::overview[]
+

+
; The NID is conceptually ambiguous from an RFC 3986 syntax perspective:
+
;
+
;       authority   = [ userinfo "@" ] host [ ":" port ]
+
;
+
; Is the NID 'userinfo' or 'host'?  In a sense, it currently is a little bit
+
; of both.  Furthermore, Radicle requires that if a hostname (+ port) is
+
; given, the NID must be given too.  Therefore, instead of using 'authority'
+
; strictly as given by RFC 3986, we allow ourselves a slightly different
+
; syntax, which is fine.  Do note also, that a 'host' can still be made to
+
; look like an NID, so programs dealing directly with the Radicle network
+
; will have to be prepared to handle that ambiguity appropriately.
+
; tag::authority[]
+
;
+
; TODO: There already are ways to resolve its NID from a hostname.
+
; For example, if `radicle-httpd` runs next to the node, then it can be
+
; queried for its NID via HTTPS. Also, there are ways to use DNS TXT records,
+
; which is also done for DNS-SD.
+
; We would therefore likely make the `rad-node` optional, or even omit it.
+
; If `rad-node` is sufficiently different from a DNS name (it probably is
+
; not), then we can even omit "@".
+
rad-auth                = rad-node [ "@" rad-host-and-port ]
+

+
; TODO: Port might be optional in the future, once we have convinced IANA
+
; to assign one to us. There currently is no default port.
+
rad-host-and-port       = host ":" port
+

+
; rad-node is a semantic symbol, to signify intent
+
rad-node                = rad-nid
+

+
; An NID is an ID that is ultimately a public key in the Radicle network.
+
; This is used to uniquely represent a node, or a user (also a node), or a
+
; namespace within a repository.
+
; It is a [multibase] and [multicodec] encoded string, using [base58btc] as the multibase
+
; and the codec for the underlying Ed25519 public key.
+
;
+
; It is always 48 characters long, including the initial "z6Mk",
+
; the `z` corresponding to the base, and `6Mk` corresponding to the codec.
+
;
+
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
+
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
+
; [multicodec]: https://github.com/multiformats/multicodec/
+
;
+
; TODO: New NIDs in the future? Even this might change, as we allow rotating
+
; keys for nodes. But this is even further out than stable IDs for users.
+
rad-nid                 = "z6Mk" 44( base58btc )
+
; end::authority[]
+

+
; tag::resource[]
+
rad-resource            = rad-repository [ "/" rad-namespace ]
+

+
; rad-repository is a semantic symbol, to signify intent
+
rad-repository          = rad-rid
+

+
; rad-namespace is a semantic symbol, to signify intent
+
rad-namespace           = rad-nid
+

+
; An RID uniquely represents a git repository in the Radicle network.
+
; It is a [multibase] encoded string of a Git OID, using [base58btc] as the multibase.
+
;
+
; It is 28 or 29 characters long, including the initial multibase identifier "z".
+
;
+
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
+
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
+
;
+
; TODO: In the near future (as soon as Git repositories with SHA-256 object
+
; format become popular, which will be in late 2026), we will have to extend
+
; the syntax of RIDs. Leave a note here so that implementors know this and
+
; make design decisions accordingly.
+
rad-rid                 = "z" 27*28( base58btc )
+
; end::resource[]
+

+
; tag::resource-type-and-identity[]
+
; 'rad-resource-type-and-identity' is empty by default, and more resource
+
; types and identities will be added further on, using the ABNF =/ operator,
+
; see RFC 5234 "3.3. Incremental Alternatives: Rule1 =/ Rule2"
+
rad-resource-type-and-identity = ""
+

+
; tag::git-type-and-identity[]
+
; For git specific resource types
+
rad-resource-type-and-identity =/ git-commit / git-tree / git-blob / git-tag
+

+
git-commit              = "/" "commit" "/" git-obj-or-ref
+
git-tree                = "/" "tree" "/" git-obj
+
git-blob                = "/" "blob" "/" git-obj
+
git-tag                 = "/" "tag" "/" git-obj-or-ref
+

+
; Note: this rule will always be ambiguous, as something that looks like a
+
; git object ID could very well be a branch or a tag.
+
; Some git commands will warn when such refs are used, but not all.
+
git-obj-or-ref          = git-obj / git-ref
+
; Currently, only sha1 identities are supported.
+
git-obj                 = git-sha1
+
; HEXDIG is defined in RFC 5234
+
git-sha1                = 40HEXDIG
+

+
; unreserved is defined in RFC 3986.  Not that this syntax allows much
+
; more than what can be specified for a git reference.  It's left to
+
; the implementation to parse and apply semantics properly.
+
; Our best reference for how to interpret and parse git references is
+
; https://git-scm.com/docs/protocol-common.html
+
git-ref                 = 1*unreserved *( "/" 1*unreserved )
+
; end::git-type-and-identity[]
+

+
; tag::rad-cob-type-and-identity[]
+
; For Radicle collaborative object resource types
+
rad-resource-type-and-identity =/ rad-cob / rad-cobs
+

+
rad-cob                 = "/" "cob" "/" rad-cob-type "/" rad-cob-obj
+
rad-cobs                = "/" "cob" "/" rad-cob-type
+

+
; The COB type is like a reversed FQDN.  The syntax spec is derived from
+
; https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
+
rad-cob-type            = rad-cob-label 1*( "." rad-cob-label )
+
rad-cob-label           = 1*( ALPHA / DIGIT ) *( "-" 1*( ALPHA / DIGIT ) )
+

+
; syntactically, radicle COB objects and object revisions are git objects
+
; semantically, they are as well, at least for now
+
rad-cob-obj             = git-obj
+
; end::rad-cob-type-and-identity[]
+
; end::resource-type-and-identity[]
+

+
; tag::rfc5234[]
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
; Copied from RFC 5234
+

+
ALPHA          =  %x41-5A / %x61-7A     ; A-Z / a-z
+
DIGIT          =  %x30-39               ; 0-9
+
HEXDIG         =  DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+

+
; Copied from RFC 3986
+
host          = IP-literal / IPv4address / reg-name
+
port          = *DIGIT
+

+
IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"
+

+
IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+

+
IPv6address   =                            6( h16 ":" ) ls32
+
              /                       "::" 5( h16 ":" ) ls32
+
              / [               h16 ] "::" 4( h16 ":" ) ls32
+
              / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+
              / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+
              / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
+
              / [ *4( h16 ":" ) h16 ] "::"              ls32
+
              / [ *5( h16 ":" ) h16 ] "::"              h16
+
              / [ *6( h16 ":" ) h16 ] "::"
+

+
h16           = 1*4HEXDIG
+
ls32          = ( h16 ":" h16 ) / IPv4address
+
IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet
+
dec-octet     = DIGIT                 ; 0-9
+
              / %x31-39 DIGIT         ; 10-99
+
              / "1" 2DIGIT            ; 100-199
+
              / "2" %x30-34 DIGIT     ; 200-249
+
              / "25" %x30-35          ; 250-255
+

+
reg-name      = *( unreserved / pct-encoded / sub-delims )
+

+
pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+

+
query         = *( pchar / "/" / "?" )
+

+
fragment      = *( pchar / "/" / "?" )
+

+
pct-encoded   = "%" HEXDIG HEXDIG
+

+
unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+
sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+
              / "*" / "+" / "," / ";" / "="
+
; end::rfc5234[]
+

+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
; Base58 Bitcoin Alphabet
+
base58btc = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

\ No newline at end of file
added 0004-general-uri-scheme/data/rad-uri.ne
@@ -0,0 +1,245 @@
+
@{%
+
    const lstr = function(d) { return d.flat(5).join(''); }
+
%}
+

+
# In the ABNF grammar, 'rad-resource-type-and-identity' defaults to empty,
+
# which effectively renders it optional.  Nearley doesn't allow empty rules,
+
# and multiple rules with the same name can be added anyway, so we make
+
# resource_type_and_identity optional here to get the same essential semantics.
+
rad                     -> scheme ":" auth_and_resource resource_type_and_identity:?
+
                           ( "?" rad_query ):? ( "#" fragment ):?
+
                           {% ([s, , aar, rtai, q, f]) =>
+
                               ({ scheme: s,
+
                                  ...aar,
+
                                  ...(rtai ? { resource: rtai} : {}),
+
                                  ...(q ? { query: q[1]} : {}),
+
                                  ...(f ? { fragment: f[1]} : {}) }) %}
+
                         | scheme ":" legacy
+
                           {% ([s, , l]) => ({ scheme: s, ...l }) %}
+

+
scheme                  -> "rad" {% lstr %}
+

+
auth_and_resource       -> "//" auth:? "/" resource             # absolute resource "path"
+
                           {% ([, auth = {}, , res]) => ({ ...auth, ...res }) %}
+
                         | resource                             # relative resource "path"
+
                           {% ([res]) => res %}
+

+
# 'legacy' are URLs defined by RIP #3.  That URL doesn't quite hold up
+
# against the general URI syntax defined in RFC3986, in so far that a node
+
# (identified with NID or NID@host) is the conceptual authority, not
+
# a repository.
+
# To be noted is that RIDs and NIDs are distinct enough that there shouldn't
+
# be any ambiguity between rad-auth-and-resource and rad-legacy.
+
# Of all current implementations, this mostly impacts the git remote helper
+
# git-remote-rad.
+
legacy                  -> "//" resource
+
                           {% ([, res]) => res %}
+

+
# The NID is conceptually ambiguous from an RFC 3986 syntax perspective:
+
#
+
#       authority   = [ userinfo "@" ] host [ ":" port ]
+
#
+
# Is the NID 'userinfo' or 'host'?  In a sense, it currently is a little bit
+
# of both.  Furthermore, Radicle requires that if a hostname (+ port) is
+
# given, the NID must be given too.  Therefore, instead of using 'authority'
+
# strictly as given by RFC 3986, we allow ourselves a slightly different
+
# syntax, which is fine.  Do note also, that a 'host' can still be made to
+
# look like an NID, so programs dealing directly with the Radicle network
+
# will have to be prepared to handle that ambiguity appropriately.
+
#
+
# TODO: There already are ways to resolve its NID from a hostname.
+
# For example, if `radicle-httpd` runs next to the node, then it can be
+
# queried for its NID via HTTPS. Also, there are ways to use DNS TXT records,
+
# which is also done for DNS-SD.
+
# We would therefore likely make the `node` optional, or even omit it.
+
# If `node` is sufficiently different from a DNS name (it probably is
+
# not), then we can even omit "@".
+
auth                    -> node ("@" host_and_port):?
+
                           {% ([node, hap]) => ({ node: node, ...(hap ? { host: hap[1] } : {}) }) %}
+

+
host_and_port           -> host ":" port {% lstr %}
+

+
# rad-node is a semantic symbol, to signify intent
+
node                    -> nid {% id %}
+

+
# An NID is an ID that is ultimately a public key in the Radicle network.
+
# This is used to uniquely represent a node, or a user (also a node), or a
+
# namespace within a repository.
+
# It is a [multibase] and [multicodec] encoded string, using [base58btc] as the multibase
+
# and the codec for the underlying Ed25519 public key.
+
#
+
# It is always 48 characters long, including the initial "z6Mk",
+
# the `z` corresponding to the base, and `6Mk` corresponding to the codec.
+
#
+
# [base58btc]: https://digitalbazaar.github.io/base58-spec/
+
# [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
+
# [multicodec]: https://github.com/multiformats/multicodec/
+
#
+
# TODO: New NIDs in the future? Even this might change, as we allow rotating
+
# keys for nodes. But this is even further out than stable IDs for users.
+
nid                     -> "z6Mk" b58x25 b58x10 b58x5 base58btc base58btc base58btc base58btc {% lstr %}
+

+
resource                -> repository ( "/" namespace ):?
+
                           {% ([repo, ns]) => ({ repo: repo, ...(ns ? { namespace: ns[1] } : {}) }) %}
+

+
# repository is a semantic symbol, to signify intent
+
repository              -> rid {% id %}
+

+
# namespace is a semantic symbol, to signify intent
+
namespace               -> nid {% id %}
+

+
# An RID uniquely represents a git repository in the Radicle network.
+
# It is a [multibase] encoded string of a Git OID, using [base58btc] as the multibase.
+
#
+
# It is 28 or 29 characters long, including the initial multibase identifier "z".
+
#
+
# [base58btc]: https://digitalbazaar.github.io/base58-spec/
+
# [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
+
#
+
# TODO: In the near future (as soon as Git repositories with SHA-256 object
+
# format become popular, which will be in late 2026), we will have to extend
+
# the syntax of RIDs. Leave a note here so that implementors know this and
+
# make design decisions accordingly.
+
rid                     -> "z" b58x25 base58btc base58btc base58btc:?
+
                           {% (d,l,r) => {
+
                                   s = d.join('')
+
                                   // Check that the RID doesn't starts
+
                                   // with "z6Mk", as that can only be
+
                                   // an NID.  Reject it if it is.
+
                                   if (s.startsWith("z6Mk")) { return r; }
+
                                   return s;
+
                               }
+
                           %}
+

+
# For git specific resource types
+
resource_type_and_identity -> git_commit {% id %}
+
resource_type_and_identity -> git_tree {% id %}
+
resource_type_and_identity -> git_blob {% id %}
+
resource_type_and_identity -> git_tag {% id %}
+

+
git_commit              -> "/" "commit" "/" git_obj_or_ref {% ([, , , o]) => ({ type: "commit", ...o }) %}
+
git_tree                -> "/" "tree" "/" git_obj {% ([, , , o]) => ({ type: "tree", obj: o }) %}
+
git_blob                -> "/" "blob" "/" git_obj {% ([, , , o]) => ({ type: "blob", ref: o }) %}
+
git_tag                 -> "/" "tag" "/" git_obj_or_ref {% ([, , , o]) => ({ type: "tag", ...o }) %}
+

+
# Note: this rule will always be ambiguous, as something that looks like a
+
# git object ID could very well be a branch or a tag.
+
# Some git commands will warn when such refs are used, but not all.
+
git_obj_or_ref          -> git_obj {% ([o]) => ({ obj: o }) %}
+
                         | git_ref {% ([r]) => ({ ref: r }) %}
+

+
# Currently, only sha1 identities are supported.
+
git_obj                 -> git_sha1 {% id %}
+
# a sha1 ID is 40 hex digits
+
git_sha1                -> hex8 hex8 hex8 hex8 hex8 {% lstr %}
+

+
# unreserved is defined in RFC 3986.  Not that this syntax allows much
+
# more than what can be specified for a git reference.  It's left to
+
# the implementation to parse and apply semantics properly.
+
# Our best reference for how to interpret and parse git references is
+
# https://git-scm.com/docs/protocol-common.html
+
git_ref                 -> unreserved:+ ( "/" unreserved:+ ):* {% lstr %}
+

+
# For Radicle collaborative object resource types
+
resource_type_and_identity -> rad_cob {% id %}
+
resource_type_and_identity -> rad_cobs {% id %}
+

+
rad_cob                 -> "/" "cob" "/" rad_cob_type "/" rad_cob_obj
+
                           {% ([, , , type, , obj]) =>
+
                              ({ type: "cob", id: { type: type, obj: obj } }) %}
+
rad_cobs                -> "/" "cob" "/" rad_cob_type
+
                           {% ([, , , type]) =>
+
                              ({ type: "cobs", id: { type: type } }) %}
+

+
# The COB type is like a reversed FQDN.  The syntax spec is derived from
+
# https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
+
rad_cob_type            -> rad_cob_label ( "." rad_cob_label ):+ {% lstr %}
+
rad_cob_label           -> alnum:+ ( "-" alnum:+ ):* {% lstr %}
+

+
# syntactically, radicle COB objects and object revisions are git objects
+
# semantically, they are as well, at least for now
+
rad_cob_obj             -> git_obj {% id %}
+

+
# While the ABNF doesn't say anything special about 'query', we can allow
+
# it for ourselves here, since nearley does a bit more than just checking
+
# syntax.
+
rad_query               -> rad_query_param ( "&" rad_query_param ):*
+
                           {% ([first, rest]) => {
+
                                  newrest = rest ? rest : [];
+
                                  newrest = newrest.reduce((tot, val) => { tot.push(val[1]); return tot }, []);
+
                                  return ([first, ...newrest].reduce((tot, val) => {
+
                                      if (tot[val['name']]) {
+
                                          tot[val['name']].push(val['value']);
+
                                      } else {
+
                                          tot[val['name']] = [ val['value'] ];
+
                                      }
+
                                      return tot;
+
                                  }, {}))
+
                              } %}
+
rad_query_param         -> ( alpha alnum:* ) "=" ( rad_query_pchar | "/" | "?" ):*
+
                           {% ([name, , value]) => ({ name: lstr(name), value: lstr(value) }) %}
+
rad_query_pchar         -> unreserved | pct_encoded | rad_query_sub_delims | ":" | "@"
+
rad_query_sub_delims    -> "!" | "$" | "'" | "(" | ")"
+
                         | "*" | "+" | "," | ";" | "="
+

+
# Helper symbols
+
alnum                   -> [a-zA-Z0-9] {% id %}
+
alnum5                  -> alnum alnum alnum alnum alnum {% function(d) { return d.join(''); } %}
+
alnum10                 -> alnum5 alnum5 {% function(d) { return d.join(''); } %}
+
alnum25                 -> alnum10 alnum10 alnum5 {% function(d) { return d.join(''); } %}
+

+
# Base58 Bitcoin Alphabet (excludes 0, O, I, l)
+
base58btc               -> [1-9A-HJ-NP-Za-km-z] {% id %}
+
b58x5                   -> base58btc base58btc base58btc base58btc base58btc {% function(d) { return d.join(''); } %}
+
b58x10                  -> b58x5 b58x5 {% function(d) { return d.join(''); } %}
+
b58x25                  -> b58x10 b58x10 b58x5 {% function(d) { return d.join(''); } %}
+

+
hex2                    -> hexdig hexdig {% function(d) { return d.join(''); } %}
+
hex4                    -> hex2 hex2 {% function(d) { return d.join(''); } %}
+
hex8                    -> hex4 hex4 {% function(d) { return d.join(''); } %}
+

+
# ABNF defines ALPHA, DIGIT and HEXDIG
+
alpha           -> [a-zA-Z] {% id %}
+
digit           -> [0-9] {% id %}
+
hexdig          -> [a-f0-9] {% id %}
+

+
# RFC 3986 defines the following (translated to nearley)
+
host            -> IP_literal | IPv4address | reg_name
+
port            -> [0-9]:+
+

+
IP_literal      -> "[" ( IPv6address | IPvFuture ) "]"
+

+
IPvFuture       -> "v" hexdig:+ "." ( unreserved | sub_delims | ":" ):+
+

+
IPv6address     ->                                                          h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" ls32
+
                 |                                                     "::" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":"         ls32
+
                 |                                                 h16 "::" h16 ":" h16 ":" h16 ":" h16 ":"                 ls32
+
                 |                                         h16 ":" h16 "::" h16 ":" h16 ":" h16 ":"                         ls32
+
                 |                                 h16 ":" h16 ":" h16 "::" h16 ":" h16 ":"                                 ls32
+
                 |                         h16 ":" h16 ":" h16 ":" h16 "::" h16 ":"                                         ls32
+
                 |                 h16 ":" h16 ":" h16 ":" h16 ":" h16 "::"                                                 ls32
+
                 |         h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 "::"                                                 h16
+
                 | h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 "::"
+

+
h16             -> hexdig | hex2 | hex2 hexdig | hex4
+
ls32            -> ( h16 ":" h16 ) | IPv4address
+
IPv4address     -> dec_octet "." dec_octet "." dec_octet "." dec_octet
+
dec_octet       -> digit                # 0-9
+
                 | [1-9] digit          # 10-99
+
                 | "1" digit digit      # 100-199
+
                 | "2" [0-4] digit      # 200-249
+
                 | "25" [0-5]           # 250-255
+

+
reg_name        -> ( unreserved | pct_encoded | sub_delims ):*
+

+
pchar           -> unreserved | pct_encoded | sub_delims | ":" | "@"
+

+
query           -> ( pchar | "/" | "?" ):*
+

+
fragment        -> ( pchar | "/" | "?" ):*
+

+
pct_encoded     -> "%" hex2
+

+
unreserved      -> alnum | "-" | "." | "_" | "~"
+
sub_delims      -> "!" | "$" | "&" | "'" | "(" | ")"
+
                 | "*" | "+" | "," | ";" | "="
added 0004-general-uri-scheme/data/test/tc00a
@@ -0,0 +1 @@
+
rad:z3gqcJUbbbbbbbbbbbbbbbCSGazv5

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc00a-expected.json
@@ -0,0 +1,6 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc00b
@@ -0,0 +1 @@
+
rad://z3gqcJUbbbbbbbbbbbbbbbCSGazv5

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc00b-expected.json
@@ -0,0 +1,6 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc00c
@@ -0,0 +1 @@
+
rad:///z3gqcJUbbbbbbbbbbbbbbbCSGazv5

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc00c-expected.json
@@ -0,0 +1,6 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc01a
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc01a-expected.json
@@ -0,0 +1,7 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc01b
@@ -0,0 +1 @@
+
rad://zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc01b-expected.json
@@ -0,0 +1,7 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc01c
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc01c-expected.json
@@ -0,0 +1,7 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc02
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc02-expected.json
@@ -0,0 +1,7 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc03
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc03-expected.json
@@ -0,0 +1,8 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc04
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc04-expected.json
@@ -0,0 +1,8 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "host": "seed.example.com:8776",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc05
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E/z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc05-expected.json
@@ -0,0 +1,9 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "host": "seed.example.com:8776",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx"
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc10a
@@ -0,0 +1 @@
+
rad:z3gqcJUbbbbbbbbbbbbbbbCSGazv5/commit/72db6dffffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc10a-expected.json
@@ -0,0 +1,18 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  },
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
+
    "resource": {
+
      "type": "commit",
+
      "obj": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc10b
@@ -0,0 +1 @@
+
rad:///z3gqcJUbbbbbbbbbbbbbbbCSGazv5/commit/72db6dffffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc10b-expected.json
@@ -0,0 +1,18 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  },
+
  {
+
    "scheme": "rad",
+
    "repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
+
    "resource": {
+
      "type": "commit",
+
      "obj": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc12
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/commit/e9cf5263ffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc12-expected.json
@@ -0,0 +1,20 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "e9cf5263ffffffffffffffffffffffffffffffff"
+
    }
+
  },
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "obj": "e9cf5263ffffffffffffffffffffffffffffffff"
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc13
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E/z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx/commit/72db6dffffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc13-expected.json
@@ -0,0 +1,24 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "host": "seed.example.com:8776",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  },
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "host": "seed.example.com:8776",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx",
+
    "resource": {
+
      "type": "commit",
+
      "obj": "72db6dffffffffffffffffffffffffffffffffff"
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc30
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example/e9cf5263ffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc30-expected.json
@@ -0,0 +1,13 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "cob",
+
      "id": {
+
        "type": "org.example",
+
        "obj": "e9cf5263ffffffffffffffffffffffffffffffff"
+
      }
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc31
@@ -0,0 +1 @@
+
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example/e9cf5263ffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc31-expected.json
@@ -0,0 +1,14 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "cob",
+
      "id": {
+
        "type": "org.example",
+
        "obj": "e9cf5263ffffffffffffffffffffffffffffffff"
+
      }
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc32
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example
added 0004-general-uri-scheme/data/test/tc32-expected.json
@@ -0,0 +1,12 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "cobs",
+
      "id": {
+
        "type": "org.example"
+
      }
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc40a
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/tree/3eb47e9fffffffffffffffffffffffffffffffff?path=src

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc40a-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "tree",
+
      "obj": "3eb47e9fffffffffffffffffffffffffffffffff"
+
    },
+
    "query": {
+
      "path": [
+
        "src"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc40b
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/tree/3eb47e9fffffffffffffffffffffffffffffffff?path=src

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc40b-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "tree",
+
      "obj": "3eb47e9fffffffffffffffffffffffffffffffff"
+
    },
+
    "query": {
+
      "path": [
+
        "src"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc41a
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/master?blob=README.md

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc41a-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "master"
+
    },
+
    "query": {
+
      "blob": [
+
        "README.md"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc41b
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/master?blob=README.md

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc41b-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "master"
+
    },
+
    "query": {
+
      "blob": [
+
        "README.md"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc42a
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz?path=foo/doc

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc42a-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "baz"
+
    },
+
    "query": {
+
      "path": [
+
        "foo/doc"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc42b
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz?path=foo/doc

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc42b-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "baz"
+
    },
+
    "query": {
+
      "path": [
+
        "foo/doc"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc43a
@@ -0,0 +1 @@
+
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz/foo?path=doc&path=src

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc43a-expected.json
@@ -0,0 +1,16 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "baz/foo"
+
    },
+
    "query": {
+
      "path": [
+
        "doc",
+
        "src"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc43b
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz/foo?path=doc&path=src

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc43b-expected.json
@@ -0,0 +1,16 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "baz/foo"
+
    },
+
    "query": {
+
      "path": [
+
        "doc",
+
        "src"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/tc44
@@ -0,0 +1 @@
+
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/refs/notes/commit?blob=e9cf5263ffffffffffffffffffffffffffffffff

\ No newline at end of file
added 0004-general-uri-scheme/data/test/tc44-expected.json
@@ -0,0 +1,15 @@
+
[
+
  {
+
    "scheme": "rad",
+
    "repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
+
    "resource": {
+
      "type": "commit",
+
      "ref": "refs/notes/commit"
+
    },
+
    "query": {
+
      "blob": [
+
        "e9cf5263ffffffffffffffffffffffffffffffff"
+
      ]
+
    }
+
  }
+
]
added 0004-general-uri-scheme/data/test/uri-scheme-tester.sh
@@ -0,0 +1,42 @@
+
#! /bin/sh
+

+
set -euo pipefail
+

+
if ! command -v nearley-test > /dev/null; then
+
    echo >&2 'The command `nearley-test` must be available to run this script. Consider running `npm install -g nearley`.'
+
    exit 2
+
fi
+

+
if ! command -v yq > /dev/null; then
+
    echo >&2 'The command `yq` must be available to run this script.'
+
    exit 2
+
fi
+

+
readonly DATA="${1:-"$(git rev-parse --show-toplevel)/0004-general-uri-scheme/data"}"
+

+
readonly RAD_URI_NE="${DATA}/rad-uri.ne"
+
readonly RAD_URI_JS="$TMPDIR/rad-uri.js"
+

+
if ! nearleyc -o "$RAD_URI_JS" "$RAD_URI_NE"; then
+
    exit 1
+
fi
+

+
readonly TMP="$(mktemp -d)"
+

+
FAILED=0
+
for EXPECTED in "${DATA}"/test/tc*-expected.json; do
+
    INPUT="${EXPECTED%-expected.json}"
+
    NAME="${INPUT##*/}"
+
    ACTUAL="${TMP}/${NAME}.actual"
+

+
    nearley-test -q -i "$(cat "$INPUT")" "$RAD_URI_JS" | yq > "${ACTUAL}"
+

+
    if ! diff -u "$EXPECTED" "$ACTUAL" ; then
+
        echo "FAIL: $NAME"
+
        FAILED=1
+
    else
+
        echo "PASS: $NAME"
+
    fi
+
done
+

+
[ "$FAILED" -eq 0 ]
added 0004-general-uri-scheme/general-uri-scheme.adoc
@@ -0,0 +1,867 @@
+
= RIP 4: General `rad:` URI Scheme
+
Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>; Richard Levitte <richard@levitte.org>
+
:doctype: article
+
:toc: preamble
+
:toclevels: 3
+
:status: Draft
+
:copyright: CC0-1.0
+
:lang: en
+

+
--
+
In <<RIP2>>, a `rad:` URN is briefly discussed as a simple Radicle
+
identifier. In <<RIP3>>, the `rad://` URL is introduced, made to allow
+
`git-remote-rad` to be a fruitful interface between Git and Radicle.
+

+
In this RIP, we discuss some modifications that are needed to have a general
+
URI scheme that fits in the syntax defined by <<URI>>, as well as further
+
extensions that allow reference to more aspects of Radicle as well as Git on a
+
Radicle network.
+

+
All extensions and modifications are intended not to introduce impossible
+
clashes with the existing schemes.
+
--
+

+
== Status Quo
+

+
References to resources on Radicle (both traditional Git objects as well as
+
collaborative objects, such as patches and issues) are communicated between
+
individuals mostly by relying on installations of <<radicle-explorer>>,
+
a web application that provides an HTTPS interface to the Radicle network.
+

+
When Alice wants to direct Bob's attention to a specific issue that she filed
+
on Radicle, you will likely see her send a URI of the form
+
`https://app.example.com/nodes/seed.example.com/rad:…/issues/…` to Bob.
+
URIs using the `rad:` schema are exchanged more rarely, and almost exclusively
+
to refer to repositories as a whole, e.g. in order to invoke `rad clone`,
+
`git clone`, etc.
+

+
As of 2024-04-23, the Radicle team also opted to have `rad:…`
+
URIs in Zulip messages automatically replaced by links to
+
`https://app.radicle.xyz/nodes/garden.radicle.xyz/rad:…`, limiting the
+
usefulness of exchange of `rad:` URIs.<<zulip-mangle>>
+

+
== Terminology
+

+
Node ID:: a Node ID, or NID for short, is an identifier for a Radicle node and a Radicle user, at the time of writing.
+
It is a <<multibase>> (<<base58btc>>) and <<multicodec>> encoded Ed25519 public key.
+

+
Namespace:: a namespace, in the Radicle ecosystem, commonly refers to a Git namespace, i.e. `refs/namespaces`,
+
that uses a *Node ID* as the namespace, e.g., `refs/namespaces/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi`.
+

+
Repository ID:: a Repository ID, or RID for short, is an identifier for a Radicle repository.
+
It is a <<multibase>> (<<base58btc>>) encoded Git object identifier, at the time of writing.
+

+
== Challenges
+

+
:url-cgit: https://git.zx2c4.com/cgit/
+
:url-sourcegraph: https://sourcegraph.com/
+

+
Every user should be able to individually control how they want to interact
+
with the network. Users may prefer to interact with Radicle via…
+

+
* a command line interface in their shell.
+
* a specific client program, such as:
+
** VSCode with a suitable extension, <<radicle-vscode>>
+
** a JetBrains IDE with a suitable plugin, <<radicle-jetbrains>>.
+
* <<radicle-explorer>>, hosted locally or by relying on a specific hosting
+
  provider.
+
* another custom program or handler, e.g. redirection to an instance of
+
  {url-cgit}[cgit] or {url-sourcegraph}[Sourcegraph].
+

+
Exchanging `https:` URIs that point at a specific hosting provider of
+
<<radicle-explorer>> is not portable. Individuals that read/receive such a URI
+
might prefer to access the repository with another method and then must modify
+
the URI (extract repository address, etc.) before access.
+

+
For the case of a reference to a repository, this is rather simple, but as
+
soon as references to resources _within_ the repositories are exchanged, there
+
are currently no standardized options. The two most common ones are referring
+
to an instance of `radicle-explorer` by means of a `https:`-URI, or
+
communicating in natural language, often in a context where the repository ID
+
is clear to all communicating parties. They will simply say "patch c863fe".
+

+
Automatic rewriting of `rad:` URIs to a specific hosting provider of
+
`radicle-explorer` contributes to additional centralization of access to the
+
network. In case the specific hosting provider goes offline, is censored, or
+
censors content, the impact is multiplied.
+

+
== Intention
+

+
The intention with this RIP is to define a `rad:` URI scheme with which one
+
can refer to:
+

+
. a Git repository in the Radicle network, which also includes the namespace
+
  for the contributions of a particular user (currently represented with a NID).
+
. optionally, a repository resource of some sort; to properly identify
+
  the resource:
+
.. the resource's type
+
.. a resource identity, specific to the resource type
+

+
****
+
We recognise that the concept of namespace and how that relates to a user is
+
a bit ambiguous.  A more detailed explanation should appear in another RIP.
+

+
For the moment, the user and their local node are identified with the same
+
identity, the NID.  Since the namespace for a user uses its identity, it may
+
appear confusing.  However, the fact remains that a namespace stores a user's
+
contribution to the repository it lives within.
+

+
In the future, users may be identified differently, and when that happens, the
+
namespace is expected to change accordingly, so that it is clear that it is the
+
user's contributions.
+
****
+

+
This can be seen as a hierarchy, expressed like this:
+

+
 repo-id [ "/" namespace ] [ "/" resource-type "/" resource-id ]
+

+
The `rad:` URI scheme is intended to be used to refer to / represent
+
repositories and resources within repositories, in such a way that it
+
can be used by applications, but also be understood by humans wishing
+
to get to the resource being referred to with what knowledge tools
+
they have available (such as the commands `git` and `rad`).
+

+
It does not imply any particular network protocol, or even that all
+
the parts of the scheme must be implemeted by all applications, and
+
applications may also make some parts of the scheme more mandatory
+
than what this RIP implies.
+

+
As a very concrete example of an application that handles `rad:` URIs,
+
`git-remote-rad` can probably never be expected to handle more than a
+
repository and namespace, a `rad:` URI with more than that should be
+
considered an error.  On the other hand, `git-remote-rad` requires the
+
presence of a namespace, i.e. makes it mandatory.
+

+
== Formalization and General Extension of the `rad:` URI Scheme
+

+
In this RIP, we denote the `rad:` scheme as a _URI_ scheme and try to
+
generally follow the nomenclature from <<URI>>.
+

+
=== The `web+rad:` URI Scheme
+

+
<<HTML>> defines that unless a URL scheme is known and on their safelist, it
+
may be registered as a custom URL handler if prefixed with `web+`.  See their
+
section on <<HTML-registerhandler,registering handlers>>.
+

+
For this RIP, it means that `web+rad:` must be compatible with `rad:` in every
+
way, i.e prepending the string `"web+"` to a valid `rad:` URI must result in a
+
valid `web+rad:`-URI, and removing the first four symbols from a `web+rad:`
+
URI must result in a valid `rad:` URI.
+

+
This should make it as easy as possible for users to register URI handlers for
+
both `web+rad:` and `rad:` on their system, according to their preferences.
+

+
=== The Old `rad:` URI Scheme
+

+
The scheme as defined in <<RIP2>> and <<RIP3>> was really pretty simple, and could
+
be summarized with just a few lines of <<ABNF>>:
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
rad             = rad-scheme ":" rad-rid                       ; (RIP #2)
+
                / rad-scheme ":" "//" rad-rid [ "/" rad-nid ]  ; (RIP #3)
+
rad-scheme      = "rad"
+
rad-rid         = "z" 27*28(ALPHA / DIGIT)
+
rad-nid         = "z6Mk" 44(ALPHA / DIGIT)
+
----
+

+
This is workable in practice, but the URL form is arguably not aligning with
+
the ideas behind the 'authority' as described in <<URI-Authority>>.  The
+
'authority' carries with it the idea of a host, possibly with some user
+
information.  In a Radicle network, it can be argued that the NID represents
+
that much more than the RID does, but the NID used in the old `rad:` URI
+
scheme does not represent that either; it represents a namespace within a
+
repository.
+

+
=== The New `rad:` URI Scheme: General Approach
+

+
To align with <<URI, RFC 3986>>, the `rad:` scheme is renewed to include a
+
possible authority, which is the particular node / host that the data should
+
be found on.  In most cases, (much like in `file:` URIs), the authority is
+
expected to be empty or not there at all.
+

+
Other than that, the new URI scheme is extended with additional syntax to
+
meet the <<Challenges, challenges>> given above.
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=overview]
+
----
+

+
==== The Authority
+

+
The NID is conceptually ambiguous from an <<URI, RFC 3986>> syntax perspective:
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
authority   = [ userinfo "@" ] host [ ":" port ]
+
----
+

+
This does not quite align semantically with how Radicle nodes are identified.
+
Radicle nodes are identified with their NID, or with `{NID}@{hostname}:{port}`.
+
This does align with <<URI, RFC 3986>> syntax well enough, with just a little
+
bit of redefinition of the authority for use in Radicle context.
+

+
****
+
The authority is most closely related to the address given to the Radicle CLI
+
command `rad node connect`.
+
****
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=authority]
+
----
+

+
==== The Repository
+

+
After the authority (if there is one) comes the repository to refer to.
+

+
With Radicle, the main anchoring resource is the repository, so its ID must
+
always be present, and specifying it alone refers to the synthesis of all
+
participants' contributions.
+

+
To refer to individual participants' separate contribution, their namespace
+
must also be included in the URI, separate from the repository ID with a slash
+
("/").  It shall be noted, though, that the namespace is only meaningful in
+
some specific circumstances, to be defined later.
+

+
****
+
Radicle repositories are _currently_ Git repositories.  We expect that other
+
version control (VCS) implementations may be used as well, and in that case,
+
we expect the VCS implementation type to be reflected in the repository
+
identity.
+
****
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=resource]
+
----
+

+
Here are a few examples of what a reference to a whole repository could look
+
like:
+

+
:tc00a: pass:c,q[footnote:tc00a[nearley-test result in link:data/test/tc00a-expected.json[]]]
+
:tc00b: pass:c,q[footnote:tc00b[nearley-test result in link:data/test/tc00b-expected.json[]]]
+
:tc00c: pass:c,q[footnote:tc00c[nearley-test result in link:data/test/tc00c-expected.json[]]]
+
:tc01a: pass:c,q[footnote:tc01a[nearley-test result in link:data/test/tc01a-expected.json[]]]
+
:tc01b: pass:c,q[footnote:tc01b[nearley-test result in link:data/test/tc01b-expected.json[]]]
+
:tc01c: pass:c,q[footnote:tc01c[nearley-test result in link:data/test/tc01c-expected.json[]]]
+
:tc02: pass:c,q[footnote:tc02[nearley-test result in link:data/test/tc02-expected.json[]]]
+
:tc03: pass:c,q[footnote:tc03[nearley-test result in link:data/test/tc03-expected.json[]]]
+
:tc04: pass:c,q[footnote:tc04[nearley-test result in link:data/test/tc04-expected.json[]]]
+
:tc05: pass:c,q[footnote:tc05[nearley-test result in link:data/test/tc05-expected.json[]]]
+

+
[options="header",cols="2a,1"]
+
|===
+
|URI
+
|Refers to
+

+
|
+
....
+
include::data/test/tc00a[]
+
....
+
{tc00a}
+
|Specific repo (RIP #2 URN form, still valid)
+

+
|
+
....
+
include::data/test/tc00b[]
+
....
+
{tc00b}
+
|Specific repo (RIP #3 URL form, considered legacy)
+

+
|
+
....
+
include::data/test/tc00c[]
+
....
+
{tc00c}
+
|Specific repo (new URI form with empty authority)
+

+
|
+
....
+
include::data/test/tc01a[]
+
....
+
{tc01a}
+
|Specific repo and namespace (new URI form, relative)
+

+
|
+
....
+
include::data/test/tc01b[]
+
....
+
{tc01b}
+
|Specific repo and namespace (RIP #3 URL form, considered legacy)
+

+
|
+
....
+
include::data/test/tc01c[]
+
....
+
{tc01c}
+
|Specific repo and namespace (new URI form with empty authority)
+

+
|
+
....
+
include::data/test/tc02[]
+
....
+
{tc02}
+
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`
+

+
|
+
....
+
include::data/test/tc03[]
+
....
+
{tc03}
+
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`
+

+
|
+
....
+
include::data/test/tc04[]
+
....
+
{tc04}
+
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
+

+
|
+
....
+
include::data/test/tc05[]
+
....
+
{tc05}
+
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
+
|===
+

+
(to test these URIs against a grammar, see <<Testing>> below)
+

+
==== The Resource Type and Identity
+

+
While <<The repository,the repository>> allows you to refer to a Git repository,
+
it may also be desirable to refer to one or more specific resources within
+
that repository.  To do this, <<The repository,the repository>> may be followed
+
by a "/", a resource type and identity.
+

+
By default, the resource type and identity is nothing, left to be added
+
to further on in this RIP, or in future RIPs:
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tags=resource-type-and-identity;!*]
+
----
+

+
==== The Query
+

+
The query part of the `rad:` URI inherits the syntax from <<URI>>.
+
The semantics of the query are to be defined with each resource type.
+

+
==== The Fragment
+

+
The fragment part of the URI inherits the syntax and semantics from <<URI>>.
+

+
=== Relative URIs
+

+
<<URI-Reference-Resolution>> discusses the resolution of relative (absolute
+
or rootless alike) URIs, based on a base URI.
+

+
This RIP has nothing specific to say about this, apart from supporting it.
+

+
== Resource Types and Identities
+

+
These are the resource types and associated identities defined in this
+
RIP.  Future RIPs may amend them, as well as define new resource types
+
with associated identities.
+

+
=== Resource Types Specific to Git
+

+
There are four Git specific resource types:
+

+
* `commit`
+
* `tree`
+
* `blob`
+
* `tag`
+

+
In a `rad:` URI, these resource types are used to refer to Git object types
+
with the same name in a repository.  These resource types must be followed by
+
an identifier that refers to a specific Git object of that type.
+

+
Some of these resource types may also be accompanied by <<The Query,query>>
+
parameters to further refine what the `rad:` URI refers to.  This RIP defines
+
the following <<The Query,query>> parameters, all of which may be given
+
multiple times:
+

+
* `tree=dir1/dir2/…`, to "dig out" a tree given by the path.
+
* `blob=dir1/dir2/…`, to "dig out" a blob given by the path.
+
* `path=dir1/dir2/…`, to "dig out" a tree or a blob given by the path.
+

+
****
+
Applications are by and large allowed to define other <<The Query,query>>
+
parameters than what is defined here.  We hope that future RIPs will help
+
standardise at least the most common ones.
+

+
A few possible examples that may be useful:
+

+
* `rad:{rid}/commit/{git-obj-or-ref}?history`, to view the history of a commit.
+
* `rad:{rid}/blob/{git-obj}?raw`, to view/download the raw contents of a blob,
+
  where one might otherwise reasonably expected it to be wrapped in some
+
  markup, such as HTML.
+
****
+

+
`commit`::
+
+
+
--
+
The identifier that follows this resource type may be:
+

+
* a Git object ID
+
* a Git reference (typically, a branch or a tag name)
+

+
In all cases, this resource type refers to a single commit object.
+

+
With this resource type, it makes sense to use the <<The Query,query>>
+
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
+
--
+

+
`tree`::
+
+
+
--
+
The identifier that follows this type may be must be a Git object identifier.
+

+
With this resource type, it makes sense to use the <<The Query,query>>
+
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
+
--
+

+
`blob`::
+
+
+
--
+
The identifier that follows this type must be a Git object identifier.
+

+
With this resource type, it makes no sense to use any of the <<The
+
Query,query>> defined above.
+
--
+

+
`tag`::
+
+
+
--
+
The identifier that follows this type may be:
+

+
* a Git object ID
+
* a Git reference (i.e. a tag name)
+

+
In all cases, this resource type refers to a single tag object.
+

+
With this resource type, it makes sense to use the <<The Query,query>>
+
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
+
--
+

+
In <<ABNF>> terms, the Git specific resource types are defined like this:
+

+
// [source,abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=git-type-and-identity]
+
----
+

+
Here are a few examples of what a reference to a Git specific resource could
+
look like:
+

+
:tc10a: pass:c,q[footnote:tc10a[nearley-test result in link:data/test/tc10a-expected.json[]]]
+
:tc10b: pass:c,q[footnote:tc10b[nearley-test result in link:data/test/tc10b-expected.json[]]]
+
:tc12: pass:c,q[footnote:tc12[nearley-test result in link:data/test/tc12-expected.json[]]]
+
:tc13: pass:c,q[footnote:tc13[nearley-test result in link:data/test/tc13-expected.json[]]]
+

+
[options="header",cols="2a,1"]
+
|===
+
|URI
+
|Refers to
+

+
|
+
....
+
include::data/test/tc10a[]
+
....
+
{tc10a}
+
|Specific commit in repo
+

+
|
+
....
+
include::data/test/tc10b[]
+
....
+
{tc10b}
+
|Specific commit in repo
+

+
|
+
....
+
include::data/test/tc12[]
+
....
+
{tc12}
+
|Specific commit on a specific node, using the explicit name for the commit object
+

+
|
+
....
+
include::data/test/tc13[]
+
....
+
{tc13}
+
|Specific commit in a specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
+
|===
+

+
(to test these URIs against a grammar, see <<Testing>> below)
+

+
And with queries:
+

+
:tc40a: pass:c,q[footnote:tc40a[nearley-test result in link:data/test/tc40a-expected.json[]]]
+
:tc40b: pass:c,q[footnote:tc40b[nearley-test result in link:data/test/tc40b-expected.json[]]]
+
:tc41a: pass:c,q[footnote:tc41a[nearley-test result in link:data/test/tc41a-expected.json[]]]
+
:tc41b: pass:c,q[footnote:tc41b[nearley-test result in link:data/test/tc41b-expected.json[]]]
+
:tc42a: pass:c,q[footnote:tc42a[nearley-test result in link:data/test/tc42a-expected.json[]]]
+
:tc42b: pass:c,q[footnote:tc42b[nearley-test result in link:data/test/tc42b-expected.json[]]]
+
:tc43a: pass:c,q[footnote:tc43a[nearley-test result in link:data/test/tc43a-expected.json[]]]
+
:tc43b: pass:c,q[footnote:tc43b[nearley-test result in link:data/test/tc43b-expected.json[]]]
+
:tc44: pass:c,q[footnote:tc44[nearley-test result in link:data/test/tc44-expected.json[]]]
+

+
[options="header",cols="2a,1"]
+
|===
+
|URI
+
|Refers to
+

+
|
+
....
+
include::data/test/tc40a[]
+
....
+
{tc40a}
+
|The `src` directory of the repo at tree object `3eb47e9`
+

+
|
+
....
+
include::data/test/tc40b[]
+
....
+
{tc40b}
+
|The `src` directory of the repo at tree object `3eb47e9`
+

+
|
+
....
+
include::data/test/tc41a[]
+
....
+
{tc41a}
+
|The `README.md` file of the repo at the "master" reference
+

+
|
+
....
+
include::data/test/tc41b[]
+
....
+
{tc41b}
+
|The `README.md` file of the repo at the "master" reference
+

+
|
+
....
+
include::data/test/tc42a[]
+
....
+
{tc42a}
+
|The `foo/doc` directory of the repo at the "baz" reference
+

+
|
+
....
+
include::data/test/tc42b[]
+
....
+
{tc42b}
+
|The `foo/doc` directory of the repo at the "baz" reference
+

+
|
+
....
+
include::data/test/tc43a[]
+
....
+
{tc43a}
+
|The `doc` and `src` directories of the repo at the "baz/foo" reference.
+

+
|
+
....
+
include::data/test/tc43b[]
+
....
+
{tc43b}
+
|The `doc` and `src` directories directory of the repo at the "baz/foo" reference.
+

+
|
+
....
+
include::data/test/tc44[]
+
....
+
{tc44}
+
|The commit notes for commit `e9cf5263…`.
+
|===
+

+
(to test these URIs against a grammar, see <<Testing>> below)
+

+
****
+
// Git version that is current at the time of writing.
+
:gitversion: 2.54.0
+

+
// Base URL of Git documentation.
+
:gitdocs: https://git-scm.com/docs
+

+
// Allows for convenient reference to definitions in the Git glossary.
+
// "gitgloss" is just short for "a definition in the Git glossary".
+
:gitgloss: {gitdocs}/gitglossary/{gitversion}#Documentation/gitglossary.txt-
+

+
:gitrevparse: {gitdocs}/git-rev-parse/{gitversion}#Documentation/git-rev-parse.txt-
+

+
[discrete]
+
==== Addressing Git Resources by Reference Names
+

+
[discrete]
+
===== Types of Resources That May Be Addressed
+

+
Resources of the types "commit" and "tag", may, in Git jargon, be
+
"committish". There are at least two definitions in the Git documentation
+
that differ slightly:
+

+
[quote,'{gitgloss}commit-ishalsocommittish[Git glossary entry for "committish"]']
+
A {gitgloss}commit_object[commit object] or an {gitgloss}object[object]
+
that can be recursively {gitgloss}dereference[dereferenced] to a commit
+
object.  The following are all commit-ishes:
+
a commit object,
+
a {gitgloss}tag_object[tag object] that points to a commit object,
+
a tag object that points to a tag object that points to a commit object,
+
etc.
+

+
[quote,'{gitrevparse}--verify[Git documentation of `git rev-parse --verify`]']
+
a commit-ish (i.e. a commit, or an annotated tag that points at a commit)
+

+
We thus restrict ourselves to only refer to commits and tags via references,
+
since no tree or blob object may ever be committish.
+

+
[discrete]
+
===== Ambiguity in Addressing by Reference Name
+

+
Note that Git references are ambiguous, even within one repository (or reference
+
namespace).
+

+
It is possible to create the following set of references in one namespace:
+

+
* `refs/heads/main`
+
* `refs/tags/main`
+
* `refs/heads/refs/heads/main`
+

+
We do not define how such ambiguity is to be resolved, but note that applications
+
will likely want to implement some mechanism for disambiguation.
+

+
See also {gitrevparse}refnameegmasterheadsmasterrefsheadsmaster[the section
+
_Specifying Revisions_ in the documentation of `git rev-parse`].
+

+
****
+

+
=== Resources Types Specific to Radicle Collaborative Objects
+

+
Alongside the usual Git objects, Radicle also implements a generalized
+
collaborative object (usually referred to as COB), identified with a COB type
+
and a COB object.
+

+
For general COB references in a `rad:` URI, these resource types are defined
+
to refer to a Radicle COB / multiple COBs:
+

+
`cob`::
+
+
+
--
+
In a `rad:` URI, this must be followed a slash (`/`) and the COB type.  The
+
COB type _may_ be followed by another slash (`/`) and the COB object.
+

+
If the `rad:` URI includes the COB object, then it refers to a single COB.
+

+
If the `rad:` URI does not include the COB object, then it refers to the set
+
of COB objects of the given COB type.
+
--
+

+
`cob` resources may also be accompanied by a set of <<The Query,query>>
+
parameters to further refine what the `rad:` URI refers to.
+

+
****
+
Because we realise that we cannot know all future COB types and how they
+
function, we cannot do too much in terms of definitions.
+
We leave it to the COB owners as well as applications to work out what query params they
+
will handle, and hopefully, some of them can be standardised in future RIPs.
+

+
A hypothetical example: `rad:{RID}/cob/example.radicle.sparkle?q=glitter`.
+
****
+

+
In <<ABNF>> terms, the COB resource types are defined like this:
+

+
// [source, abnf]
+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=rad-cob-type-and-identity]
+
----
+

+
Here are a few examples of what a reference to other collaborative objects
+
could look like:
+

+
:tc30: pass:c,q[footnote:tc30[nearley-test result in link:data/test/tc30-expected.json[]]]
+
:tc31: pass:c,q[footnote:tc31[nearley-test result in link:data/test/tc31-expected.json[]]]
+
:tc32: pass:c,q[footnote:tc32[nearley-test result in link:data/test/tc32-expected.json[]]]
+

+
[options="header",cols="2a,1"]
+
|===
+
|URI
+
|Refers to
+

+
|
+
....
+
include::data/test/tc30[]
+
....
+
{tc30}
+
|Specific COB of the type `org.example`
+

+
|
+
....
+
include::data/test/tc31[]
+
....
+
{tc31}
+
|Specific COB of the type `org.example` on specific node
+

+
|
+
....
+
include::data/test/tc32[]
+
....
+
{tc32}
+
|The set of COBs of the type `org.example`
+
|===
+

+
== Implementation Requirements and Recommendations
+

+
* Implementations may implement only part of this specification, depending on
+
what makes sense in the context of what they do.  It is advisable, however, if
+
they can parse enough of a valid `rad:` URI to allow for <<Future work,future work>>.
+
For example, it is hard to imagine that `git-remote-rad` would ever support
+
more elaborate URLs than those involving an RID and an NID:
+

+
** `rad:{RID}`
+
** `rad:///{RID}`
+
** `rad:{RID}/{NID}`
+
** `rad:///{RID}/{NID}`
+
** `rad://{RID}/{NID}` (legacy)
+

+
* Implementations that receive a URI they cannot handle - because of
+
unrecognised resource type, identifier, or query params - should signal an
+
error.
+
+
+
For fragments, there is no recommendation, that is left entirely to the
+
authors of those softwares.
+

+
* Software for human interaction on the web should be able to automatically
+
convert between `rad:` and `web+rad:` to create links.  For example, in a
+
markdown context, it would be quite viable for this:
+
+
+
`rad:z3gqcJU…CSGazv5/commit/72db6d…`
+
+
+
to be converted to:
+
+
+
`[rad:z3gqcJU…CSGazv5/commit/72db6d…](web+rad:z3gqcJU…CSGazv5/commit/72db6d…)`
+

+
* Implementations that are aware of the context may support relative URIs as
+
they see fit.  For example, a program that handles Radicle patches and issues
+
could assume the base URI `rad:///{RID}` (given that `{RID}` is the ID of
+
the repository that is being handled), allowing comments to refer to issue and
+
other patches in the same repository with `patch/{PATCH-ID}` or `issue/{ISSUE-ID}`.
+

+
== Future Work
+

+
Future work may include:
+

+
* amending <<The authority,the authority>> with Radicle network
+
  specification syntax (pending future formalization of that idea).
+
* amending <<The authority,the authority>> with multi-device IDs (also
+
  pending formalization of that idea).
+
* adding more resource types, and their respective syntaxes
+
* extending existing resource types.
+
* adding other query types
+
* viewing the history/graph of the underlying Git repository (in analogy to `git log`/`git log --graph`).
+
* viewing revision ranges of the underlying Git repository (in analogy to `git diff`, `git log`).
+

+
== Closing Thoughts
+

+
This RIP started with a status quo and challenges.  It is the hope of the
+
authors that the challenges are sufficiently addressed, while also leaving
+
doors open for more extensions in the future.
+

+
== Copyright
+

+
This document is licensed under the Creative Commons CC0 1.0 Universal license.
+

+
[bibliography]
+
== References
+

+
// https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#bibliography
+
// says that anchors like [[[ABNF]]] should render to [ANBF], but experience
+
// says that this is not quite true.  However, [[ABNF]][ABNF] seems to render
+
// what I hoped for.  /RL
+

+
[[ABNF]][ABNF]:: https://datatracker.ietf.org/doc/html/rfc5234[RFC 5234 - Augmented BNF for Syntax Specifications: ABNF]
+
[[HTML]][HTML]:: Web Hypertext Application Technology Working Group: _HTML Living Standard_ (<https://html.spec.whatwg.org>)
+
[[HTML-registerhandler]][HTML-registerhandler]:: Sec. 8.9.1.4, _Custom scheme handlers: the `registerProtocolHandler()` method_ (<https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers>)
+
[[Nearley]][Nearley]:: Kartik Chandra's Nearley grammar (<https://nearley.js.org/>)
+
[[radicle-explorer]][radicle-explorer]:: Radicle Web Interface - link:web+rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5[rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5] +
+
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5>)
+
[[radicle-jetbrains]][radicle-jetbrains]:: Radicle JetBrains plugin - link:web+rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK[rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK] +
+
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK>)
+
[[radicle-vscode]][radicle-vscode]:: Radicle VSCode extension - link:web+rad:z3Makm6fsQQXmpSFE43DZqwupaEhk[rad:z3Makm6fsQQXmpSFE43DZqwupaEhk] +
+
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3Makm6fsQQXmpSFE43DZqwupaEhk>), +
+
  link:web+vscode:extension/radicle-ide-plugins-team.radicle[vscode:extension/radicle-ide-plugins-team.radicle] +
+
  (<https://marketplace.visualstudio.com/items?itemName=radicle-ide-plugins-team.radicle>)
+
[[RIP2]][RIP2]:: @cloudhead: Identity - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md] +
+
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/1c402116983be19e754fb14aa7ce38145f0a4b09/0002-identity.md>)
+
[[RIP3]][RIP3]:: @fintohaps: Radicle Improvement Proposal #3: Storage Layout - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url] +
+
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/329dee9a4b65169ea3889a7da239892b705d0d68/0003-storage-layout.md#url>)
+
[[URI]][URI]:: https://datatracker.ietf.org/doc/html/rfc3986[RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax]
+
[[URI-Authority]][URI-Authority]:: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2[RFC 3986 - 3.2. Authority]
+
[[URI-Reference-Resolution]][URI-Reference-Resolution]:: https://datatracker.ietf.org/doc/html/rfc3986#section-5[RFC 3986 - 5. Reference Resolution]
+
[[zulip-mangle]][zulip-mangle]:: Zulip Topic: _`rad:` gets mangled by Zulip_ (#general, 2024-04-23) (<https://radicle.zulipchat.com/#narrow/stream/369274-general/topic/.60rad.3A.60.20gets.20mangled.20by.20Zulip>)
+
[[base58btc]][base58btc]:: https://datatracker.ietf.org/doc/html/draft-msporny-base58-02[Base58 Specification]
+
[[multibase]][multibase]:: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html[Multibase Specification]
+
[[multicodec]][multicodec]:: https://github.com/multiformats/multicodec/[Multiformats: multicodec]
+

+
[appendix]
+
== Full <<ABNF>> grammar
+

+
[%nowrap]
+
----
+
include::data/rad-uri.abnf[tag=!rfc5234]
+
----
+

+
[appendix]
+
== Full <<Nearley>> grammar
+

+
[%nowrap]
+
----
+
include::data/rad-uri.ne[]
+
----
+

+
[appendix]
+
== Testing
+

+
All the examples sprinkled in this RIP are tested against the <<Nearley>>
+
grammar.  Test inputs and expected parse results are in `data/test/`, where
+
each `tc__` file contains a valid URI and the corresponding
+
`tc__-expected.json` contains the expected parse result as JSON.
+

+
The test suite can be run with:
+

+
[,shell]
+
----
+
nix flake check
+
----
+

+
This compiles the grammar with `nearleyc`, runs `nearley-test` on each test
+
input, and compares the JSON output against the expected result.
+

+
To test a single URI manually:
+

+
[,shell]
+
[%nowrap]
+
----
+
# [one time] Compile the grammar into javascript
+
nearleyc -o rad-uri.js data/rad-uri.ne
+

+
# Test the chosen URI string ($URI)
+
nearley-test -q -i "$URI" rad-uri.js
+
----
+

+
`nearley-test` will return all possible results for the URI, which means that
+
where there are syntactic ambiguities, there will be more than one result.
+
For testing purposes, it is enough if one of the results match the expected
+
results linked from the footnotes associated with the examples in the tables
+
above.
modified flake.nix
@@ -13,11 +13,42 @@
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = nixpkgs.legacyPackages.${system};
+

+
      nearley = pkgs.buildNpmPackage {
+
        pname = "nearley";
+
        version = "2.20.1";
+
        src = ./nix/nearley;
+
        npmDepsHash = "sha256-a0RjAhSlzEbe6tM6QsZa06xKc0uzEe0cK6OsuVQSSzo=";
+
        dontNpmBuild = true;
+
      };
    in {
      devShells.default = pkgs.mkShell {
-
        buildInputs = [
-
          pkgs.asciidoctor
-
        ];
+
        buildInputs =
+
          (with pkgs; [
+
            asciidoctor
+
            nodejs
+
            shellcheck
+
            yq
+
          ])
+
          ++ [
+
            nearley
+
          ];
      };
+

+
      checks.grammar =
+
        pkgs.runCommand "grammar-check" {
+
          nativeBuildInputs =
+
            (with pkgs; [
+
              nearley
+
              nodejs
+
              yq
+
            ])
+
            ++ [
+
              nearley
+
            ];
+
        } ''
+
          ${./0004-general-uri-scheme/data/test/uri-scheme-tester.sh} ${./0004-general-uri-scheme/data}
+
          touch $out
+
        '';
    });
}
added nix/nearley/package-lock.json
@@ -0,0 +1,87 @@
+
{
+
  "name": "nearley-wrapper",
+
  "version": "2.20.1",
+
  "lockfileVersion": 3,
+
  "requires": true,
+
  "packages": {
+
    "": {
+
      "name": "nearley-wrapper",
+
      "version": "2.20.1",
+
      "dependencies": {
+
        "nearley": "^2.20.1"
+
      },
+
      "bin": {
+
        "nearley-test": "node_modules/.bin/nearley-test",
+
        "nearleyc": "node_modules/.bin/nearleyc"
+
      }
+
    },
+
    "node_modules/commander": {
+
      "version": "2.20.3",
+
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+
      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+
      "license": "MIT"
+
    },
+
    "node_modules/discontinuous-range": {
+
      "version": "1.0.0",
+
      "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
+
      "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
+
      "license": "MIT"
+
    },
+
    "node_modules/moo": {
+
      "version": "0.5.3",
+
      "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz",
+
      "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==",
+
      "license": "BSD-3-Clause"
+
    },
+
    "node_modules/nearley": {
+
      "version": "2.20.1",
+
      "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
+
      "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
+
      "license": "MIT",
+
      "dependencies": {
+
        "commander": "^2.19.0",
+
        "moo": "^0.5.0",
+
        "railroad-diagrams": "^1.0.0",
+
        "randexp": "0.4.6"
+
      },
+
      "bin": {
+
        "nearley-railroad": "bin/nearley-railroad.js",
+
        "nearley-test": "bin/nearley-test.js",
+
        "nearley-unparse": "bin/nearley-unparse.js",
+
        "nearleyc": "bin/nearleyc.js"
+
      },
+
      "funding": {
+
        "type": "individual",
+
        "url": "https://nearley.js.org/#give-to-nearley"
+
      }
+
    },
+
    "node_modules/railroad-diagrams": {
+
      "version": "1.0.0",
+
      "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
+
      "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
+
      "license": "CC0-1.0"
+
    },
+
    "node_modules/randexp": {
+
      "version": "0.4.6",
+
      "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
+
      "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
+
      "license": "MIT",
+
      "dependencies": {
+
        "discontinuous-range": "1.0.0",
+
        "ret": "~0.1.10"
+
      },
+
      "engines": {
+
        "node": ">=0.12"
+
      }
+
    },
+
    "node_modules/ret": {
+
      "version": "0.1.15",
+
      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+
      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+
      "license": "MIT",
+
      "engines": {
+
        "node": ">=0.12"
+
      }
+
    }
+
  }
+
}
added nix/nearley/package.json
@@ -0,0 +1,11 @@
+
{
+
  "name": "nearley-wrapper",
+
  "version": "2.20.1",
+
  "dependencies": {
+
    "nearley": "^2.20.1"
+
  },
+
  "bin": {
+
    "nearleyc": "./node_modules/.bin/nearleyc",
+
    "nearley-test": "./node_modules/.bin/nearley-test"
+
  }
+
}