Skip to main content

RFC Review: Qontak One | Billing | Self-Subs — Phase 1

Companion review for self-subs.md, produced by the rfc-reviewer skill. Cumulative across cycles R1 → R1+iterate (R2); valid for the RFC revision in reviewed_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_enabled DB 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_subscriptions request body is {flow_type, ...additional params per A-2} — the ...additional params is unresolvable until Mekari Billing shares their API contract (Open Q4). Agent would have to invent params.
  • GET /invoicable response field names in PendingSO are 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_version exact string value for V3 (Open Q7) — agent writing billing_version === 'v3' may be wrong.

Findings Ledger (carry-forward)

IDSeverityFinding (one line)RFC locationStatusFirst seenResolved inEvidence / fix
REV-1blockerPOST /self_subscriptions and GET /invoicable proxy contracts are placeholder — exact Mekari Billing request/response fields unknown§2.4, Open Q1/Q4openR1Blocked on external party; correctly flagged as Open Q1, Q3, Q4. Not fixable by RFC author.
REV-2majorDeploy order not specified — cross-layer rollout scenario matrix absent§4fixedR1R2 (iterate)Deploy order matrix added to §4 "Deploy Order and Cross-Layer Compatibility"; all 6 scenarios documented with safety analysis; cap lifted.
REV-3majorTypeScript interfaces for new composables and PendingSOWarningModal props absent — agent cannot generate TypeScript without inventing types§2.AfixedR1R2 (iterate)Detail 2.A.1 added with UseInitiateSelfSubs, UseCheckInvoicable, PendingSOWarningModalProps, and PackageInfoEligibilityProps interfaces.
REV-4majorProxy call timeout/retry values absent — agent would use framework defaults (no timeout, no retry)§2.4, §3fixedR1R2 (iterate)§2.4 API table extended with "Proxy timeout / retry" column: 5s/1-retry for initiation, 800ms/0-retry fail-open for invoicable.
REV-5minorBE structured log fields absent — "add company_id and flow_type" is too vague for agentic implementation§3 LoggingfixedR1R2 (iterate)§3 Logging extended with structured log field table: 5 events with level, event name, and required fields.
REV-6minorBrowser support matrix not stated — agent cannot know if new Web APIs are safe§3fixedR1R2 (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-7minorMigration table name uncertain — proposed migration DDL targets :organization_packages but this must be verified by grep before writing§2.3accepted-riskR1RFC already has pre-condition grep instruction in §2.3. Acknowledged; correct behavior is for agent to grep before writing chunk 1.
REV-8minorbilling_version exact string value unknown (Open Q7) — FE comparison billing_version === 'v3' may be wrong§2.A.1, Open Q7openR1Blocked 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 ElementRFC SectionCoverage
SS-S01 (V3 gate)§1.A, Detail 1.C, §2.0, §2.4Full
SS-S02 (new subscription from trial)§1.A, Detail 1.C, §2.2 Seq 1, §2.4Full
SS-S03 (renewal)§1.A, Detail 1.C, §2.2 Seq 2, §2.4Full
SS-S04 (upgrade main package)§1.A, Detail 1.C, §2.2 Seq 3, §2.4Full
SS-S05 (pending SO warning modal)§1.A, Detail 1.C, §2.A, §2.A.1, §2.2 Seq 2Full
SS-S06 (disable self-renewal modpanel toggle)§1.A, Detail 1.C, §2.3, §2.4, §4Full
PRD §6 Performance constraints§3 Performance Requirement, §3.D NFSFull
PRD §9 API/webhook behavior§2.2 sequences, §2.4 APIsFull
PRD §11 Rollout§4 Rollout Strategy, §4 Deploy OrderFull
PRD §12 Observability§3 Monitoring & Alerting, §3 LoggingFull
PRD §13 Success Metrics§1 Success CriteriaFull
PRD OQ1 (invoicable statuses)§5 Open Q1Full (flagged)
PRD OQ2 (SO failure reconciliation)§5 Open Q2Full (flagged)
PRD OQ3 (proration basis)§5 Open Q9Full (flagged)

Summary: 14 of 14 PRD elements fully covered (including open questions). 0 RFC decisions without PRD justification.


Scorecard

Full-Stack Scorecard (18 categories)

#CategorySourceScoreEvidence-Based Rationale
1PRT — PRD TraceabilityMerged9.0FE: 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
2TDC — Technical DecisionsMerged8.0FE: 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)
3CNT — Contract SpecificityFE7.5Detail 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)
4SCB — Scope BoundariesFE8.0Detail 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)
5DEP — DependenciesFE8.0All 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)
6NFS — Non-FunctionalFE7.0Detail 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
7TPS — Test PlanFE7.0Concrete 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
8DMS — Data Model & SchemaBE6.5Migration 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)
9ACV — API Contract & VersioningBE5.5Extended 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
10DIC — Data IntegrityBE7.0Hub-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
11FMC — Failure Mode CoverageMerged7.5FE: 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
12CSS — Concurrency & ScalingBE6.5Proxy 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)
13SAS — Security & AuthorizationBE8.0Auth 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
14ROL — Rollout & RollbackMerged8.0FE: 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
15OBS — ObservabilityMerged7.0FE: 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
16SBC — Service BoundaryBE8.5New 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
17CPA — Pattern AlignmentMerged9.0FE: 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)
18CDG — ComplianceBEN/ANo 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

#DecisionStatusCritical Gaps
ADR-1Extend GET /billings/info for eligibility (vs. new endpoint)Resolved
ADR-2Store flags in hub-service DB as new columns (vs. Mekari Billing attribute or FE flags)Resolved
ADR-3New SelfSubscriptions Grape namespace (vs. extending existing billing resource)Resolved
ADR-4Extend existing PUT /modpanels/subscriptions/:id (vs. new endpoint)Resolved
ADR-5Non-blocking fail-open for Invoicable check (vs. blocking)Resolved
ADR-6FE gating via billingStore.billing.* boolean fields (vs. usePackageStore code lookup)Resolved
ADR-7Mekari Pay webhook out of scope for Phase 1ResolvedOpen Q5 tracks the Phase 2 case

Aggregate: 7 of 7 decisions Resolved. 0 Partial. 0 Dangling.


UI State Audit

ComponentLoadingEmptyErrorPartialSuccessAssessment
PackageInfoComponent.vue CTA areaMpSpinner on clicked button✓ No CTAs (non-V3 or flag off)✓ Error toastn/a✓ CTAs visible + redirect4/5 — partial N/A
PendingSOWarningModal.vue✓ Spinner on Renew CTA during check✓ (modal not shown if empty list)✓ Fail-safe proceedn/a✓ Modal with 2 CTAs4/5 — partial N/A
DisableSelfRenewalToggle (moderator-be)✓ Skeleton notedn/a✓ Error toast + revertn/a✓ Confirmation toast3/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

AspectSpecified?DetailsAssessment
Keyboard navigationPartiallyCTA buttons are MpButton (keyboard-accessible by default)Adequate
Focus management (modal)YesFocus trapping + return focus to Renew button on dismiss (Detail 3.C)Adequate
ARIA labelsYesaria-modal + role="dialog" on modal (Detail 3.C)Adequate
WCAG levelNoNot statedMinor gap
Color contrastNoNot stated (inherits pixel3 defaults)Minor gap
prefers-reduced-motionNoNot addressedMinor 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 PathTransaction ScopePartial Failure BehaviorIdempotency KeyConsistencyDuplicate Handling
PUT /modpanels/subscriptions/:id (disable_self_renewal)Single-record update on existing model; within existing interactor's transaction scope422 returned; no partial staten/a (idempotent boolean toggle)Strong (DB write)Last-write-wins (acceptable for boolean flag)
POST /self_subscriptions → Mekari Billing proxyNo local DB write; proxy call onlyhub-service returns 503; no Qontak-side state to roll backNOT DEFINED — if client calls twice (back-button + retry), Mekari Billing may create 2 SOsExternal (Mekari Billing owns)Not defined for Phase 1; Open Q2 covers reconciliation
DB migration (additive columns)DDL transaction; auto-committed by Railsrails db:rollback availablen/aStructuraln/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 ResourceWritersCollision ScenarioResolutionLock FailureAssessment
1disable_self_renewal columnAdmin (PUT /modpanels)Two admins toggle simultaneouslyLast-write-winsn/aAdequate (boolean flag, no financial consequence)
2Mekari Billing SO creationMultiple client clicksUser clicks Renew twice → double POST /self_subscriptionsNOT DEFINED (no FE idempotency + no BE dedup)n/aMinor risk; FE button disabled on click mitigates; BE dedup absent

API Contract Completeness Check

EndpointRequest SchemaResponse SchemaError TaxonomyAuth SpecIdempotencyExample PayloadsAssessment
GET /api/core/v1/billings/info (extend)complete (none)complete (3 new fields named + typed)complete (422 unchanged)specific (5 scopes named)n/ano5/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)specificmissingno3/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)specificmissingno3/6 — blocked externally
PUT /api/core/v1/modpanels/subscriptions/:id (extend)complete (disable_self_renewal optional boolean)complete (200/422)completespecific (:modpanel)n/ano5/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

TriggerFound?Assessment
PIINoNew columns are boolean flags, no personal data
Payment dataNoQontak proxies checkout_url but never stores card/bank data
Auth/sessionNoAuth token used to derive organization_id but not stored or logged

CDG Status: N/A — no compliance triggers.


Cross-Layer Contract Verification

EndpointBackend Response SchemaFrontend Expected SchemaMatch?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)✓ PartialREV-1/REV-8: PendingSO field names proposed, not confirmed
PUT /api/core/v1/modpanels/subscriptions/:id200 {} / 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

ScenarioFrontendBackendWorks?Notes
Pre-deploy (baseline)OldOld✓ YesCurrent state
Backend firstOldNew✓ YesOld FE ignores new fields; new endpoints never called
Frontend firstNewOld✓ Yes (dark)New fields undefined → false defaults → no CTAs
Both deployed (target)NewNew✓ YesFull feature for enabled CIDs
Backend rollbackNewOld✓ YesSame as "Frontend first" — safe degradation
Frontend rollbackOldNew✓ YesOld 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/PhraseLocationImpactConcrete Replacement
1...additional params per A-2§2.4 POST /self_subscriptions requestAgent cannot write the request bodyBlocked on Open Q4 — correct to leave as placeholder; must resolve before chunk 3
2{so_id, amount, checkout_url, ...}§2.4 GET /invoicable responseAgent must invent PendingSO field namesBlocked on Open Q1 — Detail 2.A.1 notes this; must resolve before chunk 8
3"v3" billing_version assumption§2.A.1 commentFE string comparison may be wrongBlocked 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

ChunkAcceptance CriteriaAssessment
1 — DB migrationbundle exec rails db:migrate succeeds; rails db:rollback restores; columns existVerifiable — add grep -rn pre-step before writing
2 — Extend BillingInfoRSpec test for /info response includes 3 new fieldsVerifiable
3 — New SelfSubscriptions resourceRSpec: 200 happy path, Mekari Billing 5xx → 503, auth → 401Verifiable but blocked on Open Q1/Q4
4 — Extend modpanel PUTRSpec: toggle ON/OFF saves; non-modpanel auth → 401Verifiable
5 — Extend BillingStorepnpm type-check no errorsVerifiable
6 — PackageDetails eligibilitypnpm type-check; non-V3 page renders identicallyVerifiable
7 — CTA componentspnpm test ...PackageInfoComponent; CTAs hidden for non-V3Verifiable but blocked on Open Q7
8 — PendingSOWarningModalpnpm test ...PendingSOWarningModal; modal on pending SOVerifiable but blocked on Open Q1
9 — moderator-be toggleToggle ON/OFF saves; error revertsVerifiable 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.

  1. Resolve Open Q1, Q4, Q7 with Mekari Billing — receive exact Mekari Billing self-subscription API request/response schema, Invoicable response field names, and billing_version V3 string. Update §2.4 API table and Detail 2.A.1 TypeScript interfaces. Unblocks chunks 3, 7, 8.
  2. 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 in useInitiateSelfSubs.
  3. 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.
  4. 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.
  5. 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 PendingSO interface and GET /invoicable response schema
  • Open Q4 — Mekari Billing self-subscription API exact request params → updates POST /self_subscriptions request 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 Q7billing_version V3 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

#DecisionLocationStatus
(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.

#QuestionCategorySeverity
1Mekari Billing self-sub API + Invoicable API contracts (Open Q1/Q4) — exact request params, response field names, error codes?ACVBlocking (chunks 3, 7, 8)
2billing_version exact string value for V3 (Open Q7)?ACV/CNTBlocking (chunk 7)
3Mekari Pay redirect URL query params for success vs. failure (Open Q5)?FMCBlocking (chunk 7)
4moderator-be CID Billing Settings view file path (Open Q6)?SCBBlocking (chunk 9)
5Add idempotency key for POST /self_subscriptions (double-click / back-button risk)?DICImportant (Phase 2 candidate)
6Add SLO targets and alert thresholds to §3 OBS?OBSNice-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

CycleDateReviewed RFC revision (last_updated / commit)ScoreVerdictFindings open → fixedNotes
R12026-06-242026-06-24 / c3ca5456.5HOLD6 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.
R22026-06-242026-06-24-r2 / working tree7.5PROCEED with notes5 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.