Skip to content
Loess

Flows

These are the journeys that matter in the example apps, moments (a photo feed among friends) and chat (channels). Goals says why these promises matter, and Architecture shows the parts each flow crosses.

Each flow is a short story followed by a list, and each line of the list is both a promise and the name of the test that proves it. A name in code, such as share, refers to a row of an app's intent table or a case of the sdk's offline-first suite. The sdk's suite runs devices, storage, the relay, and committers in process over memoryNetwork, and the storage, relay, and committer service suites run the host Worker in miniflare. A line marked "not built" tests a part of the design that isn't built yet.

Three budgets hold for every flow:

BudgetTargetMeasured
A write without blobs1 request1 request
A reopened app that missed nothing1 follow, and no read from storageno read from storage
A cold view, once the device holds the circle's keys3 round trips or fewer3 round trips for the newest 20 of 30,000 messages, and 5 for 300,000, where the device reads page map leaves as it needs them. The measurements

Both apps run the full stack: a committer service the owners chose commits each circle, so a write counts within about a second, and an owner's devices commit too. A flow says so when its circle has no server.

Starting

Sign up and post

Ann opens moments for the first time, taps sign up, approves one passkey prompt, and sees her recovery phrase once. She posts a photo with a caption, and it is in her feed before storage has answered.

  • signs up with one passkey prompt, and a second device signs in with two and reads the first's circle
  • sign-up works offline, and the new identity goes to storage first, so a phrase then signs in (not built)
  • a post shows with its photo before storage answers, and is still there after a reload (share)
  • an owner's device commits its own write without waiting on another committer

Sign up and send

Ada opens chat, signs up, makes a channel, and sends a message. Her device commits the first message itself, and the app adds the committer service to the new channel.

  • the channel and the message show at once, and a reload offline keeps both (createChannel, send, CreateCircle, and Write)

Sign in on a second device

Ann opens moments on her laptop. With her passkey, she approves two prompts: one opens her identity with the passkey's PRF output, and one signs the record that adds the laptop. With her phrase, she gets no prompt. Either way "Signing in…" shows until her feed does.

  • a recovery phrase signs in a new device after every other device is gone, and it reads after a reload
  • a mistyped phrase fails as unknown before any request
  • a passkey of no identity fails after one prompt, with a message
  • a new device finds its identity's circles, and fetches only the pages a view needs
  • a new device of an owner commits the circle's waiting transactions

Sharing

Ann taps share in moments and sends Bob the link. Bob opens it, signs up if he's new, and sees Ann's posts before Ann's phone is even online. In chat, Ann invites Bob to a private channel, and Bob joins and sends a message.

  • a link shows at once, offline too, and works once its circle record is committed (createFriendLink and invite; not built: making a link needs storage)
  • storage accepts one join request from a one-use link and refuses the next
  • two people become friends with one link, and each reads the other's posts by name (acceptFriendLink and join)
  • every device of an identity reads a circle it joined by link, before admission
  • a joiner reads the circle after 4 round trips, and sends the join request and listing together
  • a link refused later fails the circle
  • a new person signs up and joins in one visit, with no other account
  • a committer accepts only a join request from a link
  • a committer admits a joiner from a link the circle record lists, with no owner online
  • a device refuses a writer admitted with no join request from a listed link
  • an owner's device moves an admitted joiner into the circle record when it next runs (not built)
  • a joiner's write shows on their device at once, and is sent once a committer admits them
  • Bob's message shows on Ann's open channel within a second, from the relay

Post with a photo, seen on another device

Ann posts a photo. Her phone shows it at once and writes the post while the photo uploads. Storage passes the post to the relay as it arrives, so her laptop and Bob's phone, both open, show it blurred from its thumbhash within a second, and sharp once the photo is stored.

A post with a photo reaches another device A post with a photo reaches another device
  • a device writes a post while its photo uploads, and sends each once
  • storage refuses a photo whose bytes don't match its hash and length, and a device checks the bytes it reads against the hash
  • another open device shows a pending post with its thumbhash before the photo is stored
  • a committer waits 60 seconds for a missing blob, and a slower upload's post is written again

Leave or remove

Ann removes Bob as a friend, or removes a lost phone from her account with her passkey. Her device commits a new circle record with new keys, and a committer makes new keys for each other circle she is in. Bob keeps what he already read and gets nothing after. In chat, Bob leaves a channel himself: his device removes the channel from his list, and an owner's device takes him out of the circle record.

Ann removes Bob as a friend Ann removes Bob as a friend
  • a removal shows at once on the owner's devices, offline too (not built: removing a member needs storage)
  • removing a friend stops new posts reaching them, while a friend who stays reads them
  • a committer makes new keys when a member's identity removes a device, with no owner online (not built)
  • storage refuses a removed member's next transaction, and a committer rejects one that waited
  • a removed device holds no commit or own key of a circle its identity owns (built only for the circles open on the removing device)
  • a transaction waiting in the inbox is committed after the key version changes, unless a removed member wrote it
  • an unsent transaction is signed again with the new write key and committed (not built)
  • a member who leaves is removed from the circle record by an owner's device (not built)
  • a removed member's device finds no key box for the new version and stops following the circle (not built)

Offline

Open offline

Bob opens moments on a plane. His feed shows, and a post he writes shows at once and is sent when he's online.

  • moments opens from the device store with storage unreachable, and so does chat
  • a query that needs a page this device never read fails at once with storage unreachable, and a live query shows it once storage answers
  • a live query runs again a second after a dropped connection
  • a service worker serves the app's files with no network

Reload warm

Bob closes the tab and opens it again online. The feed is there at once from the device store, and new posts arrive live.

  • a reopened app that missed nothing reads nothing from storage (checkReopen)
  • a follow answers with the heads and waiting transactions the device missed

Write offline on a big channel

Eve opened the newest 20 messages of a channel with 30,000, then lost her signal. She reacts to a new message, edits an old one of her own, and tries to edit someone else's old one. The reaction shows in place. The two edits touch pages her phone never read, so the app lists them as unchecked. When her signal returns, her own edit shows and the other is rejected with the reason.

  • a write shows in the outbox at once and survives a reload
  • an offline write the program can't check yet is listed as unchecked, and is checked once its pages arrive
  • a write kept over a reload while offline is sent when storage answers

Come back after a week offline

Gus's phone was off for a week. Two messages he had sent were settled and left the inbox while he was away: one accepted, one rejected. His phone jumps to the newest head, shows the first as committed, and shows the second with its reason.

  • a device that missed the commits settles its transactions from the newest head, without replaying them
  • a device that missed two circle records checks each from the one it holds to the newest
  • a device that missed two key versions opens every version from its newest key box
  • a device writes a change again when a committer rejects it for a signature made before the identity's keys changed (not built)

Send a message twice

Gus sends a message in a tunnel. Storage stores it, but the answer never reaches his phone, so the phone sends it again when the signal returns. The channel shows the message once.

  • a transaction sent twice is committed once

Many writers

Thirty people send at once

A team of 30 shares a busy channel, and a server they trust commits it. When the meeting ends, everyone sends a message in the same second.

Thirty people send at once Thirty people send at once
  • every open device shows each message as pending before any commit
  • 30 transactions sent at once all reach the inbox, and no writer retries
  • a committer settles every waiting transaction in one commit, in the order storage accepted them

A friend likes a post while the owner's phone is closed

Ann's circle lists no committer service, so only her devices commit it. Ben likes her post while her phone is in her pocket with the app closed. The like shows on Ben's phone as pending, and he can close his app. When Ann opens moments, her phone checks that Ben's transaction only adds a like, and commits it.

  • a transaction waits in the inbox until a committer opens, and then settles on the writer's device
  • a device that opens later shows the transactions waiting in the inbox as pending
  • a fact's time is the time of its commit

Two people edit the same task

Cara and Dan both rename the same task, a minute apart, on two phones. Each rename carries a hash of the name its writer saw. Cara's is committed first, so Dan's is rejected, and his app shows him Cara's name to pick again.

  • a change is rejected with the current value when the value it replaces has changed

Two committers commit at once

Cara and Dan both open the tasks app, and each phone commits the same waiting transactions. One head is accepted, and the other phone gets LostRace, reads the newer head, and finds nothing left to commit.

  • two committers of the same transactions give one commit, and the loser settles from the newer head
  • no transaction is committed twice, and none is lost

Open the newest messages of a big channel

Fay joins a channel with 30,000 messages and opens it on a new laptop. The newest 20 show after a few reads of the facts_field index, which holds every column the view shows.

  • a new device opens the newest 20 of 30,000 messages without reading past the query's pages
  • a query that would read more than its page limit fails
  • a pending message from the relay shows on top of a view opened cold

Rules and owners

Edit someone else's message

Cara opens a message Dan wrote and tries to edit it. The channel's program lets only the author edit, so if her device holds the message's pages, her app refuses the edit and says why, even offline. If her device skipped that check and sent the edit, no other device would show it, and the committer would reject it. Dan's message never changes on any device.

  • a device rejects a write the program rejects before sending it, offline too
  • a device doesn't show a pending transaction the program rejects
  • a committer rejects what the program rejects, and the writer sees the reason
  • a program accepts a change to a field it doesn't know

Change the program

The chat team wants messages of up to 8,000 characters instead of 4,000. An owner's device commits a circle record that references the new program, with the program's sealed bytes in the same commit.

  • an owner commits a circle record that changes the program, and no program runs on it (not built)
  • a committer runs the program of the circle record its commit references
  • a transaction checked under the old program is checked again under the new one when it is committed (not built)

Add a server as a committer

Ada's channel grows busy, and she adds a server the team trusts as its committer, so writes are committed while she's offline. The server can now read the channel, and every member can see in the circle record that it can.

  • an owner adds a committer, with the circle key and the commit key encrypted to it, and the next head from it is accepted (not built)
  • an owner's device that the circle no longer lists stops committing transactions (not built)

A committer tries to take over

The server that commits Ada's channel is compromised. It tries to make itself an owner and remove Ada, and it stops committing anything Ada sends. Ada removes it with one commit from her phone, which the server can't hold back.

  • storage refuses a head whose new circle record the own key didn't sign
  • a device refuses that head when storage serves it
  • a device refuses a head whose committer the circle record doesn't list (not built)
  • an owner who isn't a listed committer commits a circle record
  • a removed committer never receives the new keys (not built)

Everything the server committed before Ada removed it stays in the channel, including any write the program would have rejected.