Skip to content

feat(statistics): require Cloud terms and allow local contribution - #32

Merged
frahlg merged 6 commits into
mainfrom
feat/cloud-statistics-terms
Oct 17, 2026
Merged

frahlg merged 6 commits into
mainfrom
feat/cloud-statistics-terms

Conversation

@frahlg

@frahlg frahlg commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Statistics are part of the new FTW Cloud terms for every Cloud home. Local owners can instead contribute statistics without remote access. This follows Fredrik's 17 October product decision and pairs with srcfl/ftw#1700, srcfl/ftw-app#87 and srcfl/ftw-web#23.

Accept only current Cloud terms when collection is approved, including after restoring cached rosters at restart. A statistics-only roster has no phone keys, pairing codes, Noise key, WebPush, live cache or access scopes. Signed withdrawal removes unpublished rows, persists the withdrawal and sends the signed deletion receipt before Cloud closes access. Rejected rosters cannot create a statistics grant. Every connection must present an accepted current roster before sending reports, including when an earlier grant remains on disk. A local-only grant needs a positive start time. Ignore older roster revisions before changing a statistics grant.

Collection remains off by default and in production. Nothing here signs the C8 DPIA or establishes a lawful basis for requiring statistics within Cloud. The updated review draft records both flows, conditional-consent concerns, retention, public-cell risks, infrastructure checks and rights handling that still need evidence. Accepting terms alone does not settle those questions. Deploy compatible Core and clients and review existing-home migration before any policy activation.

Validation: vet, full race suite against the published Core protocol pin, full dev-tag suite and import-boundary checks pass. The initial runs exposed ordering assumptions in QUIC fixtures: a later stream frame could overtake the datagram the test expected first. Replace the 50/100 ms sleeps with observed heartbeat/live barriers and wait for the initial grant/table before the overtaking-datagram test. Both affected tests pass ten race-enabled repeats. Product code and deadlines are unchanged. Regressions cover signed local-only ingestion without phone access, metadata rejection, legacy-term rejection on restart, normal withdrawal and paused deletion. No raw household API was added. Non-test changes in uplink are about 30 lines; registry removes the readable-copy prerequisite and store clarifies why cached access can close.

@frahlg
frahlg marked this pull request as ready for review October 17, 2026 15:05
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T15:08:29.331458Z ff2eb5c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff2eb5ccea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/uplink/uplink.go Outdated
Comment on lines +923 to +926
if c.u.Statistics && !statisticsAccess(r) {
c.handle.DropCopy()
c.u.store.Forget(c.home)
return fmt.Errorf("%w: updated Cloud terms including statistics are required", errProtocol)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle withdrawals before enforcing statistics access

When statistics collection is enabled, every valid withdrawn roster fails statisticsAccess, so this branch returns before dropCopy and the later registry update run. As a result, DELETE /api/cloud clears in-memory readings but does not prune extension tokens, persist the withdrawal, or send the signed purged receipt; the box will keep retrying against the old stored grant. Process the withdrawn shape through the existing withdrawal path before rejecting rosters that lack the new terms.

Useful? React with 👍 / 👎.

Comment thread internal/registry/statistics.go Outdated
Comment on lines +40 to +41
on := g.Controller == cloudwire.ControllerSourceful && g.StatsPolicy == cloudwire.StatsPolicy &&
len(g.StatsID) == 16 && g.StatsSinceMs > 0 && slices.Contains(g.Purposes, protocol.CloudPurposeStats) &&
slices.Contains(g.Purposes, protocol.CloudPurposeServe)
len(g.StatsID) == 16 && g.StatsSinceMs > 0 && slices.Contains(g.Purposes, protocol.CloudPurposeStats)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not authorize statistics from a rejected roster

If a signed roster contains the current statistics purpose, ID, policy, and timestamp but lacks the required terms or includes metadata forbidden for a statistics-only roster, this predicate still creates a statistics grant before statisticsAccess rejects the roster. The box can then reconnect and send stats directly, because report ingestion checks this database grant rather than requiring an accepted roster. Only enable the grant after the full roster shape and terms have been accepted, while retaining a separate clearing path for withdrawals.

Useful? React with 👍 / 👎.

Comment thread internal/uplink/uplink.go Outdated
// statisticsOnly exposes the signed upload path and no remote recipient.
func statisticsOnly(r cloudwire.Roster) bool {
g := r.Grant
return len(g.ID) == cloudwire.GrantIDBytes && g.Controller == cloudwire.ControllerSourceful && len(g.Scopes) == 0 && len(g.Purposes) == 1 && g.Purposes[0] == protocol.CloudPurposeStats && g.StatsPolicy == cloudwire.StatsPolicy && g.TermsPolicy == cloudwire.StatsPolicy && len(g.StatsID) == 16 && len(r.Devices) == 0 && len(r.Pending) == 0 && len(r.Noise) == 0 && len(r.WebPush) == 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a start timestamp for statistics-only grants

A statistics-only roster with StatsSinceMs <= 0 passes this predicate and is stored as an accepted local contribution, but StatisticsGrant treats that same grant as disabled, so all subsequent reports are silently ignored. Include the positive timestamp requirement here so an accepted statistics-only roster cannot advertise an inert contribution.

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit e552b11 into main Oct 17, 2026
2 of 3 checks passed
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