Changes touching this path
- the metrics dashboard ships as SIX STATIC PANELS behind an operator allowlist, and the two reviews moved it from a client-fetched page with a 401 to a server-rendered one that cannot be probed: 1623 settled Variant A, the Briefing - one column, one section per question, each caveat as PROSE beside the number it qualifies, which is the structural reason it won, since a caveat there cannot be skipped without skipping its number.
the access decision is that being signed in is not the same as being the operator. anyone can sign up to this host and the data is site-WIDE - every visitor path, referrer host and install-funnel count - so the door is a custody list in the 1045 shape: a Clerk id is on it or it is not. it fails CLOSED in every branch, and the proof is that the fail-OPEN spelling was written and run: allowed.length greater than zero AND not includes, then return true, reads as careful and admits the entire internet the moment the list is empty, which is the state a box is in before its first deploy writes one. exactly two tests went red on it.
the security review then found the route around that allowlist was the wrong shape twice over. the check sat INSIDE withPrivateSession, which maps an unauthenticated caller to 401 - so a stranger probing the endpoint got a 401 where an unserved path gets 404, and the refusal meant to hide the route confirmed it, while a load-bearing comment three lines up asserted the opposite. and the page itself answered 200 to anyone, rendering a document and only saying not found after a client fetch failed. both collapse into one server function returning null for BOTH refusals, which the loader turns into a real notFound: signed-out, signed-in-not-an-operator and a path we do not serve are now one answer.
that rework is also what the ticket asked for and I had not done - the panels render into the HTML from a route loader rather than from a browser round trip, so the acceptance criterion about server-side rendering stops being approximated. the second finding is sharper than it looks: the route declared no headers, and every private route sets private no-store BECAUSE an nginx proxy_cache sits in front. an operator document without it is cacheable at the edge, which is the edge handing one caller a page meant for another. it now carries PRIVATE_HEADERS, and that constant own comment is widened to say it covers two tiers rather than left reading as private-only.
one real data bug came out of the same review and no test I had written could see it: ordering a to_char MM-DD label sorts the series as TEXT, so any window crossing new year draws january before december - the sparkline scrambled about thirty days a year on the one chart whose job is a shape over time. grouped and ordered on the timestamp now, formatted on the way out, and pinned by asserting the grouped expression is not a to_char, verified by restoring the bug and watching it go red.
built against the PRODUCERS rather than the schema, which is what the prototype was really for: visitor_key is null on every log-sourced row and permanently will be since 1632 took the address out of the log, so there are no uniques and no sessions here at all, and device, browser, os and country have no producer either. the panels that wait on one are rendered, named and marked rather than omitted - uniques on 1757, forge usage on 1758, downloads on 1647 and telemetry on 1658 - because a dashboard that silently drops what it cannot see is how you talk yourself into a wrong conclusion.
not prerendered, and that is a security property rather than a build detail: a prerendered page is served as a static FILE with the static route headers, so an operator surface would exist as bytes anyone can fetch with the door never consulted - the same reason private joined that filter in 1043. asserted, and the mutation confirms the assertion bites. it is correspondingly absent from the byte budget, which discovers prerendered surfaces, and its styles stay in the route rather than styles.css because that sheet is eager on all sixty budgeted surfaces and an operator page has no business charging them (#1794)
2c969a0e · dbf3dbe6… - the dashboard stops sending a CSP written for a different shell, because that one directive rendered it BLANK for the operator while every gate stayed green: /metrics reused PRIVATE_HEADERS, whose reason applies here exactly - nginx proxy_cache sits in front with no proxy_cache_valid, so an operator document marked cacheable is one the edge can hand to somebody else - but the set also carries PRIVATE_CSP, and that forbids inline script. the private tier can afford that because its shell loads no inline and no foreign script, which is 930 decision 4 and the 753 route split. /metrics renders through the ORDINARY root, and TanStack Start ships its SSR hydration payload as an inline script tag. so the browser blocked it, React threw Invariant failed, and the page came up empty.
METRICS_HEADERS keeps the half that was doing the work and drops the half that was never written for this shell: private no-store, frame-ancestors none as a CSP carrying only that directive so it constrains framing and says nothing about scripts, X-Robots-Tag noindex as a header rather than only the meta the route also emits, and no-referrer. PRIVATE_HEADERS goes back to naming only the private tier - widening its comment to cover two tiers was the wrong repair in the other direction - and now carries the warning that it is not reusable by a normally-rendered route, with the reason, so the next person tempted by the same reuse is told why it looks right and is not.
⭐ the finding worth keeping is about the INSTRUMENTS rather than the header. nothing in this repo could see it: the build passed, tsc passed, 416 tests passed and the byte budget passed, because no gate here executes a document against a content security policy. it took loading the page in a browser. the new test cannot execute a policy either, so it pins the incompatibility instead - this route CSP must not mention script-src at all, it must not be equal to PRIVATE_HEADERS, and the control asserts it still carries the no-store it exists for, since not constraining scripts is also satisfied by sending nothing.
the guard earned itself immediately: it caught that I had changed the import and left headers colon PRIVATE_HEADERS at the call site, which typechecks and would have shipped the identical blank page a second time.
the diagnosis is verified rather than assumed - a normally-rendered page in the built output carries exactly one inline script tag beside one with src, the src one allowed by self and the inline one being what the policy refused. and the failure itself is evidence the rest of the route is right: the operator got a DOCUMENT rather than the 404 an outsider gets, so the allowlist, the env plumbing and the container recreate were all already working (#1794)
79ee0753 · 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 drill-down takes the dashboard door and pays for react-table on its own route alone, and ADR 0087 records the metrics pipeline as one document
loot#1804 and loot#1636 in one land, because a docs-only land pays the full
gate and the drill-down is the last code the ADR describes.
The drill-down is /metrics/paths and /metrics/referrers - a react-table over
the same rows each bar panel summarises, sortable, bounded at 200 with the
distinct count beside it. It is the only interactive escalation and it lives
here, not on the dashboard: react-table is imported by this route alone,
pinned by a test and MEASURED - the dashboard chunk moved by 80 bytes gzip for
its two footer links, the drill-down chunk is 13.1 KB gzip and paid only there,
and every other surface grew by ~220 bytes for the route-tree entry, which is
the manifest cost loot#1640 predicted. Excluded from prerender by the /metrics
prefix rule, METRICS_HEADERS for the dashboard reasons.
The door is the hard part and it is the SAME door. operatorReader now lives in
operator-door.ts and both the dashboard and the drill-down reach it through a
dynamic import inside a handler, returning null for every refusal so the loader
raises one notFound - a stranger, a non-operator and an unknown kind are
indistinguishable from each other and from a path we do not serve. The kind is
refused by the LOADER before the server function runs, and the validator
narrows rather than throws, because a thrown validator would surface as a 500,
a third distinguishable answer.
Two defects were found by the BUILD and the BUDGET with the typecheck green
both times, which is why both are pinned as tests rather than remembered. A
plain exported function that a route module imports statically is not
stripped from the client bundle the way a handler body is, so the first
refactor dragged @tanstack/react-start/server into the client and the build
refused it. And a VALUE import of two constants from the pg-backed reader put
pg into every client bundle - 62 surfaces up by 24.8 KB each - so the
constants a route may need now live in lib/metrics-drill.ts with no server
dependency in the graph, and read.ts imports them back.
ADR 0087 records the pipeline: three collection points and why no one of them
can see the surface, the loot_metrics database and its three roles, the
visitor key with the two rejected salt constructions, the route-shape rule and
its two enforcement points, the account-reference amendment to the map, the
retention and rollup boundary, the edge-log posture as the reason uniques
need a beacon at all, and the departures and residue - the kept User-Agent,
the eager beacon as the answer to the gate hazard, the error log, the
out-of-scope vhosts, the shared disk. It is 0087 and not the 0074 the ticket
named: telemetry took 0074 the night after this claimed it, and twelve more
landed while this waited on its build tickets. Four glossary entries ride the
same land - Visitor key, Session, Route-shape path, Marginal rollup - each
with the framing it rejects.
624 site tests, published_surfaces gate green, budget green.
984260b2 · dbf3dbe6…diff - the dashboard reads the three sources that landed after it, each from where its facts actually live, and no raw identifier is ever selected into a result
loot#1940, closing the one gap left on map #1615: /metrics was built against
the log, and the beacon, the private tier and the CLI each wrote rows nothing
read.
People come from the ROLLUP, not from keys. rollup_day is the table built to
survive, sessions are already derived there (same key, 30-minute gap, never an
id), and its class is the one row rather than the NOT IN spelling because the
rollup keeps bot and internal as their own rows. The series ends at the
watermark and the panel says so rather than drawing a zero for a day still
being counted. Docs by section comes from rollup_section_uniques - per section,
never per page, labelled visitor-days because a returning reader counts once
per day. Forge usage is the private tier by route shape, which is all its rows
hold. The CLI panel counts reports, distinct ids and ids seen on two or more
days inside the window, and states the bound ADR 0074 §11 sets: answerable for
30 days and never longer, because the rollup carries no id.
Every one of those is an Awaiting state until its source has produced, and a
CLI panel that has never received a report reads not measured rather than zero
installs - the null-not-zero rule the download panel already keeps. A rule
boundary inside the window is named across every headline (loot#1625).
The read test list of columns nothing writes SHRANK from seven to one, because
six gained a producer; the four that are written but unread stay refused for a
different stated reason. The pin that matters is new: telemetry_id may appear
COUNTED or GROUPED and never returned - every allowed form is stripped and
nothing named telemetry_id may survive, with a control proving the strip
removed something. The mutation that returns min(telemetry_id) as a column
reddens exactly that pin.
The header of read.ts stopped claiming the beacon is unbuilt and visitor_key
is null forever, which had been false for a day. WINDOW_DAYS reaches the route
from lib/, never as a value from the pg-backed reader (loot#1804). 639 site
tests, budget and font gates green, the dashboard chunk 9 to 12 KB with no
react-table in it.
6e8987f0 · dbf3dbe6…diff - the dashboard review fixes for 1940: every CLI query is under the default view, because the collector writes our own machines reports as internal and a dogfood run read as an opted-in install that keeps running, and returning now counts distinct UTC days like the rollup does; the docs-by-section panel reads only docs sections, since SECTION_SQL buckets every path and the panel titled docs was a site-wide top-8 with the root at its head; the rule-boundary marker is the union of the raw windows rule_version and the rollups own rule_versions array, which migration 0003 put on every rollup table for this read, so the two rollup-fed series carry their boundary too; the stale Forge product usage placeholder that #1949 left below the built panel is deleted and its one true sentence about edge-scrubbed forge paths moves into the built panels caveat. each of the three query fixes is pinned in metrics-read.test.ts and each pin went red under the defect it names. the hand-maintained counts (six panels, the three sources, seven columns and six of them, two stages have no producer) become the property they counted, the last hard-coded 30 days read WINDOW_DAYS, one exported dateOnly in rollup.ts replaces two byte-identical copies, one sinceDay spelling replaces the retyped rollup window and the test counts it, the Awaiting prop is why because it carries reasons rather than ticket numbers, and the dead alias in SparkVals is gone. tsc, the reader and rollup tests, the full suite, the build and the budget are green (#1940)
947fe6f3 · dbf3dbe6…diff - a count of people is rounded before the dashboard draws it, because the exemption that keeps this site banner-free has an output condition nothing implemented: 1617s research recorded that the reports an operator reads must hold anonymous statistics only, holding under whatever combination of filters the operator selects, with present to the nearest ten as the recommended measure. that only began to bite when 1940 put people on the page, since before it every panel counted requests. presentPeople is the one place it is applied: a true zero stays zero, 1 to 9 carries no number at all and the page draws under ten rather than a zero over real activity, and anything larger goes to the nearest ten. it is a union rather than a number beside a flag, so an under-ten count has no n to read and a caller that wants a bar height has to ask for a magnitude and cannot print a floor as if it were a measurement; the boundary test proves 1 and 9 are indistinguishable and that no 4 survives into the value, and a floor that carried its count reddens exactly that pin. window totals are summed exactly and rounded once, which a mutation summing the rounded days reddens. what rounds is decided by the unit rather than by a list of panels: a field typed People rounds and a plain number does not, so a panel added later inherits the rule from its type. the rule lives in lib/metrics-drill.ts, not in read.ts, because a value import from the reader puts pg into every client bundle, which is 1804s defect and I had just written it. ADR 0087 records the decision, that under ten deviates from the recommended measure by disclosing that somebody was there, and why that beat a false zero on the operators own page (#1951)
71be502c · dbf3dbe6…diff - the private repo surface becomes a workbench: /private/<owner>/<repo> is one layout route that mounts a VS Code-like shell once behind one gate (activity bar, side panel, preview and pinned tabs, status bar, Ctrl+P quick open) and keeps it across navigation, each child route naming its document with OpenDocument rather than rendering a page; a view is one module plus its routes, WorkbenchView in components/workbench/view.ts is the contract and VIEWS the one list the shell derives from, and the shell names no view. two views ship, Files (explorer over one new /api/private/manifest read, file editor with gutter, minimap, #L links and per-file decrypt, folder overview) and History (ledger panel, ledger, path and two-sided diff documents moved from the old pages), History built second against the contract and needing no shell edit, which is the test of the operator requirement that new views such as proposals and tickets be easy to add. /api/private/repo answers the header without a tree read, /api/private/tree and the page chrome it fed are deleted, every URL #1043 served still resolves. ADR 0094 and CONTEXT.md record it; 768 site tests pass, each new test red under a named mutation and restored; no migration, no format byte, no forge change, site deploy only (#2327)
7462b904 · 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.