Take the e2e harness that reads the stylesheet off the page
The conformance job pinned flamenet-e2e at cbdadd9, whose landing-page check fetched /landing.css by name. This branch renamed the stylesheet to portal.css, so the check went 404 and the job failed on a change that had not broken anything. flamenet-e2e!43 makes that check read the href out of the markup, so a rename cannot break it again. The pin moves to e2e main at d01cd0b to pick it up. Moving the pin also takes the spec that merged in e2e meanwhile, so spec.leaf is regenerated with Scripts/build-doc-pages.sh. The other eight generated pages came out identical. This is the atomic shape the conformance job's comment asks for: bump the ref, regenerate the pages, make whatever change the new harness expects. Scripts/check-doc-pages.py reports all nine pages matching their sources, and the public page suite passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b3a2f9b943
commit
232f2d5f1b
@@ -1 +1 @@
|
||||
cbdadd9f82c2b64f2c0eb1b08b8ff2e88e48a5a7
|
||||
d01cd0bbb267c591fd2ba7de7794f4bf461019cb
|
||||
+157
-1
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED by Scripts/build-doc-pages.sh from flamenet-e2e/docs/SPEC.md.
|
||||
Edits made HERE are deleted the next time that script runs.
|
||||
Change the markdown source and re-run it.
|
||||
source-sha256: 7e3a8724ef27a6da -->
|
||||
source-sha256: 9c3e570b61488cd7 -->
|
||||
<article class="band doc doc-generated">
|
||||
<div class="wrap wrap-narrow">
|
||||
<h1 id="flamenet-messenger-end-to-end-encryption-spec">Flamenet
|
||||
@@ -1405,6 +1405,162 @@ promise was made there is nothing to report against.</p>
|
||||
<p>A client SHOULD send at most one notice per conversation per short
|
||||
window. A screenshot often produces several platform events, and a
|
||||
person who took one screenshot should not appear to have taken four.</p>
|
||||
<h3 id="628-off-the-record">6.28 Off the record</h3>
|
||||
<p>A conversation both ends agree not to write down. While it is on,
|
||||
neither client persists the messages: they live in memory for as long as
|
||||
the app is open and are gone when it closes. Nothing is deleted
|
||||
afterwards, because nothing was ever written.</p>
|
||||
<pre class="json"><code>{"fnc":1,"kind":"otr","ctl":1,"otr":1}
|
||||
{"fnc":1,"kind":"otr","ctl":1,"otr":1,"echo":1}
|
||||
{"fnc":1,"kind":"otr","ctl":1,"otr":0}</code></pre>
|
||||
<p><code>ctl</code> is set (§6.10), so a build that does not know
|
||||
<code>otr</code> ignores it rather than rendering it, and, critically,
|
||||
never turns the mode on. That is why it is a <strong>proposal and an
|
||||
echo</strong> rather than a single announcement. The proposing client
|
||||
sets the mode locally only when the echo comes back. A client that
|
||||
receives a proposal and supports the mode turns it on and replies with
|
||||
the same envelope carrying <code>echo</code>, which MUST NOT itself be
|
||||
echoed.</p>
|
||||
<p>The handshake exists because the alternative is worse than the
|
||||
feature. If one side believed the conversation was off the record and
|
||||
the other was writing it to disk, the promise would be false in exactly
|
||||
the case somebody relied on it. So the mode is never on for one end
|
||||
alone: a proposal that is not echoed leaves both ends exactly as they
|
||||
were, and the proposing client MUST say the other end did not agree
|
||||
rather than showing the mode as on.</p>
|
||||
<p>Turning it off (<code>"otr":0</code>) needs no echo. Either end may
|
||||
stop at any time, and stopping is not something the other end can
|
||||
refuse.</p>
|
||||
<p><strong>What it does not do.</strong> It stops this app writing the
|
||||
conversation to this device's storage. It does not stop a screenshot
|
||||
(§6.27), a photograph of the screen, a modified build, or a platform
|
||||
backup of anything that reached the screen. A client MUST describe it as
|
||||
"neither phone writes this down" and MUST NOT describe it as messages
|
||||
that cannot be kept.</p>
|
||||
<p>Messages sent while it is on are ordinary envelopes. Nothing on the
|
||||
wire marks them, and the relay learns nothing: the mode is a rule about
|
||||
what the two clients do with plaintext they have already decrypted.</p>
|
||||
<h3 id="629-polls-nobody-can-attribute">6.29 Polls nobody can
|
||||
attribute</h3>
|
||||
<p>A poll asked of a group where no individual vote is ever sent to
|
||||
anybody, including the person who created it and including the
|
||||
server.</p>
|
||||
<p>Hiding a vote in the interface is not hiding it. In every messenger
|
||||
that offers an "anonymous" poll today, the vote reaches somebody: a
|
||||
server, or the poll's creator, or both. Group messages here are
|
||||
encrypted to each member separately, so the recipient of any envelope
|
||||
knows who sent it, and sending "I chose B" to anybody at all attributes
|
||||
it.</p>
|
||||
<p><strong>Secure aggregation.</strong> A vote is split into random
|
||||
pieces that sum to it, one piece per member. Each member is handed one
|
||||
piece of everybody's vote, publishes only the sum of the pieces they
|
||||
hold, and the published sums add up to the counts.</p>
|
||||
<pre class="json"><code>{"fnc":1,"kind":"poll","ctl":1,"pid":"<uuid>","q":"<question>","opts":["<a>","<b>"],"mset":"<member set>","gid":"<group>"}
|
||||
{"fnc":1,"kind":"pshare","ctl":1,"pid":"<uuid>","v":[1732584193,-1732584192],"gid":"<group>"}
|
||||
{"fnc":1,"kind":"psum","ctl":1,"pid":"<uuid>","v":[-604832771,604832772],"gid":"<group>"}</code></pre>
|
||||
<p><code>ctl</code> is set on all three (§6.10), so a build that does
|
||||
not know them ignores them rather than rendering a poll it cannot take
|
||||
part in.</p>
|
||||
<p><strong>The vector.</strong> <code>v</code> is one signed 32-bit
|
||||
integer per option, in the order <code>opts</code> gives. A vote for
|
||||
option <code>i</code> is the vector that is 1 at <code>i</code> and 0
|
||||
elsewhere. Arithmetic wraps at 32 bits, and the wrapping is what does
|
||||
the hiding: every piece but the last is a uniform random integer, the
|
||||
last is whatever makes the set add up to the vote, and no single piece
|
||||
says anything about the vote it came from. Implementations MUST use
|
||||
wrapping addition over the full 32-bit range. A language that traps on
|
||||
overflow MUST mask rather than throw.</p>
|
||||
<p><strong><code>mset</code></strong> names the members the arithmetic
|
||||
is agreed over, and every client MUST compute its pieces against that
|
||||
list in that order. Two clients disagreeing about who is in the poll
|
||||
produce sums that do not add up, and the result is a wrong tally rather
|
||||
than an error, which is the failure this field exists to prevent.</p>
|
||||
<p><strong>The guarantee, exactly.</strong> Every member other than the
|
||||
voter would have to collude to recover one vote. That is what this
|
||||
provides. It is not "nobody can ever know", and a client MUST state the
|
||||
collusion bound rather than the shorter claim.</p>
|
||||
<p>The bound is worth reading carefully at small sizes. Against a group
|
||||
of two it is empty: the other member holds a piece of your vote and sees
|
||||
the tally, and subtracting one from the other gives your vote with no
|
||||
collusion at all, because there is nobody to collude with. The
|
||||
arithmetic has no threshold to enforce this at, so a client MUST NOT
|
||||
offer the word "anonymous" for a poll of two, and SHOULD set a floor
|
||||
below which it does not offer one.</p>
|
||||
<p><strong>Who voted is visible; what they voted is not.</strong>
|
||||
Holding a piece of somebody's vote is how a member learns that person
|
||||
has voted, and a client needs that to know when the poll can proceed.
|
||||
The scheme hides the content of a vote, never the fact of one. A client
|
||||
MUST NOT present participation as secret, and MAY show who has voted,
|
||||
which is also the only honest way to say who a poll is still waiting
|
||||
on.</p>
|
||||
<p><strong>Everybody publishes, or there is no tally.</strong> A missing
|
||||
sum does not give a smaller count. It leaves a uniform random number in
|
||||
the total, so a partial result is not a partial answer but a meaningless
|
||||
one. A client MUST wait for a sum from every member in <code>mset</code>
|
||||
and MUST NOT show a result before then. It MUST say plainly that it is
|
||||
waiting, and on whom.</p>
|
||||
<p>There is deliberately no fallback to a poll that is merely hidden. A
|
||||
fallback that kept the word "anonymous" while quietly sending votes to
|
||||
somebody is the one outcome that must not ship, and offering it under
|
||||
another name in the same interface invites exactly that confusion.</p>
|
||||
<p><strong>Limits.</strong> Share traffic grows with the square of the
|
||||
membership: every member sends a piece to every member. A client MUST
|
||||
NOT start a poll in a group larger than it can serve and MUST say so
|
||||
rather than starting one that will never complete. <code>opts</code>
|
||||
carries at least 2 and at most 12 options.</p>
|
||||
<h3 id="630-handing-a-call-to-another-of-your-devices">6.30 Handing a
|
||||
call to another of your devices</h3>
|
||||
<p>On a call on the phone, walk to the desk, take the call on the
|
||||
tablet, and keep talking. The other person hears a short gap and nothing
|
||||
else.</p>
|
||||
<p>This account's own devices already have a private channel during a
|
||||
call: the device that answers tells the others to stop ringing (§6.8).
|
||||
Handoff is that channel carrying more.</p>
|
||||
<pre class="json"><code>{"fnc":1,"kind":"call-handoff","call":{"id":"<uuid>","peer":4211,"video":false}}
|
||||
{"fnc":1,"kind":"call-handoff-taken","call":{"id":"<uuid>"}}</code></pre>
|
||||
<p>Both go to this account's own devices and to nobody else, by the same
|
||||
routing check the stop-ringing path already makes. Neither is ever sent
|
||||
to the peer.</p>
|
||||
<p><strong>The sequence.</strong></p>
|
||||
<ol type="1">
|
||||
<li>The device on the call offers it to this account's other devices,
|
||||
naming the call and the peer. Only an idle device offers to take
|
||||
it.</li>
|
||||
<li>The taking device builds a fresh connection and sends the
|
||||
<strong>peer</strong> a <code>call-offer</code> under the same
|
||||
<code>call.id</code>.</li>
|
||||
<li>The peer treats an offer for a call it is already on as a
|
||||
renegotiation: it answers, stays on the call screen, and logs one call
|
||||
rather than two.</li>
|
||||
<li>The taking device sends <code>call-handoff-taken</code> to its own
|
||||
devices. The original tears down its connection and sends the peer
|
||||
nothing.</li>
|
||||
</ol>
|
||||
<p><strong>The peer is never hung up on.</strong> No
|
||||
<code>call-end</code> is sent to the peer at any point during a handoff,
|
||||
in either direction. An end would make the far end drop the call and
|
||||
redial, which is the failure this whole sequence exists to avoid. The
|
||||
original device MUST tear down locally and silently.</p>
|
||||
<p><strong>A handoff that half happens leaves the original
|
||||
working.</strong> If no device takes the call within a few seconds, the
|
||||
offer expires on both sides and the call stays exactly where it is.
|
||||
Expiry is a timeout at both ends rather than a withdrawal envelope, so a
|
||||
lost withdrawal cannot leave a device believing an offer is still
|
||||
live.</p>
|
||||
<p><strong>Whether the peer can take a renegotiating offer is known
|
||||
before the handoff starts.</strong> A client that accepts a
|
||||
renegotiating <code>call-offer</code> sets <code>"rnv":1</code> on the
|
||||
<code>call-offer</code> and <code>call-answer</code> it sends, so both
|
||||
ends learn it while the call is being set up. A client MUST NOT offer a
|
||||
handoff for a call whose peer did not set it. Discovering that the far
|
||||
end cannot renegotiate by dropping the call is the wrong way to find
|
||||
out.</p>
|
||||
<p>A device that does not understand <code>call-handoff</code> ignores
|
||||
it, which is correct: it does not offer to take the call, and the call
|
||||
stays where it is.</p>
|
||||
<p><strong>The relay learns nothing new.</strong> These ride the ratchet
|
||||
like every other call signal, and the same envelopes to this account's
|
||||
own devices already exist for stopping a ring.</p>
|
||||
<h2 id="7-safety-numbers-verification">7. Safety numbers
|
||||
(verification)</h2>
|
||||
<p>For a local identity <code>IK_sig_local_pub</code> and remote
|
||||
|
||||
Reference in New Issue
Block a user