From 703e3f47c19903746bdf114e4c5350d9a1653b15 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 18:21:15 +0200 Subject: [PATCH 1/2] fix(statistics): limit household reports to 48 hours --- .changeset/statistics-48-hours.md | 5 +++ docs/adr/0008-ftw-cloud.md | 10 ++--- docs/protocol-cloud.md | 6 +-- go/internal/api/cloud_statistics.go | 2 +- go/protocol/cloudwire/stats.go | 7 +++- go/protocol/cloudwire/stats_test.go | 2 +- .../testdata/cloud-quic-vectors.json | 8 ++-- .../cloudwire/testdata/cloud-vectors.json | 40 +++++++++---------- web/project-statistics.js | 2 +- 9 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 .changeset/statistics-48-hours.md diff --git a/.changeset/statistics-48-hours.md b/.changeset/statistics-48-hours.md new file mode 100644 index 000000000..5f6a68fe0 --- /dev/null +++ b/.changeset/statistics-48-hours.md @@ -0,0 +1,5 @@ +--- +"ftw": patch +--- + +Limit project statistics to 48 hours under new Cloud and local policies. Earlier choices do not accept the new policy. Collection still waits for the signed privacy review. diff --git a/docs/adr/0008-ftw-cloud.md b/docs/adr/0008-ftw-cloud.md index 81ecdb1dc..8ad971d96 100644 --- a/docs/adr/0008-ftw-cloud.md +++ b/docs/adr/0008-ftw-cloud.md @@ -178,7 +178,7 @@ that question and the DPIA have been reviewed and signed. prices, free text, configuration documents or logs. - **`statsId`.** 128 random bits drawn by the box at consent, derived from nothing else. A new consent draws a new one. -- **Retention.** Rows go 13 months after the hour they describe. Leaving Cloud, stopping local sharing or +- **Retention.** Rows go 48 hours after the hour they describe. Leaving Cloud, stopping local sharing or a purge deletes unpublished rows when Cloud receives the signed request. An offline box sends it on reconnection. Published aggregates stay, and the consent screen says so. @@ -218,7 +218,7 @@ The privacy notice says so. | Access logs | 14 days, with a keyed hash instead of the address | | SSM sessions and deploy log | 24 months | | Backups | None; boxes refill the service | -| Statistics | 13 months | +| Statistics | 48 hours | ## Operations and cost @@ -311,8 +311,8 @@ rules, the app asks again. A public launch needs C2–C4 and the legal work. ### Project statistics policy, October 2026 -`ftw-project-stats-2026-10-v2` covers configured capacities, flexible power, -solar rating and closed-hour energy. `ftw-cloud-2026-10-v2` includes that scope +`ftw-project-stats-2026-10-v3` covers configured capacities, flexible power, +solar rating and closed-hour energy. `ftw-cloud-2026-10-v3` includes that scope for every Cloud home. An owner reads `GET /api/cloud/stats-policy`, then accepts the Cloud terms through `POST /api/cloud/grant`. Core records the scope, random stats id and start time in the same grant. An older Cloud @@ -336,7 +336,7 @@ The public view counts installations reporting in the last 24 closed hours. It sums the recorded hours, which may be incomplete; it does not claim all FTW users, full-day production or measured control capacity. Configured limits remain distinct from measured command results. Unknown values stay -absent. Cloud keeps the consented hourly rows for 13 months and publishes +absent. Cloud keeps the consented hourly rows for 48 hours and publishes only the existing group thresholds. Withdrawal deletes unpublished rows when Cloud receives the signed roster; offline withdrawal takes effect on reconnect. Published group totals remain. Native consent UI must adopt the diff --git a/docs/protocol-cloud.md b/docs/protocol-cloud.md index 50aa500f1..c931465d7 100644 --- a/docs/protocol-cloud.md +++ b/docs/protocol-cloud.md @@ -771,7 +771,7 @@ limits, with shared inverter groups counted once; it does not prove physical control. Energy is recorded ledger energy and may cover only part of an hour. No names, hardware ids, location, prices or live readings leave this path. -The service upserts by home and hour, keeps these consented rows for 13 months, +The service upserts by home and hour, keeps these consented rows for 48 hours, and deletes them when it receives statistics withdrawal or a home purge. Only cells with at least 20 homes and no home above 30 percent of the sum are public. The public view counts reporting installations, never people. @@ -1625,14 +1625,14 @@ constants, so `srcfl/ftw-cloud` need not spell them by hand. | Sessions closed | When the roster runs out | | Tokens and APNs dropped after | 30 days | | Home deleted after | 90 days without a roster; 24 hours for a new home without one | -| Statistics rows kept | 13 months from the hour they describe | +| Statistics rows kept | 48 hours from the hour they describe | | Rule N clock lead allowed | 2 s | | Rule U drift margin, lifetime | 0.01 % of the time since learned; 24 hours | ### Cloud terms and local statistics, policy version 2 -`ftw-cloud-2026-10-v2` includes `ftw-project-stats-2026-10-v2` for every Cloud +`ftw-cloud-2026-10-v3` includes `ftw-project-stats-2026-10-v3` for every Cloud home. `Grant.termsPolicy` carries the owner-accepted terms. Once the operator approves collection, Cloud admits only that bundled grant or a statistics-only roster. Old terms are not silently upgraded; they need an owner answer. diff --git a/go/internal/api/cloud_statistics.go b/go/internal/api/cloud_statistics.go index 97712bad2..5f47fea75 100644 --- a/go/internal/api/cloud_statistics.go +++ b/go/internal/api/cloud_statistics.go @@ -137,7 +137,7 @@ func (s *Server) handleCloudStatsPolicy(w http.ResponseWriter, r *http.Request) "fields": cloudwire.StatsMetricKeys, "meaning": "One reporting installation, configured flexible power and solar ratings, battery capacity, and recorded closed-hour energy. Configured limits do not prove physical control. Missing readings are unknown.", "resolution": "Closed UTC hours, energy rounded to 10 Wh, capacities and limits rounded to 10 Wh or W. No names, addresses, device ids or location.", - "retention": "13 months. Leaving Cloud or stopping local sharing deletes unpublished household rows; published group totals remain.", + "retention": "48 hours. Leaving Cloud or stopping local sharing deletes unpublished household rows; published group totals remain.", "publication_minimum_homes": cloudwire.StatsMinimumHomes, "publication_max_home_share": 0.3, "controller": "sourceful", "local_default": "ask_yes", "local_optional": true, "cloud_required": true, diff --git a/go/protocol/cloudwire/stats.go b/go/protocol/cloudwire/stats.go index 03daebf56..96001ca4a 100644 --- a/go/protocol/cloudwire/stats.go +++ b/go/protocol/cloudwire/stats.go @@ -5,10 +5,13 @@ import "slices" // StatsPolicy names the scope included in the current Cloud terms, or // accepted through the separate local statistics question. Earlier choices // never accept this policy. Collection still requires service approval. -const StatsPolicy = "ftw-project-stats-2026-10-v2" -const CloudTermsPolicy = "ftw-cloud-2026-10-v2" +const StatsPolicy = "ftw-project-stats-2026-10-v3" +const CloudTermsPolicy = "ftw-cloud-2026-10-v3" const MsgStats = "stats" const StatsHourMS = int64(3_600_000) + +// StatsRetentionMS is the maximum age of a stored household hour. +const StatsRetentionMS = 48 * StatsHourMS const StatsMinimumHomes = 20 var StatsMetricKeys = []string{ diff --git a/go/protocol/cloudwire/stats_test.go b/go/protocol/cloudwire/stats_test.go index a8bc53b39..dfe4e2e7b 100644 --- a/go/protocol/cloudwire/stats_test.go +++ b/go/protocol/cloudwire/stats_test.go @@ -14,7 +14,7 @@ func TestStatisticsOnlyClosedAllowlistedQuantizedHours(t *testing.T) { if !valid().Valid(now) { t.Fatal("valid closed hour rejected") } - cases := map[string]func(*Stats){"open hour": func(s *Stats) { s.HourMS += StatsHourMS }, "before window": func(s *Stats) { s.HourMS -= 24 * StatsHourMS }, "fine time": func(s *Stats) { s.HourMS++ }, "new field": func(s *Stats) { s.Metrics["address"] = 0 }, "fine energy": func(s *Stats) { s.Metrics["pv_generation_wh"] = 121 }, "too large": func(s *Stats) { s.Metrics["pv_generation_wh"] = 1000010 }, "old policy": func(s *Stats) { s.Policy = "old" }, "no id": func(s *Stats) { s.StatsID = nil }} + cases := map[string]func(*Stats){"open hour": func(s *Stats) { s.HourMS += StatsHourMS }, "before window": func(s *Stats) { s.HourMS -= 24 * StatsHourMS }, "fine time": func(s *Stats) { s.HourMS++ }, "new field": func(s *Stats) { s.Metrics["address"] = 0 }, "fine energy": func(s *Stats) { s.Metrics["pv_generation_wh"] = 121 }, "too large": func(s *Stats) { s.Metrics["pv_generation_wh"] = 1000010 }, "previous policy": func(s *Stats) { s.Policy = "ftw-project-stats-2026-10-v2" }, "no id": func(s *Stats) { s.StatsID = nil }} for name, change := range cases { t.Run(name, func(t *testing.T) { s := valid() diff --git a/go/protocol/cloudwire/testdata/cloud-quic-vectors.json b/go/protocol/cloudwire/testdata/cloud-quic-vectors.json index 6ede8d105..f9b4c3529 100644 --- a/go/protocol/cloudwire/testdata/cloud-quic-vectors.json +++ b/go/protocol/cloudwire/testdata/cloud-quic-vectors.json @@ -250,22 +250,22 @@ }, { "name": "statistics service ready", - "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763261746868656c6c6f5f6f6b637369675840a838159a22c63e291c08d701b0ce0f7c0fd155c45fdcfa9b50e1253bf2fa312917ecd76379072c9bbaf3d62d1bbf95b93879177765f4b0adb73dd7ef0e2dd91e", + "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", "datagram": false }, { "name": "statistics closed hour", - "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a0001443861746573746174736373696758406147f18ed9f5927d3abf1599168322a021194369ad42d8b199abbd894040cc7e87c4fcff89e551c210863d2ed4f2bdeaddd6304f5fdf00188a5232b3246c23d6", + "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", "datagram": false }, { "name": "statistics only roster", - "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f7374657263736967584018851c1cb544043d4e18b2609e5b2a3f6c321bd1ba04a136b2af103992876c90bc33c8d584840917a2d7a1fb0427b22df6deae7fad456c664e8e33f95654b4f7", + "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", "datagram": false }, { "name": "roster statistics consent", - "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f73746572637369675840a043311a41972afcec17e4aa0eae3251e985b503677b2802e5b520cd73acd472050cfe6f660ae85058d0dea532ff832759929c048f3738e4588794447552ded7", + "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", "datagram": false } ], diff --git a/go/protocol/cloudwire/testdata/cloud-vectors.json b/go/protocol/cloudwire/testdata/cloud-vectors.json index e74fdcae9..ffed95c17 100644 --- a/go/protocol/cloudwire/testdata/cloud-vectors.json +++ b/go/protocol/cloudwire/testdata/cloud-vectors.json @@ -326,41 +326,41 @@ "name": "statistics service ready", "signer": "service", "t": "hello_ok", - "body": "a563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d7632", - "signedInputSha256": "03f2f4413566430718560c06ef743aba96dd6e268f4df9e5fcd6105505729284", - "sig": "a838159a22c63e291c08d701b0ce0f7c0fd155c45fdcfa9b50e1253bf2fa312917ecd76379072c9bbaf3d62d1bbf95b93879177765f4b0adb73dd7ef0e2dd91e", - "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763261746868656c6c6f5f6f6b637369675840a838159a22c63e291c08d701b0ce0f7c0fd155c45fdcfa9b50e1253bf2fa312917ecd76379072c9bbaf3d62d1bbf95b93879177765f4b0adb73dd7ef0e2dd91e", - "frame": "0101000000e3a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763261746868656c6c6f5f6f6b637369675840a838159a22c63e291c08d701b0ce0f7c0fd155c45fdcfa9b50e1253bf2fa312917ecd76379072c9bbaf3d62d1bbf95b93879177765f4b0adb73dd7ef0e2dd91e" + "body": "a563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d7633", + "signedInputSha256": "9c9a2a7add0d201b5f546d1749392bd1dc05c5f32c9dbec6d56ac9dcaacf0d0d", + "sig": "790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", + "envelope": "a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406", + "frame": "0101000000e3a36162588da563656b6d582022222222222222222222222222222222222222222222222222222222222222226468617665a068626f784e6f6e63655820333333333333333333333333333333333333333333333333333333333333333369726f73746572536571046b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d763361746868656c6c6f5f6f6b637369675840790390a99204558bb3d80cbbc6a436d40548f3d3e865cb4b71329c2f09b2d3abb5f1814526280ba8aac35113d21ae1dd715950bf2ccf7a66b30484cfc0d1b406" }, { "name": "statistics closed hour", "signer": "box", "t": "stats", - "body": "a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a00014438", - "signedInputSha256": "401bd24650ca3cb49fa76096e3b40d310c6e31d3952bf312e005b88937f81643", - "sig": "6147f18ed9f5927d3abf1599168322a021194369ad42d8b199abbd894040cc7e87c4fcff89e551c210863d2ed4f2bdeaddd6304f5fdf00188a5232b3246c23d6", - "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a0001443861746573746174736373696758406147f18ed9f5927d3abf1599168322a021194369ad42d8b199abbd894040cc7e87c4fcff89e551c210863d2ed4f2bdeaddd6304f5fdf00188a5232b3246c23d6", - "frame": "01010000010ba3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a0001443861746573746174736373696758406147f18ed9f5927d3abf1599168322a021194369ad42d8b199abbd894040cc7e87c4fcff89e551c210863d2ed4f2bdeaddd6304f5fdf00188a5232b3246c23d6" + "body": "a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a00014438", + "signedInputSha256": "1d061b9054c924b61661b464c2fc91a0f3b08315ab61e638ccb21c7f1a9fdb03", + "sig": "134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", + "envelope": "a3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f", + "frame": "01010000010ba3616258b8a8637365711464626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b066686f75724d731b000001a130a0b18066706f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336677616c6c4d731b000001a130d7f9d8676d657472696373a270666c657869626c655f706f7765725f77192af87070765f67656e65726174696f6e5f776818786773746174734964508888888888888888888888888888888868757074696d654d731a000144386174657374617473637369675840134ad340a1190a23d332c1cd39aabafb2a8e88a6393870feb391a7178b39302ff8a38ec7fc1ba9e4a658b8d8d470176f2b979df63dbc258f31450268eb33721f" }, { "name": "statistics only roster", "signer": "box", "t": "roster", - "body": "aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107", - "signedInputSha256": "35b78f290505d453192680ada46310c9f2119b4f7dcdb3ca7d65d540fcc77075", - "sig": "18851c1cb544043d4e18b2609e5b2a3f6c321bd1ba04a136b2af103992876c90bc33c8d584840917a2d7a1fb0427b22df6deae7fad456c664e8e33f95654b4f7", - "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f7374657263736967584018851c1cb544043d4e18b2609e5b2a3f6c321bd1ba04a136b2af103992876c90bc33c8d584840917a2d7a1fb0427b22df6deae7fad456c664e8e33f95654b4f7", - "frame": "010100000188a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f7374657263736967584018851c1cb544043d4e18b2609e5b2a3f6c321bd1ba04a136b2af103992876c90bc33c8d584840917a2d7a1fb0427b22df6deae7fad456c664e8e33f95654b4f7" + "body": "aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107", + "signedInputSha256": "3b9ae29cec865ff58208b830dd4300c8966048bb4dc5ab2c64beb40a201c881c", + "sig": "002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", + "envelope": "a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b", + "frame": "010100000188a36162590133aa637365711764626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573816573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d805906764657669636573806770656e64696e678068757074696d654d731a00014ff069726f7374657253657107617466726f73746572637369675840002b962e9bdd240655dffcc799cacb23414e2f7fd7398209bb9d78b18020a32457bbe8efee41cd35d02ce021940309891e4b0aeacaa6bdcadacd6397b1c4006b" }, { "name": "roster statistics consent", "signer": "box", "t": "roster", - "body": "aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106", - "signedInputSha256": "af8b572e3c64efa52a4c52514e9e1d31c3906f19376ab5ddc9c8c61c6c5c0312", - "sig": "a043311a41972afcec17e4aa0eae3251e985b503677b2802e5b520cd73acd472050cfe6f660ae85058d0dea532ff832759929c048f3738e4588794447552ded7", - "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f73746572637369675840a043311a41972afcec17e4aa0eae3251e985b503677b2802e5b520cd73acd472050cfe6f660ae85058d0dea532ff832759929c048f3738e4588794447552ded7", - "frame": "010100000185a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76326b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76326c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f73746572637369675840a043311a41972afcec17e4aa0eae3251e985b503677b2802e5b520cd73acd472050cfe6f660ae85058d0dea532ff832759929c048f3738e4588794447552ded7" + "body": "aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106", + "signedInputSha256": "7bb5ee8429e29ae18fbede964510cb3b0527acfed65864007fe9a168ec5b0232", + "sig": "59638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", + "envelope": "a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9", + "frame": "010100000185a36162590130aa637365711564626f6f7450b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b06474746c531a00015180656772616e74a862696450444444444444444444444444444444446673636f706573806773746174734964508888888888888888888888888888888868707572706f736573826573657276656573746174736a636f6e74726f6c6c657269736f7572636566756c6b7374617473506f6c696379781c6674772d70726f6a6563742d73746174732d323032362d31302d76336b7465726d73506f6c696379746674772d636c6f75642d323032362d31302d76336c737461747353696e63654d731b000001a13069c300656e6f697365406677616c6c4d731b000001a130d7fdc06764657669636573806770656e64696e678068757074696d654d731a0001482069726f7374657253657106617466726f7374657263736967584059638b7a4468f8a7ae3210d8b83bf5f03adbdfec68c46bf4a8f56e0c7b99918927d12e9c6eea548e3f3d3f1bfd615371cd5d7bbb00c27278ef08a3abdca482d9" } ] } diff --git a/web/project-statistics.js b/web/project-statistics.js index 04f19f88f..2cf5d3cde 100644 --- a/web/project-statistics.js +++ b/web/project-statistics.js @@ -3,7 +3,7 @@ "use strict"; var read = window.fetch.bind(window); var details = "Recorded hourly energy, configured battery capacity, battery and charger power limits, and solar capacity help improve FTW and publish group totals. Values are rounded to 10 Wh or W. No names, addresses or device ids."; - var privacy = "Sourceful links reports to this box 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 sharing off deletes unpublished reports when the box reaches the collector. Published totals remain."; + var privacy = "Sourceful links reports to this box 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 sharing off deletes unpublished reports when the box reaches the collector. Published totals remain."; var status, policy, justAnswered=false; function node(tag, text, parent) { var el = document.createElement(tag); From 39c474089f6552ea2c07a28d14130029b82e1df5 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 18:29:10 +0200 Subject: [PATCH 2/2] docs(statistics): align v3 references and release class --- .changeset/statistics-48-hours.md | 2 +- docs/protocol-cloud.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/statistics-48-hours.md b/.changeset/statistics-48-hours.md index 5f6a68fe0..cd7487ef0 100644 --- a/.changeset/statistics-48-hours.md +++ b/.changeset/statistics-48-hours.md @@ -1,5 +1,5 @@ --- -"ftw": patch +"ftw": minor --- Limit project statistics to 48 hours under new Cloud and local policies. Earlier choices do not accept the new policy. Collection still waits for the signed privacy review. diff --git a/docs/protocol-cloud.md b/docs/protocol-cloud.md index c931465d7..f1a8074a5 100644 --- a/docs/protocol-cloud.md +++ b/docs/protocol-cloud.md @@ -754,7 +754,7 @@ the service find it offline; it pairs over the relay or the home network. Signed box to service only. Never forwarded to phones or agents. The box sends at most one report per closed UTC hour, after fresh consent to -`ftw-project-stats-2026-10-v1`. Older policies send nothing. +`ftw-project-stats-2026-10-v3`. Older policies send nothing. | Field | Type | Meaning | |---|---|---| @@ -1630,7 +1630,7 @@ constants, so `srcfl/ftw-cloud` need not spell them by hand. | Rule U drift margin, lifetime | 0.01 % of the time since learned; 24 hours | -### Cloud terms and local statistics, policy version 2 +### Cloud terms and local statistics, policy version 3 `ftw-cloud-2026-10-v3` includes `ftw-project-stats-2026-10-v3` for every Cloud home. `Grant.termsPolicy` carries the owner-accepted terms. Once the operator