Commit Graph
2 Commits
Author SHA1 Message Date
Paul HittandClaude Opus 5 7ab5c774cb A file in a chat keeps its name and its label
`Ref.toJson` is what the vault persists next to a message, and it never
wrote `filename`, `classification` or `compartments`. They reach the far
end correctly, as additive top-level keys on the envelope, so a received
document ARRIVED named. It was the local copy that lost them, on the
first read back out of storage. A file in a chat therefore had a name
until the app was restarted and never again, which reads as a damaged
file rather than as the app forgetting.

`fromJson` now defaults those three to null rather than to empty.
Passing nothing means "not a wire parse, read what was stored"; passing
a value, including an empty one, means the envelope said so and wins.
Empty defaults could not tell those two apart, and answering "" for both
is exactly what made a stored file nameless.

The same rule fixes `expires`. The history path hands in 0.0, and 0
means "does not expire", so every stored story read back as immortal:
its bytes were never dropped and it rendered forever. A caller that
names an expiry still means it; one that does not now gets what the
vault kept.

They are stored under the same names the shared backup format uses, so
`BackupPayload` carries them straight through. A document restored from
a backup arrived with no name and no label before this, which is worse
than not restoring it.

Four tests on the storage round trip, one on the envelope still winning,
one that an attachment which never had a name does not invent one, and
one carrying a document through a backup whole. Full suite: 1276 tests,
no failures. No new ktlint violations in Content.kt (3 at HEAD, 3 now).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 19:29:31 -04:00
Paul HittandClaude Opus 5 5a5e41bfa2 Android writes and reads the shared backup format
Section 16 pins the envelope and says nothing about what is sealed, so
each app sealed its own storage verbatim and stamped the file with the
platform that wrote it. Each then refused the other's, in as many words:
"import it on a device of the same kind". Somebody moving between an
Android phone and an iPhone lost every conversation they had.

The iPhone half landed first. This is the other half.

`BackupPayload` translates rather than dumps, which is the entire point.
This app stores a message under the keys its wire format uses (`secs`,
`fwd`, `rx`), keeps one-to-one and group threads in one map under
prefixed ids, hangs the attachment kind beside `att` rather than inside
it, abbreviates the sizes, and holds expiries in unix seconds. The
shared shape spells all of that out and counts in milliseconds.
Exporting storage as-is is what made the two formats incompatible in the
first place, so a copy would have reproduced the bug with more steps.

Federated threads are the one thing that does not fit. This app keys
them by address and the iPhone's history is keyed by numeric user id,
with a decoder that skips anything else, so an address left in `threads`
would be dropped in silence. They travel under `addrThreads` instead: an
iPhone ignores the key, an Android phone gets them back, and nothing
disappears without somebody having decided it should.

Import tells the two formats apart by SHAPE, not by file name, because a
name is whatever the person saving it chose. Section 16 announces itself
with `alg` and `ct`; the older export announces itself with `magic`. A
wrong passphrase on a section 16 file reports a wrong passphrase and
does NOT fall through to the legacy reader, which would have answered
"that file is not a Flamenet history export" for a file that is one and
sent somebody hunting for corruption instead of a typo.

Nothing writes the old format any more, and everything still reads it.

Seventeen tests: the shared side against the key names the iPhone
writes, the round trip back into this app's own storage shape, the unit
conversions in both directions, and a payload transcribed from the
iPhone's encoder. That last one is the weak point and the test says so:
it proves this app reads the shape the iPhone is BELIEVED to write. Only
a vector both apps are built against proves they agree, and
`backup-v1.json` pins the envelope alone, which is exactly how the two
shapes drifted apart.

Full suite: 1271 tests, no failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:11:35 -04:00