carlos / platform Public

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 __carlos segment (refused at the write door and ignored at lookup).
  • carlos.js vendored in the binary: a five-minute-revalidating loader plus an immutable hashed asset. Edge mode: error and unhandledrejection handlers post to /__carlos/e, carlos.track(name, fields) posts to /__carlos/a via sendBeacon; 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/e and /__carlos/a, always 202 with 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's path on that hostname; errors to the coalescing sink as source: browser, events to the meter, every refusal to dropped.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 _overflow row per source, so a browser flood cannot blind edge errors. Field keys are sanitised; a browser error at / keeps its path.
  • Console: the door switch on both settings pages; Analytics tab shows drop reasons, a Sources live line and the snippet instruction; Errors tab always names log, edge and always shows the instruction; sources on the API and carlos analytics; docs.
  • Fixed on the way: telemetry.Resolve with 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.

Commits 22
Files changed 48 +4323 −116
Discussions 0 0 open
Tasks 0/0 done

Discussions

  • No discussions on this branch yet.

Tasks

  • No tasks yet.

Activity