diff --git a/Resources/Views/metadata.leaf b/Resources/Views/metadata.leaf
index 0d6091e..e736551 100644
--- a/Resources/Views/metadata.leaf
+++ b/Resources/Views/metadata.leaf
@@ -29,7 +29,7 @@
| Kept | Why it has to be | Gone after |
| Recipient account and device | An envelope cannot be delivered without an address | #if(retention.deleteOnAck):the moment your app confirms receipt#else:#(retention.deliveredDays) day(s) once delivered#endif |
- | Arrival time and approximate size | Ordering, and the size is the ciphertext itself | #if(retention.deleteOnAck):the moment your app confirms receipt#else:#(retention.deliveredDays) day(s) once delivered#endif |
+ | Arrival time and exact size | Ordering, and the size is the ciphertext itself — so the length of a message is not hidden, to the byte. Sizes group by kind: a read receipt is not the size of a photo, and a one-word reply is not the size of a paragraph. Nothing has to be decrypted to see that, and it is a weak but real guess about what an envelope carries. Hiding it means padding messages on the sending device before they are encrypted — a change to the apps rather than to this server, because the server cannot pad what it must not read. It has not been made yet. | #if(retention.deleteOnAck):the moment your app confirms receipt#else:#(retention.deliveredDays) day(s) once delivered#endif |
| Sender account and device | Identified sends only — a sealed send stores neither | #if(retention.deleteOnAck):the moment your app confirms receipt#else:#(retention.deliveredDays) day(s) once delivered#endif |
| The ciphertext | It is the message; the server holds no key that opens it | #(retention.undeliveredDays) days if never collected |
| Attachment blobs (photos, voice notes, stories — all ciphertext) | Devices fetch them separately from the envelope that references them | #(retention.attachmentDays) days |
diff --git a/Tests/FlamenetRelayTests/PublicPagesTests.swift b/Tests/FlamenetRelayTests/PublicPagesTests.swift
index 8335083..0fbdb41 100644
--- a/Tests/FlamenetRelayTests/PublicPagesTests.swift
+++ b/Tests/FlamenetRelayTests/PublicPagesTests.swift
@@ -213,6 +213,21 @@ final class PublicPagesTests: XCTestCase {
XCTAssertEqual(offenders, [], "\n" + offenders.joined(separator: "\n"))
}
+ /// ⚠️ **The transparency page has to be accurate in the unflattering
+ /// direction too.** `/metadata` called what it stores the "approximate
+ /// size" of a message. It is not approximate: the ciphertext length is
+ /// exact, and lengths group by kind, so a byte count is a weak guess about
+ /// what an envelope carries (relay#59). A page that undersells what it
+ /// keeps is worse than one that keeps more and says so.
+ func testMetadataDoesNotUndersellWhatItKeeps() throws {
+ let page = try String(contentsOf: views.appendingPathComponent("metadata.leaf"),
+ encoding: .utf8)
+ XCTAssertFalse(page.localizedCaseInsensitiveContains("approximate size"),
+ "the stored ciphertext length is exact, not approximate")
+ XCTAssertTrue(page.localizedCaseInsensitiveContains("exact size"),
+ "the page no longer states that the size it keeps is exact")
+ }
+
func testTheSafetyPageCarriesAReachableContact() throws {
let page = try String(contentsOf: views.appendingPathComponent("safety.leaf"),
encoding: .utf8)