Changes touching this path

  • the edge log gets a parser, and the field that separates a trustworthy line from a dangerous one turns out to be structural rather than a flag: our format writes two request tokens where combined writes three, so a three-token request IS the pre-1632 signature and the parser REFUSES it rather than trusting the file it came from. that matters because 1630 stripped the address from historical logs in place and deliberately left their paths alone to keep backfill open, so those files still name private repos and sealed file paths - a backfill that read them would ingest exactly what 1621 removed from the live format, and no amount of care at the call site would catch it because the line looks fine. a refusal is a RESULT and never a throw or a silent skip, because a shipper that swallowed bad lines would report a clean run over a log it half read and the count of refusals is the only thing that could say otherwise. two privacy reductions belong to the parser rather than to a caller who might forget: the referrer collapses to its HOST, since the full value is another site url carrying its own path and query - somebody elses search terms arriving here because they clicked a link - and the raw user agent, which the log keeps deliberately for bot filtering as a departure 1630 recorded, has no column in the row and must never acquire one. a schema cannot express an absence so the pin is the nearest thing, asserting no value of the row equals or contains the raw string, and the mutation that proves it is the realistic mistake rather than a contrived one: putting the user agent in the agent field, which is exactly what that field looks like it wants. the classifier is INJECTED and not imported, so 1625s shared module stays the only one whenever it lands rather than a second door grown here; the default is unknown and never human, because a positive-match rule never matches a person and the absence of a verdict means we found no reason to exclude this. our own traffic wins over any classifier verdict, since the edge marked it using the address without logging it, which is a fact the line carries where the user agent is a string the caller controls. and the timestamp is read at the offset the line itself declares rather than at the timezone the box happens to run, because reading the second where the first is available is how a backfill of older files silently shifts by hours (#1756) 5e1dd377 · dbf3dbe6…
  • the shared classifier lands, and the review caught me pinning the exact defect 1625 filed a hard constraint against: I wrote curl into the BOTS list and asserted it IS a bot, which would report ZERO INSTALLS while installs were happening - a curl of install.sh is, per 1621, the only observable event in the middle island of the funnel. the cause was a research failure rather than a coding one: I built the ticket from 1625 resolution and the map memory and never read the comment carrying its hard constraint, so my own ticket body omitted the carve-out too. the rule is therefore keyed on WHAT WAS REQUESTED and not on who requested it. a scripted client on install.sh, install.ps1 or dl is the product working and classifies unknown; the SAME agent on docs is a crawler and is filtered, which is the control that proves the carve-out is scoped rather than a blanket exemption. a real crawler on install.sh is still a crawler, because the carve-out covers scripted clients and not everything. the signature had to widen to take the path, which log-line now passes from the line it already holds - a classifier given only the user agent cannot express the constraint at all. the tests caught a second bug that was mine: powershell anchored with word boundaries never matches WindowsPowerShell, since there is no boundary inside it, and that failure direction is the dangerous one - a missed scripted client is counted as a bot, which is the funnel deletion itself. the match is a substring now and the looser direction is argued rather than assumed. agent becomes a family name, because isbotMatch returns Google for googlebot but the WHOLE user agent for bingbot and gptbot - storing that would put a raw UA in a column and make every per-family chart ungroupable. it is non-null exactly when class is bot, which is the privacy boundary: a crawler is not a natural person and which crawler is the analytic value, but the same derivation on an unmatched request keeps a piece of a real visitor UA under another column name. the supplement carries the path rules; its own-agents half is deliberately empty and says why, since the loot CLI sends no user agent at all and the install detector is excluded at the edge. standards review landed four real ones: Verdict collided with the CONTEXT glossary term for a reconciliation outcome and becomes Classification; the header claimed three consumers in the present tense while none are wired, which is this repo own recurring doc defect written by me again; the classifier shape was declared three times and now lives once in the leaf that both importers name; and isbot was called twice where isbotMatch answers both questions (#1775) 8b1746c6 · dbf3dbe6…diff
  • the download hop lands as a counted redirect that cannot be steered, and the two things 1647 asks for that do not exist yet are named rather than faked: GET /dl/tag/asset records a start and 302s to R2. the target is CONSTRUCTED from two validated segments and no byte of the request appears in it, because /dl sits on the hostname that also hosts the identity flow and the install one-liner - a redirect an attacker can steer is a phishing primitive wearing our provenance, a link that genuinely starts at our domain and ends wherever they chose. validating and then echoing is one refactor away from that; building from a fixed base plus two matched shapes is not. the segments are read from the RAW pathname rather than decoded route params, so this route decides what decoding means instead of inheriting it. every refusal is 404 rather than 400, because a 400 separates malformed from no-such-thing and telling a prober which guess was closer is free help. sha256.sum and the installer scripts are refused even though they are real files at that prefix: the hop counts download STARTS, and the land-time site gate fetches sha256.sum from R2 on every land - admitting it would make every land a download. ⭐ a mutation found one of my own guards doing nothing. deleting the explicit percent check turned NO test red, because the anchored patterns already reject percent - so the comment claiming it comes first because patterns are the fragile direction was describing a guard by what it looks like rather than by what it does. the code stays as a second fence and the comment now says it is one. unanchoring the tag pattern turns two tests red, which is the anchors earning their place, and the absolute-URL cases stay green under that mutation because the slash check is what catches them - a more precise picture than I had. the write-behind queue is built here because 1647 is blocked on it and it does not exist; 1757 shares it. enqueue is synchronous and never awaits, because this path serves the install one-liner where the caller is a shell script already piped to sh - a database stall there is the most expensive on the site and the least recoverable. bounded, drops the NEWEST so a burst cannot evict what preceded it, counts every drop because a silently discarded row makes the dashboard wrong in the direction that looks like not much traffic, and a failed flush puts the rows BACK rather than losing a batch to a blip. ⚠ NO visitor_key, and that is a deviation stated rather than a gap missed. 1647 asks for one derived from X-Real-IP as the beacon does - but the beacon owns the daily salt and neither exists, and deriving one here would make the privacy notice statement that these records carry no address, no cookie, no key FALSE the day it deploys. downloads-per-day needs no identifier; uniques do, and they arrive with 1757. ⚠ only ONE of the two internal arms is reachable. a request with no X-Real-IP never passed nginx so it is ours by definition, which covers the deploy probes. the edge computes loot_internal for our own hosts but forwards only Host, X-Real-IP, X-Forwarded-For and X-Forwarded-Proto, so install-detect - which runs the real one-liner through the public URL once per release - is counted as an ordinary download. a handful of rows per release rather than a distortion, and naming it beats a comment implying both arms work. EventRow source widens from log to the set migration 0001 actually permits. it said log while the shipper was the only producer, which read as a fact about the schema and was a fact about the caller. /dl joins the prerender deny list, or crawlLinks follows the new links off /install and the BUILD counts a download per platform per land (#1647) db35af90 · 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 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

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.