Qontak One | Billing & Subscriptions | Billing Expired Handling
Template: NEW PRD v1.2 · Bifrost Squad
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Addo Hernando |
| PRD Version | 1.0 |
| Status | DRAFT |
| PRD Type | NEW |
| Epic | BIF-8640 |
| Squad | Bifrost |
| RFC Link | N/A — no RFC required |
| Figma Master | N/A — no new screens |
| Anchor | No — standalone, single-squad |
| Labels | epic:qontak-one | module:billing-subscriptions | feature:billing-expired-handling |
| Last Updated | 2026-06-22 |
✅ Reformat Complete — No Flags
All mandatory sections were filled. This PRD meets the same standard as a write-prd output.
Table of Contents
- HEADER BLOCK
- 3. One-liner + Problem
- 4. What Happens If We Don't Build This
- 5. Target Users + Persona Context
- 6. Non-Goals
- Scope Changes
- 7. Constraints
- 8. Feature Changes
- 9. New Features
- 10. System Flow + User Stories + ACs
- 11. Rollout
- 12. Observability
- 13. Success Metrics
- 14. Launch Plan & Stage Gates
- 15. Dependencies
- 16. Key Decisions + Alternatives Rejected
- 17. Open Questions
- PRD CHANGELOG
3. One-liner + Problem
One-liner: A limited-access mode for expired Qontak One accounts that restricts operational features while preserving access to essential data and subscription renewal.
Problem: When a Qontak One client's subscription expires, the system immediately hard-locks all users out of their account — including access to historical reports and the billing page needed to renew. This abrupt lockout affects approximately 50 CIDs per month, with around 20% (10 clients/month) raising a CS support ticket to request manual data access. The resulting support burden, combined with the negative user experience at a critical renewal moment, creates churn risk and undermines the Qontak One launch.
4. What Happens If We Don't Build This
- Users are abruptly locked out of their accounts on expiry, preventing access to historical data and reports — driving ~10 CS tickets/month for manual data retrieval.
- The Customer Support team continues to bear the manual operational cost of handling expired-account data requests, with no self-service path available.
- The negative offboarding experience at subscription expiry creates churn risk and damages the Qontak One brand perception ahead of and after launch.
5. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — Qontak One User (expired CID) | Any user in a Qontak One CID (unified_app = true) where billing_status = expired | Access historical data and manage subscription renewal after expiry | Immediately hard-locked out — cannot access reports, history, or the billing page to renew | Contacts Customer Support to request data retrieval; manually works with AM/CS to process renewal |
| Secondary — Qontak Internal CS Agent | Qontak internal Customer Support agent handling expired-account requests | Resolve expired-account data requests without manual intervention | ~10 support tickets/month from expired CIDs requesting data access; high manual effort per ticket | Manually retrieves and sends data on behalf of the expired client; coordinates renewal with AM |
(See Section 7 — Constraints for plan availability and feature flag scope.)
6. Non-Goals
- We will not build a new UI for the subscription-end notification banner — the existing banner component is reused as-is (see Open Question #3 for a discovered risk on this assumption).
- We will not change or create new dunning or warning email sequences as part of this project.
- We will not build a feature to allow expired users to export their data while in the limited-access mode.
- CIDs with
unified_app = false(non-Qontak One clients) are not in scope — they are unaffected by this feature. - Accounts with
billing_status = freeze(thefreezestatus inorganization_packages/company_accounts.status = freeze) are not in scope — they continue to follow the current hard-block behavior.
Scope Changes
- Backend —
billing_statusexpiry event handling; permission key evaluation logic againstshow_when_billing_expiredcolumn;billing_expired_limited_accessflag enforcement - Frontend — Banner trigger on
billing_status = expired; hide/disable UI elements per permission key evaluation - Mobile — Permission key enforcement on mobile (via Chat and CRM existing permission key implementation)
7. Constraints
| Field | Value |
|---|---|
| Platform | Web + Mobile — behavior determined by how each module (Chat, CRM, etc.) implements the permission_key enforcement |
| Performance | Permission key evaluation ≤ 100ms server-side. Banner render ≤ 500ms on page load. |
| Plan scope | Qontak One clients only (unified_app = true) — applies to all tiers upon billing_status = expired. Non-Qontak One CIDs (unified_app = false) are unaffected. |
| Feature flag | billing_expired_limited_access | system-triggered per account when billing_status = expired. Default: OFF. This flag does not yet exist in the Flipper/Preference system (moderator-be) or qontak-preferences service (launchpad) — it is a net-new addition. |
| Read/write | Determined per permission_key by show_when_billing_expired column: TRUE = access retained; FALSE = feature hidden/disabled for all users in the CID regardless of role. This column does not yet exist on any permissions table — it is a net-new schema addition (see Dependencies). |
| Billing status source | billing_status is derived from organization_packages.status (qontak-billing) → propagated to company_accounts.status (moderator-be, enum value 4 = expired) via BuildCache service. The unified_app flag is returned by the ModPanel API (GetUnifiedBillingResponse.UnifiedApp), not stored in a local DB field in launchpad. |
| Activation cadence | DailyDeactivatePackage cron (qontak-billing) runs once per day. Status transitions (active → grace → expired) happen in batch — limited-access activation may lag up to 24 hours after the exact subscription end timestamp. Real-time activation is not guaranteed without an additional event trigger. |
| Grace period | qontak-billing has a 7-day grace period between active and expired (active → grace → expired). It is an open question (see Open Question #4) whether limited-access activates at grace or only at expired. |
8. Feature Changes
Change ID: CHG-001 — Subscription-End Banner (Global)
| Field | Detail |
|---|---|
| Change Type | Modified behavior — existing banner component triggered by new condition |
| Scope | All pages across the Qontak One web and mobile app |
| Page Intent | Any page a user navigates to after their CID's subscription expires |
| Before | • Banner is not shown — account is active, billing_status = active |
| After | • Persistent subscription-end banner is rendered at the top of all pages for all users in a CID where unified_app = true AND billing_status = expired• Banner includes a renewal CTA navigating to the subscription page |
Figma: N/A — existing banner component; no design changes required subject to Open Question #3.
Technical note (from repo exploration): The current
expired.vueinqontak-launchpad-feis a standalone full-page component (layout:landing) — not an inline reusable banner that can be inserted at the top of other pages. It contains static content (WhatsApp links, pricing cards, data retention copy) and has no props. If the intent is to show a persistent top-of-page banner while keeping the existing page visible beneath it, a new banner component must be built or extracted from this page. Engineering must validate in the discovery spike (Open Question #3) whether this constitutes design work before implementation begins.
Change ID: CHG-002 — Module Permission Enforcement (Global)
| Field | Detail |
|---|---|
| Change Type | Modified behavior — permission key evaluation extended to check billing_status |
| Scope | All modules across the Qontak One app |
| Page Intent | Any module page a user navigates to in a CID with billing_status = expired |
| Before | • All permission_keys are active for all users in the CID regardless of billing_status |
| After | • permission_keys where show_when_billing_expired = FALSE are hidden/disabled for all users• permission_keys where show_when_billing_expired = TRUE remain accessible (read access preserved)• Full list of keys managed via the permission key sheet |
| Permission Key Example | show_when_billing_expired | Behavior on Expiry |
|---|---|---|
usman_roles_manage | FALSE | Hidden — all users cannot edit roles |
tasks_general_upload | FALSE | Hidden — all users cannot upload tasks |
subscriptions_general_view | TRUE | Accessible — all users can open subscription page |
deals_general_export | TRUE | Accessible — all users can export deals |
Figma: N/A — permission enforcement is behavioral; no new UI components.
Technical note (from repo exploration): The
show_when_billing_expiredboolean column does not exist on any permissions table today. It must be added as a new schema change to:
permissionstable in moderator-be (fields:name,alias,feature— newshow_when_billing_expired booleanneeded)- Potentially
permission_component_codestable in qontak-launchpad (permission_key,component_codes jsonb— schema ownership is an open question; see Open Question #5)The permission check middleware in qontak-launchpad (
internal/pkg/middleware/permission_check.go) currently evaluates permission_keys against static route/method mappings with an "everything" level check. It does not evaluatebilling_status. A new billing-status check layer must be added to this middleware (or as a separate middleware) that reads theshow_when_billing_expiredvalue for each key and applies restrictions whenbilling_status = expired. Redis caching of the billing state (keyed by company ID) is already the pattern used inbillings/get_unified_billing.goand should be reused here to meet the ≤100ms constraint.
9. New Features
N/A — no new screens. All UI changes are permission-key-driven modifications to existing screens (Section 8) and reuse of the existing subscription-end banner component.
10. System Flow + User Stories + ACs
10.1. System Flow
Flow: Billing Expiry → Limited Access Mode Activation Type: State Lifecycle
- Subscription end date passes →
DailyDeactivatePackagecron (qontak-billing, runs once/day) transitionsorganization_packages.status:active → grace(7-day window), thengrace → expired. - qontak-billing notifies ModPanel (
ModPanelUpdateStatus()); moderator-beBuildCacheservice propagates this tocompany_accounts.status = expired(enum value 4) and updates the Redis billing cache. - System checks
unified_appflag for the CID via ModPanel API (GetUnifiedBillingResponse.UnifiedApp). Ifunified_app = false→ no change; current behavior applies. - If
unified_app = trueANDbilling_status = expired→billing_expired_limited_accessflag is auto-enabled for the CID (Flipper/Preference system in moderator-be; qontak-preferences service in launchpad). - System evaluates all
permission_keys against theshow_when_billing_expiredcolumn (new column onpermissionstable, moderator-be). permission_keys whereshow_when_billing_expired = FALSE→ marked as disabled for all users in the CID (role-agnostic).permission_keys whereshow_when_billing_expired = TRUE→ remain active for all users.- User logs in or navigates to any page → system checks
billing_statusfor the CID (Redis cache first, fallback to ModPanel API). - If
billing_statuscheck fails server-side → fail-closed: treat asexpired, apply restriction. - Subscription-end banner is rendered persistently on all pages with a renewal CTA.
- User clicks renewal CTA → navigates to the subscription page (
subscriptions_general_view = TRUE). - User completes renewal →
billing_statustransitions back toactive. billing_expired_limited_accessflag disabled → allpermission_keys restored; banner removed.
Frozen accounts: If
billing_status = freeze(company_accounts.status = freeze, ororganization_packages.status = "freeze"), the current hard-block behavior applies — limited-access mode is NOT triggered. (Note: the PRD previously used "suspended" — the actual system status value isfreeze.)Grace period: The system has a 7-day
graceperiod betweenactiveandexpired. Whether limited-access mode activates atgraceor only atexpiredis an open question (see Open Question #4).Activation timing: Because
DailyDeactivatePackageis a once-daily batch job, there may be up to a 24-hour lag between the exact subscription end timestamp and limited-access activation. See Open Question #6.
📊 System Flow — Billing Expired Handling
graph TD
A([Subscription end date passes]) --> A1[DailyDeactivatePackage cron: active → grace → expired]
A1 --> A2[qontak-billing notifies ModPanel → moderator-be BuildCache updates company_accounts.status]
A2 --> B{unified_app = true?\nvia ModPanel API}
B -- No --> C([Current behavior — unaffected])
B -- Yes --> D[Auto-enable billing_expired_limited_access flag\nFlipper/Preference system]
D --> E[Evaluate all permission_keys vs show_when_billing_expired column]
E --> F[FALSE keys: disabled for all users]
E --> G[TRUE keys: remain accessible]
F & G --> H([User navigates to any page])
H --> I{billing_status check OK?\nRedis cache → ModPanel API fallback}
I -- Fail --> J[Fail-closed: apply expired restrictions]
I -- OK, expired --> K[Render subscription-end banner + enforce permission restrictions]
I -- OK, active --> L([Normal access])
K --> M{User clicks renewal CTA?}
M -- Yes --> N([Navigate to subscription page])
N --> O{Renewal completed?}
O -- Yes --> P[billing_status = active → restore all permissions, remove banner]
O -- No --> K
M -- No --> K
10.2. User Stories
| User Story | Importance | Mockup / Technical Notes | Acceptance Criteria |
|---|---|---|---|
| [BEH-S01] — Restrict permissions based on billing_status = expired As a user in a Qontak One CID, I want to retain access to essential modules when my subscription expires, so that I can view historical data and manage my renewal without contacting support. | Must Have | Figma: N/A — existing screens, behavioral change only. Data Fields: • billing_status (string, required) — billing engine• unified_app (boolean, required) — CID config• permission_key (string, required) — per-module• show_when_billing_expired (boolean, required) — permission sheetBefore-After Behavior: Before: all permission_keys active regardless of billing_status. After: permission_keys with show_when_billing_expired = FALSE are hidden/disabled for all users when billing_status = expired AND unified_app = true. | — Happy Path — • AC-1: Given a CID with unified_app = true AND billing_status = expired, when a user navigates to a module with a permission_key where show_when_billing_expired = FALSE, then that permission_key is hidden and the associated action is disabled for all users regardless of role.• AC-2: Given a CID with unified_app = true AND billing_status = expired, when a user navigates to a module with a permission_key where show_when_billing_expired = TRUE, then that permission_key remains visible and the action is accessible.• AC-3: Given a CID with billing_status = active OR unified_app = false, when a user navigates to any module, then all permission_keys remain active as normal — no restrictions applied.— Error / Unhappy Path — • ERR-1: Given a CID with unified_app = true, when the billing_status check fails server-side (service unavailable), then the system fails-closed — all permission_keys where show_when_billing_expired = FALSE are treated as disabled.• ERR-2: Given a CID with unified_app = true AND billing_status = expired, when a user directly navigates to a restricted URL, then access is denied and the user sees a prompt to renew — not a generic error page.— Permission Model — • CAN: All users in an expired CID ( unified_app = true) may access features where show_when_billing_expired = TRUE.• CANNOT: No user — regardless of role (admin, agent, supervisor) — may access features where show_when_billing_expired = FALSE when billing_status = expired.• Unauthorized: Feature action button is hidden — not shown as disabled. No 403 error surfaced to the user. — UI States — • Loading: Permission state evaluated server-side before page render — no flash of restricted content. • Empty: N/A — existing pages. • Error: Fail-closed applied; restricted state shown with renewal prompt. • Success: Correct permissions applied per show_when_billing_expired value.— Negative Scenarios — • NEG-1: Given a user in an expired CID ( unified_app = true), when they attempt to perform an action tied to a permission_key where show_when_billing_expired = FALSE, then the action button is not rendered — user cannot trigger the action.• NEG-2: Given a CID with billing_status = freeze, when a user attempts to log in, then the current hard-block behavior applies — limited-access mode is not triggered. |
| [BEH-S02] — Display subscription-end banner on expired CID As a user in an expired Qontak One CID, I want to see a clear notification about my account status, so that I understand why features are restricted and know how to renew. | Must Have | Figma: N/A — existing banner component reused; no design changes. Data Fields: • billing_status (string, required) — billing engine• unified_app (boolean, required) — CID configBefore-After Behavior: Before: banner not shown (billing_status = active). After: existing subscription-end banner rendered persistently on all pages when billing_status = expired AND unified_app = true. | — Happy Path — • AC-1: Given a CID with unified_app = true AND billing_status = expired, when any user navigates to any page in the app, then the existing subscription-end banner is displayed persistently at the top of the page.• AC-2: Given a CID with unified_app = false OR billing_status ≠ expired, when a user navigates to any page, then the subscription-end banner is NOT displayed.• AC-3: Given a CID with billing_status = freeze, when a user attempts to log in, then the current hard-block behavior applies and the subscription-end banner is not shown.— Error / Unhappy Path — • ERR-1: Given a CID with unified_app = true AND billing_status = expired, when the banner component fails to render, then the page still loads normally with permission restrictions still enforced — banner failure does not block the limited-access state.— Permission Model — • CAN: All users in an expired CID ( unified_app = true) see the subscription-end banner — role-agnostic.• CANNOT: N/A — banner is view-only, no write action required. • Unauthorized: N/A. — UI States — • Loading: Banner renders with page load — no additional delay beyond normal page render. • Empty: N/A. • Error: Banner fails gracefully — page and permission enforcement still function correctly. • Success: Banner visible and persistent on all pages for all users in the expired CID. |
| [BEH-S03] — User clicks renewal CTA → navigates to subscription page As a user in an expired Qontak One CID, I want to click a renewal CTA in the banner, so that I can navigate directly to the subscription page and complete my renewal without contacting support. | Must Have | Figma: N/A — existing banner CTA and subscription page reused. Data Fields: • billing_status (string, required) — billing engine• unified_app (boolean, required) — CID config• subscriptions_general_view permission_key — show_when_billing_expired = TRUEBefore-After Behavior: Before: no in-app renewal path for expired users — they must contact CS/AM. After: banner CTA navigates user directly to the subscription page, which is fully accessible in expired state. | — Happy Path — • AC-1: Given a user in an expired CID sees the subscription-end banner, when they click the renewal CTA, then they are navigated directly to the subscription/billing page. • AC-2: Given a user is on the subscription page in an expired CID, when they view the page, then the page is fully accessible ( subscriptions_general_view has show_when_billing_expired = TRUE).• AC-3: Given a CID where billing_status transitions from expired back to active (after successful renewal), when a user navigates to any page, then the limited-access banner is removed and all permission_keys are restored to their active state.— Error / Unhappy Path — • ERR-1: Given a user clicks the renewal CTA, when the subscription page fails to load, then the user sees an error message with a retry option — not a blank or broken page. — Permission Model — • CAN: All users in an expired CID may click the renewal CTA and access the subscription page. • CANNOT: N/A. • Unauthorized: N/A. — UI States — • Loading: CTA click triggers navigation immediately. • Empty: N/A. • Error: Subscription page load failure shows retry option. • Success: User lands on subscription page and can proceed with renewal. — Negative Scenarios — • NEG-1: Given a user in an expired CID, when they attempt to use an operational feature (e.g., create a deal, upload a task), then the action is hidden per the permission sheet — the renewal CTA is the only actionable path forward. |
11. Rollout
| Field | Value |
|---|---|
| Feature flag | billing_expired_limited_access — default: OFF; auto-enabled per CID when billing_status = expired AND unified_app = true |
| Stage 1 — Internal QA | Bifrost team test CIDs with unified_app = true and forced billing_status = expired |
| GA | General release — all Qontak One CIDs (unified_app = true); flag auto-triggers on billing_status = expired |
| Backward compat | Yes — active accounts (billing_status = active) are completely unaffected |
| Migration | Existing hard-locked expired accounts (unified_app = true) will enter limited-access mode at GA — billing_expired_limited_access flag triggered on next billing_status evaluation |
12. Observability
Key Events:
| Event Name | Trigger | Properties |
|---|---|---|
billing_expired_limited_access_activated | billing_status = expired AND unified_app = true — system enables limited-access mode for CID | company_id, billing_status, timestamp |
billing_expired_limited_access_deactivated | CID renews — billing_status transitions back to active | company_id, timestamp |
billing_expired_banner_rendered | Subscription-end banner renders on page load for an expired CID | company_id, user_id, page_url, timestamp |
billing_expired_renewal_cta_clicked | User clicks the renewal CTA in the banner | company_id, user_id, timestamp |
billing_expired_fail_closed_triggered | billing_status check fails server-side — fail-closed restriction applied | company_id, error_reason, timestamp |
billing_expired_permission_blocked | Weekly aggregated count of restricted permission attempts per CID | company_id, attempt_count, top_permission_keys[], week_start_date |
| Field | Detail |
|---|---|
| Dashboard owner | Bifrost team |
| Alert 1 | billing_expired_fail_closed_triggered count > 10 in any 1hr window → Slack: #bifrost-alerts (billing_status check service may be degraded) |
| Alert 2 | billing_expired_limited_access_activated fires but billing_expired_limited_access_deactivated never follows for the same company_id within 30 days → Slack: #bifrost-alerts (potential stuck expired state) |
12.1 Post-Launch Monitoring Cadence
| Field | Detail |
|---|---|
| Review cadence | Weekly for first 4 weeks post-GA, then monthly |
| Owner | Bifrost PM |
| Review scope | billing_expired_renewal_cta_clicked rate, billing_expired_fail_closed_triggered count, CS ticket volume tagged "expired account" |
| Trigger threshold 1 | billing_expired_fail_closed_triggered > 5% of total billing_expired_limited_access_activated events in any 7-day window → engineering investigation same day |
| Trigger threshold 2 | Renewal CTA click rate (billing_expired_renewal_cta_clicked / billing_expired_banner_rendered) drops below 10% in any 14-day window post-GA → PM review (users may not be finding the renewal path) |
| Rollback consideration | If fail-closed triggers exceed 20% and cannot be resolved within 24 hours, PM disables billing_expired_limited_access globally pending root cause fix |
13. Success Metrics
Adoption & Usage:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| ⭐ Support ticket reduction | % drop in CS tickets tagged "expired account data request" | ~10 tickets/month (20% of ~50 monthly expired Qontak One CIDs) | >80% reduction within 60 days of GA |
Efficiency & Impact:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| Renewal CTA click rate | % of users in limited-access state who click the renewal CTA at least once | N/A — new metric, no historical data | Establish baseline in first 30 days post-GA |
| Time-to-renew | Days from billing_status = expired to account reactivation | N/A — not currently measured | Track trend; target reduction vs. pre-release cohort after 60 days |
14. Launch Plan & Stage Gates
| Stage | Audience | Duration | Success Gate to Advance | Owner |
|---|---|---|---|---|
| Internal QA | Bifrost team test CIDs (unified_app = true, forced billing_status = expired) | 1–2 weeks | 0 P0/P1 bugs. All 3 story ACs pass. Permission key list verified against sheet. Banner renders correctly. Fail-closed behavior confirmed. | PM + QA |
| GA | All Qontak One CIDs (unified_app = true) on billing_status = expired | Ongoing | Internal QA gate cleared. CS/AM teams briefed on new limited-access behavior — no longer need to manually handle data requests for expired Qontak One accounts. | PM + Bifrost |
15. Dependencies
| Dependency | Owning Team | Repo / Service | Deliverable Needed | Blocking? |
|---|---|---|---|---|
billing_status = expired reliably triggered | Bifrost (billing engine) | qontak-billing — DailyDeactivatePackage cron | Confirmed active → grace → expired transitions working for Qontak One CIDs; ModPanel notified correctly via ModPanelUpdateStatus() | YES |
billing_status propagated to moderator-be | Bifrost | moderator-be — BuildCache service; company_accounts.status enum | company_accounts.status = expired (enum 4) set reliably when billing transitions, cached in Redis billing layer | YES |
show_when_billing_expired column — schema | Bifrost Eng | moderator-be — permissions table | New boolean column show_when_billing_expired added to permissions table; default value defined; backfilled for all existing permission_keys before dev start | YES |
show_when_billing_expired column — values finalized | PM + CS/AM | Google Sheet → permissions table | Full list of permission_keys with TRUE/FALSE values locked and reviewed before dev start — sheet | YES |
billing_expired_limited_access feature flag created | Bifrost Eng | moderator-be — Flipper/Preference system; qontak-launchpad — qontak-preferences service | Net-new flag registered in both services; company-scoped enable/disable working | YES |
unified_app flag available via API | Bifrost | qontak-launchpad — billings/get_unified_billing.go; ModPanel API | GetUnifiedBillingResponse.UnifiedApp reliable; Redis caching in place for performance | YES |
| Permission middleware extended for billing status | Bifrost Eng | qontak-launchpad — internal/pkg/middleware/permission_check.go | New billing-status check layer added to permission middleware; reads show_when_billing_expired from cache | YES |
| Subscription-end banner component | Bifrost / Frontend | qontak-launchpad-fe | Validate whether a reusable inline banner component exists or must be built from expired.vue page — see Open Question #3 | NO (blocks CHG-001 scope confirmation) |
16. Key Decisions + Alternatives Rejected
Decisions Made
| Date | Decision | Rationale |
|---|---|---|
| Dec 2025 | Reuse existing subscription-end banner — no new UI | Faster delivery; existing component already handles the visual notification; no design work required |
| Dec 2025 | Use existing permission_key system to enforce access | Avoids a parallel access-control layer; leverages existing enforcement mechanism across all modules |
| Dec 2025 | Scope to unified_app = true (Qontak One) only | Non-Qontak One clients have a different billing lifecycle and are not part of this initiative |
| Dec 2025 | Permission enforcement is role-agnostic — all roles follow the show_when_billing_expired column | Simplifies implementation; all users in an expired CID are equally affected by the subscription state |
| Jun 2026 | billing_status = freeze follows current hard-block behavior — limited-access mode applies to expired only | Suspended accounts have a different lifecycle and are not in scope for this initiative |
Alternatives Rejected
| Alternative | Why Rejected | Date |
|---|---|---|
| Keep current hard lockout for expired accounts | Creates negative UX, drives ~10 CS tickets/month, and jeopardizes Qontak One launch perception | Dec 2025 |
| Full access grace period (7–30 days post-expiry) | Removes renewal urgency; does not address the core problem of uncontrolled access and does not create a clear CTA path | Dec 2025 |
| Build new dedicated "expired account" UI page | Over-engineering — existing banner + permission system is sufficient for this phase; new UI adds design and dev cost without proportional benefit | Dec 2025 |
17. Open Questions
| # | Type | Question | Owner | Deadline | Mitigation |
|---|---|---|---|---|---|
| 1 | Risk | permission_key list is more complex than anticipated — unintended side effects may block critical paths (e.g. billing page accidentally restricted) | PM + Bifrost Eng | Before dev starts | Engineering audits all permission_keys against the sheet before implementation begins. PM + CS/AM validate show_when_billing_expired values with a dry-run checklist. |
| 2 | Risk | Existing banner component is not flexible enough to be triggered by billing_status = expired without additional UI work | Bifrost Frontend | Discovery phase | Design + Engineering validate banner reusability in a technical discovery spike before committing to implementation. If inflexible, scope a minimal banner variant. |
| 3 | Blocker — Frontend | Banner component architecture: The current expired.vue in qontak-launchpad-fe is a standalone full-page component (layout: landing), not an inline reusable banner. It cannot be inserted at the top of other pages as-is. Is there a separate banner component available in another service (e.g. hub-chat, hub-service), or does one need to be built? If it needs to be built, this invalidates the "Figma: N/A — no new screens" assumption in CHG-001 and adds design scope. | Bifrost Frontend | Discovery phase | Scope a technical spike in the frontend to locate or prototype a minimal inline banner component before committing to CHG-001 as a no-design-work change. |
| 4 | Decision — Grace Period | Limited-access activation timing: qontak-billing has a 7-day grace period between active and expired (active → grace → expired). Should limited-access mode activate when status = grace (7 days before hard expiry) or only when status = expired? Activating at grace gives users more time to see the banner and renew; activating only at expired is a cleaner cut and avoids restricting users who will likely renew in the grace window. | PM + Bifrost | Before dev starts | Default proposal: activate only at expired, not grace. Grace period is a billing buffer — users still have active status semantically. Revisit if CS data shows most churn happens during grace. |
| 5 | Decision — Schema Ownership | show_when_billing_expired column location: The permission system spans two repos — permissions table in moderator-be (Ruby) and permission_component_codes table in qontak-launchpad (Go). Which service is the canonical owner of show_when_billing_expired config? If moderator-be owns it, launchpad must fetch it via API or a shared cache. If launchpad owns it locally, moderator-be must also be updated or removed from this feature's scope. | Bifrost Eng | Before dev starts | Engineering decision spike needed. Proposed: moderator-be as canonical owner (it already owns the full permissions table), exposed to launchpad via the existing permission check API response. |
| 6 | Risk — Activation Lag | Cron cadence gap: DailyDeactivatePackage (qontak-billing) runs once per day. A CID whose subscription ends at noon may not enter limited-access until the next day's cron run — a gap of up to 24 hours. Is this acceptable? If not, a real-time event trigger (e.g. Kafka event from qontak-billing on status transition) is needed to activate limited-access mode promptly. | PM + Bifrost | Before dev starts | Acceptable if the grace period (7 days) already provides sufficient buffer — a 24h cron lag within a 7-day grace window is low-risk. If limited-access activates at expired (no grace), the lag matters more. Decision links to Open Question #4. |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.0 | 2026-06-22 | Claude | All | REFORMATTED | Reformatted from legacy Confluence PRD (Dec 2025) to Qontak PRD template v1.2. 12 sections extracted or filled via coaching interview; 1 flag remaining (S12 Observability). |
| 1.1 | 2026-06-23 | Claude | S12 | FILLED | Filled S12 Observability: 6 events defined, dashboard owner set to Bifrost team, 2 alerts added (fail-closed rate + stuck expired state at 30 days), post-launch monitoring cadence added. billing_expired_permission_blocked aggregated weekly per CID. Flag cleared. |
| 1.2 | 2026-06-23 | Claude | S6–S8, S10, S15, S17 | ENRICHED | Technical enrichment from repo exploration (moderator-be, qontak-launchpad, qontak-launchpad-fe, qontak-billing). Key changes: (1) Terminology fix — "suspended" → freeze throughout; (2) Constraints expanded: unified_app source is ModPanel API, show_when_billing_expired is net-new schema, billing_expired_limited_access flag is net-new, 24h cron activation lag noted, 7-day grace period documented; (3) CHG-001 technical note: expired.vue is a standalone page not an inline banner — design scope risk flagged; (4) CHG-002 technical note: permission middleware in launchpad needs new billing-status check layer, Redis cache reuse recommended; (5) System flow updated: DailyDeactivatePackage cron chain, grace period, ModPanel API, freeze account note; (6) Dependencies expanded to 8 rows with specific repos and deliverables; (7) 4 new open questions added (#3–#6: banner architecture, grace period activation, schema ownership, cron lag). |