README.md
-rw-r--r-- 12.8 KiB

ForgeFeed

NOTE: This specification is a W.I.P and should not yet be implemented anywhere.

ForgeFeed is a collection of specifications and recommendations which when implemented can enhance interoperability and content discovery of different code forges across the internet.

Below we cover the following specifications:

Repository URI - URI Identifier for WebFinger Queries
WebFinger Query - Metadata Queries via WebFinger
RSS Specification - Repository Discovery via RSS

Repository URI

A repository URI identifies a code repository and optionally the host that is resides on. This value is similar to RFC7565. The slug and hostname part MUST match the URI path specification as defined in RFC3986-3.3 while the hostname, if specified, must match RFC3986-3.2.2

repository-uri = prefix slug hostname
prefix = "repository:"
slug = rfc3986-path
hostname = [@ rfc3986-hostname]

Slug

The Slug represents a unique string that identifies a repository at a particular code forge. Repository refers to a VCS managed codebase of some kind, e.g. a Git repository.

Hostname

If the hostname part is missing then the address of the server receiving the query is assumed. For example the following two queries are equivalent:

https://example.org/.well-known/webfinger?resource=repository:example/spartacus
https://example.org/.well-known/webfinger?resource=repository:example/spartacus@example.org

TODO: Make this an actual spec, for now, some Python:

from urllib.parse import urlparse, quote_plus
def from_string(text):
url = urlparse(text)
if not url.path:
return (None, None)
split = url.path.split("@", 1)
if len(split) == 2:
return (split[0], split[1])
return (split[0], None)
def to_string(slug, domain=None):
if domain:
return quote_plus(f"repository:{slug}@{domain}")
else:
return quote_plus(f"repository:{slug}")

WebFinger Query

A WebFinger query may be used to identify detailed information about a public repository at a particular forge. Here is an example response about a fictitious repository:

GET https://example.org/.well-known/webfinger?resource=repository:example/spartacus

{
"subject": "repository:example/spartacus",
"aliases": [
"https://example.org/example/spartacus"
],
"links": [
{
"rel": "http://webfinger.net/rel/avatar",
"href": "https://example.org/stylized-logo.png"
},
{
"rel": "http://forge-feed.org/rel/description",
"titles": {
"en-us": "A Text Adventure Written in FORTRAN 77",
"es": "Una Aventura de Texto Escrita en FORTRAN 77"
}
},
{
"rel": "http://feed-forge.org/rel/clone",
"href": "https://example.org/example/spartacus",
"properties": {
"http://feed-forge.org/ns/vcs-type": "git"
}
},
{
"rel": "http://forge-feed.org/rel/license",
"href": "https://example.com/example/spartacus/tree/LICENSE",
"properties": {
"spdx-identifier": "GPL-2.0-or-later"
}
},
{
"rel": "http://forge-feed.org/rel/chatroom",
"href": "ircs://irc.libera.chat/#spartacus-game"
"properties": {
"http://feed-forge.org/ns/chatroom": "irc"
}
},
{
"rel": "http://forge-feed.org/rel/label",
"properties": {
"http://feed-forge.org/ns/label": "fortran"
}
},
{
"rel": "http://forge-feed.org/rel/label",
"properties": {
"http://feed-forge.org/ns/label": "text-adventure"
}
}
]
}

Properties

http://feed-forge.org/ns/vcs-type

Identifies VCS types, valid strings are:

bzr (GNU Bazaar) bazaar.canonical.com
darcs (Darcs) darcs.net
fossil (Fossil) fossil-scm.org
git (Git) git-scm.com
hg (Mercurial) mercurial-scm.org
pijul (Pijul) pijul.org
svn (Apache Subversion) subversion.apache.org

http://feed-forge.org/ns/chatroom

Hint describing the backing type of chatroom. See uri-schemes.

matrix
irc
xmpp

http://feed-forge.org/ns/spdx-identifier

Refers to a valid SPDX identifier, see license-list

Avatar

Forges that allow users to configure a logo can expose this information as an avatar for use in other applications.

{
"rel": "http://webfinger.net/rel/avatar",
"href": "https://example.org/stylized-logo.png"
}

Homepage

Link to an HTTP representation of the project codebase

{
"rel": "http://feed-forge.org/rel/homepage",
"href": "https://example.org/example/spartacus"
}

Description

A short text representation of the repository.

{
"rel": "http://example.org/rel/description",
"titles": {
"en-us": "A Text Adventure Written in FORTRAN 77",
"es": "Una Aventura de Texto Escrita en FORTRAN 77"
}
}

License

A license SPDX identifier and link to the license’s full text.

{
"rel": "http://feed-forge.org/rel/license",
"href": "https://example.com/example/spartacus/tree/LICENSE",
"properties": {
"http://feed-forge.org/ns/spdx-identifier": "GPL-2.0-or-later"
}
}

Clone links define how one can download a copy of the remote software onto their own server.

{
"rel": "http://feed-forge.org/rel/clone",
"href": "https://example.org/example/spartacus",
"properties": {
"http://feed-forge.org/ns/vcs-type": "git"
}
}

Links to chatrooms: IRC, Matrix, XMPP, etc.

{
"rel": "http://feed-forge.org/rel/chatroom",
"href": "ircs://irc.libera.chat/#spartacus-game"
"properties": {
"http://feed-forge.org/ns/chatroom": "irc"
}
},
{
"rel": "http://feed-forge.org/rel/chatroom",
"href": "matrix:r/spartacus-game#example.org",
"properties": {
"http://feed-forge.org/ns/chatroom": "matrix"
}
},
{
"rel": "http://feed-forge.org/rel/chatroom",
"href": "xmpp:example.org/spartacus-game",
"properties": {
"http://feed-forge.org/ns/chatroom": "xmpp"
}
}

Mailing Lists

Links to associated mailing lists, forms, etc.

{
"rel": "http://feed-forge.org/rel/mailing-list",
"href": "mailto://list-name@mail.example.org",
"properties": {
"http://feed-forge.org/ns/mailing-list-subscribe": "mailto://subscribe+list-name@mail.example.org",
"http://feed-forge.org/ns/mailing-list-unsubscribe": "mailto://unsubscribe+list-name@mail.example.org"
}
}

Free-Form Tags

Arbitrary, free-form tags.

{
"rel": "http://example.org/rel/label",
"properties": {
"http://feed-forge.org/ns/label": "fortran"
}
},
{
"rel": "http://example.org/rel/label",
"properties": {
"label": "text-adventure"
}
}

Security

Repositories which are not publicly available should not be identifiable by making webfinger queries at all. A repository which is private MUST return the same response as a repository which does not exist when making a webfinger request.

GET https://example.org/.well-known/webfinger?resource=repository:example/spartacus
200
GET https://example.org/.well-known/webfinger?resource=repository:example/private-repository
404
GET https://example.org/.well-known/webfinger?resource=repository:example/non-existent-repository
404

RSS Feeds

Your forge needs to expose an RSS feed in order for other peers to determine what code repositories exist in your server. Your server should expose repositories ordered by recent updates. The heuristic you use to determine “recently updated” depends on your forge. The simplest way to determine recent activity is likely by your VCS’s concept of a “commit”.

Determine if a Host Supports Forge Feed

In order to participate in ForgeFeed your forge MUST present an HTML link element such as below at the root domain of your forge. For example, code.example.org MUST have a link element present in it’s html header:

<html>
<head>
<title>My Forge</title>
<link
rel="alternate"
type="application/rss+xml"
title="Recent Forge Activity"
href="https://code.example.org/firehose.xml" />
<!-- index-url refers to the RSS link on the current webpage which contains forge updates -->
<meta name="forge-feed:index-url" content="https://code.example.org/firehose.xml"/>
</head>
<body>
...
</body>
</html>

The link MAY have a title of “Recent Forge Activity” such that it uniquely identifies this feed as being related to forge updates. A server MUST provide a meta tag with the name forge-feed:index where the content of the tag matches the href of an RSS link listed on this page which should be used for indexing by external crawlers. Applications which rely on the forge-feed:index meta tag MUST stop crawling the serving website if this tag is no longer present in the pages HTML.

An example Feed

Below is an example RSS feed with an optional repository section (described below).

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Firehose</title>
<link>https://code.example.org/browse</link>
<description>Recent Activity @ code.example.org</description>
<lastBuildDate>Tue, 03 Jun 2025 14:18:48 +0000</lastBuildDate>
<ttl>60</ttl>
<item>
<title>Spartacus</title>
<description>A Text Adventure Written in FORTRAN 77<description/>
<author>admin@example.org (Administrator)</author>
<!-- Link to an HTTP representation of the repository -->
<link>https://example.org/example/spartacus</link>
<!-- Guid may be the repository name at a particular commit id-->
<guid>https://example.org/example/spartacus@f28ce68f16e338355e55372bc788e8ea5feda470</guid>
<pubDate>Sun, 25 May 2025 18:23:03 +0200</pubDate>
<!-- Optional enclosure with an image reference if your forge supports this -->
<enclosure url="https://example.org/example/spartacus/raw/main/logo.png" length="12216320" type="image/png" />
<!-- Although highly recommended, the fields below are optional -->
<repository>
<!-- repository URI described above -->
<webfinger>repository:example/spartacus</webfinger>
</repository>
</item>
</channel>
</rss>

Repository Extension

In order to facilitate discovery by external indexes it is highly recommended that your server implement the webfinger repository specification described above so that repository indexes may populate their state with rich information about code repositories hosted on your server.

<repository>
<webfinger>repository:example/spartacus@example.org</webfinger>
</repository>

If the host section of the URI is included that is MUST match domain name of the server which is providing the feed.

Security Concerns

Private Repositories

Forge-feed enabled RSS feeds have no support for sharing private repositories and any repository that is not shared publicly on the internet must be hidden from the RSS activity feed stream. If your forge provides the ability to change a repository from public to private it must be understood that clients may already have cached versions of your repository data.

Recommendations for Enumerating Repository Events

Specify a Maximum Timeframe

Your activity feed should not include repository events that are older than 1 week.

Repository Items SHOULD be Unique

Although it is permitted to return duplicate repositories in the feed forges SHOULD only return unique repositories in a given window.

Event “Clamping”

It may be undesirable to enumerate repository events items with the simple heuristic of

end = time.now()
start = end - 6h
events = repository_events_between(start, end)

Because the oldest repository events will fall out of the time window on subsequent queries by RSS readers. This can cause some RSS readers to frequently request new content from your feed. Instead events can be “clamped” within a certain time period. For example you may choose to publish four buckets of updates per day:

00:00:00 06:00:00
06:00:00 12:00:00
12:00:00 18:00:00
18:00:00 00:00:00
For example if the current time is 2021-03-05 13:00:55 UTC you could use
the following example to return a summary of repository events from the
previous time bucket.
now = time.now()
start = now.set_time("06:00:00")
end = now.set_time("12:00:00")
events = repository_events_between(start, end)

This approach with a TTL value of 60 (minutes) will reduce excess requests from some readers but allow all clients to receive timely updates.