Closes the relay side of the 0.32 unification. Replaces #243, which cannot merge: wave 2 landed on main while that branch sat, and the two sides rewrote the same send path.
The resolution that mattered
Wave 2 extracted the envelope write, the in-process wake and the APNs push into Delivery.write. This branch still had the inline version, plus one thing wave 2 knew nothing about: the announced column, which is what the iPhone badge counts.
The extraction is kept. Delivery.Item gains announced, defaulting false exactly as Envelope.init does, and Delivery.insert writes it. Without that, the model, the migration and badgeCount would all have merged cleanly and every badge would have read zero, because nothing on the surviving path ever set the column. The submit route passes !silent; a federated deposit passes false, since nothing announced it.
The rest
docs/CONFIGURATION.md takes the union: main listed six outbound connections with the setting that enables each, this branch added the Today fetch and dropped the other five. Seven are listed now.
PostgresMigrationTests keeps both sides' tests, with the LockedRows actor left last in the file.
.e2e-ref moves to d0274ca0, the current flamenet-e2e main. The old pin was 310c315, a branch head that was not on main at all.
The generated pages were regenerated, not resolved
check-doc-pages.py reported spec, audit, engine and self-hosting all stale against their sources, so three of the eight conflicts were in files nobody should edit by hand. Scripts/build-doc-pages.sh ran against the new pin and all nine pages now match their sources.
That also fixes the spec page's section numbers. flamenet-e2e moved anonymous reads and contact links to §8.13 and §8.14 to make room for the push token and the Today file at §8.11 and §8.12, so every reference on the old page pointed at the wrong section. Eight more were renumbered by hand in Wire, RoutesAnonymous, RoutesDiscovery, ContactLinkPageTests and AnonymousReadsTests; the remaining 8.11 and 8.12 references are about push and the Today file and are correct.
Verification
swift build is clean on Swift 6.4 here. swift test could not be run locally: the Swift frontend crashed with fatalError compiling the test module, with only warnings printed, which is a macOS toolchain problem rather than a source one. The swift job in CI compiles and runs the suite on Linux, and that is what should be read here.
Closes the relay side of the 0.32 unification. Replaces #243, which cannot merge: wave 2 landed on main while that branch sat, and the two sides rewrote the same send path.
**The resolution that mattered**
Wave 2 extracted the envelope write, the in-process wake and the APNs push into `Delivery.write`. This branch still had the inline version, plus one thing wave 2 knew nothing about: the `announced` column, which is what the iPhone badge counts.
The extraction is kept. `Delivery.Item` gains `announced`, defaulting false exactly as `Envelope.init` does, and `Delivery.insert` writes it. Without that, the model, the migration and `badgeCount` would all have merged cleanly and every badge would have read zero, because nothing on the surviving path ever set the column. The submit route passes `!silent`; a federated deposit passes false, since nothing announced it.
**The rest**
- `docs/CONFIGURATION.md` takes the union: main listed six outbound connections with the setting that enables each, this branch added the Today fetch and dropped the other five. Seven are listed now.
- `PostgresMigrationTests` keeps both sides' tests, with the `LockedRows` actor left last in the file.
- `.e2e-ref` moves to `d0274ca0`, the current flamenet-e2e main. The old pin was `310c315`, a branch head that was not on main at all.
**The generated pages were regenerated, not resolved**
`check-doc-pages.py` reported spec, audit, engine and self-hosting all stale against their sources, so three of the eight conflicts were in files nobody should edit by hand. `Scripts/build-doc-pages.sh` ran against the new pin and all nine pages now match their sources.
That also fixes the spec page's section numbers. flamenet-e2e moved anonymous reads and contact links to §8.13 and §8.14 to make room for the push token and the Today file at §8.11 and §8.12, so every reference on the old page pointed at the wrong section. Eight more were renumbered by hand in `Wire`, `RoutesAnonymous`, `RoutesDiscovery`, `ContactLinkPageTests` and `AnonymousReadsTests`; the remaining 8.11 and 8.12 references are about push and the Today file and are correct.
**Verification**
`swift build` is clean on Swift 6.4 here. `swift test` could not be run locally: the Swift frontend crashed with `fatalError` compiling the test module, with only warnings printed, which is a macOS toolchain problem rather than a source one. The `swift` job in CI compiles and runs the suite on Linux, and that is what should be read here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
GET /today.json (§8.11) serves one public, signed file, the same bytes
for everybody. It comes from FN_TODAY_FILE, read again when the file
changes, or from FN_TODAY_SOURCE, fetched every 15 minutes give or take
a minute, with a 10 second deadline over the whole answer and a 512 KB
cap. Nothing is served until it verifies against a key in FN_TODAY_KEYS
(kid = first eight hex characters of the key's SHA-256), and a file that
fails the check never replaces the last one that passed. FN_TODAY_CACHE
keeps that copy across a restart.
404 {"error":"not_offered"} with no source set, 503 while nothing has
verified. A 200 is the bytes unchanged as application/json, public for
900 seconds, with the quoted SHA-256 of those bytes as the ETag, so every
client gets the same one; If-None-Match gets a 304. The route reads no
credential, cookie or query, writes no log line per request, and adds no
table.
Documented in docs/CONFIGURATION.md, docs/API.md and .env.example. The
flamenet.io builder's dev-signed sample is a test fixture; the dev key's
private half is not in the repository.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hello link is https://<relay>/hi#<address> (§6.34). The address is in
the fragment, which no browser sends, so /hi and /es/hi are one static
page for every link, and Public/hi.js reads the address on the device:
fnid: and 52 characters of the apps' base32 alphabet, then @ and a host
of letters, digits, hyphens and dots, the rule both apps use. A good
address is shown as text with "Open in Flamenet Messenger"
(flamenetmessenger://hi#<address>) and a copy button; anything else gets
a plain explanation. Both carry the install links and the iPhone
App Store or TestFlight switch from /get.
The script makes no request and builds no URL but the app link. A test
reads it for every way a page can send something, and on macOS runs it
in JavaScriptCore against stand-ins for the page's elements. Both pages
ask not to be indexed (header and meta tag), stay out of the sitemap,
load nothing from another host, and are in the staging gate's page list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DRAFT. Not for deploy until the owner approves the wording, and a native
speaker the Spanish. One line at the end of "The short version" on
/metadata and /es/metadata saying the server offers one public Today file,
the same for everybody, and that asking for it tells the server nothing
it does not already see. It renders only on a relay that offers the
file, because the page describes the running configuration, and each
copy carries an HTML comment marking it as a draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The badge was the number of undelivered envelopes for the device, and
delivery keys, receipts and answers wait in the same mailbox as messages,
so one message read as 2. Each envelope now carries one bit, announced,
set from the send's silent flag (urgent wins, as it does for the push),
and the badge counts envelopes that are undelivered and announced. A
deposit from another relay sends no push, so it is not announced. The bit
lives in the row and goes with it.
AddEnvelopeAnnounced adds the column NOT NULL DEFAULT false in one ADD,
which SQLite accepts because of the default; on Postgres a constant
default is a catalogue change. Rows already waiting take false: nothing
recorded whether they were announced, false can only leave such a message
out of the count until its device collects it, and true would count the
silent envelopes already queued, the over-count being fixed. A new
Envelope starts unannounced, so a row written by any other path can only
leave the badge short.
The /metadata row about the silent flag says it is never written down,
which this makes false. Replacement wording is drafted for the owner and
nothing public is changed here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row said the flag is never written down. With the badge counting
announced envelopes only, it is kept, inverted, as one bit on the
envelope until the envelope goes. The English row is the owner's
approved wording. The Spanish row is the draft, marked in the page as
waiting for a native speaker's check before any deploy, so the old
Spanish line, false once this ships, does not go out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"A closed relay opens no outbound connections at all" stopped being true
once FN_TODAY_SOURCE existed. It now says the exception, and that the
fetch is all it opens one for. Wording approved by the owner; the
generated page is rebuilt from the markdown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Today file at /today.json, the /hi page a hello link opens, and the
drafted /metadata line for the Today file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPhone badge counts announced envelopes only, with the /metadata row
that says what is now kept on a waiting envelope.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§8.11 is push-token registration, which shipped relay and iOS code
already cite, so the spec gives the Today file §8.12 (DESIGN 34). Every
0.32 citation of the Today file moves to §8.12: TodayFile.swift,
Config.swift, Entrypoint.swift, TodayFileTests, docs/CONFIGURATION.md and
docs/API.md, with configuration.leaf and api.leaf rebuilt from the
markdown. The push-token citations stay §8.11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DESIGN 41. JavaScript's toLowerCase folds a Kelvin sign (U+212A) into
"k", so the page took relay.e<Kelvin>ample.com as relay.ekample.com, a
host both apps refuse. The host is now lowered A to Z only; any other
character stays as it is and fails the ASCII host check. Whitespace
anywhere in the address, around the @ included, is refused outright, and
the 400-character fragment cap stays.
Two JavaScriptCore tests: the Kelvin case (plain and percent-encoded),
which failed before this change, and spaces around the @, which the old
patterns already refused and now pins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.e2e-ref moves to d07ed14 (docs/spec-0-32). spec.leaf and engine.leaf
are rebuilt from that commit with Scripts/build-doc-pages.sh (pandoc
3.11); the other seven pages come out unchanged, and
check-doc-pages.py reports all nine current.
The pinned commit has to be on the remote before this pipeline runs, so
the spec branch is pushed first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 conflict hunks across 8 files. Wave 2 landed on main while this branch sat,
and the two sides rewrote the same send path in different directions.
Routes.swift and RoutesFederation.swift: wave 2 extracted the write, the wake
and the push into `Delivery.write`, and this branch still had the inline
version plus one thing wave 2 had no idea about, the `announced` column. The
extraction is kept; `Delivery.Item` gains `announced`, defaulting false as
`Envelope.init` does, and `Delivery.insert` writes it. Without that the model,
the migration and `badgeCount` would all have merged cleanly and every badge
would have read zero, because nothing on the surviving path ever set the
column. The submit route passes `!silent`; a federated deposit passes false,
since nothing announced it.
docs/CONFIGURATION.md: the union. Main lists six outbound connections with the
setting that enables each; this branch added the Today fetch and dropped the
other five. Seven are listed now.
PostgresMigrationTests: both sides' tests, with the `LockedRows` actor left
last in the file.
.e2e-ref moves to d0274ca0, the current flamenet-e2e main. The old pin was
310c315, which was a branch head and not on main at all.
The three generated doc pages were not resolved by hand. `check-doc-pages.py`
reported spec, audit, engine and self-hosting all stale against their sources,
so `Scripts/build-doc-pages.sh` ran against the new pin and all nine pages now
match. That also fixes the spec page's section numbers: flamenet-e2e moved
anonymous reads and contact links to 8.13 and 8.14 to make room for the push
token and the Today file.
Eight more references renumbered by hand, in Wire, RoutesAnonymous,
RoutesDiscovery, ContactLinkPageTests and AnonymousReadsTests. The remaining
8.11 and 8.12 references are about push and the Today file and are correct.
`swift build` is clean on Swift 6.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The relay's `swift` job failed on this and `swift build` on macOS did not:
TodayFile.swift💯 error: stored property 'key' of 'Sendable'-conforming
struct 'Key' has non-sendable type 'Curve25519.Signing.PublicKey'
`Curve25519.Signing.PublicKey` is Sendable in Apple's CryptoKit and is not in
swift-crypto, which is what the Linux build uses. The struct now holds the 32
raw bytes, which are Sendable everywhere, validates them in `init` exactly as
before, and rebuilds the verifying key at the one place a signature is
checked. A key that passed `init` rebuilds, so the `?? false` is unreachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Routes.placeDeposits` is gone: wave 2 split it into `resolveDeposits`, which
turns a peer's envelopes into `Delivery.Item`s, and `Delivery.write`, which
inserts them and then wakes and pushes. The test still called the old one and
the Linux test build stopped on "type 'Routes' has no member 'placeDeposits'".
It now does what the route does, in the same two steps, and still asserts the
same thing: a deposit is written with `announced` false, so the badge does not
count it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the relay side of the 0.32 unification. Replaces #243, which cannot merge: wave 2 landed on main while that branch sat, and the two sides rewrote the same send path.
The resolution that mattered
Wave 2 extracted the envelope write, the in-process wake and the APNs push into
Delivery.write. This branch still had the inline version, plus one thing wave 2 knew nothing about: theannouncedcolumn, which is what the iPhone badge counts.The extraction is kept.
Delivery.Itemgainsannounced, defaulting false exactly asEnvelope.initdoes, andDelivery.insertwrites it. Without that, the model, the migration andbadgeCountwould all have merged cleanly and every badge would have read zero, because nothing on the surviving path ever set the column. The submit route passes!silent; a federated deposit passes false, since nothing announced it.The rest
docs/CONFIGURATION.mdtakes the union: main listed six outbound connections with the setting that enables each, this branch added the Today fetch and dropped the other five. Seven are listed now.PostgresMigrationTestskeeps both sides' tests, with theLockedRowsactor left last in the file..e2e-refmoves tod0274ca0, the current flamenet-e2e main. The old pin was310c315, a branch head that was not on main at all.The generated pages were regenerated, not resolved
check-doc-pages.pyreported spec, audit, engine and self-hosting all stale against their sources, so three of the eight conflicts were in files nobody should edit by hand.Scripts/build-doc-pages.shran against the new pin and all nine pages now match their sources.That also fixes the spec page's section numbers. flamenet-e2e moved anonymous reads and contact links to §8.13 and §8.14 to make room for the push token and the Today file at §8.11 and §8.12, so every reference on the old page pointed at the wrong section. Eight more were renumbered by hand in
Wire,RoutesAnonymous,RoutesDiscovery,ContactLinkPageTestsandAnonymousReadsTests; the remaining 8.11 and 8.12 references are about push and the Today file and are correct.Verification
swift buildis clean on Swift 6.4 here.swift testcould not be run locally: the Swift frontend crashed withfatalErrorcompiling the test module, with only warnings printed, which is a macOS toolchain problem rather than a source one. Theswiftjob in CI compiles and runs the suite on Linux, and that is what should be read here.🤖 Generated with Claude Code
GET /today.json (§8.11) serves one public, signed file, the same bytes for everybody. It comes from FN_TODAY_FILE, read again when the file changes, or from FN_TODAY_SOURCE, fetched every 15 minutes give or take a minute, with a 10 second deadline over the whole answer and a 512 KB cap. Nothing is served until it verifies against a key in FN_TODAY_KEYS (kid = first eight hex characters of the key's SHA-256), and a file that fails the check never replaces the last one that passed. FN_TODAY_CACHE keeps that copy across a restart. 404 {"error":"not_offered"} with no source set, 503 while nothing has verified. A 200 is the bytes unchanged as application/json, public for 900 seconds, with the quoted SHA-256 of those bytes as the ETag, so every client gets the same one; If-None-Match gets a 304. The route reads no credential, cookie or query, writes no log line per request, and adds no table. Documented in docs/CONFIGURATION.md, docs/API.md and .env.example. The flamenet.io builder's dev-signed sample is a test fixture; the dev key's private half is not in the repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>