Skip to main content

RFC Review: WA Campaign — Register Campaign Type to CAA (Chatbot Autoreply), Backend

Executive Summary

  • Overall Score: 9.0/10
  • Rating: Agentic-Ready
  • RFC Type: backend
  • Sub-Type: new-feature
  • Assessment Confidence: High
  • Applied Caps/Gates: none — no category < 7.5; DMS/DIC/FMC ≥ 8.5; zero dangling decisions
  • Implementation Readiness Verdict: PROCEED — executable end-to-end; one agent-facing item (chunk 3 serializer) should take the PM's one-line answer to Open Q#4 first; everything else is external sign-off, not design.
  • Report Path: revenue/whatsapp-campaign-register-campaign-type-to-caa/rfcs/rfc-wa-campaign-register-campaign-type-to-caa-be-review.md
  • RFC Author: Isna Rahmatul Khoir | Reviewed: 2026-07-13 (rev5)

rev5 closes the three findings from the rev1 review (8.0). An AI agent can now implement the RFC end-to-end from the spec without a clarification meeting: the payload is one consistent campaign_context envelope with a per-field type/nullability table, the case-2 read is batched (no N+1), and every decision carries full options + rationale. The biggest strength is decision completeness anchored in verified code (file:line throughout). The single residual agent-facing ambiguity is the serializer's header/footer format (Open Q#4 — a PRD self-contradiction the RFC resolved by choosing the AC, pending a one-line PM confirmation). The one thing worth doing before coding chunk 3: get that confirmation.


Quick Verdict

Why this RFC can be implemented agentically:

  • Every decision (D1–D8 + a minimum-coverage block) is a full ADR with options, pros/cons, and a specific rationale; zero "TBD"/dangling choices.
  • The contract is one consistent, fully-typed campaign_context envelope (§2.4) with a last_campaign→campaign_context migration mapping — a consumer can build against it directly.
  • Every anchor is verified with file:line (Source Verification), incl. the interactive-log condition + resolution the discriminator reuses, and the list-all / window shapes the reads mirror.

Why an agent might still pause:

  • Chunk 3 (serializer) header/footer output is contingent on Open Q#4 (PRD table vs CAA-S03 AC); the RFC picks the AC but flags it — a one-line PM confirmation removes the guess.
  • Case-2 supporting index is asserted, not yet verified (Open Q#2 — an EXPLAIN check scheduled inside chunk 5).

PRD → RFC Traceability Matrix

PRD ElementRFC SectionCoverage
FR-1 enrich inbound webhook with campaign context§1 PRD-to-Schema, §2.1/2.2/2.4, D3/D6Full
FR-1 5 content fields + identity§2.4 element table (10 fields, typed)Full
FR-1 content-to-text rulesDecision 5, §4.C ch.3Partial — header/footer format pending Open Q#4
FR-2 backdated (UC4), 30-day windowDecision 2, §2.2, CAA-S02Full
FR-3 chatbot reads context§2.D row 7 (cross-squad)Full (correctly delegated)
CAA-S01 (multiple in room)Case 2 array (D6/D7)Full
CAA-S02 backdated inclusive boundaryDecision 2 (..)Full
CAA-S03 serialization + missing-field→""Decision 5Partial — header/footer (Open Q#4)
CAA-S04 eligible/interactive discriminationDecision 3 (interactive-log reuse)Full
CAA-S05 chatbot consumption§2.D row 7Full (n/a — cross-squad)

Summary: 8/10 elements fully covered; 2 partial, both on the same external PRD ambiguity (header/footer). No unjustified additions.


Scorecard (Backend)

CategoryScoreEvidence-Based Rationale
PRT — PRD Traceability9.0Bidirectional matrix + PRD Section Coverage + Detail 1.C; all 5 stories mapped.
TDC — Technical Decisions9.5rev5: D1–D8 full ADR (Context/Options±/Decision/Rationale/Consequences/Reversibility) + minimum-coverage block (caching/third-party/consistency/tenancy/rollout). No dangling choices.
DMS — Data Model & Schema8.5No DDL by design (Decision 1, justified: partitioned hot table + new-sends-only); both read queries shown, case-2 batched; EXPLAIN gate. Docked: supporting index asserted, verified in chunk 5 (Open Q#2).
ACV — API Contract & Versioning9.0rev4/5: single campaign_context envelope with per-field type/nullability table + enum values + last_campaign mapping + examples for case1/case2/empty/flag-off.
DIC — Data Integrity & Consistency8.5Read-only; fail-safe empty envelope on error; interactive-log/reply-record writes untouched; replica-lag documented.
FMC — Failure Mode & Retry Coverage8.5Reuses CaaWorker retry:3 + circuit breaker + auto-disable (cited); new read rescue→empty envelope; Branch & Skip catalog; Error Response Catalog N/A-justified (no sync API).
CSS — Concurrency & Scaling8.5rev3/5: N+1 removed via batched MessageBroadcast.where(id:).includes(:message_template) (D7, Bullet-clean); cap 20; read-only collision map.
SAS — Security & Authorization8.5Tenancy enforced (organization_id+room_id on every read, cited AGENTS.md), org-scoped URL lookup; Role×Endpoint matrix; no new input surface.
MRP — Migration & Rollout Plan8.5Flag register_campaign_type_to_caa (default OFF); stages, kill-switch, rollback = flag off; Config Contract; last_campaign deprecation window (Open Q#10).
OBS — Observability Definition8.0Two named metrics w/ tags (trigger,count,truncated,reason), structured log (no body text), alert threshold; grounded in existing CaptureCustomMetric. Docked: no dashboard panel ref.
SBC — Service Boundary & Coupling9.0hub_core only; single cross-squad handoff (chatbot); workers/model untouched.
CPA — Consistency & Pattern Alignment9.0Patterns table + base classes; reuses interactive-log condition, mark_replied/conversation_sessions shapes, enrich_carousel_cards service style.
CDG — Compliance & Data Governance7.5Triggered (campaign body text → external webhook). Handled: tenant-scoped delivery, no new PII column. Docked: event-log masking deferred to infosec (Open Q#6).

Overall: 9.0 — Agentic-Ready. (9.0 gate met: all ≥ 7.5; DMS/DIC/FMC ≥ 8.5; no dangling decisions.)

Resource & Cost Advisory

Negligible — case 1: 1 read + 1 resolution; case 2: 1 window read + 1 batched resolution (cap 20) on an already-async path. No new infra/write/storage.


Decision Closure Assessment

Decision Index

#DecisionStatusCritical Gaps
1Derive at read time (no column)Resolvednone
2Lookup windows & queries (reuse shapes)Resolvednone
3Case discriminator (reuse interactive-log condition)Resolvedorder-msg/BUTTON_REPLY edge documented (Open Q#3)
4Sync enrichment in build_messageResolvednone
5Content serializer (new)Partialheader/footer output pending Open Q#4 (PRD self-contradiction)
6Single campaign_context envelopeResolvednone — options A/B/C + rationale + mapping
7Case-2 batched + cappedResolvednone (N+1 removed)
8Reuse CAA event vs new webhookResolvednone
9–13Caching / third-party / consistency / tenancy / rollout gateResolvedeach stated with rationale

Aggregate: 12 of 13 Resolved, 1 Partial (Decision 5, external PRD ambiguity), 0 Dangling.

Decision 5 — Content serializer (the only Partial)

What was decided: new CampaignContentSerializer; rules table; header+footer → "{header}: {body} — {footer}" per CAA-S03 AC. Why Partial: the PRD conversion table says header/footer are "not available" — a self-contradiction the RFC resolves by following the AC but flags (Open Q#4). Impact: an agent would implement the AC form and may need a one-line PM confirmation. Suggested resolution: PM confirms the AC is authoritative; then the branches are fully pinned (both present / header-only / footer-only / neither). Grounding: extract_content (create.rb:154-164), user_send_hsm.rb:155-177 — verified.

(Decisions 1–4, 6–13 assessed Resolved — chosen option named, alternatives rejected with concrete reasons, grounded in cited files, failure handled, agent-implementable. Abbreviated per the >7.0 optimization.)


Data Integrity Deep-Dive

Write PathTransaction ScopePartial FailureIdempotencyConsistencyDuplicate Handling
Enrichment (this RFC)None — read-onlyerror → empty campaign_context envelope + log; CAA still deliversper-delivery uniq_idread-time → consistent w/ template stateinbound dedup upstream; enrichment is pure

Strongest possible integrity posture (no writes; interactive-log/reply-record writes explicitly left to their workers). Only residual: replica-lag omission (documented, accepted).

Concurrency Collision Map

Shared ResourceWritersCollisionResolutionAssessment
CAA payload buildrapid replies same roomeach builds own payloadread-only, no shared state; per-delivery uniq_idadequate
messages read vs broadcast insertenrichment vs campaign writereplica lagreplica snapshotadequate (documented)

No true collision points (read-only). Efficiency: case-2 batched load removes the rev1 N+1.

API Contract Completeness Check

EndpointRequestResponseError TaxonomyAuthIdempotencyExamplesAssessment
custom_agent_allocation webhook (outbound POST → chatbot)n/a (we send)complete — envelope + per-field type/nullability tablen/a (delivery via CaaWorker)specific (org-registered URL + org setting + flag)consumer-side, same body across retriesyes (case1/case2/empty/flag-off)6/6

No synchronous inbound endpoint added (correctly N/A). The one contract that matters (outbound envelope) is now fully typed with a legacy-field migration map.

Async Job / Event Consumer Spec

Job/ConsumerTriggerInputRetryDLQConcurrencyIdempotencyTimeoutAssessment
Webhooks::CaaWorker (existing, unchanged)perform_asyncdata incl. campaign_contextretry:3 + backoffSidekiq dead queue:custom_agent_allocationconsumer-sidePigeon timeout7/7 (reused as-is)
Rooms::CampaignContexts (new, synchronous — not a job)in-process from build_message(message, room)none; error → empty envelopen/ainherits CAA pathn/a (read)inherits caller6/7 — pin read timeout expectation

Compliance Trigger Check

TriggerFound?LocationClassificationAssessment
PII (indirect)yesmessage_body/message_content_text may embed personalized copylow-sensitivity business content (already in messages)handled — tenant-scoped delivery, no new column
User content w/ retentionyescampaign content re-emitted to webhookexisting retentionhandled
Cross-bordernoorg's own registered URLn/an/a
CDG Status: Active — scored 7.5. Residual: event-log masking (Open Q#6).

Strengths

  • Decision completeness (rev5) — 8 full ADRs + a minimum-coverage block; the TDC category is now near-exemplary and every choice states why.
  • Consistent, typed contract (rev4) — one campaign_context envelope, per-field types, and a last_campaign→campaign_context migration map; the payload is no longer two competing shapes.
  • Grounding + reuse — the case-1 discriminator reuses the exact MessageBroadcastInteractiveLog condition/resolution; reads mirror mark_replied/conversation_sessions; all file:line-verified.
  • Self-correcting history — the rev1 findings (array-ordering, N+1, per-field types) are all closed and recorded in §6.

Biggest Gaps

  • Serializer header/footer (Open Q#4) — PRD table vs CAA-S03 AC; the one remaining agent-facing ambiguity. Impact: chunk 3 needs a one-line PM confirmation.
  • Case-2 index unverified (Open Q#2) — pruning asserted, EXPLAIN scheduled in chunk 5. Impact: possible slow query if no suitable composite index exists.
  • Order-msg / BUTTON_REPLY disambiguation (Open Q#3) — documented to fall to case 2; worth a Chatbot confirmation. Impact: minor; behaviour is defined.

Priority Actions

  1. Open Q#4 — PM confirms CAA-S03 AC is authoritative for header/footer → pin the serializer branches (unblocks chunk 3 fully).
  2. Open Q#2 — verify/add the (organization_id, room_id, is_campaign, created_at) composite index; capture the EXPLAIN in the RFC during chunk 5.
  3. Open Q#3 / Q#6 / Q#9 — Chatbot confirms the BUTTON/BUTTON_REPLY split; infosec on event-log masking; Chat-1 accepts the additive envelope.
  4. Infosec approver — fill the Approver(s) metadata before AGREED.

Implementation Readiness Checklist

  • PRD → RFC traceability complete
  • All technical decisions resolved with alternatives + rationale (12/13; 1 partial on external PRD ambiguity)
  • Failure modes handled; error catalog N/A-justified
  • Configuration contract (flag, default OFF)
  • Pattern alignment verified
  • Rollout + rollback (flag off) + last_campaign deprecation window
  • Observability metrics + alert
  • Task decomposition with per-chunk AC (8 chunks)
  • Schema: N/A by design, justified; both reads shown, batched, EXPLAIN gate
  • API contract: fully typed envelope + legacy mapping + examples
  • Transaction boundaries: read-only, fail-safe
  • Concurrency: read-only collision map; N+1 removed
  • Security: tenancy + org-scoped URL
  • Service boundary documented (hub_core only)
  • Compliance handled (tenant-scoped; masking → Open Q#6)

Fix before/at implementation

  • Open Q#4 (header/footer) — before chunk 3
  • Open Q#2 (index EXPLAIN) — within chunk 5

Verdict: Ready to implement — confirm Open Q#4 before chunk 3; verify the index during chunk 5.


Dangling Decisions Log

None. (Decision 5 is a Partial pending an external PRD clarification, not an unresolved RFC choice.)

Open Questions

#QuestionCategorySeverity
4Is CAA-S03 AC or the PRD table authoritative for header/footer serialization?TDC/ACVBlocking chunk 3
2Composite index for the case-2 room lookup — exists, or add?DMSImportant
3Order-msg BUTTON / BUTTON_REPLY fall to case 2 — confirmTDCImportant
6Mask message_body/message_content_text in the webhook event log?CDGImportant
9Chat-1 accepts the additive campaign_context envelopeACV/SBCImportant
10last_campaign deprecation/removal windowACVNice-to-have (post-GA)

Evidence Notes

  • §2 Technical Decisions (rev5) — full ADRs + minimum-coverage; primary reason TDC → 9.5 and overall → 9.0.
  • §2.4 — typed envelope + last_campaign mapping; lifted ACV to 9.0.
  • Decision 7 + §2.3 — batched resolution; lifted CSS to 8.5 (was 7.0 in rev1).
  • Decision 5 + Open Q#4 — the sole remaining agent-facing ambiguity; keeps a clean 10.0 out of reach.
  • §2.3 Open Q#2 — index verification deferred to chunk 5; the reason DMS is 8.5 not higher.