`Label` and `Clearance`: the two-dimensional lattice Files is built on, and the
ten-byte encoding that is authenticated data for every construction using it.
🔴 The encoding vectors were computed independently, not read off this
implementation. A label that encodes differently on two platforms does not warn
— it produces a decryption failure, or plaintext that quietly belongs to the
wrong sensitivity. A vector taken from the code under test would pin whatever
the code happens to do.
🔴 Two dimensions, and the tests are shaped around the failure that READS AS
WORKING: an implementation comparing classification and forgetting compartments
behaves correctly on every test built from levels alone, right up until
somebody sees a compartment they are not in. Domination needs a superset of
areas, not an overlap; two labels can be incomparable in both directions.
⚠️ Bit 63 is the sign bit of a Long. Building the mask with an Int shift loses
it, and the compartment that vanishes is the one nobody notices until somebody
sees a file they are not cleared for. Pinned, along with the all-ones case where
a sign-extension mistake still produces plausible-looking bytes.
🔴 **A finding, and a divergence I deliberately did NOT create.** Files
implements the simple security property (no read up) and NOT the ★-property (no
write down) — on either client. iOS gates `ensureLabelKey` and `use` on
`clearance.permits` and nothing else, so Kotlin's `mayWrite` calls the same
check, with a comment saying why and a test asserting the current behaviour that
will fail loudly the day it changes.
Implementing it on Android alone would mean a file that saves on one phone and
is refused on the other, with neither person able to tell which rule they had
met — worse than the gap, because an apparent bug gets worked around. Filed as
android#15 / ios#65 with the trade written out: no-write-down is the half a
lattice actually exists for, and it is also the half that is awkward enough to
need a stated downgrade path rather than a bare refusal. It interacts with
sharing a file into a chat — a write to a place with no label at all — so it
needs an answer before that ships.
16 tests. App builds, every JVM suite green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Create a wallet, restore one from a phrase, see the balance and the history,
and take a fresh address to be paid at.
🔴 An unread balance is an em dash, never 0.00. Null means this app could not
read the balance; zero means the account is empty. Drawing the first as the
second tells somebody their money is gone — on every flaky connection, which is
exactly when they are most likely to believe it. The same rule governs the
activity list: "not read yet" and "nothing yet" are different sentences and the
screen says whichever is true. Both cards are always present once a wallet
exists, because a screen that hides them until it has something looks broken at
the moment somebody wants reassurance nothing went wrong.
🔴 The phrase card sits ABOVE everything about the chain. Somebody who has just
made a wallet has one job — write these words down — and putting a balance in
front of it invites them to skip it and come back later, which for most people
means never. The card says both halves at once: anybody who has these words
owns the wallet, and anybody who loses them loses it. Saying only the first
makes people hide it; saying only the second makes them screenshot it.
⚠️ Copy is offered for the phrase and is the SECOND option, not the first. It
puts the words on a clipboard every other app can read — offered anyway because
people who are not given it photograph the screen, which is worse and permanent.
The text asks for paper.
⚠️ The receive card says the address changes BEFORE somebody writes one down. A
fresh address per payment is the one linkability a wallet can prevent, and an
address that silently changed would read as a bug to anybody who had saved it.
Older addresses keep working, and the card says that too.
⚠️ Reached from Account, not the tab bar. The bar is four icons by design — the
kit's artboard is "Bottom Bar/Without Label" — and Alerts was taken OUT of it in
August for being a fifth. A wallet people open occasionally does not earn the
slot that decision refused.
🔴 An account below the rent-exempt minimum is RECLAIMED by the network, so
`spendable` holds back the fee and that minimum: emptying a wallet to the last
lamport does not leave it empty, it deletes it. Named constants rather than an
expression, so the reason survives.
⚠️ http endpoints are ignored rather than honoured. A balance read over plain
http is one anybody on the path can rewrite, and a wallet showing a number an
attacker chose is worse than one showing nothing.
8 display tests. App builds, every JVM suite green. Three PARITY wallet rows
are now ✅.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>