diff --git a/README.md b/README.md index 0d54018..521de6b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Your home's energy, from wherever you are. This is FTW's installable web client. It talks to the FTW box in your home, which is the authority on your -energy system. Sourceful's cloud carries the traffic and cannot read it. +energy system. Sourceful's relay carries the traffic and cannot read it. ## Product direction and contributions @@ -50,12 +50,17 @@ cached projection of it, never the original. on screen in the first frame, timestamped honestly, while fresher data is fetched behind them. There is no spinner on a white background. -**The cloud is blind.** It serves this bundle, relays encrypted frames and -knows which opaque handle is online. It cannot read a watt, a device name or -a command. This is enforced by the protocol, and a CI test dumps the relay's -database and fails if anything recognisable is in it. +**The relay is blind.** Sourceful serves this bundle, and its relay carries +encrypted frames and knows which opaque handle is online. The relay cannot read +a watt, a device name or a command. This is enforced by the protocol, and a CI +test dumps the relay's database and fails if anything recognisable is in it. -If a household asks, the cloud will also hold one sealed copy of its home, so a +This app does not use FTW Cloud. FTW Cloud is a separate service for the +iPhone app, proposed and not yet built. If a box owner turns it on, Sourceful +can read what the owner enabled; it never sends control. +[ADR 0008](https://github.com/srcfl/ftw/blob/master/docs/adr/0008-ftw-cloud.md) records it, and the relay and escrow stay blind either way. + +If a household asks, the escrow will also hold one sealed copy of its home, so a new phone gets back in with a passkey instead of a trip to the box. Sourceful holds a sealed copy it cannot open, with an opaque id and nothing beside it — [`escrow/README.md`](escrow/README.md) is the claim, the file of fixed slots it diff --git a/docs/architecture.md b/docs/architecture.md index e132a0e..759ecbe 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -36,6 +36,11 @@ Sourceful's cloud serves this bundle and relays encrypted frames. On that path it knows that an opaque handle is online and roughly how much traffic passed. It cannot read a watt, a device name or a command. +FTW Cloud is a separate service for the iPhone app, proposed in +[srcfl/ftw ADR 0008](https://github.com/srcfl/ftw/blob/master/docs/adr/0008-ftw-cloud.md) and not yet built. If a box owner turns it on, it +holds a readable copy of what the owner enabled, and it never sends control. +This app does not use it. The relay and escrow stay blind either way. + A separate daily fleet-report endpoint is the stated exception. Unless the box owner opts out, it receives six coarse labels with no box id and reduces them to UTC-day counters on arrival. It keeps neither the report nor the source @@ -209,9 +214,11 @@ home. The mitigations are an append-only build transparency log, a service worker that refuses a bundle whose hash is not in it, no third-party scripts and no analytics. They reduce the risk; they do not remove it. -So the claim is **"Sourceful's services cannot decrypt your energy data"** — -not "it is mathematically impossible for Sourceful to ever access anything". -The second sentence is marketing. The first is true. +So the claim is **"Sourceful's relay and escrow cannot decrypt your energy +data"** — not "it is mathematically impossible for Sourceful to ever access +anything". The second sentence is marketing. The first is true. It does not +cover FTW Cloud, which can read what a box owner turns on; +[ADR 0008](https://github.com/srcfl/ftw/blob/master/docs/adr/0008-ftw-cloud.md) states that service's own claim. ### What is not yet true diff --git a/relay/README.md b/relay/README.md index 4bb0315..044517a 100644 --- a/relay/README.md +++ b/relay/README.md @@ -1,7 +1,10 @@ # The blind relay Carries encrypted frames between an FTW box and the browsers allowed to see it. -It is the only remote path between the two, and it cannot read a watt. +It is the only remote path that carries commands, and it cannot read a watt. +FTW Cloud, which a box owner may turn on for the iPhone app, is a separate +service that can read what the owner enabled. It never touches the relay +([ADR 0008](https://github.com/srcfl/ftw/blob/master/docs/adr/0008-ftw-cloud.md)). ``` box ──ws──▶ ┌────────────┐ ◀──ws── phone diff --git a/tests/cloud-claims.test.ts b/tests/cloud-claims.test.ts new file mode 100644 index 0000000..6b9a390 --- /dev/null +++ b/tests/cloud-claims.test.ts @@ -0,0 +1,66 @@ +/* What Sourceful can read, said the same way everywhere it is said. + * + * Until FTW Cloud, "Sourceful cannot read your data" was true of every + * Sourceful service this app touches. FTW Cloud (srcfl/ftw ADR 0008) is a + * separate service for the iPhone app, and it can read what a box owner turns + * on. The relay and the escrow are as blind as before, so their claims stand. + * A sentence that says "Sourceful's services" or "the cloud" without naming + * them now claims more than is true. + * + * Nothing goes red when a document goes stale, so this file reads them. Two + * rules, both mechanical: + * + * 1. The broad sentences are gone. No document says that Sourceful's + * services, or the cloud as a whole, cannot read the data. + * 2. Every document that tells a reader what Sourceful cannot read also + * names FTW Cloud and links the ADR, so the exception sits beside the + * claim. + */ + +import { describe, it, expect } from 'vitest' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' + +// From the project root, which is where vitest runs. +const read = (path: string) => + readFileSync(join(process.cwd(), path), 'utf8').replace(/\s+/g, ' ') + +/** The documents that tell a reader what Sourceful can and cannot read. */ +const DOCS = ['README.md', 'docs/architecture.md', 'relay/README.md'] + +/** The sentences that were true before FTW Cloud and are too broad now. */ +const BROAD = [ + /Sourceful's services cannot decrypt/i, + /Sourceful's cloud carries the traffic and cannot read it/i, + /\bthe cloud is blind\b/i, + /only remote path between the two/i, +] + +const ADR = 'srcfl/ftw/blob/master/docs/adr/0008-ftw-cloud.md' + +describe('what Sourceful can read', () => { + for (const path of DOCS) { + it(`${path} claims nothing broader than the relay and escrow`, () => { + const text = read(path) + for (const pattern of BROAD) { + expect(text, `${path} still says ${pattern}`).not.toMatch(pattern) + } + }) + + it(`${path} names FTW Cloud beside its claim`, () => { + const text = read(path) + expect( + text, + `${path} no longer says what Sourceful cannot read; if that is on purpose, drop it from DOCS` + ).toMatch(/cannot (read|decrypt)/i) + expect(text, `${path} makes the claim without naming the exception`).toContain('FTW Cloud') + expect(text, `${path} names FTW Cloud without linking the decision`).toContain(ADR) + }) + } + + it('states the claim for the relay and the escrow by name', () => { + expect(read('docs/architecture.md')).toContain( + `**"Sourceful's relay and escrow cannot decrypt your energy data"**` + ) + }) +}) diff --git a/tests/relay-blindness.test.ts b/tests/relay-blindness.test.ts index e59c56a..66bbed1 100644 --- a/tests/relay-blindness.test.ts +++ b/tests/relay-blindness.test.ts @@ -3,7 +3,8 @@ /* The proof. * * docs/architecture.md makes one claim about the cloud that is architecture - * rather than policy: "Sourceful's services cannot decrypt your energy data." + * rather than policy: "Sourceful's relay and escrow cannot decrypt your energy + * data." This file proves the relay half of it. * This file is what that sentence is worth. It runs a real box against a real * app across a real relay, collects everything the relay touched — every byte * routed, every line logged, everything held in memory — and fails if anything