1// What each telemetry event means, column by column: the one place the 2// Worker's writer (events.ts) and the reader (`nix run .#events`, 3// tools/events/) both take it from. Plain data with no imports, so Node 4// runs this file as it is. 5// 6// Every event goes to one Workers Analytics Engine dataset (the EVENTS 7// binding, wrangler.json), which is a single table of positional columns: 8// 9// index1 the event's name ('jev.relay'): what a query filters on, and 10// the key Analytics Engine samples by, so a busy event is 11// thinned without touching a rare one (weigh rows by 12// _sample_interval when counting or averaging) 13// blob1…20 strings, per event below; null when not known 14// double1 which of the event's doubles are known: bit (k - 2) is set 15// when double k holds a value, since a double cannot be null 16// (`bitAnd(toUInt32(double1), 1) > 0` for double2) 17// double2…20 numbers, per event below; 0 when not known (see double1) 18// 19// A column's meaning is fixed once written: old rows keep it for the 20// dataset's three months. So a field is only ever added, at the next free 21// column; never renamed onto another column, moved, or reused. A new 22// meaning is a new field (or a new event). events.test.ts checks every 23// event's columns run from blob1 and double2 with no gap or repeat. 24// 25// What an event may hold: timing, sizes, counts, statuses and bounded 26// labels. Never an address, a visitor or rate-limit key, a request body, 27// or a header as sent (worker/CLAUDE.md, "Logs carry no visitor"). A blob 28// is a label to group by, not a place for text. 29 30export const EVENTS = { 31 'jev.relay': { 32 about: 'One per call to the Jev relay (relay.ts), with the same fields as its jev.relay log line.', 33 blobs: { 34 outcome: { 35 column: 'blob1', 36 about: 37 'refused (the relay answered itself: method, key, rate limit, size or shape), forwarded (TypeSafe answered, whatever its status), cached (the same request was answered within the hour; TypeSafe not asked) or unreachable (TypeSafe could not be reached)', 38 }, 39 colo: { column: 'blob2', about: 'the Cloudflare data centre that ran the call' }, 40 }, 41 doubles: { 42 status: { column: 'double2', about: 'the HTTP status the page got' }, 43 upstreamMs: { 44 column: 'double3', 45 about: "ms until TypeSafe's response headers; unknown when TypeSafe was not asked (refused, cached)", 46 }, 47 requestBytes: { column: 'double4', about: 'the body as forwarded; unknown when refused before reading it' }, 48 sectionInS: { 49 column: 'double5', 50 about: 'seconds before its section began that the page sent the call (negative once begun); unknown outside a song', 51 }, 52 attempt: { column: 'double6', about: "the page's attempt: 0, then its retries" }, 53 }, 54 }, 55 'jev.budget.exhausted': { 56 about: 57 "One per signed-in user and UTC day, on the relay call that spent the last of that user's daily Jev budget (budget.ts). The user is not recorded; counting these by day is how many listeners ran out.", 58 blobs: { 59 colo: { column: 'blob1', about: 'the Cloudflare data centre that ran the call' }, 60 }, 61 doubles: { 62 limit: { column: 'double2', about: 'the daily budget that was spent (JEV_DAILY_PER_USER at the time)' }, 63 secondsToReset: { column: 'double3', about: 'seconds from then until the budget resets at UTC midnight' }, 64 }, 65 }, 66 'jev.screen': { 67 about: 68 "One per attempt to screen a listener's item before it is public (content-jobs.ts): Jev's verdict, or why it is still pending. The item and its author are not recorded.", 69 blobs: { 70 kind: { column: 'blob1', about: 'what was screened: revision (a song), cover (its alt text), comment or pitch' }, 71 outcome: { column: 'blob2', about: "fine (now public), spam or abuse (held), or pending (not screened this time; see why)" }, 72 why: { 73 column: 'blob3', 74 about: 75 "for pending: budget (the author's daily Jev budget is spent), budget-unavailable, unreachable (TypeSafe, or no key), upstream (TypeSafe answered an error) or unanswered (no verdict in the answer); null otherwise", 76 }, 77 }, 78 doubles: { 79 status: { column: 'double2', about: "TypeSafe's HTTP status; unknown when it was not asked or not reached" }, 80 upstreamMs: { column: 'double3', about: "ms until TypeSafe's response headers; unknown when not asked" }, 81 attempt: { column: 'double4', about: 'which attempt this was: 0, then each retry' }, 82 confidence: { column: 'double5', about: "Jev's confidence in the verdict, 0 to 1; unknown when pending or not given" }, 83 requestBytes: { column: 'double6', about: 'the screening request as sent; unknown when not sent' }, 84 }, 85 }, 86 'jev.score': { 87 about: 88 "One per attempt to score a listener's song revision with the art critic (content-jobs.ts), after it was screened fine. The song and its author are not recorded.", 89 blobs: { 90 outcome: { column: 'blob1', about: 'scored, or pending (not scored this time; see why)' }, 91 why: { column: 'blob2', about: "for pending, as jev.screen's why; null when scored" }, 92 }, 93 doubles: { 94 status: { column: 'double2', about: "the worst HTTP status of the runs' TypeSafe calls; unknown when not asked or not reached" }, 95 upstreamMs: { column: 'double3', about: 'ms until the slowest run had its response headers; unknown when not asked' }, 96 attempt: { column: 'double4', about: 'which attempt this was: 0, then each retry' }, 97 art: { column: 'double5', about: 'the mean art of the runs, 0 to 1; unknown when pending' }, 98 runs: { column: 'double6', about: 'how many runs were asked (and charged)' }, 99 }, 100 }, 101} as const;