Changes touching this path

  • Scaffold site/ + wire Vercel #2, loot. subdomain, install proxy (#257) Thin end-to-end tracer for loot.millerbyte.com (spec docs/specs/loot-site.md SS1-3): site/ = TanStack Start SSG consuming published @millerbyte/ui@^0.1.0 (theme.css at the root), five placeholder surface routes prerendered, deployed on Vercel project #2 (root site/) at https://loot.millerbyte.com. Install proxy: the spec's vercel.json external-rewrite mechanism was falsified live (Vercel passes GitHub's releases/latest 302 through to the client), so /install.sh and /install.ps1 are Start server routes that fetch upstream and stream the bytes back 200 - spec SS2 amended in place. site/** explicitly public in .lootattributes; .vercelignore allowlists site/ so .loot/ keys and docs/pitch/ can never reach the Vercel builder; site build artifacts lootignored. 1931146c · dbf3dbe6…
  • primary catch-up: checkout at the #291 squash (site/ scaffold) folded for the drift reconcile (#297) e6f7079a · dbf3dbe6…diff
  • Scaffold site/ + wire Vercel #2, loot. subdomain, install proxy (#257) (#291) * Scaffold site/ + wire Vercel #2, loot. subdomain, install proxy (#257) Thin end-to-end tracer for loot.millerbyte.com (spec docs/specs/loot-site.md SS1-3): site/ = TanStack Start SSG consuming published @millerbyte/ui@^0.1.0 (theme.css at the root), five placeholder surface routes prerendered, deployed on Vercel project #2 (root site/) at https://loot.millerbyte.com. Install proxy: the spec's vercel.json external-rewrite mechanism was falsified live (Vercel passes GitHub's releases/latest 302 through to the client), so /install.sh and /install.ps1 are Start server routes that fetch upstream and stream the bytes back 200 - spec SS2 amended in place. site/** explicitly public in .lootattributes; .vercelignore allowlists site/ so .loot/ keys and docs/pitch/ can never reach the Vercel builder; site build artifacts lootignored. Landed via break-glass git branch: the loot-native lane land was blocked by two live shared-store bugs (review-ferry folds a described WIP into a reconcile finalize; describe/status disagree on the working change under concurrent sessions) - tickets to follow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Apply #291 review findings Cache only successful upstream responses in the installer proxy (a transient GitHub failure must not be CDN-cached for 5 minutes); extract the install one-liners to one shared module (three copies drifted apart otherwise); fix the vite.config "fully static" comment and spec SS0 framing to name the two server-route exceptions; tighten the .lootattributes rationale (the interim manual deploy is gated by .vercelignore, not the GitHub projection); drop the cargo-culted overrides block (its pins targeted server deps the site lacks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 231eb5f8diff
  • resolve conflict at site/src/routes/__root.tsx 2cb5212e · dbf3dbe6…diff
  • the forge's published surfaces render themselves anonymously, and each response's cache class rides out of the selector that produced it (#751) #749 built the read tier and nothing called it. This is the six surfaces #466's table named -- profile, repo home, tree, blob, history, diff -- as SSR routes over that tier, plus robots and a per-owner sitemap. Three properties are structural rather than configural, which was the point: STRICTLY ANONYMOUS (#486 Q4). No handler in site/src/server/forge.ts reads a request, a header, a cookie or a token; each takes only what the URL already says. So 'never serve one identity's view to another' is a fact about the module's signature rather than a cache rule someone must remember. Adding an identity parameter is the one edit that would break it. THE CACHE CLASS IS CARRIED, NEVER RE-DERIVED. resolveSelector already answers how a resolution may be cached; routes read loaderData.cache and map it in one place. That is what keeps the ADR 0032 trap shut: a change_id LOOKS like an address but is the durable handle carried across amends, so it resolves 'revalidate', and a pinned version_id is the only cache-forever class. Asserted twice -- a unit test on the header text, and a live render proving one URL family splits three ways. HONEST RENDERING (#497). A locked path shows its visibility tag and no bytes; grant_log is never queried, so a grantee set cannot leak from a surface with no way to learn one. Burned reads as a tombstone, sealed as 'Not published', absent as absent -- three outcomes a blank page would have made indistinguishable. The 404 is deliberately one 404: 'no such repo' and 'metadata private' must not be told apart. Also here: a published-only repo (metadata private, paths published) gets its index and nothing else -- no tree, no history, no diff -- which is 0005's barrier showing through the UI unchanged. Per #748 the profile lists metadata-public repos only, there is no global directory, and the sitemap is therefore per-owner: readable at a known URL is not consent to be advertised. vite.config.ts grows the prerender deny-list its own comment called unnecessary -- true until a route read a database. Acceptance: 49 tests green (31 new pure ones; the 8 read-tier tests against a throwaway 0001-0005 cluster), npm run build clean with exactly the five authored surfaces prerendered, and a 34-assertion end-to-end smoke -- real sealed bytes through R2 -> decrypt -> unzstd -> highlighted HTML, all three cache classes, every honest-rendering outcome. The client bundle carries no pg, no SQL and no credential. Publication: README.md, LICENSE-MIT and LICENSE-APACHE reach @world here, the set .lootattributes has declared since #750. Operator-authorised at the landing sign; nothing else in the tree changes tier. 2aa21bbe · dbf3dbe6…diff
  • the browser mints the seed and the account binds the key: Clerk in the site's own routes, variant-B onboarding, and the account tier's one producer (#753) Migration 0007 adds account_seed (the passphrase-wrapped envelope, opaque ciphertext the server cannot open) and forge_identity, a NOLOGIN role scoped to exactly the identity tier -- SELECT/INSERT on tenant, SELECT/INSERT plus a column-scoped UPDATE of retired_at on account_key, the full row on account_seed, and nothing else (pinned live by forge_identity_is_scoped_to_the_identity_tier). The producer lives in site/src/server/identity/ behind a verified Clerk session (verifyToken in the site's own route handlers, reusing the millerbyte Clerk instance and pattern, never the gateway service). Registration is one transaction: upsert tenant on the verified clerk_user_id, add the key, store the envelope, retire what rotation names -- and never zero live keys. The Rust service still writes none of the tier; tests/account_tier.rs now guards that boundary instead of a vacancy. Onboarding is #487's variant B, the two-column contract: the app owns the 32 bytes (crypto.getRandomValues -> 24-word BIP39 over the ENTROPY, never the 64-byte derivation -> ed25519 pubkey via @noble/curves, held to the engine's from_seed by the pinned RFC 8032 vector), a WebCrypto PBKDF2+AES-GCM envelope, a .txt download that excludes the passphrase, and two consent checkboxes that no download waives. Clerk mounts only under /account, which joins /k/ on the prerender deny-list; every static surface stays static. Follow-ons filed rather than folded: #926 (CLI raw-seed import, so BIP39 recovery reaches the CLI) and #927 (account-aware may_push, burn entitlement via account, and the #506 historical-key 301s the anonymous read tier deliberately cannot serve today). 278e1886 · dbf3dbe6…diff
  • the receipts are served, not linked: the Evidence page stops pointing at a repo its readers cannot open (#840) Twelve github.com links, every one a 404 for the only audience this site has — Connor-Miller/loot is private permanently (ADR 0045). Nine of them were the Evidence page run-output and evidence-doc links, i.e. the receipt for every thesis claim that page makes, so it asserted a set of proofs and handed the reader nothing to open. The other three were the nav/footer repo links and a LICENSE link, dead twice over since no file of that name exists either. The nine receipts are now inlined at build time with `?raw` and disclosed in place under each excerpt. A link — to GitHub, to a forge surface, to a route of our own — is a promise checked at READ time by someone not here to fix it; an import is checked by `vite build`, so moving one of these files breaks the build in the commit that broke it. This page cannot go stale the way it just spent months being stale. Not served from the forge, which ADR 0045 says is where the world should read loot: measured today, /k/connor and /k/connor/loot both answer 404 — it carries no repo yet, so pointing there would have swapped nine dead links for two. When it does, these become links again and the imports stay as the build-time check. The license moved to a page of its own rather than being dropped: a reader running curl | sh has a real reason to want the terms, and both texts are imported rather than transcribed so they cannot drift from the repo. A sweep test asks the reader question of the whole src/ tree, not the two files that happened to be wrong. It bans href/to/url pointing at github.com and deliberately allows the string in prose — installerProxy documents what dist bakes into the installers, and install.tsx prints cargo install --git as text (that command is #841 own decision). ⭐ The defect was never a wrong URL: it was that nobody ever opened one anonymously, and the person editing these files is always signed in. Verified: no github.com href on any of the 6 prerendered pages, /license prerenders, suite 77 passed | 23 skipped, build green. ⚠ Residue: the inlined evidence DOCUMENTS quote issue URLs in their own prose. They render as inert text in a <pre>, not as links, and rewriting a historical evidence document to hide its citations would falsify the thing it is evidence of. ad95716f · dbf3dbe6…diff
  • the browser opens what the forge cannot: own-private read ships an owner read tier, a once-per-device unlock, and a grant mailbox that never acks (#1043) c8dd4da1 · dbf3dbe6…diff
  • the trust matrix says what each reader can actually read, and /why stops claiming the relay cannot read your code (#1130) Publishes /trust as the one canonical answer to who reads what: six readers against four tiers, with a regardless-of-tier block for path names and DAG metadata, and footnotes on the two cells whose plain answer misleads — Restricted vs the git mirror rests on ADR 0028 projection omission rather than keys, and an embargo reveal is enforced by the relay clock rather than by maths (ADR 0027, written down nowhere until now). #1089 falls out of the matrix as its ticket predicted: the /why headline was false over ordinary code, since Internal is the default and a relay reads it by design. Every other surface now cites /trust instead of answering independently, which is what let /why and /docs drift into contradicting each other. ADR 0069 records that Internal stays the default deliberately — Restricted by default would make a clone readable by nobody and empty the git projection. ADR 0068 records the at-rest design for #1138: .loot/keyring is raw oid-key pairs beside the ciphertext it opens, so the unit of exposure is the whole .loot/ directory. Carries ADR 0067 from #1132, which shared this tree uncaptured. 361b2026 · dbf3dbe6…diff
  • the Known Issues page publishes what costs a tester their work, nine findings re-run against v0.4.16, and it is updated on report against a dated last-review rather than a cadence nobody keeps (#1143) 48f5788f · dbf3dbe6…diff
  • the known-issues pin becomes four tests that each say what broke, the phrase-issuer guard asserts the whole public surface of the mnemonic module instead of guessing a function name, and the trust versus known-issues split has one canonical copy the other sites point at (#1217) f751adc3 · dbf3dbe6…diff
  • the site gets a typeface: Geist and Geist Mono self-hosted from a subset derived from the site own characters rather than a stock list that silently drops the arrow the landing page renders, and twenty-one ad-hoc sizes collapse into a six-step scale 43c52bad · dbf3dbe6…diff
  • the site chrome becomes an instrument: a slim mono Console bar carries the six links, the release tag it now imports rather than retypes and the account key chip on every page, the footer band collapses into a one-line end-mark that still carries the disclosure route, docs trades its in-page contents list for a prop-driven side navigation any later surface can mount, and the account page is re-presented as a dashboard of key rows and a four-step ceremony with every Clerk call, every byte of crypto and every load-bearing sentence untouched - the #1323 prototype verdict, raising every surface ceiling on the record for the shared stylesheet it costs 786e5a21 · dbf3dbe6…diff
  • the two nav entries that leave your machine carry an EXP marker, keyed on their own href so the bar still says nothing about who is reading, and a strict 20rem suppression keeps the chip from adding fourteen pixels of horizontal scroll to a Fold 112c1212 · dbf3dbe6…diff
  • the Console bar stops calling the forge a relay and stops asserting an ok no static document can know, keeping only the release tag because that is the one thing up there a test already fetches, and the eight budget reasons stop claiming no new dependency now that lucide costs 682 bytes on every surface fc920df7 · dbf3dbe6…diff
  • the Console bar stops asking for more width than its box has at 280px, and the half of this ticket that had not landed turns out to be worse than it was filed: the recorded 22px shortfall reproduces to the pixel only once a scrollbar is suppressed, because the ticket took innerWidth as the box - with a classic scrollbar the same row spills 5px past the viewport on seven of the eight budgeted surfaces, and only the landing escapes because its gutter is wider than its own over-run, so the sentence saying the page does not scroll was true of the overlay shape alone. the fix is spacing and nothing else, three rules inside the media block that already cuts the EXP hole, tightening a row gap and two padding pairs while every item, its content and the gutter that aligns the bar to the text column stay exactly as they were - after it, all eight surfaces read zero horizontal scroll with nine pixels of slack in the box and twenty-four on the overlay shape, and 240, 300, 320 and 390 were swept for regressions. every reading carried a positive control injected in the same page evaluation and removed after, so no zero here comes from a probe that was not first shown able to see an overflow. the durable half is a budget rather than a render, since ADR 0071's gate weighs bytes without ever rendering at a width and nothing in this repo can: spacing is read live out of the stylesheet, brace-matched and shorthand-aware, against a box computed from the live gutter declaration, with one recorded font-metric constant labelled as recorded - the model reproduces four independently measured layouts to a hundredth of a pixel, and eleven mutations were each run red, including one that only moves a rule below the media block and changes no string at all. what it cannot catch is a font change or a new glyph, which is written at the top of the file rather than implied away; and the stale claim about fourteen pixels of scroll is narrowed in all three places that carried it, since correcting one of three is the trap #1629 records (#1360) dd791126 · dbf3dbe6…diff
  • 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…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 shell stops dying a minute after /account: it authenticated with Clerk 60-second __session cookie while no Clerk JS ran on /private to refresh it, so every private call 401d and a reload wiped the vault. per the operator grill (ADR 0096, reversing #930 dec. 4), Clerk JS now runs on the private routes, loaded on demand behind PrivateGate so public pages carry none, under the strict nonce CSP widened only for Clerk (its frontend API host read out of the publishable key, img.clerk.com, the Turnstile and protect hosts, and worker-src blob: for its session timers) with the request nonce passed to ClerkProvider; every private call goes through one privateFetch that sends a Bearer from getToken and retries once with a fresh token, and /api/private/* no longer accepts the cookie; the vault is wiped only when Clerk reports signed out or a different user, or on Lock, and an HTTP status never wipes it; signed-out /private renders SignIn in place and returns to the requested URL, and the private bar gains Sign out beside Lock. the vault, private shell and CSP comments and CONTEXT.md now say what is true: the wrap key can decrypt, so any script on this origin can recover the seed, and the wrap protects it at rest; the boundary is this origin script set, loot code plus Clerk. tests for the CSP, bearer lane, retry and wipe triggers and the built server, each red under a named mutation and restored; site gate 804 passed, every surface lighter; a CSP-enforcing signed-out load renders SignIn with no violation and /why loads no Clerk; workspace suite 4549 passed. the operator live check closes the ticket (#2396) 25ab3fa8 · 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.