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.
Marketing tree
One voice, one place. Every later artifact draws on positioning.md — do not
invent framing per-asset.
positioning.md # source of truth: wedge, voice, banned phrasings, approved claims
calendar.json # scheduled pushes: date, channel, status
briefs/<channel>.md # per-channel rules, format, limits, what "good" looks like
drafts/<id>/ # agent output: copy.md, assets/, rationale.md
pending/ approved/ posted/ # human gate by directory move
metrics/weekly.md # funnel snapshot, generated
listening.md # buying-intent threads + drafted replies
Hard rule
approved/ is the only thing a human ever publishes from. Nothing posts itself.
Workflow: agent drafts into drafts/<id>/ → moves to pending/ when complete →
a human reviews and moves it to approved/ → the human publishes from approved/
under their own account → the artifact (or a pointer) lands in posted/.
Nothing in drafts/ or pending/ is ever publishable, by anyone, by any means.
The weekly read
bun --env-file=.env scripts/weekly-metrics.ts (or bun run metrics:weekly)
rewrites metrics/weekly.md from the live databases: the staged funnel, the
campaign breakdown (getAnalyticsSummary.topCampaigns' grouping), and the one
stage that moved. It needs TURSO_DB_URL, TURSO_DB_TOKEN, NOOK_DB_URL and
NOOK_DB_TOKEN — no other production secret. The file is generated; never
hand-edit it. The numbers are engineering's; the interpretation is the CMO's.