Files
freno-dev/marketing/drafts/privacy-page.md
fixture fd91b0d3ef privacy: name the IP address and describe the TestFlight build as it ships (FRE-1480)
CTO verdict at FRE-1480: two sentences at db3e695 were false.

(b) sendDefaultPii = false does not stop Sentry receiving the connection
IP (sentry-cocoa 9.26.1 sets infer_ip = auto for Cocoa; only the Sentry
project's Prevent Storing of IP Addresses switch stops it) — the shipped
Settings rows already say so. The IP sentence is restored.

The iPhone paragraph described an opt-in build no tester has: the build
in TestFlight (ios-v0.3.2, 2026-10-22) predates FRE-355 and starts
Sentry on every launch with no off switch. The paragraph now says that.

Header comment and marketing/drafts/privacy-page.md rows corrected to
match (release range narrowed to 0.3.0-0.15.0; evidence rows record the
IP and the TestFlight provenance). privacy-page.patch regenerated.

Suite: merge tree of a0e6ff1 + this branch runs the same failure set as
clean a0e6ff1 (2 junit failures, the known Lineage-redirect
environmental pair); the host's env-validation aborts account for the
count noise. Zero new failures.
2026-11-06 13:26:24 +00:00

16 KiB
Raw Blame History

/privacy — replacement copy, FRE-354 corrections applied 2026-11-04

Status: applied on branch faq/from-objections-2026-10-29 (commit after bb6cf27), per the CTO's FRE-354 verdict — "apply the corrections above and the page is signed." The two copy decisions the verdict delegated to the CMO are taken: the fuller iPhone paragraph (its push-token evidence row is recorded in the evidence table below and routed to FRE-333's owner to append to the signed inventory), and the full (c) block with the LAN/tailnet sentence, which pulls the corresponding Section 3 clause in. Nothing here is published; the sponsor's merge + push on card 392a8465 is still the deploy step. Sequencing fact, updated 2026-11-06: the contingency has fired. v0.15.1 (tagged 2026-11-04, first tag containing FRE-355's merge 6e74b261) and v0.15.2 are live on the appcast (freno.me/api/TheNook/appcast.xml, newest item 0.15.2). Shipped behaviour when enabled: crash/error reports, session pings (enableAutoSessionTracking untouched), traces at 0.1, and Sentry also receives the connection's IP address (sendDefaultPii = false is the SDK default and does not suppress it; only the Sentry project's Prevent Storing of IP Addresses switch does — FRE-1480). Paragraph (b) and the iPhone paragraph are therefore swapped to the opt-in form in this commit, with two corrections against the pre-written swap text: pings and traces are named (the contingency's own completeness warning), and the IP sentence is kept. Releases 0.3.0 through 0.15.0 started Sentry on every launch with no off switch; the page describes the current release. Patch: marketing/drafts/privacy-page.patch — applies cleanly to c1c498e (git apply --check, verified 2026-10-29; parse-checked with esbuild, not the site suite). Evidence base: FRE-333 egress-inventory rev 1, eleven destinations, file:line per row, read at The-Nook 3c3ba7ee; and the same document's §G, which proposed a page text. This patch is the §G text corrected against the inventory — the differences are listed below, each with the row that forces it. Why this exists: FRE-326 + FRE-334 recorded option A — the privacy claim is withdrawn everywhere, and no privacy claim returns until the inventory exists, /privacy matches it, the CTO verifies both, and the sponsor approves the replacement copy. The inventory landed on 2026-10-29. This is the page half.

Field Value
Claim this page carries None. It is a disclosure page, not a claim: it states what the app sends and what it does not. The one favourable sentence is (j), which is the CTO's FRE-322 §2 code fact and which he may still decline.
Audience Someone deciding whether to pay $10 for a tool that reads their prompts. They read this page before the FAQ.
Channel nook.freno.me/privacy, one page
Owner CMO (copy + commit), CTO (verification), sponsor (approval of the replacement copy), Chief of Staff (publish switch)
Date Prepared 2026-10-29. Applied 2026-11-04 on the branch with the FRE-354 corrections; the contingency (b)/iPhone swap applied 2026-11-06. Last Updated on the page now reads November 6, 2026 — re-check it against the actual deploy date at push. Not a public date.
What has to be true before it ships 1) CTO signs the page against The-Nook HEAD, paragraph by paragraph — FRE-354, due 2026-11-02. 2) Sponsor accepts the replacement copy. 3) The Sentry paragraph matches the build being deployed — see Contingency. 4) It deploys in the same push as the FAQ withdrawal; a page that discloses Sentry next to a FAQ that still says "anonymous usage telemetry" is worse than either alone.

What changed against the live page

The live page (fetched 2026-10-29T09:2xZ and again 09:4xZ, HTTP 200, Last Updated: August 26, 2026, unchanged) says the app "runs locally on your device and sends only the minimal data required for trial and licensing" and lists four things. Against the inventory that is wrong in two directions: it omits Sentry (crash reports with IP address, every launch, no off switch), the relay and its install ID, the licence key crossing to the relay, sealed phone payloads, weather coordinates going to the relay and to Apple, the GeoIP third parties, the speed test, the changelog fetch, bug-report contents and update checks. It also calls the hardware UUID "masked", which the code contradicts — it sends the raw IOPlatformUUID.

The replacement is organised by connection, not by data type, because that is how a reader checks it: eleven rows in the inventory, ten of them reachable from the page, one (Sentry) prominent because it is the only one a reader can turn off entirely and still miss in older builds. On the current Mac release (v0.15.1+) it is opt-in and off by default; the iPhone build in TestFlight (ios-v0.3.2, 2026-10-22) predates it. See Contingency.

Differences from FRE-333 §G, and what forces each

§G is the Security Engineer's proposed text; this patch is that text corrected against §G's own inventory. Six deliberate differences, none of them stylistic:

# §G says This patch says Why
1 Opening: "processed on your device and are never sent to us" Opening drops "never sent to us"; (c) and (j) state the two paths agent data does take Sealed frames still transit our relay (§A.5, §B), and a remote machine carries them by design (§C). §G's own §B says the relay routes them. "Never sent to us" is the class of sentence that got the FAQ answer withdrawn.
2 (d) Weather: relay registration implied only with Phone (d) adds that Weather registers even with Phone off, and on a licensed Mac that registration presents the license key §A.3 + §D.2. SidecarController.weather() routes through the same keystore.relayCredential(licenseKey:) as the phone path (SidecarController.swift:269-279, SidecarRelayAPI.swift:298-312). §G under-disclosed it.
3 (a) trial/activate/status only (a) adds /deactivate (key + UUID) and the trial email (email + UUID, sent once) §A.2, /D.4. §G omitted both endpoints.
4 (i) "The email you give at trial start or checkout delivers your license key" (i) checkout delivers the key; a trial email is one reminder before the trial ends LicenseService.submitTrialEmail + OnboardingExtrasStep say the trial email is an expiry reminder; the key is issued at purchase. §G's sentence promised a key that the trial email does not deliver.
5 (b) Sentry: opt-in, "off unless you turn it on" (b) is the opt-in form, applied 2026-11-06: off unless enabled in Settings, and it names session pings and the trace sample; the IP sentence, kept The contingency fired with v0.15.1. Corrections against the pre-written swap text: pings/traces named because they still flow when enabled; IP kept because sendDefaultPii = false does not stop Sentry receiving the connection IP (FRE-1480); the Contingency text already said so.
6 No (j) (j) carries the CTO's FRE-322 §2 code fact, with the remote-machine exception Not required for truth; it is the one favourable sentence, and the CTO may decline it. Delete the block if he does.

Two facts the inventory corrected and the page does not repeat wrongly: Sparkle's automatic checks are off by default here (UpdateManager owns the schedule; FRE-322 §2.4 said "on by default" and is stale on this row), and the UUID is raw, not masked.

Paragraph-level evidence

Paragraph What it now says Evidence at 3c3ba7ee
(a) Licensing/trial raw UUID + device name; every launch while trialing; key-only once licensed; deactivate sends key + UUID; trial email sends email + UUID once; trial/status fail open, activation needs a connection once LicenseService.swift:300-313 (/trial), :271-284 (/status), :247-262 (/activate), :286-302 (/deactivate), :219-231 (/trial-email)
(b) Crash reporting opt-in, off by default; when enabled: crash/error reports, session pings, traces at 0.1; Sentry also receives the IP address (Options.swift:267-275 at 9.26.1: sendDefaultPii = false is the default and does not suppress it); no prompts or code Sources/NookApp/main.swift startIfEnabled — sendDefaultPii = false (policy.sendDefaultPii), tracesSampleRate = 0.1 while on (CrashReporting.onTracesSampleRate); CrashReporting.registerDefaults pins enabledKey: false; evidence read 2026-11-06, IP row per FRE-1480
(c) Phone relay off by default; relay is the default path, LAN/tailnet direct pairing available; install ID + license key at registration; sealed payloads; relay sees device IDs/names/platforms, timing and volume, not content SidecarRelayAPI.swift:144-153, SidecarPayload.swift:484-490, RemoteEndpoint.swift:14-24, docs/sidecar-frames.md (sealing), inventory §B
(d) Weather 0.1° grid snapped on the Mac; registers with the relay even with the phone off, licence key included on a licensed Mac; Apple sees place lookups and position WeatherForecast.swift:25-30, SidecarController.swift:269-279, SidecarRelayAPI.swift:298-312, WeatherLocation.swift:14-77, :89-143
(e) Network panel third-party IP lookup sees your IP; speed test uploads zeros only NetworkInfoService.swift:82,276, SpeedTest.swift:10-11,36
(f) Update checks ship off; turning them on fetches freno.me, app version + IP UpdateManager.swift:48-55 (automaticallyChecksForUpdates = false, default key false)
(g) Changelog fetches release notes from freno.me ChangelogRelease.swift:75-78
(h) Bug reports user-sent only; machine snapshot listed in full BugReportClient.swift:44-94, BugReportSnapshot.swift:17-36
(i) Email/payment checkout email delivers the key; trial email is a reminder; Stripe LicenseService.swift:219-231, OnboardingExtrasStep.swift:86-110
(j) What does not leave your Mac hook and bridge are not a cloud service; helper has no networking; two paths carry agent data across the network, both user-switched: remote machines over SSH or the tailnet, and the sealed phone relay hook/transport.go:143 (resolveBridge), BridgeBindPlan.swift:9-23, RemoteEndpoint.swift:14-24, inventory §B + §C
iPhone companion (TestFlight beta) the TestFlight build (ios-v0.3.2 = 2e322da5, 2026-10-22) predates FRE-355: unconditional start, sendDefaultPii = true, traces 1.0, no off switch (ios/AgentNookMobile/App.swift:13-20); the opt-in code exists at v0.15.2 but no ios-v* tag carries it; other traffic is the phone connection in (c) plus the APNs push token given to the relay RelayLink.pair RelayLink.swift:104-118, updatePushToken :226-232 — row added per FRE-354, TestFlight provenance per FRE-1480; append to the FRE-333 inventory

Contingency — the Sentry paragraph is the only one that moves

FIRED 2026-11-06 — applied on the branch. v0.15.1 is the first release containing FRE-355 and is live on the appcast. The swap below was applied with the corrections recorded in the sequencing paragraph at the top of this document (pings and traces named; IP sentence kept — the pre-written text below had it, and dropping it was wrong; FRE-1480). The original text is kept for the record:

(b) as committed describes today's build. The inventory's recommendation (§E.1) is an opt-in setting, default off, dropping sendDefaultPii and cutting tracesSampleRate, on Mac and iOS. If that ships, (b) is replaced in the same commit that changes the behaviour:

(b) Crash and error reporting — off unless you turn it on. If you enable crash reporting in Settings, The Nook sends crash and error reports to Sentry, our crash-reporting processor. Reports include the app version, macOS version, Mac model and what the app was doing when it failed, and Sentry also receives your IP address. They never include your prompts or code.

Sequence call, and who owns it. Shipping (b)-with-no-off-switch is honest, but it puts "no off switch" in front of a buyer during launch week. The better order is the build change first, then this page — that is a build/priority call, not mine: it is proposed on the board as FRE-355 (Senior Engineer, date proposed 2026-11-06, CTO to confirm), and if the CTO says the build change cannot land before the page, the page ships with today's text. Publishing the truthful-today version is never blocked by that choice; the preferred order is the other way.

This is why (b) must not be softened on the way out. If the opt-in setting ships but tracesSampleRate or session tracking stays on, (b)'s opt-in form is incomplete again — it names only "crash and error reports" while performance traces and session pings still flow. The paragraph and the build change move together or neither does. (FRE-354 rewrote this warning: the original named a macOS username that sentry-cocoa never sends.)

If the CTO declines paragraph (j)

(j) is the only favourable sentence on the page and the only one carrying a CTO verdict rather than an inventory row. It is FRE-322 §2 — "verified as a code fact, not publishable yet… publishing it before the disclosure work lands would put a true sentence next to false ones." The disclosure work is this page. If the CTO still declines it, delete the whole (j) block; nothing else on the page depends on it, and the page stays complete and true.

Open questions for the CTO's read

  1. The iOS app. §D.10: the iOS target starts the same Sentry DSN with the same sendDefaultPii (ios/AgentNookMobile/App.swift:12-20). This page says "runs on your Mac". Is any iOS build in users' hands? If yes, the page needs one sentence (or the build needs the same opt-in); if no, say so and the page stays Mac-only.
  2. Weather registration without a licence key. §E.3 suggests decoupling weather registration from the licence key. If that ships before the page, (d) loses its licence-key clause; if after, the page is true today.
  3. (j) — publish, or cut.

Apply checklist — what the apply commit must do, in order

  1. Re-read the live page and the inventory's HEAD; if either moved, re-derive (a)–(j) before applying. The inventory is pinned to 3c3ba7ee.
  2. Bump Last Updated to the deploy date — the patch carries 2026-10-29 as prepared, and that line is the one place the date must change at apply.
  3. If the Sentry opt-in build change is in the same release, swap (b) for the Contingency paragraph — one hunk.
  4. Apply privacy-page.patch and the FRE-326 faq-page.patch + positioning-privacy-withdrawal.patch in the same commit, so no page states the old claim while another corrects it.
  5. Run the site's suite — bun test — which this host cannot do (no bun; node_modules present, package.json test script is bun test). A parse/transpile check with esbuild is not that suite and must not be recorded as one.

Rules this page obeys

  • No competitor named (category nouns only, and none needed here).
  • No claim without a row in the inventory. Where the inventory has no row, the page says nothing.
  • No roadmap promise: the page never says a feature or fix is "coming", so the Sentry paragraph does not promise an off switch.
  • No price, no discount, no date, no launch language.
  • It is a correction of an existing page, not new marketing; nothing was added to sell anything.
  • It does not add a /faq privacy answer. That answer's return is gated separately (FRE-326 privacy-return-path).