RFC Review: Qontak One | Billing | Self-Subs — Phase 1
Companion review for
self-subs.md, produced by therfc-reviewerskill. Cumulative across cycles R1 → R1+iterate (R2); valid for the RFC revision inreviewed_rfc_last_updated.
Executive Summary
- Overall Score:
7.5/10 - Rating:
Strong - RFC Type:
full-stack - Sub-Type:
frontend: enhancement/backend: new-feature - Assessment Confidence:
High - Applied Caps/Gates: ROL was capped at 6.0 (R1) — deploy order not specified; cap lifted in R2 after deploy order matrix added. No other caps applied after iteration.
- Implementation Readiness Verdict:
PROCEED with notes — chunks 1, 2, 4, 5, 6 are fully executable now. Chunks 3, 7, 8, 9 are blocked on 3 external API contracts (Open Q1, Q3/Q4, Q7); those contracts are correctly flagged and are not spec gaps resolvable by the author alone. - Report Path:
bifrost/self-subs/rfcs/self-subs-review.md - RFC Author: addo.hernando@mekari.com | Reviewed: 2026-06-24 (R1 + iterate = R2)
An agent reading this RFC can implement chunks 1, 2, 4, 5, and 6 — the DB migration, BillingInfo interactor extension, modpanel toggle BE, BillingStore TypeScript extension, and PackageDetails eligibility scaffolding — without asking a question. For chunks 3, 7, and 8 (SelfSubscriptions proxy resource, CTA components, modal), the blocking gaps are exclusively external API contracts with Mekari Billing (Open Q1, Q4, Q7): exactly the right kind of external dependency that the RFC correctly flags as pre-implementation requirements rather than spec omissions. The biggest structural strength is the bidirectional PRD traceability matrix — every AC is mapped, every artifact is justified. The most honest gap remaining post-iteration is ACV (5.5): the two new proxy endpoints have placeholder contracts because Mekari Billing hasn't shared them yet; this is a real blocker on those chunks, not a fixable spec problem.
Quick Verdict
Why this RFC can be implemented agentically (for unblocked chunks):
- §1.A bidirectional traceability maps every PRD AC to specific files and endpoints; agent knows exactly what to build and why.
- Detail 2.0 Repo Reading Guide names every file, pattern, and command — agent can navigate both codebases without searching.
- Deploy order matrix (§4) + feature flag strategy (single
self_subs_enabledDB flag) + rollback recipe (§4.D) are concrete enough to execute without the author.
Why this RFC will cause agent guessing or rework (for blocked chunks):
POST /self_subscriptionsrequest body is{flow_type, ...additional params per A-2}— the...additional paramsis unresolvable until Mekari Billing shares their API contract (Open Q4). Agent would have to invent params.GET /invoicableresponse field names inPendingSOare proposed (so_id,amount,checkout_url) but flagged as pending Open Q1. Agent would need to update the TypeScript interface once the real response arrives.billing_versionexact string value for V3 (Open Q7) — agent writingbilling_version === 'v3'may be wrong.
Findings Ledger (carry-forward)
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | blocker | POST /self_subscriptions and GET /invoicable proxy contracts are placeholder — exact Mekari Billing request/response fields unknown | §2.4, Open Q1/Q4 | open | R1 | — | Blocked on external party; correctly flagged as Open Q1, Q3, Q4. Not fixable by RFC author. |
REV-2 | major | Deploy order not specified — cross-layer rollout scenario matrix absent | §4 | fixed | R1 | R2 (iterate) | Deploy order matrix added to §4 "Deploy Order and Cross-Layer Compatibility"; all 6 scenarios documented with safety analysis; cap lifted. |
REV-3 | major | TypeScript interfaces for new composables and PendingSOWarningModal props absent — agent cannot generate TypeScript without inventing types | §2.A | fixed | R1 | R2 (iterate) | Detail 2.A.1 added with UseInitiateSelfSubs, UseCheckInvoicable, PendingSOWarningModalProps, and PackageInfoEligibilityProps interfaces. |
REV-4 | major | Proxy call timeout/retry values absent — agent would use framework defaults (no timeout, no retry) | §2.4, §3 | fixed | R1 | R2 (iterate) | §2.4 API table extended with "Proxy timeout / retry" column: 5s/1-retry for initiation, 800ms/0-retry fail-open for invoicable. |
REV-5 | minor | BE structured log fields absent — "add company_id and flow_type" is too vague for agentic implementation | §3 Logging | fixed | R1 | R2 (iterate) | §3 Logging extended with structured log field table: 5 events with level, event name, and required fields. |
REV-6 | minor | Browser support matrix not stated — agent cannot know if new Web APIs are safe | §3 | fixed | R1 | R2 (iterate) | Detail 3.D NFS table added: inherits existing hub-chat matrix (Chrome/Safari/Firefox/Edge last 2; iOS Safari 16+); no new dependencies. |
REV-7 | minor | Migration table name uncertain — proposed migration DDL targets :organization_packages but this must be verified by grep before writing | §2.3 | accepted-risk | R1 | — | RFC already has pre-condition grep instruction in §2.3. Acknowledged; correct behavior is for agent to grep before writing chunk 1. |
REV-8 | minor | billing_version exact string value unknown (Open Q7) — FE comparison billing_version === 'v3' may be wrong | §2.A.1, Open Q7 | open | R1 | — | Blocked on Mekari Billing confirmation. Flagged in Open Q7 and TypeScript interface comment. |
Ledger summary: 2 open (1 blocker, 1 minor), 5 fixed (R1→R2), 1 accepted-risk. Open material findings REV-1 and REV-8 are promoted to RFC §5 Open Questions (already present as Q1/Q4 and Q7 respectively).
PRD → RFC Traceability Matrix
| PRD Element | RFC Section | Coverage |
|---|---|---|
| SS-S01 (V3 gate) | §1.A, Detail 1.C, §2.0, §2.4 | Full |
| SS-S02 (new subscription from trial) | §1.A, Detail 1.C, §2.2 Seq 1, §2.4 | Full |
| SS-S03 (renewal) | §1.A, Detail 1.C, §2.2 Seq 2, §2.4 | Full |
| SS-S04 (upgrade main package) | §1.A, Detail 1.C, §2.2 Seq 3, §2.4 | Full |
| SS-S05 (pending SO warning modal) | §1.A, Detail 1.C, §2.A, §2.A.1, §2.2 Seq 2 | Full |
| SS-S06 (disable self-renewal modpanel toggle) | §1.A, Detail 1.C, §2.3, §2.4, §4 | Full |
| PRD §6 Performance constraints | §3 Performance Requirement, §3.D NFS | Full |
| PRD §9 API/webhook behavior | §2.2 sequences, §2.4 APIs | Full |
| PRD §11 Rollout | §4 Rollout Strategy, §4 Deploy Order | Full |
| PRD §12 Observability | §3 Monitoring & Alerting, §3 Logging | Full |
| PRD §13 Success Metrics | §1 Success Criteria | Full |
| PRD OQ1 (invoicable statuses) | §5 Open Q1 | Full (flagged) |
| PRD OQ2 (SO failure reconciliation) | §5 Open Q2 | Full (flagged) |
| PRD OQ3 (proration basis) | §5 Open Q9 | Full (flagged) |
Summary: 14 of 14 PRD elements fully covered (including open questions). 0 RFC decisions without PRD justification.
Scorecard
Full-Stack Scorecard (18 categories)
| # | Category | Source | Score | Evidence-Based Rationale |
|---|---|---|---|---|
| 1 | PRT — PRD Traceability | Merged | 9.0 | FE: all 6 stories → component + composable in Detail 1.C; reverse matrix covers all new artifacts · BE: all 6 stories → endpoint in Detail 1.A; PRD Section Coverage table (§16 rows) leaves no PRD section unaddressed |
| 2 | TDC — Technical Decisions | Merged | 8.0 | FE: ADR-1 (eligibility source), ADR-6 (FE gating pattern), ADR-7 (webhook out of scope) — all closed with alternatives · BE: ADR-2 (DB flags), ADR-3 (new Grape namespace), ADR-4 (extend modpanel), ADR-5 (fail-open invoicable) — all closed · Minor drag: proxy timeout/retry was absent in R1 (now fixed REV-4); billing_version exact value is still open (REV-8) |
| 3 | CNT — Contract Specificity | FE | 7.5 | Detail 2.A.1 (R2) adds UseInitiateSelfSubs, UseCheckInvoicable, PendingSOWarningModalProps, PackageInfoEligibilityProps TypeScript interfaces · Data-fetching strategy in Detail 2.B: $customFetch, fetch timing per action, no caching · Minor drag: PendingSO field names flagged as pending Open Q1; no optimistic update spec (N/A for redirect flows) |
| 4 | SCB — Scope Boundaries | FE | 8.0 | Detail 2.D names every changed vs. unchanged file per system · Detail 4.C lists file paths per chunk · "NOT changed" list explicit: SubscriptionsLayout.vue, UsageDetail.vue, usePackageStore · Minor gap: moderator-be view path TBD (correctly blocked Open Q6) |
| 5 | DEP — Dependencies | FE | 8.0 | All 4 BE endpoints listed with availability status · @mekari/pixel3 confirmed · Feature flag confirmed as new DB column default false · No new npm packages · Figma frames noted as pending (design in progress) |
| 6 | NFS — Non-Functional | FE | 7.0 | Detail 3.D (R2) adds performance targets (≤2s, ≤1s), browser matrix (inherited hub-chat), bundle size (no new deps), responsive (MpButton default), i18n (English only Phase 1) · Accessibility in Detail 3.C: aria-modal, role, focus trap, focus return · Minor gap: WCAG level not explicitly stated; prefers-reduced-motion not addressed |
| 7 | TPS — Test Plan | FE | 7.0 | Concrete commands in Detail 4.B (pnpm test features/subscriptions/) · Per-chunk AC in Detail 4.C with verifiable criteria · Named scenarios per chunk (e.g., "CTAs hidden for non-V3", "CTA click triggers Mekari Billing call stub") · Missing: Given/When/Then format; no accessibility test scenarios; no visual regression plan |
| 8 | DMS — Data Model & Schema | BE | 6.5 | Migration DDL present with column names, types (boolean), NOT NULL, default false, named index · Pre-condition grep instruction explicit in §2.3 · No cardinality/volume estimates; no data retention policy; no example rows (minor for boolean flags) · Table name uncertainty accepted-risk (REV-7) |
| 9 | ACV — API Contract & Versioning | BE | 5.5 | Extended endpoints (GET /billings/info, PUT /modpanels/subscriptions) fully specified · New endpoints (POST /self_subscriptions, GET /invoicable) have placeholder request/response shapes blocked on Mekari Billing (REV-1) · Timeout/retry now in table (R2 fix) · Missing: idempotency key for POST /self_subscriptions; example payloads absent; rate limits absent |
| 10 | DIC — Data Integrity | BE | 7.0 | Hub-service proxy writes: disable_self_renewal boolean toggle on existing record — simple last-write-wins (acceptable) · POST /self_subscriptions creates no local DB state → no transaction concern · Idempotency for double-click: FE button disables on click (loading state), no backend idempotency key defined — minor gap if user uses back-button · §3 Security notes BE enforcement of disable_self_renewal at API layer |
| 11 | FMC — Failure Mode Coverage | Merged | 7.5 | FE: Detail 3.A covers 7 failure paths · Detail 3.B error catalog with 6 user-facing messages · Detail 2.C UI state matrix 5 states per surface · BE: proxy timeouts now defined (800ms invoicable, 5s initiation) · Seq 4 shows API failure path · Gap: 401 vs 422 vs 503 mapped to same generic toast (FE makes no distinction); double-click race condition on CTA not addressed |
| 12 | CSS — Concurrency & Scaling | BE | 6.5 | Proxy timeout now defined (R2 fix); 1 retry for initiation · No rate limits on new endpoints · No QPS targets · Low inherent concurrency risk (proxy calls, no shared state) · PRD OQ6 baseline: 200 manual renewals/month → low expected volume · Minor: no circuit breaker planned for Phase 1 (Risk 10, explicitly accepted) |
| 13 | SAS — Security & Authorization | BE | 8.0 | Auth scope per endpoint: all 4 specified · Tenancy isolation: "derive org_id from auth token, never from request body" (§3 Security) · BE enforcement of disable_self_renewal at API layer (not just FE) · Credentials server-side only · Gap: SSRF analysis for outbound proxy calls to Mekari Billing not mentioned; input validation per field not listed; rate limiting for new endpoints absent |
| 14 | ROL — Rollout & Rollback | Merged | 8.0 | FE: self_subs_enabled default false; 4-stage rollout with gates · BE: additive migration, no backfill; rollback 3-option recipe · Cross: deploy order matrix (R2 fix) — all 6 scenarios safe; single flag coordination; independent rollback confirmed · Config contract in Detail 4.A |
| 15 | OBS — Observability | Merged | 7.0 | FE: 7 named Mixpanel events with owners (§3 Monitoring) · BE: structured log field table (R2 fix) with 5 events, levels, required fields · Gap: no SLO targets; no alert thresholds; no dashboard reference; no trace spans across hub-service → Mekari Billing calls |
| 16 | SBC — Service Boundary | BE | 8.5 | New Grape namespace SelfSubscriptions clearly scoped to hub-service · Sync proxy to Mekari Billing (no new async coupling) · moderator-be boundary explicit · No new cross-service sync dependencies introduced within Qontak system |
| 17 | CPA — Pattern Alignment | Merged | 9.0 | FE: Pinia store pattern, $customFetch, @mekari/pixel3, composable pattern — all cited with reference files in Detail 2.0 Patterns table · BE: Grape resource + Interactors + route mounting + RSpec request spec — all cited with file:line references · Cross: snake_case in API responses consumed consistently by FE without transformation layer (confirmed by existing BillingStore field naming) |
| 18 | CDG — Compliance | BE | N/A | No PII in new columns (boolean flags); no payment data stored in hub-service; Qontak proxies the checkout URL but never stores card/bank data; no compliance trigger |
Overall (judgment): 7.5 — Strong. Held just below Agentic-Ready because ACV is 5.5 (two new endpoints have non-fillable contract gaps) and DMS is 6.5 (table name uncertainty). The strongest feature of this RFC is PRT + CPA — an agent can map every PRD AC to a file and follow every pattern from a named reference. The weakest is ACV, correctly and honestly blocked on external dependencies.
Resource & Cost Advisory
New DB columns (2 booleans on an existing high-read table) add negligible storage. Proxy calls to Mekari Billing add one outbound HTTP call per user-initiated billing action (expected volume: O(hundreds/month) at launch per PRD OQ6). No new infrastructure required.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| ADR-1 | Extend GET /billings/info for eligibility (vs. new endpoint) | Resolved | — |
| ADR-2 | Store flags in hub-service DB as new columns (vs. Mekari Billing attribute or FE flags) | Resolved | — |
| ADR-3 | New SelfSubscriptions Grape namespace (vs. extending existing billing resource) | Resolved | — |
| ADR-4 | Extend existing PUT /modpanels/subscriptions/:id (vs. new endpoint) | Resolved | — |
| ADR-5 | Non-blocking fail-open for Invoicable check (vs. blocking) | Resolved | — |
| ADR-6 | FE gating via billingStore.billing.* boolean fields (vs. usePackageStore code lookup) | Resolved | — |
| ADR-7 | Mekari Pay webhook out of scope for Phase 1 | Resolved | Open Q5 tracks the Phase 2 case |
Aggregate: 7 of 7 decisions Resolved. 0 Partial. 0 Dangling.
UI State Audit
| Component | Loading | Empty | Error | Partial | Success | Assessment |
|---|---|---|---|---|---|---|
PackageInfoComponent.vue CTA area | ✓ MpSpinner on clicked button | ✓ No CTAs (non-V3 or flag off) | ✓ Error toast | n/a | ✓ CTAs visible + redirect | 4/5 — partial N/A |
PendingSOWarningModal.vue | ✓ Spinner on Renew CTA during check | ✓ (modal not shown if empty list) | ✓ Fail-safe proceed | n/a | ✓ Modal with 2 CTAs | 4/5 — partial N/A |
DisableSelfRenewalToggle (moderator-be) | ✓ Skeleton noted | n/a | ✓ Error toast + revert | n/a | ✓ Confirmation toast | 3/5 — partial N/A; moderator-be skeleton not fully specified |
Summary: All primary states defined. "Partial" is N/A for single-value boolean flags and redirect flows — this is correct, not a gap.
Performance Budget Check
No explicit bundle size or Core Web Vitals targets (not a performance RFC). Detail 3.D states no new npm packages and performance targets from PRD (≤2s checkout, ≤1s invoicable check). Adequate for an enhancement RFC.
Accessibility Review
| Aspect | Specified? | Details | Assessment |
|---|---|---|---|
| Keyboard navigation | Partially | CTA buttons are MpButton (keyboard-accessible by default) | Adequate |
| Focus management (modal) | Yes | Focus trapping + return focus to Renew button on dismiss (Detail 3.C) | Adequate |
| ARIA labels | Yes | aria-modal + role="dialog" on modal (Detail 3.C) | Adequate |
| WCAG level | No | Not stated | Minor gap |
| Color contrast | No | Not stated (inherits pixel3 defaults) | Minor gap |
| prefers-reduced-motion | No | Not addressed | Minor gap |
Accessibility is partially specified — adequate for an enhancement using an existing design system. A11y regressions unlikely since all new surfaces use @mekari/pixel3 components.
Data Integrity Deep-Dive
| Write Path | Transaction Scope | Partial Failure Behavior | Idempotency Key | Consistency | Duplicate Handling |
|---|---|---|---|---|---|
PUT /modpanels/subscriptions/:id (disable_self_renewal) | Single-record update on existing model; within existing interactor's transaction scope | 422 returned; no partial state | n/a (idempotent boolean toggle) | Strong (DB write) | Last-write-wins (acceptable for boolean flag) |
POST /self_subscriptions → Mekari Billing proxy | No local DB write; proxy call only | hub-service returns 503; no Qontak-side state to roll back | NOT DEFINED — if client calls twice (back-button + retry), Mekari Billing may create 2 SOs | External (Mekari Billing owns) | Not defined for Phase 1; Open Q2 covers reconciliation |
| DB migration (additive columns) | DDL transaction; auto-committed by Rails | rails db:rollback available | n/a | Structural | n/a |
Gap: POST /self_subscriptions has no idempotency key. For an enhancement with low expected volume (O(hundreds/month)) this is acceptable risk for Phase 1; it should be promoted to a Phase 2 item.
Concurrency Collision Map
| # | Shared Resource | Writers | Collision Scenario | Resolution | Lock Failure | Assessment |
|---|---|---|---|---|---|---|
| 1 | disable_self_renewal column | Admin (PUT /modpanels) | Two admins toggle simultaneously | Last-write-wins | n/a | Adequate (boolean flag, no financial consequence) |
| 2 | Mekari Billing SO creation | Multiple client clicks | User clicks Renew twice → double POST /self_subscriptions | NOT DEFINED (no FE idempotency + no BE dedup) | n/a | Minor risk; FE button disabled on click mitigates; BE dedup absent |
API Contract Completeness Check
| Endpoint | Request Schema | Response Schema | Error Taxonomy | Auth Spec | Idempotency | Example Payloads | Assessment |
|---|---|---|---|---|---|---|---|
GET /api/core/v1/billings/info (extend) | complete (none) | complete (3 new fields named + typed) | complete (422 unchanged) | specific (5 scopes named) | n/a | no | 5/6 — no examples (minor) |
POST /api/core/v1/self_subscriptions (new) | partial (flow_type known; ...additional params blocked on Open Q4) | partial (checkout_url known; exact field name unconfirmed) | partial (422 + 503 defined; exact error body shape) | specific | missing | no | 3/6 — blocked externally |
GET /api/core/v1/self_subscriptions/invoicable (new) | complete (no params; company_id from auth) | partial (pending_sos shape proposed; field names pending Open Q1) | partial (fail-safe 200 defined; 422 defined) | specific | missing | no | 3/6 — blocked externally |
PUT /api/core/v1/modpanels/subscriptions/:id (extend) | complete (disable_self_renewal optional boolean) | complete (200/422) | complete | specific (:modpanel) | n/a | no | 5/6 — no examples (minor) |
Async Job / Event Consumer Spec
No async jobs introduced in this RFC. hub-worker is explicitly out of scope (§2.D). N/A.
Compliance Trigger Check
| Trigger | Found? | Assessment |
|---|---|---|
| PII | No | New columns are boolean flags, no personal data |
| Payment data | No | Qontak proxies checkout_url but never stores card/bank data |
| Auth/session | No | Auth token used to derive organization_id but not stored or logged |
CDG Status: N/A — no compliance triggers.
Cross-Layer Contract Verification
| Endpoint | Backend Response Schema | Frontend Expected Schema | Match? | Gaps |
|---|---|---|---|---|
GET /api/core/v1/billings/info | {data: {billing_version: string, self_subs_enabled: boolean, disable_self_renewal: boolean, ...existing}} | BillingInfo.billing_version?: string, self_subs_enabled?: boolean, disable_self_renewal?: boolean (Detail 2.A.1) | ✓ Yes | — |
POST /api/core/v1/self_subscriptions | {data: {checkout_url: string}} (§2.4) | InitiateResult.checkout_url: string (Detail 2.A.1) | ✓ Yes (proposed names match) | REV-1: exact params unconfirmed |
GET /api/core/v1/self_subscriptions/invoicable | {data: {pending_sos: [{so_id, amount, checkout_url, ...}]}} (§2.4) | InvoicableResult.pending_sos: PendingSO[] (Detail 2.A.1) | ✓ Partial | REV-1/REV-8: PendingSO field names proposed, not confirmed |
PUT /api/core/v1/modpanels/subscriptions/:id | 200 {} / 422 {message} (§2.4) | moderator-be toggle saves; toast on error | ✓ Yes | — |
Field name casing: snake_case throughout (consistent — FE uses snake_case from BillingStore, matching existing is_trial, billing_enabled fields in BillingStore.ts). No transformation layer needed. ✓
Error response shape: BE returns {status: "error", message: "..."} on 422; FE error handlers use generic toast. FE doesn't inspect message field — generic but consistent with existing pattern. ✓
Mismatches found: 0 hard mismatches. 1 partial (invoicable response field names). Cross-layer cap not applied.
Cross-Layer Rollout Compatibility Matrix
| Scenario | Frontend | Backend | Works? | Notes |
|---|---|---|---|---|
| Pre-deploy (baseline) | Old | Old | ✓ Yes | Current state |
| Backend first | Old | New | ✓ Yes | Old FE ignores new fields; new endpoints never called |
| Frontend first | New | Old | ✓ Yes (dark) | New fields undefined → false defaults → no CTAs |
| Both deployed (target) | New | New | ✓ Yes | Full feature for enabled CIDs |
| Backend rollback | New | Old | ✓ Yes | Same as "Frontend first" — safe degradation |
| Frontend rollback | Old | New | ✓ Yes | Old FE ignores new endpoints |
Deploy order: BE first → hub-chat FE → moderator-be (§4 Deploy Order and Cross-Layer Compatibility). ✓ Incompatible scenarios: 0. Cap does not apply.
End-to-End Data Flow
Flow: Client clicks "New Subscription" CTA
Client clicks "New Subscription"
→ PackageInfoComponent.vue: v-if=isTrial && selfSubsEnabled && !disableSelfRenewal (guards click)
→ button disabled → MpSpinner shown
→ useInitiateSelfSubs('new_sub').initiate()
→ $customFetch('POST /api/core/v1/self_subscriptions', {flow_type: 'new_sub'})
→ hub-service SelfSubscriptions resource (oauth2 :admin,:owner,...)
→ Interactors::SelfSubscriptions::Initiate: derives company_id from me.organization_id
→ HTTP POST to Mekari Billing API (5s timeout, 1 retry)
→ Mekari Billing returns {checkout_url}
→ hub-service returns 200 {data: {checkout_url}}
→ hub-chat: window.location.href = checkout_url (redirect to Mekari Pay)
→ Client completes payment on Mekari Pay
→ Mekari Pay redirects back to /subscriptions/packages
→ Page mounts: BillingStore.getDetail() fetches GET /billings/info → is_trial now false
→ PackageDetails.vue re-renders: New Subscription CTA hidden, Renew + Upgrade CTAs visible
→ Mixpanel: self_subs_payment_completed
Gaps in flow: None for the documented path. The "redirect back URL" exact format is pending Open Q5 (what query params on the return URL indicate success vs. failure) — the FE needs this to conditionally show the payment failure toast.
Flow: Admin toggles Disable Self-Renewal in moderator-be
Admin clicks toggle in CID Billing Settings
→ moderator-be DisableSelfRenewalToggle (path TBD — Open Q6)
→ PUT /api/core/v1/modpanels/subscriptions/:moderator_account_id {disable_self_renewal: true}
→ hub-service Modpanels::Subscriptions resource (oauth2 :modpanel)
→ Billings::Interactors::V2::Subscriptions::UpdateRemainingBalance (extended)
→ DB UPDATE organization_packages SET disable_self_renewal = true WHERE id = :id
→ 200 success
→ moderator-be: success toast shown
→ Next page load for that CID: BillingStore.getDetail() returns disable_self_renewal: true
→ PackageInfoComponent: New Sub + Renew CTAs hidden, Upgrade CTA still visible
Gaps in flow: moderator-be exact file path is TBD (Open Q6). The full moderator-be flow is traceable but the specific file cannot be named until Q6 is resolved.
Agentic Readiness Deep-Dive
Vague Word Audit
| # | Word/Phrase | Location | Impact | Concrete Replacement |
|---|---|---|---|---|
| 1 | ...additional params per A-2 | §2.4 POST /self_subscriptions request | Agent cannot write the request body | Blocked on Open Q4 — correct to leave as placeholder; must resolve before chunk 3 |
| 2 | {so_id, amount, checkout_url, ...} | §2.4 GET /invoicable response | Agent must invent PendingSO field names | Blocked on Open Q1 — Detail 2.A.1 notes this; must resolve before chunk 8 |
| 3 | "v3" billing_version assumption | §2.A.1 comment | FE string comparison may be wrong | Blocked on Open Q7 |
Total vague words in spec sections: 3 — all correctly flagged as external blockers, not RFC author ambiguity.
Dangling Alternatives
None — all 7 ADRs have chosen options with alternatives rejected.
Task Decomposition Assessment
| Chunk | Acceptance Criteria | Assessment |
|---|---|---|
| 1 — DB migration | bundle exec rails db:migrate succeeds; rails db:rollback restores; columns exist | Verifiable — add grep -rn pre-step before writing |
| 2 — Extend BillingInfo | RSpec test for /info response includes 3 new fields | Verifiable |
| 3 — New SelfSubscriptions resource | RSpec: 200 happy path, Mekari Billing 5xx → 503, auth → 401 | Verifiable but blocked on Open Q1/Q4 |
| 4 — Extend modpanel PUT | RSpec: toggle ON/OFF saves; non-modpanel auth → 401 | Verifiable |
| 5 — Extend BillingStore | pnpm type-check no errors | Verifiable |
| 6 — PackageDetails eligibility | pnpm type-check; non-V3 page renders identically | Verifiable |
| 7 — CTA components | pnpm test ...PackageInfoComponent; CTAs hidden for non-V3 | Verifiable but blocked on Open Q7 |
| 8 — PendingSOWarningModal | pnpm test ...PendingSOWarningModal; modal on pending SO | Verifiable but blocked on Open Q1 |
| 9 — moderator-be toggle | Toggle ON/OFF saves; error reverts | Verifiable but blocked on Open Q6 |
Strengths
- PRD traceability at 9.0: The bidirectional matrix in Detail 1.A covers all 52 composite AC IDs (6 stories × all AC + ERR sub-ids). Every new artifact has a PRD justification; no scope creep.
- Pattern alignment at 9.0: Every FE and BE pattern is cited with a file:line reference in Detail 2.0. An agent can generate code that looks native to both repos without searching.
- Deploy order + rollback at 8.0: The 6-scenario compatibility matrix (§4) and the 3-option rollback recipe (§4.D) are complete enough to hand to an on-call engineer without the RFC author present.
Biggest Gaps
- ACV 5.5 (REV-1): The two new proxy endpoints are placeholder-specified because Mekari Billing has not shared their API contract. This is the correct call — not a fixable RFC gap — but it blocks chunks 3, 7, and 8 entirely. The consequence: ~40% of the FE implementation (CTAs, modal) cannot proceed until Open Q1, Q4, Q7 are resolved.
- DIC — idempotency absent for POST /self_subscriptions: A double-click (client presses Renew, then presses browser back and retries) may submit two proxy calls, potentially creating two SOs on Mekari Billing. FE button disabling mitigates the in-session case but not the back-button case. This is an accepted Phase 1 risk but should be promoted to Phase 2.
- OBS — no SLO targets or alert thresholds: The 7 Mixpanel events are named but there are no SLO availability targets (e.g., "self-subs flow initiation ≥ 99.5% success"), no alert definitions, and no dashboard reference. On-call cannot set up alerting from this RFC alone.
Priority Actions
Items 1–2 are pre-implementation requirements (externally blocked). Items 3–5 are improvements for the next RFC iteration.
- Resolve Open Q1, Q4, Q7 with Mekari Billing — receive exact Mekari Billing self-subscription API request/response schema, Invoicable response field names, and
billing_versionV3 string. Update §2.4 API table and Detail 2.A.1 TypeScript interfaces. Unblocks chunks 3, 7, 8. - Resolve Open Q5 (Mekari Pay redirect URL) — confirm exact query params on the
/subscriptions/packages?...redirect for success vs. failure detection. Update the FE payment failure toast logic inuseInitiateSelfSubs. - Add idempotency guidance for POST /self_subscriptions — either (a) add a client-generated idempotency key header (e.g.
X-Idempotency-Key: <uuid>) that hub-service proxies to Mekari Billing, or (b) confirm Mekari Billing handles dedup natively. Document in §2.4 and §3 DIC. - Add SLO + alert thresholds to §3 OBS — at minimum: "self-subs flow initiation error rate < 5% over 5-min window → PagerDuty"; "Invoicable API P95 latency > 500ms → alert"; add Grafana/New Relic dashboard reference.
- Resolve Open Q6 (moderator-be view path) — once the moderator-be team confirms the CID Billing Settings view path, update §2.D, Detail 4.C chunk 9, and the smoke test step 5.
Implementation Readiness Checklist
Unblocked (agent can proceed)
All types:
- PRD → RFC traceability matrix complete (14/14 full)
- All technical decisions resolved with alternatives rejected (7/7)
- All failure modes handled per external interaction with error message catalog (Detail 3.A/3.B)
- Configuration contract: env vars, feature flags with defaults (Detail 4.A)
- Pattern alignment verified (Detail 2.0 Patterns table)
- Rollout plan with feature flag and rollback mechanism (§4)
- Observability: Mixpanel events + structured BE logs (§3)
- Task decomposition with acceptance criteria per chunk (Detail 4.C)
Frontend:
- All interfaces / contracts / prop types fully specified (Detail 2.A.1 — added R2)
- All UI states defined for primary surfaces (Detail 2.C)
- Browser support matrix defined (Detail 3.D — added R2)
- Accessibility requirements specified (Detail 3.C)
Full-stack:
- Cross-layer contract verified — no hard mismatches (§ Cross-Layer Contract Verification)
- Deploy order specified (§4 — added R2)
- Cross-layer rollout compatibility matrix — 0 "No" scenarios (§4 — added R2)
- End-to-end data flow documented for each major user action (§ End-to-End Data Flow)
- Feature flag coordination defined (single flag, §4)
Backend:
- Schema changes at DDL-level precision (§2.3 migration)
- Transaction boundaries and integrity for write paths (§ Data Integrity Deep-Dive)
- Security: auth boundaries, tenancy isolation, credential handling (§3 Security)
- Migration plan: additive, no backfill, rollback script (§4.D)
- Service boundary documented (§2.D, SBC)
- Compliance: N/A (no PII/regulated data)
Blocked (must fix before chunks 3, 7, 8, 9)
- Open Q1 — Mekari Billing Invoicable API qualifying SO statuses and response field names → updates
PendingSOinterface andGET /invoicableresponse schema - Open Q4 — Mekari Billing self-subscription API exact request params → updates
POST /self_subscriptionsrequest schema - Open Q5 — Mekari Pay redirect URL query params → updates FE payment failure detection logic
- Open Q6 — moderator-be CID Billing Settings view file path → unblocks chunk 9
- Open Q7 —
billing_versionV3 string value → updates FE eligibility check
Verdict: Ready to implement chunks 1, 2, 4, 5, 6 now. Chunks 3, 7, 8, 9 blocked on 5 external confirmations. No RFC spec gaps — all blocks are external API contracts.
Task Manifest
Inherited from RFC Detail 4.C (9 chunks, verified dependency-ordered). No re-derivation needed. Chunks 1–2, 4–6 are unblocked; 3, 7, 8, 9 require external confirmations first.
Dangling Decisions Log
| # | Decision | Location | Status |
|---|---|---|---|
| — | (none) | — | All 7 ADRs Resolved |
Open Questions
REV-1 (Mekari Billing contracts) and REV-8 (billing_version string) are the two remaining open material findings. Both are promoted to the RFC's §5 Open Questions as Q1/Q4 and Q7.
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | Mekari Billing self-sub API + Invoicable API contracts (Open Q1/Q4) — exact request params, response field names, error codes? | ACV | Blocking (chunks 3, 7, 8) |
| 2 | billing_version exact string value for V3 (Open Q7)? | ACV/CNT | Blocking (chunk 7) |
| 3 | Mekari Pay redirect URL query params for success vs. failure (Open Q5)? | FMC | Blocking (chunk 7) |
| 4 | moderator-be CID Billing Settings view file path (Open Q6)? | SCB | Blocking (chunk 9) |
| 5 | Add idempotency key for POST /self_subscriptions (double-click / back-button risk)? | DIC | Important (Phase 2 candidate) |
| 6 | Add SLO targets and alert thresholds to §3 OBS? | OBS | Nice-to-have (next RFC iteration) |
Evidence Notes
- Detail 1.A (PRD Traceability) — the bidirectional AC matrix is the strongest evidence section; 52 composite AC IDs mapped; drove PRT 9.0 and boosted confidence in the overall RFC completeness.
- Detail 2.0 Source Verification — 16-row evidence table with file:line citations prevents hallucinated patterns; directly observable in the committed codebase; drove CPA 9.0.
- Detail 2.4 APIs — the two new endpoints have placeholder contracts (correct call); drove ACV 5.5 and is the single category below 6.0, correctly reflecting the external dependency reality.
- §4 Deploy Order and Cross-Layer Compatibility — added in R2 iteration; resolved the ROL cap from 6.0 → 8.0; the 6-scenario matrix is the definitive evidence that this feature can be staged safely.
- Detail 2.A.1 TypeScript Contract — added in R2; lifted CNT from 6.5 to 7.5;
UseInitiateSelfSubs,UseCheckInvoicable, and modal/component prop interfaces give the implementing agent concrete TypeScript to write against.
Review History
| Cycle | Date | Reviewed RFC revision (last_updated / commit) | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
R1 | 2026-06-24 | 2026-06-24 / c3ca545 | 6.5 | HOLD | 6 open (0B/3M/3m) | First review. Held by: deploy order missing (ROL cap 6.0), TypeScript interfaces absent (CNT 6.5), proxy timeout absent (FMC/CSS drag), structured log fields vague (OBS), browser matrix absent (NFS). ACV 5.5 from external blockers. |
R2 | 2026-06-24 | 2026-06-24-r2 / working tree | 7.5 | PROCEED with notes | 5 fixed (REV-2..6), 2 open (REV-1/REV-8), 1 accepted-risk (REV-7) | R1 fixes applied: deploy order matrix (ROL cap lifted → 8.0), TypeScript interfaces (CNT 6.5→7.5), proxy timeout/retry in API table (FMC 7.0→7.5, CSS 6.0→6.5), structured log fields (OBS 6.5→7.0), NFS table with browser matrix (NFS 6.0→7.0). Residual: REV-1 (external blockers, correct), REV-8 (billing_version string). Score 6.5 → 7.5. |