Hold the two pairs 2-A clean-ups behind FN_PAIRS_CLEANUP
ForgetVouchProvenance and ClearConversationFlags are irreversible. The owner wants the relay deployed now, ahead of the 0.32 apps, with those two held until people on 0.31 have moved over (2026-10-07: each irreversible step gets its own go-ahead). They now join the boot list only when FN_PAIRS_CLEANUP=1. Fluent records migrations by name, so switching it on later runs exactly these two, once. The wave 0 upgrade tests ask for them explicitly; a new suite checks the switch both ways. CONFIGURATION.md and the generated page document it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
025ac20f14
commit
f1d79d4b3b
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED by Scripts/build-doc-pages.sh from docs/CONFIGURATION.md.
|
||||
Edits made HERE are deleted the next time that script runs.
|
||||
Change the markdown source and re-run it.
|
||||
source-sha256: 5f1afde0526529d4 -->
|
||||
source-sha256: a11cb92253fc1671 -->
|
||||
<article class="band doc doc-generated">
|
||||
<div class="wrap wrap-narrow">
|
||||
<h1 id="configuring-a-relay">Configuring a relay</h1>
|
||||
@@ -716,6 +716,15 @@ delete the next message unread. <code>0</code>, <code>false</code>,
|
||||
<code>no</code> or <code>off</code> turns it off.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>FN_PAIRS_CLEANUP</code></td>
|
||||
<td>unset</td>
|
||||
<td><code>1</code> runs the two irreversible pairs stage 2-A clean-ups
|
||||
at the next boot: clear the stars and archives stored on the relay, and
|
||||
forget who issued and redeemed each spent invite code. Off so the relay
|
||||
can ship while people on older apps still keep stars here. Once run they
|
||||
are recorded and never run again. Take a database backup first.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>FN_DELIVERED_RETENTION_DAYS</code></td>
|
||||
<td><code>1</code></td>
|
||||
<td>The backstop for a collected envelope whose app never polls again to
|
||||
|
||||
@@ -750,8 +750,16 @@ struct TuneUserDataAutovacuum: AsyncMigration {
|
||||
|
||||
/// The schema, in the order Fluent must apply it.
|
||||
enum RelayMigrations {
|
||||
static func all() -> [any AsyncMigration] {
|
||||
[
|
||||
/// `pairsCleanup` adds the two irreversible pairs stage 2-A clean-ups.
|
||||
/// They are held off by default (`FN_PAIRS_CLEANUP=1` turns them on) so
|
||||
/// the relay can ship ahead of the 0.32 apps while people on 0.31 still
|
||||
/// have stars and archives stored here. Owner's decision, 2026-10-07:
|
||||
/// each irreversible step gets its own go-ahead. Fluent records migrations
|
||||
/// by name, so turning the switch on later runs exactly these two.
|
||||
static func all(
|
||||
pairsCleanup: Bool = Environment.get("FN_PAIRS_CLEANUP") == "1"
|
||||
) -> [any AsyncMigration] {
|
||||
var list: [any AsyncMigration] = [
|
||||
CreateDevice(), CreatePreKey(), CreatePQPreKey(), CreateEnvelope(),
|
||||
CreateGroup(), CreateGroupMember(), CreateAttachment(), CreateDeliveryKey(),
|
||||
CreateUser(), CreateAppCredential(),
|
||||
@@ -789,16 +797,20 @@ enum RelayMigrations {
|
||||
GuardKeyLogAppendOnly(), CreateKeyLogWatermark(),
|
||||
// Last, so it can name every table above it.
|
||||
TuneUserDataAutovacuum(),
|
||||
// Pairs stage 2-A (2026-10-07). Each irreversible, each on its
|
||||
// own, and each dropping only data nothing reads. Appended after
|
||||
// the tuning above, so a table created from here on applies its
|
||||
// own autovacuum settings.
|
||||
ForgetVouchProvenance(), ClearConversationFlags(),
|
||||
]
|
||||
// Pairs stage 2-A (2026-10-07). Each irreversible, each on its own,
|
||||
// and each dropping only data nothing reads. After the tuning above,
|
||||
// so a table created from here on applies its own autovacuum settings.
|
||||
if pairsCleanup {
|
||||
list += [ForgetVouchProvenance(), ClearConversationFlags()]
|
||||
}
|
||||
list += [
|
||||
// Additive: new tables beside delivery_keys and roster_tags.
|
||||
CreatePerDeviceDigests(),
|
||||
// Wave 0b, additive: introductions, contact links, and the
|
||||
// Listed/Unlisted flag (every existing account stays listed).
|
||||
CreateIntroTokens(), CreateContactLinks(), AddUserDiscoverable(),
|
||||
]
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import Testing
|
||||
|
||||
@testable import FlamenetRelay
|
||||
|
||||
/// The two pairs stage 2-A clean-ups are irreversible, so the relay ships
|
||||
/// without them unless FN_PAIRS_CLEANUP=1 (owner, 2026-10-07).
|
||||
@Suite("the pairs clean-ups wait for their own switch")
|
||||
struct PairsCleanupSwitchTests {
|
||||
|
||||
@Test("off: neither clean-up is in the boot list")
|
||||
func offByDefault() {
|
||||
let all = RelayMigrations.all(pairsCleanup: false)
|
||||
#expect(!all.contains { $0 is ForgetVouchProvenance })
|
||||
#expect(!all.contains { $0 is ClearConversationFlags })
|
||||
// The additive wave 0 tables still ship.
|
||||
#expect(all.contains { $0 is CreatePerDeviceDigests })
|
||||
#expect(all.contains { $0 is CreateIntroTokens })
|
||||
}
|
||||
|
||||
@Test("on: both run, before the additive wave 0 tables")
|
||||
func onWhenAsked() throws {
|
||||
let all = RelayMigrations.all(pairsCleanup: true)
|
||||
let forget = try #require(all.firstIndex { $0 is ForgetVouchProvenance })
|
||||
let clear = try #require(all.firstIndex { $0 is ClearConversationFlags })
|
||||
let digests = try #require(all.firstIndex { $0 is CreatePerDeviceDigests })
|
||||
#expect(forget < digests && clear < digests)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ enum WaveZeroUpgrade {
|
||||
}
|
||||
|
||||
static func run(on app: Application) async throws {
|
||||
let all = RelayMigrations.all()
|
||||
let all = RelayMigrations.all(pairsCleanup: true)
|
||||
guard let cut = cut(all) else {
|
||||
Issue.record("the wave 0 migrations are not in the boot list")
|
||||
return
|
||||
@@ -120,7 +120,7 @@ enum WaveZeroBUpgrade {
|
||||
}
|
||||
|
||||
static func run(on app: Application) async throws {
|
||||
let all = RelayMigrations.all()
|
||||
let all = RelayMigrations.all(pairsCleanup: true)
|
||||
guard let cut = cut(all) else {
|
||||
Issue.record("the wave 0b migrations are not in the boot list")
|
||||
return
|
||||
|
||||
@@ -230,6 +230,7 @@ The relay purges on its own timer, in-process. Nothing external is needed.
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_DELETE_ON_ACK` | on | Delete a collected envelope the moment the recipient's app confirms it, which is its next poll. Postgres only: on SQLite it stays off whatever this says, because SQLite reuses row ids and the cursor would delete the next message unread. `0`, `false`, `no` or `off` turns it off. |
|
||||
| `FN_PAIRS_CLEANUP` | unset | `1` runs the two irreversible pairs stage 2-A clean-ups at the next boot: clear the stars and archives stored on the relay, and forget who issued and redeemed each spent invite code. Off so the relay can ship while people on older apps still keep stars here. Once run they are recorded and never run again. Take a database backup first. |
|
||||
| `FN_DELIVERED_RETENTION_DAYS` | `1` | The backstop for a collected envelope whose app never polls again to confirm it, and the only window when delete-on-ack is off. |
|
||||
| `FN_UNDELIVERED_RETENTION_DAYS` | `30` | Must stay below the client's retired-signed-prekey retention of 45 days. |
|
||||
| `FN_ATTACHMENT_RETENTION_DAYS` | `30` | |
|
||||
|
||||
Reference in New Issue
Block a user