Skip to content

docs: propose FTW Cloud (ADR 0008) and its protocol - #1589

Closed
frahlg wants to merge 2 commits into
masterfrom
claude/cloud-c1b-adr-128531
Closed

frahlg wants to merge 2 commits into
masterfrom
claude/cloud-c1b-adr-128531

Conversation

@frahlg

@frahlg frahlg commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

What this adds

Documents only. Nothing here has shipped, and no code, CI or contract file changes.

  • docs/adr/0008-ftw-cloud.md, status proposed, about 2,500 words. FTW Cloud keeps a signed, readable copy of what a home's owner turns on, serves it to the iPhone app, its widgets and APNs, and never controls. It records Fredrik's decisions of 12 October 2026 with their reasons, what FTW Cloud is not, the control boundary, access, the statistics rules, security and retention, cost, the turn away from Local-only operation is the full product: no feature may require the relay or Sourceful services #1323, what changes from ADR 0006, milestones, alternatives and risks. Wire detail lives in the protocol.
  • docs/protocol-cloud.md, normative: connections, unpadded lane 1 frames, the envelope and signing rule, the handshake bound to the TLS session, every message class with a field table per scope, hb, cadence and backpressure, the closed set the service may send the box, the roster, the app session, the extension token, push payloads, freshness with eight test vectors in text, the grant and its routes, revocation, and the new registry names. The byte vectors come with the code. The Go types live in go/protocol (refactor(protocol): move the wire format into its own Apache-2.0 module #1588).
  • VISION.md: one paragraph after the "authorized cloud agent" paragraph. It says FTW Cloud is proposed, that the iPhone app is the way into it and may become paid, that it never sends control or chooses actions, and that this turns away from the aim that no feature should need a Sourceful service.
  • AGENTS.md: two invariants. "FTW Cloud may hold a readable copy of what the owner enabled. It never sends control and never chooses actions." And the lane 0 rule, with the line that the FTW Cloud uplink is unpadded and so uses lane 1, never lane 0.
  • docs/architecture.md: a short pointer in Remote access boundary. docs/adr/0006-app-uplink.md: a "Revisited by" line.

Docs only, so no changeset (the .md and docs/ allowlist).

Decisions recorded

Fredrik decided 1–5 and 7 on 12 October 2026. The ADR writes them as decided.

  1. AWS eu-north-1 in its own account. ftwcloudd in a public srcfl/ftw-cloud under AGPL-3.0-only. The wire format in go/protocol here, under Apache-2.0 (refactor(protocol): move the wire format into its own Apache-2.0 module #1588).
  2. Readable, not encrypted, so statistics are possible; very secure in return. Reads, never controls, never chooses actions. Every message box-signed. Relay and escrow unchanged.
  3. The iPhone app is the way into FTW Cloud and may become paid. Local-only households run the box themselves, updates included, with the box's page, the webapp and ntfy.
  4. Consent at first use. FTW Cloud on contract; statistics on a separate switch, off by default, on consent. A lawyer confirms. The statistics rules are set in the ADR now.
  5. No padding; lane 1 frames.
  6. Assumed, not answered: Apple sees signed event codes in the push payload.
  7. Lane 0 and the relay do not change.

Milestones: the core first

Following Fredrik's "the core first, with a clear plan for what comes after", the ADR orders the work so the core shows first: C2 uplink (grant, key, signed live, lp, prices, hb, roster), C3 cache (ftwcloudd with the memory cache and app session), C4 first screen (the app's socket, carrier cloud, freshness, relay fallback, the start flow). Alerts, widgets, Live Activity and statistics follow as C5–C8. The statistics switch ships with the start flow in C4, so the box records consent from day one; nothing is sent until C8, and the backfill covers the gap.

Where this departs from design revision 2

  • The service moves to srcfl/ftw-cloud (decision 1). The import ban on the service side becomes a property of the module boundary.
  • The handshake is bound to the TLS session on both sides. Revision 2 had Caddy end TLS and said a signed challenge stops a TLS-intercepting proxy. It does not: a proxy can pass the signed reply through and still read the stream. Now hello and hello_ok each carry the sender's TLS exporter value, so no proxy may end TLS in front of ftwcloudd (a plain TCP load balancer is fine). Worth a look before C2.
  • One signing rule. Every envelope signs ftw.cloud.v1\0 followed by CBOR [home, t, b]. hello and roster use it too, instead of separate domain tags.
  • purged joins the closed set the service may send the box, as the signed purge receipt. Keep-alive is a WebSocket ping, not a message.
  • Heartbeat timing counts only live and hb. An hb names the live and lp it covers and carries each source's reading age, so the phone grades a reading by the box's own ladder. Revision 2 let a reading up to twice staleAfterMs old show as live.
  • Statistics are set now, not in C7 (decision 4). statsId lives as long as the consent, with no rotation. Rows are kept 13 months from the hour they describe, so the backfill reaches back at most that far; revision 2 asked for a 730-day backfill under a 13-month retention. The cell rule is one number, 20 homes; revision 2 also named 10 as a floor without saying when it applied.
  • Byte budget set at 20 MB per 24 hours, four times the measured 5 MB.

Review in this session

A separate agent reviewed the first version. It found real holes in the freshness rules, where stale data could still show as live: an hb that vouched for a dropped live, a reboot that kept the old table, clock rules that let data look fresher, one seq counter that broke snapshots, and timers that disagreed. The second commit closes them and adds eight vectors (16 in all). It also binds hello to TLS, states what Apple sees, handles restores and purge receipts, and fixes the lane 1 wording ("without the bulk buckets", since the app protocol pads lane 1 too).

Not in this PR

  • contract/registry.yaml and contract/push-catalogue.yaml. The protocol lists the new names (ftw.events.read, cloud_scopes, cloud_purposes, cloud.link, carrier cloud, cloud.granted, cloud.revoked). A later paired PR adds them in ftw-webapp first.
  • The go/protocol extraction: refactor(protocol): move the wire format into its own Apache-2.0 module #1588 (open).
  • The workspace AGENTS.md, which is not in a repository.

Paired PR

srcfl/ftw-webapp#79 narrows the "Sourceful cannot read your data" claims in ftw-webapp to the relay and escrow, and adds a test that holds them there. Its links point at this ADR on master, so merge this one first or both together.

Open PRs on the same files

Proposed comment for #1323

Not posted. For Fredrik to post, edit or drop:

Direction update, 12 October 2026 (ADR 0008, proposed in #1589).

The rule this issue asks for still holds for the box. Control, planning, safety, history, the box's own page and ntfy alerts must work with no Sourceful service, and that should become a tested rule.

It no longer holds for the iPhone app. The app is the way into FTW Cloud, a readable, read-only service that may become paid. Its alerts, widgets, Live Activities and fast start need FTW Cloud. A household that wants no Sourceful service runs the box itself, updates included, with the box's own page and ntfy. One that accepts the blind relay adds the webapp and web push.

So the acceptance changes:

  • The sentence in AGENTS.md and VISION.md says local-only operation is a supported configuration of the box. Everything needed to run the house works with app_link off and with the relay, escrow, push service and FTW Cloud unreachable. The iPhone app is outside that promise, and the sentence says so.
  • Beside the relay-only list in docs/architecture.md, an FTW Cloud-only list: iOS alerts, widgets, Live Activities and the app's fast start, each with its reason.
  • The boot test stays as written and adds a case with FTW Cloud unreachable.
  • Question 3 is settled: ntfy is the local answer for alerts while the app is closed.

How to review

Read the rendered Markdown. The protocol's class tables are meant to become the allowlist that a test in C2 checks field by field.

🤖 Generated with Claude Code

ADR 0008, status proposed, records Fredrik's decisions of 12 October
2026: a readable, read-only copy of what a home's owner turns on, served
to the iPhone app, its widgets and APNs from ftwcloudd in srcfl/ftw-cloud
on AWS eu-north-1. The box signs every message; the service never
controls and never chooses actions; the relay, escrow and lane 0 do not
change. It states the statistics rules now, because consent comes at
first use, orders the work core first (uplink, cache, first screen),
and records the turn away from #1323 and ADR 0006's single remote path.

docs/protocol-cloud.md is the normative wire format: unpadded lane 1
frames, one signing rule, a handshake bound to the TLS session, a field
table per class and scope, cadence, the closed set the service may send
the box, roster, extension token, push, freshness with eight vectors
described in text, and the new registry names.

VISION.md and AGENTS.md gain the matching paragraph and invariants;
architecture.md and ADR 0006 point to the ADR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent review found ways stale data could still show as live.

- hb now names the newest live and lp it covers (liveSeq, lpSeq) and
  carries each source's reading age, so the phone grades a reading by
  the box's own ladder instead of keeping it live for another S.
- The box sends no hb while a changed live or lp waits, nor past the
  byte budget, so an hb never vouches for values the box has not sent.
- The phone keys its table by boot, tracks seq per class, takes the
  earlier of wallMs and arrival under rule N, adds a drift margin and a
  24 h lifetime under rule U, and shows old alerts without buttons.
- hello now carries the box's TLS exporter value too, so a proxy that
  ends TLS cannot hold a socket for the home; the label starts with
  EXPERIMENTAL as RFC 5705 asks.
- Restores, roster expiry, purge receipts, tiles for phones, Live
  Activity tokens and what Apple sees are now stated; timers agree
  between the ADR and the protocol. Eight new vectors cover the cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg

frahlg commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Included in #1604 (ADR 0008 with what C2 and C3 built), squash-merged to master.

@frahlg frahlg closed this Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant