Every automation that runs on its own. A "cron" is just the technical word for a scheduled wake-up: a robot = a program + its wake-up. Families below: Cloudflare robots (CRON1-16), n8n robots on the VPS (publication), local Mac robots, routines and event triggers.
05-05-2026—UPDATECron = Cloudflare Workers cron (eo-backup-worker, scheduled handler). Deterministic, server-side, zero LLM tokens. For mechanical jobs (count rows, push Notion API).
Routine = Claude Code scheduled task (~/.claude/scheduled-tasks/<id>/SKILL.md). Spawns a Claude session. Costs tokens. For reasoning jobs (audit, brief, review).
Event = Application code fired on a user or system event (page load, form submit, file upload, webhook). Not scheduled, not LLM.
CONTENT tag = any cron / routine / event that touches the content pipeline (scripts, shorts, carousels, quotes, thumbnails, scheduling, n8n workflows, etc.). Visual flag so the content-related automation surface is auditable in 1 glance. Cards carrying this tag should mirror in /backdata/content-system.
CF Worker code: ~/Projects/eo-backup-worker/.
Single AL inbox — every automated email produced by any cron, routine or event MUST land at al@eliteoutsiders.com. Never alexandre.corne.ac@gmail.com (personal inbox, off-limits to ops noise).
How it's enforced — every Resend send in eo-backup-worker reads env.NOTIFY_TO with hardcoded fallback al@eliteoutsiders.com. Audit grep: grep -rnE "to:.*[\"'].*@" src/ — every match must end with @eliteoutsiders.com in the fallback string.
Override — if a single cron ever needs a different recipient, set a per-cron secret (e.g. NOTIFY_TO_WEEKLY) via wrangler secret put on the worker. Do not change the fallback. Last audit: 2026-05-08 — 4 stale fallbacks fixed (weekly-snapshot, events-archive, url-audit, personal-ops).
Monthly Kit + D1 backup to private R2. Quarterly dry-run on preview DB. Email recap.
0 3 1 * * · R2 target: eo-backups bucket · doc: Notion ch. 13 (Monitoring/alerting) + sub-page Setup backup mensuel Kit + D1 — 2026-04-30
Daily check that Cal.com slots are bookable across the 3 brands. Email if any slot is dead.
cal_slot_health:v2) — stores state per (event × date) to detect transitions.0 7 * * * · code: eo-backup-worker/src/cal-monitor.js · manual trigger: POST /admin/cal-monitor-now?token=...
Weekly chaos magic: re-randomize Cal.com slot times + brand rotation across the 4 weeks ahead.
STARTS constant in code (MON 13:00-16:00 / WED 10:00-11:00 / FRI 18:30-19:00). Edit + redeploy to change a bound. Recap email after each run.0 21 * * SUN · code: eo-backup-worker/src/cal-randomize.js · manual trigger: POST /admin/cal-randomize-now?token=...
Monday morning report: 7-day counters + delta vs prev 7d + top pages, posted to Notion + email.
alexandre.corne.ac@gmail.com (override via NOTIFY_TO_WEEKLY).eo-backup-worker/src/weekly-snapshot.js + Notion helper src/notion.js. Email subject: EO weekly — YYYY-WXX.0 8 * * 1 · code: eo-backup-worker/src/weekly-snapshot.js · manual trigger: POST /admin/weekly-snapshot-now?token=...
Monthly archive of prior-month events to R2 + Notion, then DELETE old rows from D1.
⚠ Heads-up — this cron DELETEs rows from the live events D1 table after archiving them to R2. Safe in normal conditions (R2 backup-first, current-month events untouched, R2-fail-HARD prevents data loss), but it's the only cron on this page that touches data destructively. Yellow button = think before you click.
0 3 1 * * — by design so the R2 dump captures an already-cleaned table), takes prior-month events, dumps JSON to R2 (eo-backups/events-archive/YYYY-MM/), creates a Notion archive page with aggregations (event_type / top paths / activity per day), then only after that runs DELETE FROM events WHERE created_at < <month_start>.renderNotionBlocks().wrangler r2 object get eo-backups/events-archive/2026-04/events-archive-2026-05-01.json0 2 1 * * · code: eo-backup-worker/src/events-archive.js · manual trigger: POST /admin/events-archive-now?token=... (idempotent on mid-month clicks; current-month events are never touched, R2-fail-HARD prevents data loss if backup fails)
Monday crawl of eliteoutsiders.com sitemap, upsert page_inventory + button_inventory in D1.
eliteoutsiders.com/sitemap.xml, walks each public page, and extracts every clickable element (<a> / <button> / [role="button"] / [data-track-id]). Result: complete inventory of pages + buttons in D1 (page_inventory + button_inventory), even buttons that have never been clicked.0 4 * * 1 · code: eo-backup-worker/src/page-flow-crawl.js · manual trigger: POST /admin/page-flow-crawl-now?token=...
Monthly crawl of every known URL (open + me + admin + tools), email anomaly report.
0 3 1 * * to avoid loading the network during the R2 upload), this robot crawls every known URL of the site (open + me + admin + tools), compares against the EXPECTED baseline in the code, and sends a Resend email summary to AL.EXPECTED in eo-backup-worker/src/url-audit.js.0 4 1 * * · code: eo-backup-worker/src/url-audit.js · manual trigger: POST /admin/url-audit-now?token=...
Semi-annual snapshot of D1 row counts per analytics table, append to Notion source-of-truth.
event_type values in the events table (30d + total). It then appends a snapshot block to the Notion source-of-truth page 📊 EO Analytics — Source of truth so AL has a history of how the analytics stack has grown over time.TABLES list in eo-backup-worker/src/analytics-map-refresh.js AND the mirror in eliteoutsiders-tools/functions/api/admin/analytics-map.js. Keep both in sync.0 5 1 1,7 * · code: eo-backup-worker/src/analytics-map-refresh.js · manual trigger: POST /admin/analytics-map-refresh-now?token=...
Every 30 min, rsync uncommitted changes from 3 working dirs to wip-autosave GitHub branch.
wip-autosave. Like a photocopier passing through your office every 30 min, photographing what's lying on the table without touching anything. If your Mac dies or you close by mistake, max 30 min of lost work.~/Library/Application Support/eo-wip-autosave/. Each run rsyncs your working tree (excluding node_modules, .wrangler, dist, logs) to its mirror, commits + pushes to the wip-autosave branch (never main). Your main working tree is NEVER touched. No main history pollution.git fetch origin wip-autosave && git checkout wip-autosave in any clone of the repo, or directly on GitHub. Commits are timestamped: WIP autosave 2026-05-02T01:39:59Z.0/30 * * * * (StartInterval 1800s launchd) · script: eo-backup-worker/scripts/wip-autosave.sh · plist: ~/Library/LaunchAgents/com.eo.wip-autosave.plist · log: ~/Projects/eo-backup-worker/logs/wip-autosave.log
Every hour, reads the Long Form Content Planner in Notion and makes the Finder video folders match it: every planner row with Status Ongoing or Planned gets a folder named V{N} - {title} inside videos/ongoing/. New row → folder created from _template. Title changed in Notion → folder renamed. Never deletes anything.
04428224657182ee8f3d81fc0fbe50b0, rows with Status = Ongoing or Planned. Folder name = V{N} - sanitize(title) (slashes/colons stripped, length capped)._template · folder found under an old title → renamed in place (contents untouched) · row removed or status changed → folder LEFT ALONE (no deletion, ever — cleanup stays a human decision).ongoing/), not at V{N} folders. Fix tracked in backlog.eliteoutsiders-tools/scripts/v-folder-sync.mjs · plist: ~/Library/LaunchAgents/com.eo.v-folder-sync.plist (source eliteoutsiders-tools/scripts/launchd/) · log: ~/Library/Logs/eo-v-folder-sync.log · secret: NOTION_API_KEY from ~/.config/eo/secrets.env
Every 30 min, scan Google Calendar for mentorship RDVs outside Cal.com, upsert bookings in D1.
bookings with is_mentorship=1. So the "Your Bookings" cell turns green in the client's /me without you touching the DB by hand.bookings rows are created only by the Cal.com webhook. If you book with a client outside Cal.com (manual email, direct GCal event), the client sees nothing in their /me. This robot fills that gap.oauth_tokens with service='google_calendar', see migration 0010). Code to write: eo-backup-worker/src/gcal-scan.js running on 30-min cron, calls Google Calendar API, normalizes events, INSERT/UPSERT in D1 bookings with idempotency on external_id.*/30 * * * * · status: SPEC · context: added 2026-05-01 at AL's request
Every day, query D1 error_events for any rows in last 24h. If > 0, email AL via Resend with the breakdown (level / source / status_code / latest 20 rows). If 0, silent — no spam.
functions/_lib/log-error.js (e.g. 500 on /api/admin/trauma-maps/[id]) are silent until AL clicks the broken thing. This watchdog flags them within 24h of occurrence./api/events, /api/auth/magic-link (Resend fail), /api/trauma-map/save (DB error), /api/admin/trauma-maps/[id]. Any new endpoint can opt-in by importing logError and calling it in the catch block.0 6 * * * · code: eo-backup-worker/src/error-events-watchdog.js · manual trigger: POST /admin/error-events-watchdog-now?token=$BACKUP_HEALTH_TOKEN
Keeps the content databases honest against each other. Reads both boards of the LF cross-platform process from D1, then checks every configured Notion link, every empty PROMPT/URL button, every remaining XXX placeholder and every task with no prompt and no URL. Report stored in D1; email only when something gets worse — never a "all good" email.
XXX placeholders · tasks with neither prompt nor URL · wave distribution · phases with zero step · SBY parking size. SBY content is excluded from drift on purpose.notion_access: limited, moves them to unverified_links and stays silent instead of crying wolf.NOTION_API_KEY). Until then the structural checks still run.notion_access: limited persists over 2 consecutive runs, the cron emails AL with the fix instruction instead of staying silent — the main check is never allowed to be a silent no-op for weeks.SELECT value_json FROM intranet_state WHERE key='content_sync_check'.0 9 * * 1 · code: eo-backup-worker/src/content-sync-check.js · manual trigger: POST /admin/content-sync-check-now?token=$BACKUP_HEALTH_TOKEN
Kills the "frozen page" problem on /architecture section A. Every day it asks the Notion API for the REAL last-edited date of every Notion page linked in that section, and stores it in D1. The page then shows a dated dot next to each link: grey = untouched, gold = edited after AL last ticked "Up to date" (re-read it), red = the hand-typed date in the Notion title is older than the real edit (the stamp lies).
architecture_notion_ids. Add a link tomorrow, it is tracked the day after. No manual list to maintain.notion_freshness (per page: real last_edited_time, current title, and the date parsed out of that title). The page only renders it, it never calls Notion itself.0 10 * * * · code: eo-backup-worker/src/notion-freshness.js · reads: architecture_notion_ids · writes: notion_freshness · secret: NOTION_API_KEY
Answers one question: is every person who consented actually in Kit? It reads the D1 contacts rows that carry a consent and no revocation, reads the live Kit subscriber list, and compares. Anyone missing is pushed into Kit on the spot and their Kit id is written back to D1. AL only gets an email when something was repaired or could not be repaired. Silence means the two bases agree.
/api/trauma-map/save threw away the result of its Kit call, so a failure left no trace anywhere. Fixed the same day (3 retries + a row in error_events + kit_subscriber_id written back), but a code fix only protects the future. This robot is the net that catches whatever still slips.@eliteoutsiders.test, @example.com, smoke-, e2e-, mail-tester, and friends) so the report only ever counts real humans.kit_subscriber_id in D1 (back-filled), present in Kit but absent from D1 (reported only, never inserted blind).kit_subscriber_id and kit_synced_at on existing rows. No INSERT into contacts, no other column touched.0 5 * * 1,4 · code: eo-backup-worker/src/kit-sync-check.js · reads: contacts + Kit API · writes: kit_sync_check, contacts.kit_subscriber_id · secrets: KIT_API_KEY, RESEND_API_KEY
The missing link between the newsletter drafts and an actual send. Every morning it reads the Notion base "Weekly Newsletter Per Long-Form Stack", takes every row where AL set Validation = Yes, and creates a SCHEDULED broadcast in Kit at the exact date and time written on the row. AL never opens Kit: he reads the draft in Notion, flips Validation to Yes, and the email leaves on its own at the planned slot. A confirmation email lists what was scheduled.
30 6 * * * · code: eo-backup-worker/src/newsletter-dispatch.js · reads: Notion base 5a249cf… · writes: Kit broadcasts, newsletter_dispatch, Notion Kit Broadcast ID · secrets: NOTION_API_KEY, KIT_API_KEY, RESEND_API_KEY
The button nobody was pressing. The n8n quotes robot (repaired 28/07) schedules every armed quote, but only a webhook or a manual click could start it. This wake-up presses the button once a week with the shared webhook key. AL's weekly gesture drops to zero: arm rows in Notion, they get scheduled the following Monday. Careful with Run now: it REALLY starts the quotes robot.
30 4 * * 1 · code: eo-backup-worker/src/quotes-trigger.js · calls: /webhook/quotes-trigger (header key) · writes: quotes_trigger · secret: N8N_WEBHOOK_KEYReads the Shorts Posting Planning base (rows AL set to Ready), reads posting slots LIVE from content-system, refuses past dates with a visible error, targets YouTube + Instagram + Facebook. TikTok shorts never go through it: they are 100% manual via AL's Thursday routine.
NgJNAYfiTgxqehu2 · instance: 72.62.187.71:5678Takes every eligible quote (Validation = Yes, Status = In progress, FUTURE Posting Date), schedules the 5 platforms in PostFast at the Notion date, direct APPROVED so AL never approves in PostFast anymore, then writes Done + Posted At. A past-dated eligible row raises a visible error and nothing is published: the silent skip that killed Q10 to Q16 is gone. The banned hashtag was removed from the code.
yuWyWuPpjiI77ppd · quote base: c00a94af…e340Now reads the RIGHT base (Carousels Content Planner: it silently queried the Substack base for months) and publishes at the Notion Posting Date instead of inventing "next Wednesday". Music dropped on carousels (AL 28/07): IG + TikTok + Facebook go out SCHEDULED + APPROVED like LinkedIn / X / Threads, no draft inbox step anymore.
lmr1cbW3emlCucZw · canonical base: d26f5ed1…22c4Emails AL whenever a publication robot errors. Before 28/07 it only watched the carousels: a shorts or quotes failure was invisible. Now all three publishers report to it, which makes the "past date = visible error" rule actually reach AL's inbox.
QB2gHEg5a7Y9qffiFuture publisher for the Pinterest system (1 authored pin per day, boards = the 7 Quote Kinds, destination eliteoutsiders.com). Deliberately INACTIVE: AL classed Pinterest as wave 2, the boards do not exist on the real account yet and the prompt has never been rehearsed. Alignment audit against the 28/07 doctrine (closed webhook, error notifier, date read from the planner, slot in content-system) ran the same evening; see the Pinterest Grammar for the live state.
KLWzgswqM59G3hIE · webhook: /webhook/pinterest-triggerPublisher for the lifestyle photos: reads the Lifestyle Content Planner (rows AL sets to In progress), posts the single photo with its caption to Instagram + TikTok + Facebook at the Notion Posting Date, APPROVED direct, then writes Done + Posted At. Refuses to publish a row with an empty Caption (caption prompt first) or an empty PostFast Image Key (upload first): a half-ready row is skipped with a visible warning, never posted naked.
QRVpsd9Z3r9vBbpU · webhook: /webhook/lifestyle-trigger · planner: a32a49e3…Monthly Anthropic Cloud routine: scan 3 repos for new CRON / ROUTINE / EVENT, open PR if missing.
~/.claude/scheduled-tasks/), or EVENT-triggered code (page load handlers, form submit POSTs, webhook handlers) AL built without listing on /crons. For each new entry, it adds a card in the right outer dropdown (CRONS / ROUTINES / EVENTS) on a separate git branch and opens a Pull Request for AL to validate. Like a teacher checking each new drawing is taped to the wall, leaving a sticky note for any missing.Crons row, the new admin endpoint to the Admin endpoints row, the new secret to the Secrets row if applicable, and any new R2/D1/KV binding to the Bindings row. Same branch + PR as Scope 1.<!-- monthly audit YYYY-MM-DD: no new crons / no infra changes --> and exit. No PR for nothing.data-last-update spans:
public/crons.html, right of the "Crons" H1public/infra-tree.html, right of the "Infra Tree" H1DD-MM-YYYY—UPDATE (e.g. 03-05-2026—UPDATE). Both date changes go into the PR (or the silent commit if no PR).trig_01UU96BG1d6FXknfdYHDEY21. Runs in an isolated sandbox with HTTPS clones of the 3 repos via the GitHub account alexandrecorne. Never modifies main directly — always branch + PR.Run now. The on-demand run executes the same prompt as the monthly cron and stays idempotent.0 9 1 * * · management: claude.ai/code/routines · out-of-scope (mentioned in PR): ~/.claude/scheduled-tasks/*/SKILL.md (local only)
| What | Value |
|---|---|
| Worker | eo-backup-worker · Cloudflare account 88ff6259a88cb5fb8e8b9e13e082d7c4 |
| URL | https://eo-backup-worker.alexandre-corne-ac.workers.dev |
| Repo | ~/Projects/eo-backup-worker/ |
| Crons | 0 2 1 * * (events-archive) · 0 3 1 * * (backup) · 0 4 1 * * (url-audit) · 0 4 * * 1 (page-flow-crawl) · 0 5 1 1,7 * (analytics-map-refresh) · 0 7 * * * (cal-monitor) · 0 8 * * 1 (weekly-snapshot) · 0 21 * * SUN (cal-randomize) — 8 deployed |
| Secrets | RESEND_API_KEY · KIT_API_KEY · D1_API_TOKEN · D1_PREVIEW_API_TOKEN · BACKUP_HEALTH_TOKEN · CAL_API_KEY · NOTIFY_FROM · NOTIFY_TO · NOTION_API_KEY · NOTION_WEEKLY_PARENT_ID · NOTION_ARCHIVE_PARENT_ID · NOTIFY_TO_WEEKLY (optional) |
| Bindings | R2 eo-backups (+ prefix events-archive/YYYY-MM/ from CRON5 LIVE) · D1 eliteoutsiders + eliteoutsiders-preview · KV BACKUP_STATE |
| Admin endpoints (POST + ?token=BACKUP_HEALTH_TOKEN) | /admin/run-now · /admin/cal-monitor-now · /admin/cal-randomize-now · /admin/weekly-snapshot-now · /admin/events-archive-now · /admin/page-flow-crawl-now · /admin/url-audit-now · /admin/analytics-map-refresh-now |
| Healthcheck (GET + ?token=BACKUP_HEALTH_TOKEN) | /backup/last-run |
| State KV (cal-monitor) | namespace id 09082b5063a34c238de4b117efaf7d93 · key cal_slot_health:v2 |
Weekly local Claude Code routine: produce next Saturday quote (Canva visual + PostFast upload + Notion satellites X/Threads) unattended through Phase 8. AL gets a final report + manual Phase 9 PostFast approval.
collection://9078c89a-5334-43fa-b501-7ac535d31c0d), pick the row with Posting Date = upcoming Saturday + Validation=Yes + Status="Not started", and run the full Quote pipeline single-row per Quote Grammar v1.5 and Grammar/Prompt doctrine V1.~/.claude/scheduled-tasks/eo-quote-weekly/SKILL.md. Runs only while a Claude Code app is open on AL's Mac; if closed, fires on next launch.0 18 * * 5 (Paris local time) · management: ~/.claude/scheduled-tasks/eo-quote-weekly/ · out-of-scope crons (CF Workers): /crons CRONS section above
| What | Value |
|---|---|
| Trigger | Local Claude Code scheduled-task eo-quote-weekly · cron 0 18 * * 5 Paris |
| Storage | ~/.claude/scheduled-tasks/eo-quote-weekly/SKILL.md |
| Notion DBs touched | Quote Content Planner 9078c89a-5334-43fa-b501-7ac535d31c0d (read+update) · X 81b3d7ba-d64e-40af-bd99-03627abfb9f0 (create) · Threads 90a1b900-bdc9-4691-aab9-45e6cacf0ed7 (create) |
| Canva | Brand template EAHIywfFr_k · folder FAHKGTx8peY · 5 slots per Grammar D.5 |
| PostFast | API api.postfa.st · header pf-api-key · endpoint POST /file/get-signed-upload-urls |
| Secrets read | ~/.config/eo/secrets.env: CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRET, POSTFAST_API_KEY |
| Output path | ~/Library/Mobile Documents/com~apple~CloudDocs/iCloud Documents/DD/Elite Outsiders/content/quotes/2026/ kebab-case Qxx-title.png or -p1/-p2/...png |
| n8n downstream | EO Quotes Weekly workflow (reads Notion publication rows Status="In progress" + PostFast Image Key, creates SCHEDULED+PENDING_APPROVAL post in PostFast) |
When /alsboard loads, auto-purge weeks whose end date has passed.
/alsboard, it automatically removes from the display the weeks whose end date has passed — the page cleans itself.eliteoutsiders-tools/public/alsboard.html · code: parseWeekEndDate + purgePastUpcomingWeeks · persists via PUT /api/admin/state/eisenhower_matrix_al
When user submits Trauma Map quiz, send results email via Resend (fire-and-forget).
{ok:true} — the email goes out in background via waitUntil.functions/api/trauma-map/save.js calls buildTraumaMapEmail + sendEmail. Fire-and-forget. No retry yet.POST /api/trauma-map/save · file: eliteoutsiders-site/functions/api/trauma-map/save.js
When client uploads to /me/toolkit, store blob in R2 + index in D1 toolkit_resources.
/me/toolkit (PDF, DOCX, image, audio, video — up to 100 MB), this robot stores the file in the private R2 vault eo-user-toolkit and writes the index card in D1 toolkit_resources (with columns r2_key/mime/bytes added via migration 0018). Like a librarian shelving the book on the client's private shelf and noting the location. Nobody else (besides the client and the admin) can access the file — every read re-verifies identity before serving the blob.GET /api/me/toolkit/file/<id> streams the R2 blob with ownership check (WHERE user_email = <jwt-email>) and Range header support so videos can scrub. Annotation side: toolkit_annotations table (migration 0019) — 3-color highlights + margin comments, same mechanics as the manifesto reader. Annotations active only on text types (TXT, MD, DOCX rendered to HTML via mammoth.js); PDF/image/audio/video read-only for V1./workwithalex turns green and redirects to /me/toolkit as soon as a purchases row with product='coaching' and status='paid' exists for the logged-in email. Single source of truth: /api/me.POST /api/me/toolkit/upload · files: eliteoutsiders-site/functions/api/me/toolkit/*.js + public/me/toolkit.html · binding: R2 USER_TOOLKIT → bucket eo-user-toolkit