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 notice stops contradicting itself in two places, and both were created by the corrections that fixed the claims they contradict: the retention section was rewritten to say the database rows have no enforced time bound, and the erasure section three screens down still said everything ages out within thirty days - flatly the opposite, and the more flattering of the two. the objecting section said the opt-out is the one cookie this site will ever set, and the sign-in section added in the same pass says Clerk sets session cookies. each sentence was defensible alone; neither survived being read next to its neighbour. ⭐ found by loading the rendered page and reading it, not by any check. this is the same class as the four false statements the review caught, arriving by a different route: there, a sentence disagreed with the code; here, a sentence disagrees with another sentence about the same fact. a test can compare prose to prose, which is what the two new assertions do - the page must not claim the opt-out is the only cookie, and must not say everything ages out - each paired with the positive form so that deleting the sentence is not a way to pass. the wording that replaces them is narrower rather than softer. the opt-out is the only cookie set for anyone who is NOT SIGNED IN, which is true and is the distinction a reader actually needs, and erasure now says the logs age out within the window while the summary counts, which carry nothing about a person, stay as the retention section describes. no claim moved in the flattering direction (#1624) 16ba821b · dbf3dbe6…diff
  • the beacon counts PEOPLE where the log can only count requests, and the notice that describes it was rewritten in the same change because deploying one without the other publishes a falsehood loot#1757, over loot#1620 and loot#1617. POST /api/beacon takes one short message per view and derives SHA-256(daily_salt || ip || ua || site) truncated to 16 bytes, in process, discarding both inputs. It is canonical for uniques and sessions and for nothing else: the log stays canonical for volume, because it sees the ~53 prerendered pages, the cache hits and curl, none of which run any JavaScript. Two sources counting volume would be two numbers to reconcile. The key is length-prefixed per field rather than concatenated, and that is a real defect closed rather than a flourish. One of the fields is the User-Agent, which the CALLER chooses: with plain concatenation ip=1.2.3.4 ua=5 and ip=1.2.3.45 ua= hash identically, so a visitor could be merged onto a neighbour. The mutation that drops the framing reddens exactly that pin and nothing else. Two decisions are recorded rather than defaulted. NO SALT, NO ROW: a beacon row without a key adds to a count of beacon rows while contributing nothing to the only two things this source is canonical for, so it would trade what we came for against a number competing with the log. And the module is EAGER, which is the answer to the ticket first hazard - ADR 0071 weighs the document plus eager assets, so a deferred chunk is paid by every visitor and counted by nobody. Measured at ~450 B on each of 62 surfaces and RECORDED, and loot#1640 is the precedent that makes this more than taste: it measured a lazy route at 581 bytes against 270 eager. The edge half is new and had to be, because this is the site first unauthenticated row-writing endpoint and the site vhost had no rate limiting at all. limit_req at the /api/ PREFIX, not at /api/beacon, so ADR 0074 telemetry arrives protected rather than silently exposed. Idempotence is remove-then- reinsert between markers, and loot#1647 install-detect arm is closed in the same patch since it needed the same only-if-absent vhost step. Its ordering is load-bearing: the blanket header insert runs while only hand-written blocks exist, which makes a duplicate unrepresentable instead of guarded against. Found by RUNNING it, which is the whole argument for the fixture: the marker ended in (generated), which awk read as a GROUP, so the strip silently never fired - the second run appended a second /api/ block while grep -v tore the limit_req line out of the first, leaving a vhost that would have failed nginx -t on the box. Pinned now, with a control proving the predicate can say no. The notice had to move with the code. Five published statements would have gone false, the worst being that these records carry no key and nothing links two requests to the same person. Rewritten to lead with the key, say the secret is DESTROYED rather than merely rotated, and disclose that a visitor spanning midnight is counted twice. FOUR more contradictions were then found only by RENDERING the page, which is the third time that has been the only thing that worked. Objecting still said the log is the only thing we collect from, so this control has nothing to suppress - three paragraphs after the new text says the browser sends nothing when you object. Erasure and Your rights both still claimed nothing is keyed to you. And JSX drops a newline beside a tag, so the page shipped visitorkey and notstored as single words. The font gate then caught a star glyph absent from the shipped subset, which would have rendered as tofu. A test that asserted we do NOT collect a visitor key survived all of this green, because its regex matched one verb phrasing the new prose never uses. It is inverted now: the page MUST name the key, MUST say destroyed, MUST disclose the midnight double-count. Leaving it would have let a later edit delete the disclosure and stay green. 532 site tests, 322 scripts tests, budget green with the notice raise on the record. 3c22dcae · dbf3dbe6…diff
  • the private tier counts ACTIONS at the one seam every handler enters through, and the account reference it carries is the notice second identifier rather than a footnote loot#1758, the third of loot#1619 three collection points. Every api.private.* request enters through withPrivateSession, so the row is queued there on the way OUT with the status actually sent - one instrumentation point rather than one per route, which means a new endpoint is counted by construction and a test pins that every api.private.* file uses the seam. Deliberately narrow: the beacon owns views, the log owns volume, this owns actions. Counting anywhere wider would either double-count with the beacon or undercount behind the 60-second cache. The path is the ROUTE SHAPE, which on this tier is not a formality: owner, repo and file path all travel in the QUERY STRING, and routeShape drops the query before any rule runs. The pin asserts the repo name is absent from the serialised row, not merely that the path looks right. account_ref is a keyed SHA-256 digest of the Clerk id, 32 hex, stable ON PURPOSE - the opposite choice from the visitor key, because its entire job is to let an erasure request find every row of one account. A read of the metrics database yields nothing a third party issued; an erasure can still compute the value to delete by. A 401 row carries NULL rather than a reference to nobody, so the one-subject-tier CHECK reads it as no subject. The notice moved with it, in nine sentences. The page said in so many words that the records carry NO account reference and that there is ONE identifier; both are now false and both are now pinned in the other direction. What the page does NOT claim is as deliberate: loot#1622 says the reference lives with the account and is deleted on request, and only the second half is built - no account-deletion hook removes these rows - so the page says on request and says the automatic link is not built, and a test refuses the flattering version. Rendering found two more sentences that had become misleading rather than false: Erasure said deleting the account deletes it, which now read as covering the counted rows, and the retention section said keyed rows lose their meaning when the salt dies, which is true of visitor keys and the opposite of true for account codes. Both now say which rows they mean. 547 site tests pass; budget green with 263 B of slack left on /privacy, so the next notice edit will need a recorded raise. 3bb326ce · dbf3dbe6…diff
  • the retention number becomes an OUTCOME: a daily rollup writes the marginal totals that survive forever and only then drops raw partitions that are both rolled up and past the window loot#1759, decided by loot#1622 and amended by loot#1625. Until this, thirty days was a sentence: nothing dropped a row and the raw table grew without bound on the Postgres volume the relay, the forge and the site share. The drop is gated on the watermark, never on time alone, and that is the ticket whole thesis. event_drop_expired() in migration 0003 takes NO argument: it reads rolled_up_through from the table, uses its own UTC clock, and applies a retention CONSTANT - so the rollup role cannot widen the drop by lying to it, and with no watermark it drops nothing. A partition is due only when its day is at-or-below the watermark AND its whole day is outside the window, so a stalled rollup ACCUMULATES partitions, which is the observable failure mode we want, rather than deleting a day nobody aggregated. The ticket acceptance test is pinned as written: freeze the watermark and nothing is dropped however old the partitions are. The mutation that makes the rule time-based reddens exactly that pin and its BOTH-conditions sibling and leaves every time-only test green, which is the discrimination the ticket asked for. A day that fails to roll up STOPS the run with the watermark at the last success; skipping it would advance past a day with no aggregates, the silent loss the watermark exists to prevent. Every surviving table is MARGINAL and a test PARSES the CREATE TABLE statements to prove it: at most one dimension beyond (day, class), never two visitor-descriptive columns together, never one with path, and no table carries visitor_key, account_ref or telemetry_id - which is what makes an erasure complete once the raw rows go. class rides every aggregate and agent gets its own per-family table (loot#1625); rule_versions reaches every row so rule boundaries outlive the raw window. No country table, because nothing produces country. A THIRD role, metrics_rollup: SELECT on event, which the writer may never have; write on the rollup tables and the watermark, which the reader may never have; NO grant on daily_salt; and DDL only through two SECURITY DEFINER doors. The partition door moves a day rows out of event_default in one transaction with an explicit UTC bound - p_day::timestamptz would have taken the session time zone and filed one row under two days depending on who created the partition. The TypeScript twin of the drop rule PREDICTS what the database will drop and the summary reports a disagreement loudly with a non-zero exit, because two implementations of a retention rule parting silently is exactly how a retention promise stops being true while every timer reads healthy. The bundle was RUN, not just built: with no credential it starts, reaches main and fails naming the cause, so the cjs/pg hazard the shipper found is absent here. The notice moved with it, and a test that asserted the page must NOT claim a database bound is now inverted: it must claim it, from the shared constant, and must say the deletion is gated on the totals - a page saying only deleted after 30 days would describe the time-based expiry loot#1622 rejected. The account-reference sentences from loot#1758 shortened from as long as the account to at most 30 days, which is stricter and simpler. Rendered and read. One header claiming nothing installs a shipper timer yet, a week after one was installed, is corrected while the rollup was given the same shape. 587 site tests, budget green with the notice raise on the record. 50fe5557 · dbf3dbe6…diff
  • the opt-in telemetry channel is built, and off means no call because a paired test says so rather than a sentence loot#1658, building what ADR 0074 specified and loot#1626 shipped only the guard for. loot telemetry on | off | status | show; an append-only accumulator that writes one line per dispatched verb, the dispatch-table name, with burn and maroon collapsed to other AT WRITE TIME so no local trace of an incident exists either; a sender that runs in main after the verb has printed and decided its exit code, at most once per 24 hours, behind a 3-second hard timeout, that can never print, never change the code, and never delay a verb; a spool that IS the outbox, so show reads the same entry the sender transmits; and a 16-byte id minted on opt-in and deleted on opt-out, never machine-id. The guarantee is the pairing. One local listener, the real binary, a sandboxed config home: init, status, doctor and show with telemetry off make ZERO connections; on, the same verbs make EXACTLY ONE, and the same verbs again in the same period make no more; off again makes none. A lone assert-zero passes when the listener is broken, the binary crashed early, or it pointed at the wrong port - the exact shape of the logrotate -f check that could never fail. The endpoint override is https only with the loopback LITERAL the sole exception, enforced twice, and localhost is refused because what it resolves to is the resolver business; the refusal happens BEFORE an id is minted, so a broken override never leaves an identifier that can never be used. Clause 5 of the field rule is a property of the wire bytes, not a promise: a test parses every string out of the JSON and requires each to be a compile-time constant, a member of a fixed array, a dispatch-table name, or the hex id. The platform is a build-script constant rather than a runtime assembly from std consts, which is what the rule forbids spelled from constants. The verb emits machine output - show --json IS the report - so it joins the emitting tier rather than the prose-only debt list the ceiling forbids growing. Every count that moved was moved in the documents that state it, because the censuses read them: 129 verb lines in three sites, 82 dispatched and 80 in the table and 25 emitting in ADR 0066 with telemetry in the named list, all 82 verbs and the block in the README, the CLI page, and ADR 0074 eight-entry allowlist - because the endpoint is now the binary ONE destination it contacts rather than prints, and its ALLOWED row says under what condition. The destination census flagged three lines that dial nothing: a printed hint naming http://127.0.0.1 and the two scheme-prefix tests in check_endpoint. They are respelled rather than exempted - the check splits on the separator and compares the scheme WORD, same rule, no allowlist row - because a row is a sentence someone has to be able to defend and these had nothing to defend. The site half: POST /api/telemetry validates a report against the SAME closed sets the binary declares, pinned cross-repo by reading the Rust source, and refuses WHOLE on any field outside them. A valid report becomes source=cli rows, one per verb invocation under /cli/<verb> plus two bucket rows, all stamped with the arrival time so no ordering exists to reconstruct; the telemetry_id rides the row and expires with the 30-day partitions. It sits behind the same /api/ limit_req the beacon needed, which is why the ADR put it there. doctor prints on/off, the EFFECTIVE endpoint, last send and the spool. The notice gains the CLI tier, as ADR 0074 §11 requires: sends nothing unless you run on, the id links reports and off deletes it, what is never in a report, show prints it before you decide, and the erasure route - quote your id within 30 days, you hold the key. The test that asserted the page does NOT collect telemetry is inverted, as it was for the visitor key. 1214 lib and 118 bin tests, 51 suites, 634 site tests, budget and font gates green, rendered and read. 8a919b01 · dbf3dbe6…diff
  • the privacy notice stops saying a visitor address sits in the error log for the whole retention window, now that the box has reported otherwise: the site deploy of 2026-09-19 printed 18 error-log lines carrying a client field and none carrying an address, which is the non-vacuous outcome the ticket made a precondition, so the passages that name the error log are corrected rather than softened. the page still says the address IS written, because it is - the error log format is not configurable, so the address is removed after the fact rather than prevented - and it now says what removes it, the hourly loot-error-log-scrub timer in the scripts repo plus a strip and an outcome check on every deployment, and it keeps the unflattering half: an address written just after a run stays until the next one, so the window is up to about an hour rather than the retention window. the not-collected bullet stops asserting that the named exception is the whole of the set, and the retention paragraph keeps its claim for both logs while saying that what lasts that long in the error log is the line and not the address. the last-updated date moves to the day the wording changed. the pins move with it: the error-log pin now refuses the flattering draft review caught before publication, and three new ones pin the cadence, the window and the retention half. red under mutation, counts read each time: the page claiming no address is written (1 failed, 36 passed), the removal and window sentences deleted (2 failed, 35 passed), the period made vague (1 failed, 36 passed) and the retention clause deleted (1 failed, 36 passed). that third mutation first ran GREEN against a bare once-an-hour pin, because the summary database is parsed once an hour in another section, so the pin was tied to the sentence that does the removing before it was believed. no rust, no migration and no wire or format byte moves; the scrub this describes is already built and deployed, so what this owes is a site deploy to publish the wording. the site gate is green end to end (662 passed and 61 skipped over 60 files, 62 surfaces) and /privacy stays under its ceiling with nothing recorded (#1867) 6a1272b7 · dbf3dbe6…diff
  • a written docs page with no usable body publishes nothing and is refused at the gate, where under #2088 it published the planned template and stayed indexable: content/docs renders CONTENT[slug], so docs-content.test.ts builds its filed set from that resolved value instead of from Object.keys and the set the gate checks is the set the renderer uses, with a null or undefined value a missing body to both. routes/docs.$.tsx asks isPlanned once, for the body and for the noindex meta, so the two that drifted apart are read off one expression, and it asks for planned positively so a status added to DocStatus later renders no claim. the template was pinned nowhere and is pinned now over the built documents: every planned page must carry the sentence and its noindex, every written page neither. red first with the counts read each time - with the #2088 renderer back, the Object.keys keying back and concepts/docks filed as null, the three registry checks stayed green while the built document of a written page carried the sentence (2 failed and 6 passed, the second failure the eager-asset check losing the prose it reads); with the value keying restored the gate saw the null too (3 failed and 5 passed); with the renderer fixed and the null still filed the document went clean and the gate alone refused (2 failed and 6 passed). /privacy stops promising the deploy-time strip of any deployment at all, which was wider than the code - installEdgeLogging runs only from setupEdge, which setup-site.js calls when SETUP_EDGE is set - and instead names the deploy that reconfigures the web server and says the hourly job runs whether or not anything is deployed, the half that holds with no deploy; it is narrowed and not weakened, since the strip and the timer are built and on the box. the pin moves with the sentence, refusing the unqualified phrase over the whole source, comments included, and reading the narrowed claims off the component, because a sentence a reader never meets is not the published claim (1 failed and 37 passed with the wide claim restored, and again with the timer clause deleted). ADR 0066 says what its #1883 amendment amends: the expired reason was no longer above to expire, the same edit having replaced it, so the note says the paragraph lost a reason and gained one, names the surviving CARGO_PKG_VERSION half, and records the correction. tsconfig.test.json stops resting on a sentence that reads as a class and is one file - type-program.test.ts walks the import graph out of test, the program a test-only include would hold, and reddens when nothing it reaches under src carries the vite/client reference (1 failed and 1 passed with the directive deleted from components/private.tsx, the one carrier it reaches). not done here: the counted list in the #1883 closing record is a github comment, which this session does not write. the published wording moved, so this owes a site deploy. the workspace suite is green (3977 passed over 122 binaries, 7 ignored) and the site gate is green end to end (668 passed and 61 skipped over 61 files, 62 surfaces) with no ceiling moved (#2098) dd749359 · 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.