v4.67 Streak Insurance for Supporters — Rollout Playbook
Milestone: v4.67 — Streak Insurance for Supporters Feature flag: streak_insurance_enabled (PostHog flag key, project 368400) Owner: Engineering (release) Last updated: 2026-09-20 Related: docs/features/habits.md, docs/features/supporter.md, docs/operations/game-balance.md, docs/development/feature-flags.md, .planning/milestones/v4.67-streak-insurance-for-supporters-ROADMAP.md
Summary
v4.67 ships a third streak-recovery lane — Streak Insurance — reserved to Supporters, free of XP cost, and rationed to one claim per calendar month across all of a Supporter's habits. It reuses the existing 48-hour repair window and freeze-backfill machinery rather than adding new streak math: Gamification::ClaimStreakInsurance calls GoalTracking::UseStreakFreeze(system_granted: true) for each missed date, the same call the free XP-repair lane (Gamification::RepairStreak) already makes.
The whole surface is gated behind a single PostHog flag, streak_insurance_enabled, evaluated by the frontend (isFeatureFlagEnabled('streak_insurance_enabled') / useFeatureFlag('streak_insurance_enabled'), ionic_frontend/src/lib/featureFlags.ts) and the backend (FeatureFlagService.enabled?('streak_insurance_enabled', user:), called from both Gamification::ClaimStreakInsurance and StreakRepairOfferResolver#insurance_result). This is a monetization change touching a live free mechanic (the existing streak-repair card), so the kill switch was a hard requirement from kickoff, not an optional add-on (OBJ-1134 kickoff checklist item 4).
Flag registered across Phase 1–4a — created disabled at 0% rollout via node scripts/sync-posthog-feature-flags.mjs --apply in the Phase 4a PR (posthog-flag-drift CI job enforces this in the same PR that adds a flag key). All seven build phases have shipped (1, 2, 3, 3b, 4a, 4b, 4c — the last, wiring the live Dashboard call site, merged 2026-09-20), but the flag has not been ramped past 0% as of this writing — no real user can reach the surface yet.
When the flag evaluates true for a Supporter with an eligible break:
- Frontend: the Dashboard's
StreakRepairCard.vuerenders the insurance lane (states S1, S2, S4, S6 per the UI-SPEC) alongside the existing free XP-repair option, which is never hidden or de-emphasised. Tapping the insurance CTA opensRepairConfirmationModal.vue's insurance variant; confirming dispatches theclaimStreakInsurancemutation. - Backend:
claimStreakInsurancemutation resolves for a Supporter with an eligible target;streakRepairOffer'sinsurance*fields populate.
When the flag evaluates false (default, fail-closed): the card renders byte-identical to today — no insurance lane, no upgrade line, no insurance* fields treated as eligible client-side. This is asserted by spec (Phase 4a/4c acceptance criteria), not just eyeballed.
Not gated (deliberately): the free lanes this milestone does not touch — GoalTracking::UseStreakFreeze (proactive), Gamification::RepairStreak (XP repair), and Mutations::AcceptStreakMercy — are unaffected by this flag at any rollout percentage. They were live before this milestone and stay live identically regardless of where streak_insurance_enabled sits.
Ramp plan
Three stages, matching the standard lifecycle in docs/development/feature-flags.md. No external dependency (legal, third-party API) gates this ramp — the free lanes are untouched and the perk itself is additive, so the only real gate at each stage is Sentry/PostHog signal.
Pre-ramp prerequisite — before moving the flag off 0%
Two user-facing artifacts were deliberately deferred while the flag sat at 0% rollout, and both become due the moment Stage 1 begins:
- OBJ-4058 —
docs/product/user-guide.md§15's Supporter tier framing (the "purely cosmetic" language and the perk list, which currently omits Streak Insurance) must be corrected before or with the first ramp above 0%. This page is mirrored tohelp.objectuve.comand is the most public statement of the PBC pricing commitment; leaving it uncorrected once real users can reach the surface understates a real perk. ionic_frontend/CHANGELOG.md's user-facing entry — deferred in Phase 5 for the same reason (see the[Unreleased]engineering entry inCHANGELOG.md) — is likewise due at first ramp, not at Stage 3 (see the note under Stage 3 below).
Promote OBJ-4058 to todo and land the ionic_frontend/CHANGELOG.md entry before executing Stage 1's PostHog config change.
Stage 1 — Internal/QA (PostHog cohort filter)
- PostHog config: PostHog → Feature Flags →
streak_insurance_enabled→ Conditions → add a condition matchingis_staff = true→ 100% rollout for that condition only. Leave the flag's general rollout at the synced default (disabled / 0%). - Audience: internal/QA accounts with an active Supporter tier (the demo account is a permanent paid Supporter — see
docs/development/gotchas.md's "Demo account is a permanent paid Supporter" entry — useful for exercising the eligible states, but structurally cannot exercise the non-Supporter states; use a second free-tier account for those). - Goal: exercise all eight UI-SPEC states (S1–S8) against real accounts — including the D8 case (a goal that already spent its lifetime XP repair, insurance-only) and the cross-lane guard (a claim never touching
streak_repaired_count). - How to enable: requires a
feature_flag:write-scoped PostHog personal API key. Always go through the PostHog UI or the sync script for flag state changes — never hand-roll aPATCHagainst the API (per Feature Flags § Credential gotchas).
Stage 2 — Limited rollout (percentage)
- PostHog config:
streak_insurance_enabled→ bump generalrollout_percentageto a small slice (e.g. 5–10%) while keeping the Stage 1 internal/QA condition active. - Audience: a limited slice of Supporters (non-Supporters see no change at any percentage — the card differs only for an eligible Supporter).
- Goal: validate claim volume, the monthly-allowance unique-index race guard under real concurrency, and the
supporter_upgrade_prompt_tappedfunnel (S6's upgrade line) against organic usage. Watch Sentry + the rollback signal triggers below. - Gate to advance: no rollback-signal trigger fires for at least 48h at this rollout percentage.
Stage 3 — Public (100% rollout) — not performed as part of Phase 5
- Precondition: clean Stage 2 window (above) and a green Sentry error-rate check on
Gamification::ClaimStreakInsuranceandStreakRepairOfferResolver. - How to enable (a human executes this): PostHog →
streak_insurance_enabled→ set generalrollout_percentageto 100%. - Audience: all Objectuve users (the surface itself only changes behavior for Supporters with an eligible break).
- By this stage: the user-facing
ionic_frontend/CHANGELOG.mdentry and the OBJ-4058user-guide.mdcorrection (see § Pre-ramp prerequisite above) must already be live — both were due at first ramp (Stage 1), not deferred to Stage 3 — do not pre-announce the perk to users before it's reachable.
Rollback procedure
- Disable the flag (PostHog → Feature Flags →
streak_insurance_enabled→ set rollout to 0% /active: false). Effect is global within the flag's normal propagation window; both frontend and backend evaluate fail-closed. NeverPATCHthe PostHog API directly — use the UI or the sync script. - Verify: confirm the Dashboard repair card renders without an insurance lane or upgrade line for a Supporter test account, and that
claimStreakInsurancereturns aforbiddenGraphQL error regardless of tier. - Disabling the flag does not touch already-created
StreakInsuranceClaimrows or already-restored streaks — it only stops new claims and hides the surface. Phases 1–2 have no other caller ofClaimStreakInsurance, so nothing else is affected. - No database rollback is needed — the claim table and its unique index are additive and were live (inert) since Phase 1; nothing about them depends on the flag.
- Notify Slack
#engineeringof the rollback and root cause.
Rollback signal triggers
- Sentry error rate on
Gamification::ClaimStreakInsuranceorGamification::StreakRepairOfferResolverexceeds 1% of invocations over a 15-minute window. - Any confirmed report of a claim consuming the free XP-repair allowance (
streak_repaired_countincremented by an insurance claim) — this is the D5 hard constraint; any occurrence is a correctness bug, not a tuning signal, and should roll back immediately. - Any confirmed report of a double claim in one calendar month for the same user (the unique index on
(user_id, period_key)should make this structurally impossible; an occurrence means the guard has a real gap). - Any confirmed report of the free XP-repair card, proactive freeze, or Streak Mercy behaving differently with the flag on than with it off — the hard constraint this milestone must not violate (D1/D3).
Appendix — flag key + observability quick reference
| Surface | Wrapper | Reads from |
|---|---|---|
| Frontend | isFeatureFlagEnabled('streak_insurance_enabled') / useFeatureFlag('streak_insurance_enabled') (ionic_frontend/src/lib/featureFlags.ts) | PostHog client (posthog.isFeatureEnabled) |
| Backend | FeatureFlagService.enabled?('streak_insurance_enabled', user:) (rails_api/app/services/feature_flag_service.rb) | PostHog server-side via POSTHOG_CLIENT |
| Dev override | localStorage.setItem('objectuve_streak_insurance_enabled_override', 'on' | 'off' | 'auto') | local browser (development mode only) |
| Observability | Where |
|---|---|
| Sentry — Streak Insurance errors | Project filter: transaction: ClaimStreakInsurance or StreakRepairOfferResolver |
| PostHog — flag state | https://us.posthog.com/project/368400/feature_flags → streak_insurance_enabled |
| Domain events | gamification.streak_repaired with via: 'insurance' in the payload distinguishes an insurance claim from an XP repair (payload has no via: key at all) — see DDD § Domain Events |
| Success metric | "30%+ of Supporters use streak insurance at least once" — no PostHog funnel exists yet for this metric; building the dashboard is explicitly out of scope for this milestone (ROADMAP § Open questions 2) |