Skip to content

Feature flags

Every meaningful workflow chunk has a row in the feature_flags D1 table that controls whether it runs in shadow mode (predictions only) or real mode (real sends/writes).

Schema

CREATE TABLE feature_flags (
workflow_name TEXT PRIMARY KEY,
shadow_mode INTEGER NOT NULL, -- 0 = real, 1 = shadow
enabled INTEGER NOT NULL, -- 0 = off entirely, 1 = on
notes TEXT,
updated_at TEXT
);

Current rows

workflow_nameshadow_modeenabledWhat it gates
daily-flag-sync01Daily 06:00Z workflow spawner
booking-flag01Per-booking workflow phases (arrival/departure/checkout-eve flag writes)
cleaner-sms11When 1, SMSes route to Bill instead of cleaners
cleaner-email11When 1, emails route to Bill instead of cleaners
pre-arrival-messages01T-14/T-5/T-3/T-1/T-0 phases
form-bridges01Pre-checkin/checkin/checkout bridge actions

How to flip

Terminal window
npx wrangler d1 execute stayonthesnow --remote --command="\
UPDATE feature_flags SET shadow_mode=0 \
WHERE workflow_name='pre-arrival-messages';"

Shadow-mode semantics

Each workflow phase respects shadow_mode differently depending on the flag. Common patterns:

  • Write predictions onlyshadow_predictions row gets inserted with the planned action_at + fields, but no real send / API call happens
  • Skip with reasonrunFormBridge returns {skipped: true, reason: "shadow_mode"} so callers know
  • Log shadow eventmigration_log row added for traceability

When to flip something to shadow

  • Right before disabling a BA AA (to A/B compare predictions vs. BA behavior)
  • After spotting a regression — pull the flag back to 1 to stop real sends while investigating

When to flip back to real

  • After disabling the matching BA AA in the BA panel
  • After validating with smoke tests / /admin/delivery-status
  • After confirming shadow_predictions parity for the workflow

Source

  • src/lib/flags.tsgetFeatureFlag(env, name)
  • Migrations 0001 / 0003 / 0010 / 0014 / 0028 / 0034 — each adds a row