analytics: make each weekly funnel stage separately measurable (FRE-366) #2
Reference in New Issue
Block a user
Delete Branch "agent-code-review/freno-dev:freno-dev/FRE-366-funnel-stages"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is
FRE-366: make each weekly funnel stage separately measurable. Splits the one
shared download label into
stage = 'click'(the doors) vsstage = 'served'(the DMG file route), records a created Stripe session as the checkout stage
(
POST /api/the-nook/checkout), and records the real client address plus thehost each request addressed (
stage/hostcolumns through the existingVISITOR_ANALYTICS_ADDED_COLUMNSbootstrap path). Every analytics entry istruncated to an address prefix (/24 v4, /56 v6) before storage — the
FRE-369 privacy floor — and a daily retention cron (
/api/analytics-retention,12:00 UTC, Bearer
ANALYTICS_RETENTION_SECRET ?? CRON_SECRET, fail-closed)bounds row lifetime. The weekly job now renders seven separately measured
stages with computed caveats.
Base relationship (read before merging)
This branch is based on the FRE-202 tip
89f56fc, which is not yet onmaster. Landing order: FRE-202 first, then this branch; once FRE-202 ison master this PR narrows to the FRE-366 delta automatically. Merging stays
with the repo owner/CTO — this PR is the landing act, not a merge.
Security gate (Security Engineer, FRE-369 / FRE-370)
src/server/request-client.tsis the client-IP trust boundary. Two findingsfrom the FRE-369 review (leftmost
x-forwarded-forhop is attacker-chosentext; full IPs with no retention) are fixed in this head. FRE-370 verified
the deploy-side precondition and recorded: this read must not deploy ahead
of the FRE-371 topology-correct fix (rate-limit / abuse paths). The value
stays truncated and out of every security path; see the module doc for the
stated residual.
Gap 1 — click and served share one label: trackDownload now takes a stage ("click" | "served") carried on a new VisitorAnalytics.stage column; the Nook door and the tRPC proxy record clicks, the DMG file route records served. summariseDownloads() and the /analytics downloads block are untouched — the stage is a separate column, so the path grouping cannot change. Gap 2 — checkout start records nothing: the checkout route records a created Stripe session through the shared trackRequest writer (same referrer, campaign, country dimensions as every door) under its own path, and STAGES.checkout reads it instead of the /checkout page proxy. Gap 3 — loopback addresses, no host dimension: every writer now resolves the client address through resolveClientIp, which trusts x-forwarded-for only when the direct peer is the loopback production proxy (trust boundary stated in request-client.ts), and records the host on each row through the same site-context extractor routing uses. stage and host reach the table through VISITOR_ANALYTICS_ADDED_COLUMNS; the weekly job applies the same bootstrap before reading so it renders seven stages against a database no deploy has migrated yet. The weekly read splits downloadClick/dmgServed, scopes visits to nook.freno.me, and computes its caveats from the window: pre-instrumentation rows are stated and excluded, and the loopback warning now fires only when rows recorded since the instrumentation still carry it.