analytics-step3
Analytics and errors, step 3 of the spec: the browser door and carlos.js. A member adds one script tag and their app's JavaScript errors appear on the Errors tab and their named events on the Analytics tab, with no wake, no cookie, silent 202s outward and honest dropped counts inward.
Spec: docs/superpowers/specs/2026-09-07-errors-and-analytics-design.md §3.1, §3.4, §3.6, §4, §6, §8 (step 3 rulings appended). Plan: docs/superpowers/plans/2026-09-07-analytics-step3-browser-door.md. Branch cut from main df7d1d2 (step 2 and the password gate merged).
What lands:
- The edge reserves
/__carlos/on every route, answered between the route lookup and the observing writer: nothing under it is billed, compressed, cached, gated or able to wake an instance (pinned by tests, including one against the real activator on an asleep route). The prefix-routing map can never steer the__carlossegment (refused at the write door and ignored at lookup). carlos.jsvendored in the binary: a five-minute-revalidating loader plus an immutable hashed asset. Edge mode:errorandunhandledrejectionhandlers post to/__carlos/e,carlos.track(name, fields)posts to/__carlos/aviasendBeacon; no pageview is ever sent. The §3.1 sanitisation floor runs in the page (tested behaviourally under node) and again in the sink.- The door:
POST /__carlos/eand/__carlos/a, always202with an empty body. Door switch checked before the body is read; Origin must be https on the edge's configured port (http only under--dev) and resolve through the edge's own map to this hostname's route, Referer only when Origin is absent, cross-site refused; 16 KB body, 8 KB stack, 32×256 fields, capped kind/name/fp; per-app 600/min per door and per-visitor 60/min token buckets with a 10,000-entry LRU; attribution to the app owning the beacon'spathon that hostname; errors to the coalescing sink assource: browser, events to the meter, every refusal todropped.e|a.{rate,size,origin,malformed}. - The host pushes both switches (
count,door) per app into the edge, once at startup and on every pass. - The coalescing budget is now per (app, source) with one
_overflowrow per source, so a browser flood cannot blind edge errors. Field keys are sanitised; a browser error at/keeps its path. - Console: the
doorswitch on both settings pages; Analytics tab shows drop reasons, aSources liveline and the snippet instruction; Errors tab always nameslog, edgeand always shows the instruction;sourceson the API andcarlos analytics; docs. - Fixed on the way:
telemetry.Resolvewith an empty app (the account settings page never rendered its analytics form on a DirStore console).
Rulings made on Paul's behalf are listed in spec §8 ("Step 3 rulings", 1–11), each cheap to overturn. Two worth a look: drops are recorded whenever the door is open regardless of the count switch (the fleet default is count off / door on, so otherwise every refusal is invisible); the reserved path bypasses the password gate by construction of the spec's placement.
Review record: fresh implementer and reviewer per task (Opus on the serving path and the Origin rule, Sonnet elsewhere); fix rounds on Tasks 3 (app bucket checked before the visitor table — a plan defect), 4 (five defects in the plan's own JavaScript, then a quadratic-regex regression from the fix, both closed), 7 (shut door checked first), 8 (a vacuous no-start assertion replaced by an asleep-route test), 9 (Sources seeded before early returns). Whole-branch final review (Opus): no Critical; four Important (Origin port hard-coded, shared fingerprint budget, hidden Errors-tab copy, unsanitised field keys) plus two rulings revisited, all fixed in one wave and re-reviewed.
Gate: build/vet/test green, gofmt clean, darwin/arm64 and windows/amd64 cross-builds clean; grep -rn __carlos platform-infrastructure empty.
Deferred minors (ledger): the drop path takes the meter mutex unthrottled; doorLimiter.apps entries are never reclaimed (bounded by the switch map); the rate and sanitiser-timing tests are wall-clock sensitive (no clock seam on Edge); base64 lookahead is O(n²) under the 16 KB pre-cap; credential regex crosses newlines; If-None-Match: * not honoured; caps count UTF-16 units in the page; window.carlos clobbers a same-named global; maxBeaconFP reused as the field-key cap; Resolve(app=="") has no unit test in its own package; appPage panics on a nil Releases store (pre-existing).
Roll order: nothing here depends on console-before-box or the reverse; a box on an older build serves /__carlos/ to the app as before, and an older console ignores sources.
Next: step 4, the server door on the control socket (POST /errors, POST /events), and the website-repo getting-started skill question from step 2.
Discussions
- No discussions on this branch yet.
Tasks
- No tasks yet.
Activity
- paul@keymail.dev pushed 6ffd8cae8 agent, console, spec: bound the pre-listen telemetry pass; drop a duplicated sentence
- paul@keymail.dev pushed 057ec98d1 spec: step 3 built on analytics-step3; the eleven step 3 rulings in §8
- paul@keymail.dev pushed d9a5bc0e5 agent: push the telemetry switches once before the first request is served
- paul@keymail.dev pushed e54f09104 edge: the origin rule follows the listen port, and drops are recorded whenever the door is open
- paul@keymail.dev pushed f71d8c94d console: the Errors tab names log and edge always, and always shows the snippet
- paul@keymail.dev pushed 44bab238c apperr, analytics: per-source error budget, sanitised field keys, Dropped.Total
- paul@keymail.dev pushed 65960d5b8 console: fix round 1 for task 9 — seed Sources before the early returns
- paul@keymail.dev pushed c20c350e8 console, cli, docs: the door switch, drop reasons, the coverage line and the one-line snippet
- paul@keymail.dev pushed 20ccb770f activator: prove the beacon test isn't vacuous against an asleep route
- paul@keymail.dev pushed 062be2823 activator: a beacon to a hibernating route leaves lastActive alone
- paul@keymail.dev pushed beb0460a2 edge: a shut door records nothing — check it before the body is read
- paul@keymail.dev pushed 60da8d015 edge: the browser door — origin, size, shape, rate, attribution, then the sink or the meter
- paul@keymail.dev pushed fd9a1211c analytics: the meter records door drops by door and reason
- paul@keymail.dev pushed 2978aab91 edge: beacon shapes with their caps, and the Origin rule
- paul@keymail.dev pushed 93684bc57 edge: carlos.js fix round 2 — pre-cap san() input, JSON-safe test harness
- paul@keymail.dev pushed 128bf6ac1 edge: carlos.js fix round 1 — sanitiser and beacon-boundary fixes
- paul@keymail.dev pushed 357920d77 edge: carlos.js vendored, a revalidating loader and an immutable hashed asset
- paul@keymail.dev pushed 00054ea1d edge: check the door bucket before touching the visitor LRU; refill() dedupe
- paul@keymail.dev pushed ccb12c315 edge: the browser door's token buckets, per app, per door, per visitor
- paul@keymail.dev pushed 987ffd191 edge: /__carlos/ is reserved on every route, answered before the observer
- paul@keymail.dev pushed 0fb915727 analytics: the switch map carries count and door together
- paul@keymail.dev pushed 42691777d plan: analytics step 3, the browser door and carlos.js, task by task