Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cloud-tun-cap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": patch
---

A box whose FTW Cloud link carries the phones' sessions says so in `hello_ok.caps` as `cloud.tun`, a new registry capability, so a phone uses that path only when the box takes it. The registry also names the carrier state `tunnel` for a session that FTW Cloud carries.
5 changes: 5 additions & 0 deletions .changeset/cloud-tunnel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": minor
---

A phone can reach its box through FTW Cloud, with the relay unreachable. The box links to `cloud.ftw.energy` when it first shows a QR pairing code or has a paired phone, and carries each phone's own Noise session (`ftw.session.v1`) as `tun {dev, sid, d}` on that link. FTW Cloud cannot read or forge `d`; the box admits a session by the key its handshake proves, exactly as over the relay, and drops one the service put under another device. A new phone can pair through FTW Cloud: while a QR code is shown, the roster lists the code's hash, never the code. The readable copy stays a separate grant: with it off the box sends only its roster and the tunnel, and turning it off now deletes the copy at the service while the link and the home id stay. The link ends, and `cloud.key` with it, when no phone may use it and no code is shown. The tunnel has its own queues and a 250 MB a day budget apart from the readable copy's 20 MB: a command and its answer go first, and bulk is paced under FTW Cloud's limits. Each device now holds one session across the relay and FTW Cloud, in place of four places shared by all, and may open at most 12 through FTW Cloud a minute. `GET /api/cloud` shows `link`, `tunBytesLast24h`, `tunBudgetBytes` and `tunDropped`. For every linked home, FTW Cloud sees a stable home id, the box's address and when traffic goes. The relay keeps working as before.
5 changes: 3 additions & 2 deletions .github/scripts/check-cloudlink-imports.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# go/internal/cloudlink streams the box's state to FTW Cloud, and FTW Cloud
# reads and never controls. This check keeps the uplink's build graph honest:
# go/internal/cloudlink links the box to FTW Cloud, which never chooses an
# action: it carries the phones' sessions unread and, when an owner allows
# it, a readable copy. This check keeps the uplink's build graph honest:
# it may import go/protocol and nothing else of the box's own, so no control,
# API, loadpoint, planner or driver code can be reached from the code that
# talks to the service. Its tests are held to the same rule.
Expand Down
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ Read [docs/architecture.md](docs/architecture.md) for the system map and
- Database queries stay in [`go/internal/state`](go/internal/state).
- Lane 0 frames have a fixed length and cadence; the padding is a privacy
control. The proposed FTW Cloud uplink is not padded, so it sends lane 1
frames without the bulk buckets and never lane 0.
- FTW Cloud may hold a readable copy of what the owner enabled. It never sends
control and never chooses actions. See
[ADR 0008](docs/adr/0008-ftw-cloud.md), proposed.
frames without the bulk buckets and never lane 0. A phone's own session
rides inside it as `tun`, with its lane 0 padding intact.
- FTW Cloud may hold a readable copy of what the owner enabled. It carries the
phones' sessions with the box as ciphertext it cannot read or forge, and it
never chooses actions; the box judges what arrives as it does over the
relay. See [ADR 0008](docs/adr/0008-ftw-cloud.md), proposed, and the
tunnel in [docs/protocol-cloud.md](docs/protocol-cloud.md).

## Drivers

Expand Down
10 changes: 7 additions & 3 deletions VISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,13 @@ FTW Cloud is such an endpoint. It is proposed in
the way into it, and it may become a paid service. When an owner turns it on,
the box sends Sourceful a signed, readable copy of what the owner enabled, so
the app starts fast and its alerts, widgets and Live Activities work while it
is closed. FTW Cloud never sends control and never chooses which actions a
user is offered; commands still go from the app to the box over the encrypted
session. Statistics need their own consent, off by default. This turns away
is closed. From the first pairing the box also links to FTW Cloud, so the
phones reach it through one service: their encrypted session with the box
rides through FTW Cloud as ciphertext it cannot read or forge, and the box
judges every command as it does over the relay. For every linked home the
service sees a stable home id, the box's address and when traffic goes, with
the readable copy off too. FTW Cloud never chooses which actions a user is
offered. Statistics need their own consent, off by default. This turns away
from [#1323](https://github.com/srcfl/ftw/issues/1323), which asks that no
feature need a Sourceful service: the iPhone app needs FTW Cloud, while the
box does not. A household that does not want FTW Cloud runs the box itself,
Expand Down
8 changes: 7 additions & 1 deletion contract/registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ capabilities:
# only and offers no FTW Cloud. It says nothing about whether the owner
# turned FTW Cloud on; the box's grant decides that.
- cloud.link
# The box takes its app session through FTW Cloud, end to end, as `tun`
# (docs/protocol-cloud.md, Tunnel, in srcfl/ftw). Absent means a phone
# reaches the box through the relay, even when it knows the home id.
- cloud.tun
# The box can take a watch: it opens the relay's exchange socket and mints
# watch codes. Absent means the phone offers no Add watch. The wire is
# docs/watch-exchange.md in srcfl/ftw-webapp.
Expand Down Expand Up @@ -274,7 +278,9 @@ client_errors:
# the two into one enum cannot express "connected, but the inverter is quiet".
# ---------------------------------------------------------------------------
source_states: [live, lagging, stale, down, never]
carrier_states: [webrtc, relay, cloud, cache, none]
# tunnel is the box's own session carried by FTW Cloud, which cannot read it.
# cloud is the box-signed copy FTW Cloud holds and can read.
carrier_states: [webrtc, relay, tunnel, cloud, cache, none]

# ---------------------------------------------------------------------------
# History resolutions backed by what the box actually stores.
Expand Down
14 changes: 9 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,18 +409,22 @@ The properties that matter:
intact.

Proposed, not shipped: [ADR 0008](adr/0008-ftw-cloud.md) adds FTW Cloud, a
second outbound connection for homes whose owner turns it on. It carries to
`cloud.ftw.energy` a signed, readable copy of what the owner enabled, and it
never carries control. Commands and long history stay on the relay path above.
second outbound connection to `cloud.ftw.energy`. A box links when it is first
paired. The link carries each phone's own session with the box as `tun`,
ciphertext the service cannot read or forge, so a phone reaches the box with
the relay unreachable; and, when an owner turns it on, a signed, readable copy
of what the owner enabled. The box admits a session through the tunnel by the
key its handshake proves, exactly as over the relay, and keeps one session per
device across both. The relay path above stays while every client moves.
[protocol-cloud.md](protocol-cloud.md) specifies its wire format.

[ADR 0009](adr/0009-watch-exchange.md) lets an Apple Watch work without the
phone. watchOS allows no WebSocket, so each step is one HTTPS request. Commands
go to the box through the relay's exchange: the box keeps a second socket,
`/r/<epoch>/<handle>/exchange`, under the uplink's handle, and the relay hands
it one Noise message per request behind an id of its own. The box holds those
sessions in a table of eight, apart from the phones' four, so a watch never
pushes a phone out; each ends after four exchanges, a minute idle or three
sessions in a table of eight, apart from the phones' sessions, which hold one
slot per device, so a watch never pushes a phone out; each ends after four exchanges, a minute idle or three
minutes. Every frame on it is lane 0 apart from `hello_ok`, and every answer is
two frames, so a read, a command and a refusal have one length. A command
answers no sooner than 400 ms after it arrives, so an applied one and a refused
Expand Down
Loading
Loading