Changes touching this path
- the site gets the privacy notice it has been collecting without, and every sentence is written against what is ACTUALLY retained rather than against the pipeline design: 1624 says do not ship the collector before the notice is live and the ordering is the point, and the collector shipped first - this closes that, late, and says so rather than pretending the sequence held.
writing it to the design would have published a claim that we collect MORE than we do. the beacon, the download hop and the telemetry channel are unbuilt, and visitor_key, country, device, browser and os have no producer at all, so a notice describing a daily visitor salt or country-level geo would be false in the direction nobody checks. what is actually kept is the access log for 30 days - time, method, address, status, bytes, referring URL, full User-Agent, cache status, response time, and NO client address, written as a literal dash - plus rows derived from it with the referrer reduced to a host and the User-Agent reduced to a coarse category.
⚠ one sentence of my own first draft was false and the scrubber refuted it: I wrote that the log records a route shape rather than the address you asked for, which is true only for repository and private-repository URLs. ordinary page addresses are recorded in full, so the claim understated collection on nearly every request. corrected to name the exception as an exception, checked against scrubPath rather than against memory.
the word anonymous is deliberately not used of the log, per 1617 - the rows carry no identifier, but a full User-Agent kept for thirty days could in principle distinguish an unusual browser, and claiming anonymity is the wording that turns a data-protection question into a consumer-protection one on a product sold on privacy. the page explains why it avoids the word instead of using it.
the objection is real machinery over a currently empty set, and the page says exactly that. one cookie, set only on request, holding a constant rather than an identifier - the opposite of a cookie that counts you. it suppresses collection that happens in the APP, it cannot reach the nginx access log which is written before the app sees the request, and today the log is the only source. so it presently suppresses nothing, which the notice states plainly rather than implying a working control. every future app-side collector must consult hasOptedOut, which is why it lives in lib rather than inside one caller.
the retention number gets the 1779 treatment, because it is the one number here that is a published statement about personal data: 30 lives in site/src/lib/retention.ts, the bound is enforced by RETAIN_DAYS in the scripts repo, and setup-site now refuses to deploy when they disagree - a copy with no check is how the notice quietly outlives the thing it describes, and the dangerous direction is raising retention on the box while the page still says thirty.
terms ships beside it with two deliberate absences: no governing law or jurisdiction, and no liability cap - inventing either would be a legal claim written by nobody qualified to make it, and a wrong one is worse than none. what is there instead is factual and checkable, including that backups are OPERATOR disaster recovery and not a per-account restore service, which is ADR 0046 and the opposite of what a reader would assume.
contact is security@millerbyte.com, the address that exists and is monitored, rather than a privacy@ alias that would have to be created at the mail host first - publishing an address that does not resolve would be worse than reusing one that does. /trust gets a pointer rather than a second copy, since two copies drift and then one of them is false. the footer carries both links on every surface, which costs about 650 bytes per surface and is recorded in budget.json with that reason, because a notice reachable only by typing its URL is not published in any sense that matters (#1624)
12951042 · dbf3dbe6… - the privacy notice stops containing four false statements, all of them found by review BEFORE the page was ever published, and the worst of them was the flattering kind: the page said No IP addresses, anywhere on a site sold on privacy, and nginx error log carries client addresses on every failed request for the same thirty days. scripts/lib/nginx-log.js says so in terms - error.log carries its own client ip exposure and is NOT addressed - and the strip step skips it deliberately because an error line leads with a date and the access-log expression would blank the wrong field. so the notice now discloses it in its own paragraph rather than burying it, says it is never read for analytics and never reaches the database, and says we would like to remove it and have not.
the second was the exact failure the retention constant was built to prevent, committed in the same change: the page promised the summary rows are kept 30 days and that both bounds are enforced by time, and NOTHING deletes from event. migration 0001 says it outright - until 1759 lands rows land here and nothing is dropped - and that same comment forbids the fix I first reached for, since expiry there is a partition DROP gated on rolled_up_through and NEVER time-based, so a silently failed rollup must accumulate partitions instead of deleting a day nobody aggregated. adding a time-based prune would have contradicted a recorded decision to make a sentence true. the sentence changes instead: the logs are bounded and the rows are not, said plainly, with what they contain so a reader can judge it.
third, no cookie set at all unless you ask for one below was false - Clerk is mounted on /account and sets session cookies - and Clerk is also a third party the notice never named. there is now a section on signing in, and a recipients section naming Clerk, the hosting provider and R2. fourth, the log never accumulates a list of the repositories or the paths of files inside them was over-broad: only forge verbs and /private are scrubbed, so browsing a PUBLIC repository logs owner, repo and file path whole, deliberately, because those addresses are already public and are how we tell whether anyone reads what is published. the page now says that instead of denying it.
three Article 13 items were simply missing and are added: the right to complain to a supervisory authority naming the ICO, the recipients above, and a rights section. two remain outstanding because they need facts I do not have rather than words - the controller identity and the hosting provider by name - and the page says the true general thing until those land.
the standards half found something sharper than a style point: the opt-out component re-implemented the cookie parser with a startsWith, which read loot_no_analytics=12 as an objection where the module correctly refuses it, and hardcoded the value the module names - which meant hasOptedOut had ZERO production callers and ten tests covered a function nothing ran while the page used the untested copy. it calls hasOptedOut now. /trust had retyped the retention number as a literal, escaping both the test and the deploy cross-check, in the same change that built that check; it uses the constant, and the test now scans both pages rather than one (#1624)
810dc185 · dbf3dbe6…diff - the notice names who is responsible and who else touches the data, and the supervisory-authority sentence is rewritten because the operator is in the US: Article 13 wants a controller identity and one person is not one, so the page says Connor Miller, as an individual rather than a company, from the United States, and terms says the same. the recipients section stops gesturing - a rented virtual server named nobody, and a reader cannot assess a processor they cannot identify - so it is Hostinger for the server, Clerk for sign-in and Cloudflare R2 for published artifacts, with the analytics stated as processed entirely on that one server and sent nowhere.
the ICO sentence was wrong in the direction I had not considered. naming the Information Commissioner as the authority to complain to reads as a claim that this site answers to it, and a US operator with no UK or EU establishment has no lead authority at all - under the extraterritorial route a visitor complains to their OWN national authority. so the page says that instead: no establishment in the UK or the EU, complain to your own national data protection authority, with the ICO named as the UK example rather than as ours. the same sentence is now true whichever way the jurisdiction question is answered, which is the point, because that question is genuinely open and is not one this page should pretend to settle.
⚠ recorded rather than resolved: whether UK or EU data protection law reaches this site at all is a question for somebody qualified, and the honest position for a US solo operator is that the sharper exposure is deceptive-practices rather than GDPR - which is exactly what the review found four times before publication, and exactly why 1617 warned against the word anonymous. the page is written to be accurate under either reading.
three assertions ride along, because an identity item is the kind of thing that gets trimmed for length later: the controller is named, the three recipients are named, and the page does NOT claim the ICO as its own authority - that last one pinned on the two sentences that make the distinction rather than on the word ICO, which appears either way (#1624)
51b69499 · dbf3dbe6…diff - the site dated markers are pinned to the text they date: site/test/last-updated.test.ts scans every file under site/src, and every file a module there imports as ?raw, for a Last updated marker, and holds each to a sha256 of its page taken with the date slotted out, comments stripped by the TypeScript printer and whitespace collapsed, so a page whose text changes without its marker and pin moving is red at the gate while a comment or a reflow is not, a marker in a format the scan cannot read is refused rather than skipped, and the failure prints the entry to write with the local date of the day it runs. a pinned table was chosen over reading git at build time because a land builds the site in a lane, which is not a git checkout. both markers the scan finds were already true and are unchanged: terms.tsx last changed on 13 September local time (ad38475), so the premise that it was stale is corrected, and privacy.tsx on 20 September (928f213). that a deploy is not an edit is written in the test header and in a comment beside each marker; no notice text moved. red under mutation, counts read each time: a word of the terms text changed (1 failed and 7 passed), the privacy date moved without its pin (1 and 7), a marker written as 2026-09-13 (2 and 5), an unpinned marker on /trust (2 and 7), an unpinned marker in a ?raw evidence file (2 and 7), comment stripping dropped (3 and 5), the date slot dropped (3 and 5), the ?raw reach dropped (1 and 8), each restored to green. no published wording moved, so this owes no deploy. the site gate is green (678 passed and 62 skipped over 62 files, 62 surfaces) with no ceiling moved, and the workspace suite is green (4323 passed over 137 binaries, 12 ignored) (#2096)
94da98ff · dbf3dbe6…diff - every compiled-in and current-tense loot host moves to loot.build (map #2412, ADR 0099): loot --help names security@loot.build and https://loot.build/trust, ALPHA_PROMISE_URL is https://loot.build/install and the telemetry DEFAULT_ENDPOINT https://loot.build/api/telemetry, so the next release carries them; the site install one-liners, R2_BASE and HOP_BASE (https://dl.loot.build, https://loot.build/dl), the metrics SITE_SCOPE and own-host referrer, the disclosure links on trust, terms, privacy and known-issues, and the install, quickstart and guides pages follow, with the privacy and terms markers moved to 25 September 2026 and re-pinned since readers see the new address; README, CONTEXT.md (the forge door now forge.loot.build), the release checklist, site CI and test fixtures follow, and the two specs carry a note instead of a partial rewrite. the destination census gains build in WEB_TLDS, without which security@loot.build would be invisible to its bare-host clause, and exempts the two shipped lines that tld makes read as hosts (state.build, r.build()) by their exact text; ADR 0074 section 9 says twelve-entry to match. 37 lines still name millerbyte.com outside ADR bodies, evidence, research and scratch, each kept on purpose: dated history, millerbyte.com the site and its @millerbyte/ui tokens, the two noted specs, and relay-era tooling with no successor host, filed as #2438. census red with build dropped from WEB_TLDS, restored; site gate 819 passed and within budget; workspace suite 4559 passed over 142 binaries, 13 ignored (#2418)
c5663115 · dbf3dbe6…diff
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.