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_name | shadow_mode | enabled | What it gates |
|---|---|---|---|
daily-flag-sync | 0 | 1 | Daily 06:00Z workflow spawner |
booking-flag | 0 | 1 | Per-booking workflow phases (arrival/departure/checkout-eve flag writes) |
cleaner-sms | 1 | 1 | When 1, SMSes route to Bill instead of cleaners |
cleaner-email | 1 | 1 | When 1, emails route to Bill instead of cleaners |
pre-arrival-messages | 0 | 1 | T-14/T-5/T-3/T-1/T-0 phases |
form-bridges | 0 | 1 | Pre-checkin/checkin/checkout bridge actions |
How to flip
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 only —
shadow_predictionsrow gets inserted with the planned action_at + fields, but no real send / API call happens - Skip with reason —
runFormBridgereturns{skipped: true, reason: "shadow_mode"}so callers know - Log shadow event —
migration_logrow 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_predictionsparity for the workflow
Source
src/lib/flags.ts—getFeatureFlag(env, name)- Migrations 0001 / 0003 / 0010 / 0014 / 0028 / 0034 — each adds a row