Skip to content

feat(wad-d): unblock daily cron via Railway-side HTTP trigger#73

Merged
masonwyatt23 merged 1 commit into
mainfrom
feat/wadd-admin-trigger-endpoint
May 23, 2026
Merged

feat(wad-d): unblock daily cron via Railway-side HTTP trigger#73
masonwyatt23 merged 1 commit into
mainfrom
feat/wadd-admin-trigger-endpoint

Conversation

@masonwyatt23
Copy link
Copy Markdown
Member

Summary

Closes the Option-A networking blocker called out in #70: ships a bearer-gated POST /admin/jobs/daily-wad-d-aggregate endpoint so the GitHub Actions runner can curl the Railway-deployed server, which reaches the in-container bun:sqlite DB the runner cannot. Activates the daily 02:00 UTC schedule: that #70 left commented out.

This completes the Q1 WAD-D plumbing:

What ships

  • server/src/routes/admin-jobs.tsPOST /admin/jobs/daily-wad-d-aggregate. Bearer auth via ASHLR_ADMIN_TRIGGER_TOKEN. 503 when the token env var is unset (NOT 401 — surface stays dark when not configured). 60s wallclock budget. Constant-time bearer compare via crypto.timingSafeEqual. Internal error messages NEVER leaked — only { error: "Aggregator failed", requestId }. Emits cron_start / cron_end structured logs matching the weekly-digest shape.
  • server/src/index.ts — mounts adminJobsRouter before adminRouter so its bearer-gated routes aren't swallowed by the user-token admin middleware.
  • .github/workflows/daily-wad-d-aggregate.yml — schedule activated (0 2 * * *); local-CLI invocation replaced with a curl --fail-with-body --max-time 60 call to the new endpoint; secrets validated up-front; asserts ok=true in the response; surfaces the WAD-D value in the workflow run summary.
  • server/tests/admin-jobs.test.ts — 11 tests covering 401/503/200/500/400 paths, dryRun/date/thresholdDays propagation, and the leak-redaction assertion.

Security posture

  • Endpoint is intentionally undiscoverable from any user-facing surface — no link from the founder dashboard (release: v1.20.2 — status-line session counter (real fix) + bench methodology #20), no entry in the user-facing admin dashboard. Discoverability lives in scheduler config + this source file only.
  • Bearer is rotated independently from the user-facing admin auth.
  • mock.module() deliberately not used in tests (leaks across files in bun:test); a tiny _setWadDAggregator DI seam swaps the implementation instead.

Required secrets

GitHub repo settings — Secrets and variables → Actions:

  • ASHLR_ADMIN_URL — e.g. https://api.ashlr.ai
  • ASHLR_ADMIN_TRIGGER_TOKEN — generate with openssl rand -hex 32; same value goes in Railway env

Railway env (ashlr-server service):

  • ASHLR_ADMIN_TRIGGER_TOKEN — matches the GitHub Actions secret

Until both are set, the workflow fails fast at the "Validate required secrets" step and the endpoint returns 503.

Test plan

  • bun test tests/admin-jobs.test.ts — 11 / 0
  • bun test tests/daily-wad-d-aggregate.test.ts (real aggregator, regression check) — 5 / 0
  • Full server suite: 538 / 0
  • YAML lint via python3 yaml.safe_load
  • bunx tsc --noEmit clean
  • After merge: rotate ASHLR_ADMIN_TRIGGER_TOKEN into Railway env + repo secrets, then workflow_dispatch once with dry_run=true to confirm round-trip before tonight's 02:00 UTC schedule fires.

🤖 Generated with Claude Code

PR #70 staged the GitHub Actions workflow with the schedule commented out
because the runner can't reach the bun:sqlite DB inside Railway's container
filesystem. This patch ships the Option A path documented there:

- POST /admin/jobs/daily-wad-d-aggregate on the server, gated by an
  ASHLR_ADMIN_TRIGGER_TOKEN bearer secret. 60s wallclock budget. Constant-
  time bearer compare. Returns 503 (NOT 401) when the token env var is
  unset so the surface is dark by default.
- Mounts the router BEFORE adminRouter so the bearer-gated route isn't
  swallowed by the user-token admin middleware.
- Workflow activates `schedule: 0 2 * * *` and replaces the local-CLI step
  with a curl-to-Railway call. Validates secrets up-front, asserts ok=true
  in the response, surfaces wad_d in the workflow summary.
- 11 tests covering 401/503/200/500/400 paths + dryRun/date/thresholdDays
  propagation + redacted error leakage. Full server suite: 538 / 0.

Endpoint is intentionally undiscoverable: no link from any user-facing
admin dashboard, secret rotated independently from the dashboard auth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ashlr-plugin-site Building Building Preview, Comment May 23, 2026 1:09am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant