Files
accounted/docker/crontab.self-hosted
T
Jakob Wennberg bf5ca2c615 feat(whatsapp-inbox): GDPR retention cron and RoPA entry (#1341)
PR5b, the final code piece of the WhatsApp intake track. A daily cron
(04:15) enforces the channel's retention table; the receipt itself stays
7-year WORM under BFL and is never touched.

Retention actions (lib/retention.ts, each isolated and idempotent):
- whatsapp_messages transcripts past 90 days: body_text + raw_payload
  cleared in id batches under a wall-clock budget; the row skeleton
  (wamid, direction, timestamps, status, inbox_item_id) survives for
  audit. Only rows still carrying content match.
- Rows with phone_link_id IS NULL (unknown senders, orphans) past
  30 days: deleted.
- Link codes expired more than 24h ago: deleted, used or not.
- Sender rate counters idle 2+ days: deleted (minute/day window keys
  are dead weight after that).
- Links revoked 90+ days ago: phone_enc crypto-shredded to '' (column
  is NOT NULL), one-shot via neq guard; phone_hash and phone_masked
  kept for uniqueness history and audit display.

Route mirrors the sweep cron exactly: withCronContext + registry gate
(503 EXTENSION_DISABLED when the extension is off). vercel.json gets
the schedule and both Docker crontabs are regenerated.

Compliance: new whatsapp.receipt_intake activity in .compliance/ropa.yaml
covering purpose, Art 6(1)(b)/(c)/(f) bases with the Art 14(5)(b) note
for third-party attendee names, Meta Platforms Ireland as processor
(Cloud API, EU SCC addendum, Local Storage region DE), the differentiated
retention table, and security measures.

Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:35:41 +02:00

45 lines
3.4 KiB
Plaintext

# AUTO-GENERATED: do not edit. Run `npm run crontabs:generate` to regenerate.
#
# Variant: self-hosted (bind-mounted by docker-compose.yml).
# Source of truth: the `crons` array in vercel.json. Paths, order and
# cadence mirror it exactly; scripts/__tests__/generate-crontabs.test.ts
# fails CI if this file drifts from it.
#
# Consumed by supercronic (docker/cron.Dockerfile), bind-mounted read-only
# at /etc/supercronic/crontab. Times are UTC: the cron container sets no TZ
# and Alpine defaults to UTC, same as Vercel Cron. supercronic skips a tick
# whose previous run is still going (overlapping is off by default), so a
# slow endpoint cannot pile up.
#
# Extension endpoints are listed unconditionally, including extensions this
# image's preset does not enable. Each answers 200 with a no-op body when
# its extension is unconfigured, so an unused one costs a single cheap
# request, and enabling it later needs no crontab change.
#
# Load note: /api/webhooks/dispatch/cron runs every minute, 1440 requests a
# day. Each tick is one indexed query that returns immediately when no
# delivery is due, and the per-minute cadence is what makes the dispatcher
# 60-second first retry actually happen after 60 seconds. Change it only
# through SCHEDULE_OVERRIDES in scripts/generate-crontabs.ts, never by
# editing this file.
0 6 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/deadlines/status/cron
0 * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/invoices/recurring/cron
30 4 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/settings/booking-templates/sync/cron
0 0 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/tax-deadlines/cron
0 5 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/enable-banking/sync/cron
30 3 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/stripe/transactions/cron
0 3 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/documents/verify/cron
0 4 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/sandbox/cleanup/cron
0 2 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/events/cleanup/cron
0 * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/cloud-backup/auto-sync/cron
30 * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/idempotency/cleanup/cron
30 2 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/pending-operations/expire/cron
0 4 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/skatteverket/skattekonto/sync/cron
*/15 * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/skatteverket/agi/kvittenser/cron
30 */2 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/skatteverket/vat/kvittenser/cron
* * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/webhooks/dispatch/cron
* * * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/whatsapp-inbox/sweep/cron
15 4 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/extensions/whatsapp-inbox/retention/cron
15 5 * * * curl -sf -H "Authorization: Bearer ${CRON_SECRET}" ${APP_URL}/api/bookkeeping/accruals/post-due/cron