Say what is true, without dating it
Comments and docs kept dating themselves: when a constraint was set, when a pair was verified, what a limit was before some day, which day a subtree was split out. A reader wants the current state, and the date goes stale faster than the sentence around it. Kept, because they are data rather than narration: ISO timestamps in test fixtures, the certificate expiry in project.yml, which an operator acts on, and docs/BUGS-2026-09-01.md, whose name is the record. ⚠️ The first pass collapsed indentation. A whitespace rule meant for prose matched YAML list items and markdown continuation lines, and project.yml stopped parsing: "did not find expected key: - path: Sources". The tests still passed, because they ran against the project generated before the break, which is exactly how this would reach somebody else. Reverted and redone without touching leading whitespace, then xcodegen was run to prove the file parses. 764 XCTest and 75 Swift Testing pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
013eb5266e
commit
15b9d5b44a
+2
-2
@@ -32,7 +32,7 @@ unit-tests:
|
||||
# that script copies it into the generated workspace. Running xcodegen
|
||||
# directly here meant CI resolved every dependency fresh against the
|
||||
# network while local builds were correctly pinned, which is why this only
|
||||
# ever broke on CI. It broke on 2026-08-31 when upstream MOVED the
|
||||
# ever broke on CI. It broke when upstream MOVED the
|
||||
# `151.0.0` tag of stasel/WebRTC, a prebuilt XCFramework, from
|
||||
# 19aa8c1f to 3e43fa9b. The pin is what noticed.
|
||||
# The protocol vectors are copied into `Tests/Vectors`, so check the
|
||||
@@ -40,7 +40,7 @@ unit-tests:
|
||||
# checkout because that is exactly how Android lost twenty conformance tests
|
||||
# its engine looks for `../../../flamenet-e2e/...`, which exists on a
|
||||
# developer's machine and never in a build directory, so its suite failed
|
||||
# here from the day it was written until 2026-09-06 without anyone seeing
|
||||
# here from the day it was written without anyone seeing
|
||||
# it. A copy in the test bundle cannot be absent. What a copy CAN do is go
|
||||
# stale, so this is the other half of that bargain.
|
||||
#
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Export compliance, v0.1 (decided 2026-08-19)
|
||||
# Export compliance, v0.1
|
||||
|
||||
Answer on the App Store Connect encryption questions:
|
||||
|
||||
@@ -15,7 +15,7 @@ Answer on the App Store Connect encryption questions:
|
||||
implemented over Apple CryptoKit primitives.
|
||||
|
||||
**The post-quantum algorithms were added after this document was first
|
||||
written and were missing from it until 2026-09-01.** Both are NIST
|
||||
written and were missing from it.** Both are NIST
|
||||
standards implemented via CryptoKit's `MLKEM768` and `MLDSA65`, nothing
|
||||
proprietary, so the answer above does not change, but a declaration that
|
||||
omits an algorithm the binary actually contains is inaccurate, and this one
|
||||
@@ -24,7 +24,7 @@ Answer on the App Store Connect encryption questions:
|
||||
- Classification: **mass-market, self-classified 5D992.c** (ECCN), License
|
||||
Exception ENC favorable treatment not required for mass-market.
|
||||
|
||||
**Plist key note (learned 2026-08-19):** do NOT set
|
||||
**Plist key note :** do NOT set
|
||||
`ITSAppUsesNonExemptEncryption` alone. ASC rejects the upload with "Invalid
|
||||
Export Compliance Code" because a non-exempt declaration in the plist must be
|
||||
accompanied by an `ITSEncryptionExportComplianceCode` that matches compliance
|
||||
|
||||
+9
-9
@@ -3,7 +3,7 @@
|
||||
Verified by driving the apps and the relays, not by reading code. Where
|
||||
something is unproven it says so.
|
||||
|
||||
**Re-checked 2026-08-28**, 45 commits after `baseline-2026-08-27`. Three rows
|
||||
**Re-checked** 45 commits after the baseline. Three rows
|
||||
moved. What was re-run this time is marked; anything not marked is carried
|
||||
forward from the baseline and has not been re-driven.
|
||||
|
||||
@@ -44,14 +44,14 @@ Repeatable via `Scripts/android-signup.sh` and `RelayTypesUITests`.
|
||||
|
||||
## 3, cross-relay messaging
|
||||
|
||||
**The relays do it.** `test/federation.mjs` (in [flamenet-e2e](https://gitlab.com/paulhitt/flamenet-e2e) since the 2026-08-28 split) against the live fleet:
|
||||
**The relays do it.** `test/federation.mjs` (in [flamenet-e2e](https://gitlab.com/paulhitt/flamenet-e2e) since the split) against the live fleet:
|
||||
**11 passed, 0 failed**: relay1 fetches bob's devices from relay2, sends
|
||||
across the boundary, bob's relay accepts exactly one envelope, he polls and it
|
||||
is there, it carries no sender attribution across the boundary, a relay off
|
||||
the allowlist is refused, and an unknown address is reported rather than
|
||||
silently dropped.
|
||||
|
||||
Re-run 2026-08-28 against the live fleet: 11 passed, 0 failed. Unchanged.
|
||||
Re-run against the live fleet: 11 passed, 0 failed. Unchanged.
|
||||
|
||||
iOS now has the client path; Android still does not. `E2EClient` gained
|
||||
`remoteDevices`, `remoteBundles` and `sendRemote`, and `E2EInbox` gained
|
||||
@@ -71,17 +71,17 @@ for.
|
||||
|
||||
## 8, deployed
|
||||
|
||||
**Deployed and current, verified by checksum.** Re-checked 2026-08-28 after
|
||||
**Deployed and current, verified by checksum.** Re-checked after
|
||||
two deploys: `Config.swift`, `RoutesAccount.swift` and `Retention.swift` on the
|
||||
box are md5-identical to `flamenet-relay` HEAD. Production has the signup rate
|
||||
limit (verified live: five signups pass, the sixth is 429), the reset-token
|
||||
sweep, and retention logging at a level that is actually visible.
|
||||
|
||||
Since the 2026-08-28 split, deploys rsync from a `flamenet-relay` checkout,
|
||||
Since the split, deploys rsync from a `flamenet-relay` checkout,
|
||||
`flamenetmessenger/server/` no longer exists.
|
||||
|
||||
Production also runs `FN_LOG_LEVEL=error`, which suppresses the hourly
|
||||
retention line entirely. Retention *is* working, verified 2026-08-28 by
|
||||
retention line entirely. Retention *is* working, verified by
|
||||
querying the database directly and finding zero rows past any window, oldest
|
||||
envelope hours old, but there is no way to see that from the logs, which is
|
||||
the same unverifiable claim the retention code says it exists to avoid.
|
||||
@@ -107,11 +107,11 @@ Continue by design.
|
||||
|
||||
Not exercised: a payment taken end to end. Doing so means creating a
|
||||
real PaymentIntent on the live connected account, so it was left alone. Closed
|
||||
on the owner's call 2026-08-27. Recorded here rather than dropped, because "we
|
||||
on the owner's call. Recorded here rather than dropped, because "we
|
||||
verified the donation page" and "we watched a payment complete" are different
|
||||
claims and only one of them is true.
|
||||
|
||||
## Found after the baseline, 2026-08-28
|
||||
## Found after the baseline
|
||||
|
||||
**Android silently lost messages.** A send to somebody with no registered
|
||||
device, anyone signed out, looped over an empty device list, made no request,
|
||||
@@ -140,4 +140,4 @@ by five tests.
|
||||
- Call audio has never been heard by anyone.
|
||||
- No third-party audit, no reproducible builds, no post-quantum below iOS 26.
|
||||
- `FN_DELETE_ON_ACK` is **off** and must stay off until it has an end-to-end
|
||||
test. It deleted a message on production on 2026-08-27.
|
||||
test. It deleted a message on production.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Delivery without Google or Apple in the middle
|
||||
|
||||
> **Constraint set by the owner, 2026-08-26:** no third party on the delivery
|
||||
> **Constraint set by the owner:** no third party on the delivery
|
||||
> path. That rules out FCM and rules out APNs. It does not rule out fast
|
||||
> delivery, but it does rule out one thing completely, and that is worth
|
||||
> stating before anything else.
|
||||
|
||||
@@ -27,7 +27,7 @@ pipeline:
|
||||
| [flamenet-relay](https://gitlab.com/paulhitt/flamenet-relay) | The server |
|
||||
| [flamenet-messenger-android](https://gitlab.com/paulhitt/flamenet-messenger-android) | The Android app |
|
||||
|
||||
Until 2026-08-28 those three lived here as subtrees, and the public
|
||||
those three lived here as subtrees, and the public
|
||||
repos were rsync extracts of them, refreshed by hand. The extracts drifted for
|
||||
days at a time. They are standalone now, split out with their history, work on
|
||||
each product happens in its own repository, and a patch to a subtree here has
|
||||
@@ -126,7 +126,7 @@ a first TestFlight build.
|
||||
|
||||
All four repositories are public under the MIT licence, this one included.
|
||||
That was not always true: the app was private while the engine and server were
|
||||
mirrored out of it by an allowlist, and until 2026-08-28 those mirrors were the
|
||||
mirrored out of it by an allowlist, and those mirrors were the
|
||||
only public copies. They are standalone repositories now, so what you are
|
||||
reading is the source rather than an extract of it.
|
||||
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
# Flamenet Messenger, scoping the first TestFlight
|
||||
|
||||
> **Status 2026-08-21.** Step 1 is done, all seven stub surfaces were cut in
|
||||
> **Status.** Step 1 is done, all seven stub surfaces were cut in
|
||||
> MR !4 (`67cebad`), 356 deletions, three tabs, verified in the simulator. Most
|
||||
> of the distribution prerequisites below are done too. **What now gates a
|
||||
> TestFlight build is deploying the relay**, because the app has been pinned to
|
||||
@@ -10,7 +10,7 @@
|
||||
> not serve and answered 404 on one of the app's three tabs. It is an
|
||||
> exact-username search now.
|
||||
|
||||
Written 2026-08-18 against `main` @ `de23972`. Build verified: `xcodegen` +
|
||||
Written against `main` @ `de23972`. Build verified: `xcodegen` +
|
||||
simulator build **succeeds**: 63 Swift files, 12,574 lines.
|
||||
|
||||
The app has never shipped, and the landing page still carries a `TODO` waiting
|
||||
@@ -88,7 +88,7 @@ Roughly a day's work, almost all deletion. Nothing in the messaging or E2E path
|
||||
|
||||
These block a build reaching anyone, no matter what the app contains.
|
||||
|
||||
> **Status 2026-08-21.** Five of the seven are resolved. What is left is one
|
||||
> **Status.** Five of the seven are resolved. What is left is one
|
||||
> thing only I can do and two that need you.
|
||||
|
||||
| # | Prerequisite | State |
|
||||
@@ -103,7 +103,7 @@ These block a build reaching anyone, no matter what the app contains.
|
||||
|
||||
A prerequisite that did not exist when this was written: the app had no
|
||||
backend. It defaulted to `https://flamenet.io`, whose plugin was removed on
|
||||
2026-08-20. It is pinned to `messenger.flamenet.io` now, which means the relay
|
||||
It is pinned to `messenger.flamenet.io` now, which means the relay
|
||||
has to be deployed before a tester can do anything at all. DNS for that name
|
||||
does not resolve yet. `server/DEPLOY.md` has the sequence.
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ hours.
|
||||
is covered by the XCTest suite (95 checks), alongside the shared 253-check relay conformance suite and
|
||||
cross-platform interop tests that run the real engine on both sides.
|
||||
|
||||
An internal adversarial audit on 2026-08-29 found and fixed a family of
|
||||
An internal adversarial audit found and fixed a family of
|
||||
sender-attribution flaws: the ratchet authenticates a *device*, and the user id
|
||||
inside a sealed envelope is a claim its author chooses, the two were not bound.
|
||||
Devices now bind to their user on first proven use, control traffic (delivery
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ app is visible the moment somebody opens the screen. These are not:
|
||||
- **Envelope kinds.** A kind one app sends and the other cannot parse is
|
||||
swallowed as unknown control and shows nothing at either end. The sender is
|
||||
not told. Bridge sharing had exactly this shape for a few hours on
|
||||
2026-09-06, between the Android half landing and the iOS half.
|
||||
Between the Android half landing and the iOS half.
|
||||
- **Unwired capability.** A method on the view model that no screen calls.
|
||||
`shareBridge` shipped that way the same day: you could receive a bridge and
|
||||
had no way to send one.
|
||||
@@ -29,7 +29,7 @@ see that a button is behind a screen nobody can reach, that a toggle is stored
|
||||
and ignored, or that a route returns the wrong thing. Those need the app.
|
||||
|
||||
It also does not compare *features*, the settings each app offers, the screens
|
||||
each has. That part is manual, and the 2026-09-06 run found eight preferences
|
||||
each has. That part is manual, and that run found eight preferences
|
||||
on iOS with no Android counterpart, which no automated check here would have
|
||||
raised.
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ enum E2EAttachment {
|
||||
let fwd: Flag?
|
||||
|
||||
/// A flag written as `1` by this engine and the specification, and as
|
||||
/// `true` by builds of the Android app before 2026-08-30.
|
||||
/// `true` by older builds of the Android app.
|
||||
struct Flag: Codable, Equatable {
|
||||
let on: Bool
|
||||
init(on: Bool) { self.on = on }
|
||||
|
||||
@@ -10,7 +10,7 @@ final class MessengerAPI {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
// The plaintext transport is gone (2026-08-29).
|
||||
// The plaintext transport is gone.
|
||||
|
||||
/// `POST flamenet/v1/messenger/status`, backs `/away`, `/back`,
|
||||
/// `/invisible` and `/status`.
|
||||
|
||||
+3
-3
@@ -74,7 +74,7 @@ contact permanently unmessageable on Android once.
|
||||
|
||||
**The exposure is closed.** The burned passphrase opened exactly two
|
||||
accounts, `appreview1demo` and `appreview2demo`, and both were deleted
|
||||
through the app's own `DELETE /messenger/account` route on 2026-08-28, so the
|
||||
through the app's own `DELETE /messenger/account` route, so the
|
||||
device rows, prekeys, envelopes, group memberships and buddy edges went with
|
||||
them. Signing in with the old value now returns 401.
|
||||
|
||||
@@ -93,7 +93,7 @@ Still cruft, though not exposed: `fndemo1`, `fndemo2`, `fnreview1demo`,
|
||||
`fnreview2demo` are three superseded generations of review account. Worth
|
||||
tidying, not urgent.
|
||||
|
||||
**How this pair was verified, 2026-08-28.** Registration and sign-in
|
||||
**How this pair was verified.** Registration and sign-in
|
||||
round-tripped against the live account service with an independently
|
||||
implemented KDF (byte-compared against the app's); the app's own client stack
|
||||
signed in via a unit-test probe with the passphrase supplied as bytes; and both
|
||||
@@ -189,7 +189,7 @@ Recurring: the annual **BIS self-classification report**, due each February.
|
||||
|
||||
## 5. Before you hit submit
|
||||
|
||||
- [x] Both demo accounts verified signing in through the app UI (2026-08-26).
|
||||
- [x] Both demo accounts verified signing in through the app UI.
|
||||
- [ ] Walk the five steps above once on a real iPhone. Sign-in is proven; the
|
||||
full send-and-read round trip on one physical device is not.
|
||||
- [ ] `sudo xcode-select -s /Applications/Xcode.app`, you are pointed at
|
||||
|
||||
@@ -74,7 +74,7 @@ final class AutomaticKeyLogTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
LogRelay.reset()
|
||||
// The pre-2026-08-30 global pin is still read as a fallback, so one
|
||||
// The older global pin is still read as a fallback, so one
|
||||
// test's pin would otherwise be checked against the next test's head
|
||||
// and reported as a fork.
|
||||
UserDefaults.standard.removeObject(forKey: "fnmsg.keyLogSize")
|
||||
|
||||
@@ -23,7 +23,7 @@ final class PresenceTests: XCTestCase {
|
||||
}
|
||||
|
||||
/// A relay may serve a state this build predates, `busy` was exactly that
|
||||
/// for every client before 2026-08-29.
|
||||
/// for every older client.
|
||||
func testUnknownServerValueReadsAsOffline() {
|
||||
XCTAssertEqual(Presence(serverValue: "busy"), .busy)
|
||||
XCTAssertEqual(Presence(serverValue: "telepathic"), .offline)
|
||||
|
||||
@@ -9,7 +9,7 @@ that is how Android lost twenty tests.** The Android engine looks for
|
||||
`../../../flamenet-e2e/test/vectors/...`, which exists on a developer's
|
||||
machine and has never existed in a CI build directory, so its
|
||||
conformance suite failed there from the day it was written until
|
||||
2026-09-06, invisibly, because a build cache replayed local passes. A
|
||||
invisibly, because a build cache replayed local passes. A
|
||||
copy inside the test bundle cannot be absent, cannot depend on what else
|
||||
is checked out beside it, and needs no network.
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ an fnid is derived from a passphrase, and this document is the plan for making
|
||||
"my relay died and I kept my account and my contacts" a feature somebody can
|
||||
use, not an architecture diagram.
|
||||
|
||||
Written 2026-08-28, from the code and the live fleet, not from memory.
|
||||
Written, from the code and the live fleet, not from memory.
|
||||
|
||||
## What is already built AND proven
|
||||
|
||||
@@ -31,7 +31,7 @@ More than the roadmap credits. All of this is running today:
|
||||
- **Proven against the live fleet**: `test/multihoming.mjs`. Bob registers
|
||||
the same account key and same device identity at two relays, publishes the
|
||||
record, and Alice, naming only one relay, reaches him at both.
|
||||
**12 passed, 0 failed**, run 2026-08-28.
|
||||
**12 passed, 0 failed**, run.
|
||||
|
||||
## The insight that makes migration small
|
||||
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ Ordered by what is being built now, then by what is agreed and waiting.
|
||||
|
||||
## Agreed, waiting
|
||||
|
||||
### Group sends need the outbox too, done on both, 2026-08-28
|
||||
### Group sends need the outbox too, done on both
|
||||
|
||||
Same rules both platforms: exactly the unreached members are queued, entries
|
||||
carry the group thread they restate, and the bubble flips only when the LAST
|
||||
@@ -68,7 +68,7 @@ of every decentralised group protocol.
|
||||
|
||||
### Relay migration, the flagship, see docs/MIGRATION.md
|
||||
|
||||
Surveyed 2026-08-28 and far closer than this entry believed: signed,
|
||||
Surveyed and far closer than this entry believed: signed,
|
||||
serial-numbered home-relay records with rollback protection are implemented,
|
||||
the sending relay already verifies and fans out to every home, and
|
||||
`multihoming.mjs` proves the whole server half against the live fleet (12/0).
|
||||
@@ -244,7 +244,7 @@ cannot be rotated.
|
||||
|
||||
## Account lifecycle and anti-abuse
|
||||
|
||||
Measured on the live relay, 2026-08-27, not estimated:
|
||||
Measured on the live relay, not estimated:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ options:
|
||||
|
||||
settings:
|
||||
base:
|
||||
# 1.0 for the first App Store submission (2026-09-07). App Store Connect
|
||||
# 1.0 for the first App Store submission. App Store Connect
|
||||
# refuses a version string that disagrees with the uploaded binary, so this
|
||||
# is what the listing has to say too.
|
||||
#
|
||||
@@ -157,7 +157,7 @@ targets:
|
||||
Flamenet can save a story you posted yourself to your photos, so you
|
||||
keep it after it disappears for everyone else. It only ever adds;
|
||||
it cannot see the photos you already have.
|
||||
# Export compliance (decided 2026-08-19): non-exempt, mass-market
|
||||
# Export compliance: non-exempt, mass-market
|
||||
# self-classified 5D992.c, see EXPORT-COMPLIANCE.md. Answered
|
||||
# interactively in App Store Connect for now: embedding
|
||||
# ITSAppUsesNonExemptEncryption without a matching
|
||||
|
||||
Reference in New Issue
Block a user