From f0239f28854175cb74e74d6f3ee9f115ffb22ae8 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 18:30:14 +0200 Subject: [PATCH 1/2] fix(privacy): use 48-hour Cloud terms and declare collected data --- appleApp/FTW/PrivacyInfo.xcprivacy | 41 ++++++++++++- .../Sources/FTWKit/Identity/CloudGrant.swift | 8 +-- .../Tests/FTWKitTests/CloudConsentTests.swift | 4 +- .../Tests/FTWKitTests/CloudWireTests.swift | 2 +- .../Resources/cloud-quic-vectors.json | 25 ++++++++ .../FTWKitTests/Resources/cloud-vectors.json | 50 ++++++++++++++++ appleApp/RELEASING.md | 59 +++++++++---------- appleApp/store/en-US/description.txt | 4 +- appleApp/store/sv-SE/description.txt | 4 +- 9 files changed, 153 insertions(+), 44 deletions(-) diff --git a/appleApp/FTW/PrivacyInfo.xcprivacy b/appleApp/FTW/PrivacyInfo.xcprivacy index dba570db..05f1daeb 100644 --- a/appleApp/FTW/PrivacyInfo.xcprivacy +++ b/appleApp/FTW/PrivacyInfo.xcprivacy @@ -7,7 +7,46 @@ NSPrivacyTrackingDomains NSPrivacyCollectedDataTypes - + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDataTypes + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + NSPrivacyCollectedDataTypePurposeAnalytics + + + NSPrivacyAccessedAPITypes diff --git a/appleApp/FTWKit/Sources/FTWKit/Identity/CloudGrant.swift b/appleApp/FTWKit/Sources/FTWKit/Identity/CloudGrant.swift index 9d3764ce..c187939a 100644 --- a/appleApp/FTWKit/Sources/FTWKit/Identity/CloudGrant.swift +++ b/appleApp/FTWKit/Sources/FTWKit/Identity/CloudGrant.swift @@ -6,10 +6,10 @@ import Observation @Observable @MainActor public final class CloudConsent { - nonisolated public static let policy = "ftw-cloud-2026-10-v2" - nonisolated public static let statsPolicy = "ftw-project-stats-2026-10-v2" + nonisolated public static let policy = "ftw-cloud-2026-10-v3" + nonisolated public static let statsPolicy = "ftw-project-stats-2026-10-v3" nonisolated public static let statisticsDetails = "Recorded hourly energy, configured battery capacity, battery and charger power limits, and solar capacity help Sourceful improve FTW and publish group totals. Values are rounded to 10 Wh or W. No names, addresses or device ids." - nonisolated public static let statisticsPrivacy = "Sourceful links reports to your Cloud home for deletion. They are not anonymous and stay for 13 months. Public values need at least 20 homes, with no home above 30% of a sum. Turning FTW Cloud off deletes unpublished reports when the box reaches Cloud; published totals remain." + nonisolated public static let statisticsPrivacy = "Sourceful links reports to your Cloud home for deletion. They are not anonymous and stay for 48 hours. Public values need at least 20 homes, with no home above 30% of a sum. Turning FTW Cloud off deletes unpublished reports when the box reaches Cloud; published totals remain." nonisolated public static let statisticsWaiting = "Hourly collection waits for privacy review and policy activation. Your choice can be saved now; no hourly report leaves before approval." nonisolated public static let included = "Everyone using FTW Cloud contributes to project statistics. There is no separate statistics switch. If you do not want to contribute, use FTW locally without Cloud. Local users can choose to share statistics without enabling remote access." @@ -164,7 +164,7 @@ public enum CloudWords { static let sv: [String: String] = [ "Demo: no reports leave this phone.": "Demo: inga rapporter lämnar telefonen.", CloudConsent.statisticsDetails: "Dela registrerad timenergi, inställd batterikapacitet, effektgränser för batterier och laddare samt solkapacitet med Sourceful för att förbättra FTW och publicera gruppsummor. Värden avrundas till 10 Wh eller W. Inga namn, adresser eller enhets-id:n.", - CloudConsent.statisticsPrivacy: "Sourceful kopplar rapporterna till ditt Cloud-hem för att kunna radera dem. De är inte anonyma och sparas i 13 månader. Publika värden kräver minst 20 hem och att inget hem står för mer än 30 procent av summan. Stänger du av FTW Cloud raderas opublicerade rapporter när boxen når Cloud; publicerade gruppsummor finns kvar.", + CloudConsent.statisticsPrivacy: "Sourceful kopplar rapporterna till ditt Cloud-hem för att kunna radera dem. De är inte anonyma och sparas i 48 timmar. Publika värden kräver minst 20 hem och att inget hem står för mer än 30 procent av summan. Stänger du av FTW Cloud raderas opublicerade rapporter när boxen når Cloud; publicerade gruppsummor finns kvar.", CloudConsent.statisticsWaiting: "Timinsamlingen väntar på integritetsgranskning och aktivering. Ditt val kan sparas nu; ingen timrapport lämnar boxen före godkännandet.", CloudConsent.included: "Alla som använder FTW Cloud bidrar till projektstatistik. Det finns inget separat statistikval. Vill du inte bidra använder du FTW lokalt utan Cloud. Lokala användare kan välja att dela statistik utan att slå på fjärråtkomst.", "Accept Cloud terms": "Godkänn Cloud-villkoren", diff --git a/appleApp/FTWKit/Tests/FTWKitTests/CloudConsentTests.swift b/appleApp/FTWKit/Tests/FTWKitTests/CloudConsentTests.swift index 77bc3c22..066c7296 100644 --- a/appleApp/FTWKit/Tests/FTWKitTests/CloudConsentTests.swift +++ b/appleApp/FTWKit/Tests/FTWKitTests/CloudConsentTests.swift @@ -7,8 +7,8 @@ import Testing func defaults() throws -> UserDefaults { try #require(UserDefaults(suiteName: "ftw-consent-\(UUID().uuidString)")) } - static let currentPolicy = #"{"policy":"ftw-project-stats-2026-10-v2","cloud_policy":"ftw-cloud-2026-10-v2","cloud_includes_statistics":true,"available":false}"# - static let granted = #"{"state":"on","policy":"ftw-cloud-2026-10-v2","cloudPolicy":"ftw-cloud-2026-10-v2","cloudTermsAccepted":true,"statisticsPolicy":"ftw-project-stats-2026-10-v2","statisticsActive":true,"connected":true}"# + static let currentPolicy = #"{"policy":"ftw-project-stats-2026-10-v3","cloud_policy":"ftw-cloud-2026-10-v3","cloud_includes_statistics":true,"available":false}"# + static let granted = #"{"state":"on","policy":"ftw-cloud-2026-10-v3","cloudPolicy":"ftw-cloud-2026-10-v3","cloudTermsAccepted":true,"statisticsPolicy":"ftw-project-stats-2026-10-v3","statisticsActive":true,"connected":true}"# func home(role: String = Contract.roleOwner, caps: [String] = [Contract.capCloudLink, Contract.capAPIPassthrough], cloud: Bool = false) async throws -> CloudSite { let w = try CloudSite() w.open() diff --git a/appleApp/FTWKit/Tests/FTWKitTests/CloudWireTests.swift b/appleApp/FTWKit/Tests/FTWKitTests/CloudWireTests.swift index c2d246aa..a62ec481 100644 --- a/appleApp/FTWKit/Tests/FTWKitTests/CloudWireTests.swift +++ b/appleApp/FTWKit/Tests/FTWKitTests/CloudWireTests.swift @@ -118,7 +118,7 @@ func cloudSign(_ key: P256.Signing.PrivateKey, home: String, t: String, b: Bytes let v = try CloudVectors.load() let boxPK = Bytes(hex: v.boxKey.pk) let servicePK = Bytes(hex: v.serviceKey.pk) - #expect(v.messages.count == 27) + #expect(v.messages.count == 32) for m in v.messages { let frame = Bytes(hex: m.frame) diff --git a/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-quic-vectors.json b/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-quic-vectors.json index bf679d45..f9b4c352 100644 --- a/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-quic-vectors.json +++ b/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-quic-vectors.json @@ -242,6 +242,31 @@ "name": "lp car tapped", "envelope": "a361625901ada7637365711264626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06677616c6c4d731b000001a130d7f20868666565646261636b8068757074696d654d731a00013c686a636f6e74726f6c52657619032e6a6c6f6164706f696e747381b8186269646b6a33485a4f524e43574873637372636b4455595757516d6e2d586765626f6f7374f46563617249646b464e4b6962697466655530666d616e75616cf6666e6578745768f666706f776572570067706c7567676564f56773657373696f6e703764316630633261396233653466353067736f6341676553190221696e657874456e644d73f669726561647942794d731b000001a133a1bc8069726563757272696e67f56973657373696f6e57680069736f63536f757263656776656869636c656a636f6d6d616e64656457f66b6e65787453746172744d73f66b706c616e50656e64696e67f46b736f635065726d696c6c65188c6b737572706c75734f6e6c79f46c677269644465666572726564f46e7461726765745065726d696c6c651903206f6361724964656e7469666965644279637461706f636f6d6d616e646564526561736f6e64706c616e6174626c7063736967584089b4a6faa2af341cdc4da9ad0247aa2135f40e1aec94554cf1855392d22412cb2191b2a594084e4490faada200f68abbdcdd80b7b0bc3e7fb4ff65e5572f3fcf", "datagram": false + }, + { + "name": "roster browser notice", + "envelope": "a361625901e7ab637365711364626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a4626964406673636f7065738068707572706f736573806a636f6e74726f6c6c657260656e6f697365406677616c6c4d731b000001a130d7f5f0676465766963657381a66269646762726f77736572636b6579582055555555555555555555555555555555555555555555555555555555555555556374756ef464726f6c65656f776e65726565706f6368016673636f706573816f6674772e6576656e74732e726561646770656e64696e6780677765627075736881a662637458806666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666626964736f70617175652d737562736372697074696f6e636465766762726f77736572646175746870766170696420626f782d7369676e65646565706f63680168656e64706f696e74781b68747470733a2f2f707573682e6578616d706c652f6f706171756568757074696d654d731a0001405069726f7374657253657105617466726f737465726373696758409c84115970bee55473418d660236e759a1c818333cc1a1c0d7b5aba44d0eaa2f9fc08e617b72d788232ca0bd46e0d4602074c27222725d7c8d7e83c54ea340c9", + "datagram": false + }, + { + "name": "statistics service ready", + "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", + "datagram": false + }, + { + "name": "statistics closed hour", + "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", + "datagram": false + }, + { + "name": "statistics only roster", + "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", + "datagram": false + }, + { + "name": "roster statistics consent", + "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", + "datagram": false } ], "notes": { diff --git a/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-vectors.json b/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-vectors.json index 52a05b70..ffed95c1 100644 --- a/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-vectors.json +++ b/appleApp/FTWKit/Tests/FTWKitTests/Resources/cloud-vectors.json @@ -311,6 +311,56 @@ "sig": "89b4a6faa2af341cdc4da9ad0247aa2135f40e1aec94554cf1855392d22412cb2191b2a594084e4490faada200f68abbdcdd80b7b0bc3e7fb4ff65e5572f3fcf", "envelope": "a361625901ada7637365711264626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06677616c6c4d731b000001a130d7f20868666565646261636b8068757074696d654d731a00013c686a636f6e74726f6c52657619032e6a6c6f6164706f696e747381b8186269646b6a33485a4f524e43574873637372636b4455595757516d6e2d586765626f6f7374f46563617249646b464e4b6962697466655530666d616e75616cf6666e6578745768f666706f776572570067706c7567676564f56773657373696f6e703764316630633261396233653466353067736f6341676553190221696e657874456e644d73f669726561647942794d731b000001a133a1bc8069726563757272696e67f56973657373696f6e57680069736f63536f757263656776656869636c656a636f6d6d616e64656457f66b6e65787453746172744d73f66b706c616e50656e64696e67f46b736f635065726d696c6c65188c6b737572706c75734f6e6c79f46c677269644465666572726564f46e7461726765745065726d696c6c651903206f6361724964656e7469666965644279637461706f636f6d6d616e646564526561736f6e64706c616e6174626c7063736967584089b4a6faa2af341cdc4da9ad0247aa2135f40e1aec94554cf1855392d22412cb2191b2a594084e4490faada200f68abbdcdd80b7b0bc3e7fb4ff65e5572f3fcf", "frame": "0101000001fea361625901ada7637365711264626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06677616c6c4d731b000001a130d7f20868666565646261636b8068757074696d654d731a00013c686a636f6e74726f6c52657619032e6a6c6f6164706f696e747381b8186269646b6a33485a4f524e43574873637372636b4455595757516d6e2d586765626f6f7374f46563617249646b464e4b6962697466655530666d616e75616cf6666e6578745768f666706f776572570067706c7567676564f56773657373696f6e703764316630633261396233653466353067736f6341676553190221696e657874456e644d73f669726561647942794d731b000001a133a1bc8069726563757272696e67f56973657373696f6e57680069736f63536f757263656776656869636c656a636f6d6d616e64656457f66b6e65787453746172744d73f66b706c616e50656e64696e67f46b736f635065726d696c6c65188c6b737572706c75734f6e6c79f46c677269644465666572726564f46e7461726765745065726d696c6c651903206f6361724964656e7469666965644279637461706f636f6d6d616e646564526561736f6e64706c616e6174626c7063736967584089b4a6faa2af341cdc4da9ad0247aa2135f40e1aec94554cf1855392d22412cb2191b2a594084e4490faada200f68abbdcdd80b7b0bc3e7fb4ff65e5572f3fcf" + }, + { + "name": "roster browser notice", + "signer": "box", + "t": "roster", + "body": "ab637365711364626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a4626964406673636f7065738068707572706f736573806a636f6e74726f6c6c657260656e6f697365406677616c6c4d731b000001a130d7f5f0676465766963657381a66269646762726f77736572636b6579582055555555555555555555555555555555555555555555555555555555555555556374756ef464726f6c65656f776e65726565706f6368016673636f706573816f6674772e6576656e74732e726561646770656e64696e6780677765627075736881a662637458806666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666626964736f70617175652d737562736372697074696f6e636465766762726f77736572646175746870766170696420626f782d7369676e65646565706f63680168656e64706f696e74781b68747470733a2f2f707573682e6578616d706c652f6f706171756568757074696d654d731a0001405069726f7374657253657105", + "signedInputSha256": "31f3869339e89ade757686917fddee449040257f0da2c55058419624a7f55e99", + "sig": "9c84115970bee55473418d660236e759a1c818333cc1a1c0d7b5aba44d0eaa2f9fc08e617b72d788232ca0bd46e0d4602074c27222725d7c8d7e83c54ea340c9", + "envelope": "a361625901e7ab637365711364626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a4626964406673636f7065738068707572706f736573806a636f6e74726f6c6c657260656e6f697365406677616c6c4d731b000001a130d7f5f0676465766963657381a66269646762726f77736572636b6579582055555555555555555555555555555555555555555555555555555555555555556374756ef464726f6c65656f776e65726565706f6368016673636f706573816f6674772e6576656e74732e726561646770656e64696e6780677765627075736881a662637458806666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666626964736f70617175652d737562736372697074696f6e636465766762726f77736572646175746870766170696420626f782d7369676e65646565706f63680168656e64706f696e74781b68747470733a2f2f707573682e6578616d706c652f6f706171756568757074696d654d731a0001405069726f7374657253657105617466726f737465726373696758409c84115970bee55473418d660236e759a1c818333cc1a1c0d7b5aba44d0eaa2f9fc08e617b72d788232ca0bd46e0d4602074c27222725d7c8d7e83c54ea340c9", + "frame": "01010000023ca361625901e7ab637365711364626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a4626964406673636f7065738068707572706f736573806a636f6e74726f6c6c657260656e6f697365406677616c6c4d731b000001a130d7f5f0676465766963657381a66269646762726f77736572636b6579582055555555555555555555555555555555555555555555555555555555555555556374756ef464726f6c65656f776e65726565706f6368016673636f706573816f6674772e6576656e74732e726561646770656e64696e6780677765627075736881a662637458806666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666626964736f70617175652d737562736372697074696f6e636465766762726f77736572646175746870766170696420626f782d7369676e65646565706f63680168656e64706f696e74781b68747470733a2f2f707573682e6578616d706c652f6f706171756568757074696d654d731a0001405069726f7374657253657105617466726f737465726373696758409c84115970bee55473418d660236e759a1c818333cc1a1c0d7b5aba44d0eaa2f9fc08e617b72d788232ca0bd46e0d4602074c27222725d7c8d7e83c54ea340c9" + }, + { + "name": "statistics service ready", + "signer": "service", + "t": "hello_ok", + "body": "a563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d7633", + "signedInputSha256": "9c9a2a7add0d201b5f546d1749392bd1dc05c5f32c9dbec6d56ac9dcaacf0d0d", + "sig": "790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", + "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", + "frame": "0101000000e3a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406" + }, + { + "name": "statistics closed hour", + "signer": "box", + "t": "stats", + "body": "a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a00014438", + "signedInputSha256": "1d061b9054c924b61661b464c2fc91a0f3b08315ab61e638ccb21c7f1a9fdb03", + "sig": "134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", + "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", + "frame": "01010000010ba3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f" + }, + { + "name": "statistics only roster", + "signer": "box", + "t": "roster", + "body": "aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107", + "signedInputSha256": "3b9ae29cec865ff58208b830dd4300c8966048bb4dc5ab2c64beb40a201c881c", + "sig": "002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", + "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", + "frame": "010100000188a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b" + }, + { + "name": "roster statistics consent", + "signer": "box", + "t": "roster", + "body": "aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106", + "signedInputSha256": "7bb5ee8429e29ae18fbede964510cb3b0527acfed65864007fe9a168ec5b0232", + "sig": "59638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", + "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", + "frame": "010100000185a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9" } ] } diff --git a/appleApp/RELEASING.md b/appleApp/RELEASING.md index 79005f81..50ec932f 100644 --- a/appleApp/RELEASING.md +++ b/appleApp/RELEASING.md @@ -254,38 +254,33 @@ ftw-web and FTW's SUPPORT.md link to. No phone number is published. ### 4. App Privacy -**Apps → FTW → App Privacy → Get Started → "No, we do not collect data -from this app" → Save → Publish.** - -Apple's test: data is collected when it leaves the device "in a way that -allows you and/or your third-party partners to access it for a period -longer than what is necessary to service the transmitted request in real -time". In M1: - -- The app's only network peer is `wss://relay.ftw.energy`. Everything it - sends is end-to-end encrypted to the user's box; the relay has no key. -- The relay keeps no traffic. Its access log drops addresses and - rendezvous handles (`relay/deploy/Caddyfile` in ftw-webapp). -- No analytics, ads, third-party code or tracking. - -One edge: when the relay fails to proxy a connection, Caddy's error log -may still hold the client address and request URI (rotated, 3 × 10 MB). -**Decide** before you publish the answer: - -- remove addresses from that log too, in ftw-webapp, so "Data Not - Collected" holds exactly; or -- declare **Other Diagnostic Data**: not linked to the user, not used for - tracking, purpose App Functionality. - -The privacy pages in srcfl/ftw-web#18 already mention the log. - -The privacy manifests from #13 match this answer. Both the app's and the -widget's declare no tracking, no tracking domains and no collected data. -Required-reason APIs: file timestamps (`C617.1`) in both, since the cache -sorts App Group files by date, and `UserDefaults` (`CA92.1`) in the app. - -Both answers change when FTW Cloud ships: it will hold readable data the -user turns on. +The old M1 answer, “Data Not Collected”, does not cover FTW Cloud. +Cloud holds a readable home copy, home and phone identifiers and push tokens. +The versioned statistics scope adds rounded energy and configured capacities. +Collection of those hourly reports still waits for the signed privacy review. + +Prepare **Apps → FTW → App Privacy** with this mapping: + +| Type | Use | Linked | Tracking | +|---|---|---|---| +| User ID | Home and statistics ids: App Functionality and Analytics | Yes | No | +| Device ID | Phone keys and push tokens: App Functionality | Yes | No | +| Other Data Types | Home readings, plans and configuration: App Functionality; project statistics: Analytics | Yes | No | + +Pseudonymous home ids still link records. No advertising or data broker receives +them. Local encrypted sessions and the on-phone demo do not add a server copy. +The app's privacy manifest declares the same categories. Extensions keep their +own declarations: reading an App Group cache alone is not collection. + +Before publishing, compare the actual shipped Cloud fields with this table, +check the Swedish and English privacy pages, and preview Apple's label. +**Publish** includes a legal attestation; the responsible person must approve +that final step. Record its date and a screenshot with the statistics DPIA. +A code change or upload does not update App Store Connect's answers. + +Sources checked 17 October 2026: [Apple's definitions](https://developer.apple.com/app-store/app-privacy-details/), +[App Privacy steps](https://developer.apple.com/help/app-store-connect/manage-app-information/manage-app-privacy/) +and [manifest fields](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacycollecteddatatypes). ### 5. Encryption and export diff --git a/appleApp/store/en-US/description.txt b/appleApp/store/en-US/description.txt index 74532556..3300aa11 100644 --- a/appleApp/store/en-US/description.txt +++ b/appleApp/store/en-US/description.txt @@ -22,8 +22,8 @@ PRICE WIDGETS PRIVACY • No account and no password. Scan the pairing code your box shows and confirm with a passkey. -• Your data stays on your box and your phone. The app reaches the box through Sourceful's relay, which carries encrypted traffic it cannot read. -• No ads, no analytics, no tracking. +• Your box keeps the record. FTW Cloud connects the app and can hold a readable state copy; Sourceful can access that copy. +• No ads or advertising tracking. Cloud terms include project statistics; hourly collection waits for privacy approval. Reports stay for 48 hours and public totals need at least 20 homes. Local use can contribute after a separate Yes/No question. WHAT YOU NEED • An FTW box at home with the FTW app turned on in its settings. FTW runs on a Raspberry Pi or another Linux computer. Learn more at ftw.energy. diff --git a/appleApp/store/sv-SE/description.txt b/appleApp/store/sv-SE/description.txt index 5e118609..7b5a9a16 100644 --- a/appleApp/store/sv-SE/description.txt +++ b/appleApp/store/sv-SE/description.txt @@ -22,8 +22,8 @@ PRISWIDGETAR INTEGRITET • Inget konto och inget lösenord. Skanna parkopplingskoden som boxen visar och bekräfta med en lösenordsnyckel. -• Din data stannar i boxen och i telefonen. Appen når boxen genom Sourcefuls relä, som bär krypterad trafik det inte kan läsa. -• Ingen reklam, ingen analys, ingen spårning. +• Boxen behåller historiken. FTW Cloud kopplar appen till boxen och kan hålla en läsbar kopia av läget; Sourceful kan läsa den kopian. +• Ingen reklam eller reklamspårning. Cloud-villkoren omfattar projektstatistik; timinsamlingen väntar på godkänd integritetsgranskning. Rapporter sparas i 48 timmar och publika summor kräver minst 20 hem. Lokal drift kan bidra efter en separat Ja/Nej-fråga. DET HÄR BEHÖVER DU • En FTW-box hemma med FTW-appen påslagen i boxens inställningar. FTW körs på en Raspberry Pi eller en annan Linuxdator. Läs mer på ftw.energy. From faf59e42b9f090d76a73313a6829c6f31ca07813 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 18:43:55 +0200 Subject: [PATCH 2/2] Align App Review notes with Cloud and its privacy declaration --- appleApp/RELEASING.md | 6 ++++-- appleApp/store/review-notes.txt | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/appleApp/RELEASING.md b/appleApp/RELEASING.md index 50ec932f..da47289b 100644 --- a/appleApp/RELEASING.md +++ b/appleApp/RELEASING.md @@ -436,7 +436,9 @@ declaration is free. - **M3, push:** an APNs key (**Certificates, Identifiers & Profiles → Keys → (+) → Apple Push Notification service**). Not before. -- **FTW Cloud:** update the privacy pages, App Privacy, `review-notes.txt` - and the description before it ships. +- **FTW Cloud:** keep privacy pages, App Privacy, the manifest, + `review-notes.txt` and the description in step when its data use changes. + The October 2026 declaration includes project statistics; it does not + enable hourly collection or replace the signed C8 review. - **April 2027:** apps on the iOS 27.1 SDK or later also need iPhone Duo screenshots. diff --git a/appleApp/store/review-notes.txt b/appleApp/store/review-notes.txt index 73fcbb4f..c75e454b 100644 --- a/appleApp/store/review-notes.txt +++ b/appleApp/store/review-notes.txt @@ -1,4 +1,4 @@ -FTW is the phone app for FTW, open-source home energy software that runs on a box in the user's home (a Raspberry Pi or another Linux computer). The app talks only to that box, through an end-to-end encrypted relay. There is no account and nothing to sign in to. You do not have a box, so the app has a built-in demo for you. +FTW is the phone app for FTW, open-source home energy software that runs on a box in the user's home (a Raspberry Pi or another Linux computer). The app carries its encrypted app-to-box session through FTW Cloud at cloud.ftw.energy. An optional readable copy in Cloud supports faster views and notifications; the privacy notice explains it. There is no account and nothing to sign in to. You do not have a box, so the app has a built-in demo for you. THE DEMO 1. Open the app. On the first screen, tap "Try the live demo". @@ -8,7 +8,7 @@ THE DEMO 5. "Exit demo" at the top returns to the first screen. WITH A REAL BOX (needs the hardware) -The owner opens the box's local dashboard, chooses Settings → FTW app → Show pairing code and scans the QR code with this app or the iPhone camera (links to https://app.ftw.energy/p). The app then creates a passkey for app.ftw.energy and connects through wss://relay.ftw.energy. +The owner opens the box's local dashboard, chooses Settings → FTW app → Show pairing code and scans the QR code with this app or the iPhone camera (links to https://app.ftw.energy/p). The app then creates a passkey for app.ftw.energy and connects through FTW Cloud at cloud.ftw.energy. The separate relay is retired. WIDGETS The price widgets show prices the app saved from a real box. The demo saves nothing, so in review a widget says "Open FTW to see prices". @@ -17,6 +17,8 @@ OTHER - Privacy policy: "Privacy" at the foot of Now and of the first screen, and in About. - No account to delete. "Remove this phone" at the foot of Now unpairs the phone. - Camera: used only to read the pairing QR code. -- No sign-in, no in-app purchases, no ads, no analytics, no tracking. +- No username/password sign-in, no in-app purchases, no ads and no cross-app tracking. +- Cloud holds pseudonymous home and device identifiers. App Privacy declares User ID, Device ID and Other Data as linked to the user. +- The planned project statistics are included in new Cloud terms; local-only users get a separate choice. Household reports have a 48-hour retention limit. Hourly collection remains disabled pending privacy review and policy activation. - Encryption: Noise protocol built on Apple's CryptoKit only. - The demo stands in for the hardware, not for a login: the app has no login.