From 2095aba8528e08d76652fce23cff86b93272194d Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Wed, 14 Oct 2026 10:14:02 +0200 Subject: [PATCH] feat(contract): role watch and capability cmd.exchange ADR 0009 step 2. An owner phone adds an Apple Watch that works without the phone. The box names it with role watch, which carries ftw.live.read, ftw.plan.read, ftw.events.read and ftw.dispatch.write, and says it can take one with capability cmd.exchange. contract.ts gains the role, so the registry test still reads both directions. The app uses neither name otherwise. Paired with srcfl/ftw and srcfl/ftw-app, which copy the file byte for byte. Co-Authored-By: Claude Opus 5.5 --- .changeset/watch-registry-names.md | 5 +++++ contract/registry.yaml | 11 +++++++++++ src/lib/protocol/contract.ts | 2 ++ 3 files changed, 18 insertions(+) create mode 100644 .changeset/watch-registry-names.md diff --git a/.changeset/watch-registry-names.md b/.changeset/watch-registry-names.md new file mode 100644 index 0000000..bf14ec4 --- /dev/null +++ b/.changeset/watch-registry-names.md @@ -0,0 +1,5 @@ +--- +"ftw-webapp": patch +--- + +Name role `watch` and capability `cmd.exchange` in the shared contract, for an Apple Watch that works without the phone. This app labels a watch as Watch in the device list and uses neither name otherwise. diff --git a/contract/registry.yaml b/contract/registry.yaml index 5d71d3a..18f3cd5 100644 --- a/contract/registry.yaml +++ b/contract/registry.yaml @@ -80,6 +80,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 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. + - cmd.exchange # --------------------------------------------------------------------------- # Scopes. One object axis, two verb axes: .. @@ -104,6 +108,13 @@ roles: viewer: label: Viewer scopes: [ftw.live.read] + # A watch that an owner phone added. That phone is its parent, and each check + # keeps only the scopes below that the parent holds at that moment, so a + # watch never does more than its phone can do now. Read and charge: no site + # mode, no members, no history and no passthrough. + watch: + label: Watch + scopes: [ftw.live.read, ftw.plan.read, ftw.events.read, ftw.dispatch.write] # --------------------------------------------------------------------------- # FTW Cloud. What an owner can let the box share with FTW Cloud, and why. diff --git a/src/lib/protocol/contract.ts b/src/lib/protocol/contract.ts index f5088a0..455a2ce 100644 --- a/src/lib/protocol/contract.ts +++ b/src/lib/protocol/contract.ts @@ -38,12 +38,14 @@ export type Scope = (typeof SCOPES)[number] export const ROLE_SCOPES: Record = { owner: SCOPES, viewer: ['ftw.live.read'], + watch: ['ftw.live.read', 'ftw.plan.read', 'ftw.events.read', 'ftw.dispatch.write'], } /** The label the registry gives each role. The app owns no other wording. */ export const ROLE_LABELS: Record = { owner: 'Owner', viewer: 'Viewer', + watch: 'Watch', } /** Whether a role carries a scope. An unknown role carries nothing. */