Skip to main content

RFC Review: Phase 1 — Scorecard Settings & Rubric Config

Review of rfc-phase-1-settings-and-rubric-config.md against the rfc-reviewer full-stack rubric. Judgment-based, agentic-execution-readiness gate.

Task Manifest

FieldValue
RFC typefull-stack
Sub-typeFE new-feature + BE modify-feature (enhancement)
Rubricrubric-fullstack.md (18 categories; CDG triggered → 18 scored)
Source PRD../prds/phase-1-settings-and-rubric-config.md (v1.2)
Review cycleR3 (R1 scored → in-session revision → R2 re-scored; R3 is a refresh/confirm cycle re-grounding every citation against current HEADs)
ConfidenceHigh — all sections present across both layers; contracts match; flows traceable; every cited file re-opened this cycle

Overall

Overall score8.5 / 10 (R1 8.0 → R2 8.5 → R3 8.5, held)
Rating bandAgentic-Ready (lower edge) / Strong
Agentic verdictPROCEED — agent can execute both layers; the only true gate (FE pixel fidelity) is an external Figma dependency the RFC honestly flags, not a spec gap. R3 re-grounding found zero broken citations; two citation-drift notes are cosmetic and one (REV-9) sharpens the already-open REV-1.

R3 Re-Grounding Summary (the core of this cycle)

Every "existing-code" citation in the RFC was re-opened against the current HEADs (chatbot fa6dd8b79, chatbot-fe f5b80d5a, hub-service 8cfe79c61, hub-core 68586c45ee). Result: no citation is broken; all load-bearing claims hold. Three drift notes:

CitationRFC claimCurrent codeVerdict
NameUniquenessValidator#validate_create (REV-5)exists; 422 "The name field is already exist…"chatbot/app/core/repositories/gpt/scorecard_custom_parameter/name_uniqueness_validator.rbvalidate_create (L12), invoked create.rb:74, exact message at create.rb:75VALID
scorecard_preference/patch.rb:18-22 rule(:passing_grade) 1–99human range 1–99if value < 1 || value > 99key.failure('Passing grade only between 0 - 99 are allowed')VALID (note: the human message text is literally "0 - 99"; ADR-8's "1–99" describes the guard, which is < 1 || > 99 — consistent)
entities/.../scorecard_preference.rb:7-9 defaultsDEFAULT_PASSING_GRADE=75, DEFAULT_AUTO_SCORE=trueDEFAULT_PASSING_GRADE = 75.to_f (L7), DEFAULT_AUTO_SCORE = true (L9)VALID
scorecard_preferences schemais_auto_score bool default false + passing_grade floatchatbot_gpt_schema.rb:456-457 exactVALID
scorecard_custom_parameters.prompt is stringunused t.string "prompt"chatbot_gpt_schema.rb:422 t.string "prompt"; migration 20241113041150 t.string :promptVALID (still string — widen is genuinely needed)
BE mountsgpt_api.rb:28,33; gpt_service/api.rb:21,24gpt_api.rb:28 pref→/v1/gpt/omnichannel/scorecard_preferences; :33 custom→/v1/gpt/scorecard_custom_parameters; gpt_service/api.rb:21/v1/scorecards/preferences; :24/v1/scorecards/parameters/customVALID
Feature flag (BE)OrganizationFeatures::FindFeature + call_by_organization; used in ai_knowledge_sources/search.rbfind_feature.rb:5-19 + call_by_organization (L17); used search.rb:49VALID
Role enum (hub-core){owner,admin,supervisor,agent,member} user.rb:38-44enum at L38-44; same 5 values (declaration order admin,supervisor,agent,owner,member)VALID (set identical; only listing order differs — not load-bearing)
/users/me (hub-service) surfaces rolesingle role from tokenapp/services/api/core/v1/users/resources/users.rb get :me (L371); role via UserViewProfile/AnotherUserViewProfileVALID
auto_agent_scoring.rb scores human; OpenAI client:6,76-79; :160-179 request_timeout:240, max_attempts=2, Rollbar.errorscores agent; max_attempts=2 (L161), OpenAI::Client.new(request_timeout: 240) (L179), Rollbar.error (L57/228)VALID (line ~76-79 is the agent-fetch/auto-score region; substance holds)
room-resolve skip guardroom_resolve_interactions.rb:48-63 unless is_custom_parameter || scorecard_existsguard at L51, gating AutoAgentScoringWorker.perform_async (L52)VALID
paper_trail on both models:5 / :6scorecard_preference.rb:5 + scorecard_custom_parameter.rb:6 has_paper_trailVALID
FE has no scorecard codegrep → 0 hitsgrep scorecard|passing_grade|is_auto_score over common/store/pages/modules0 hits; pages/settings/scorecard/ absentVALID
ai-assist.ts:151-184 service pattern$apiMain + AbortControllerget_preference (151-161), update_preference (163-175) use new AbortController() + $apiMain(endpoint, {method, signal})VALID (returns {fetch, controller})
endpoint.ts:123-157structure to add scorecard: blockai_assist block lives at 123-157 of a nested {v1,v2,v3} mapVALID anchor
botSubscriptionFeature.ts:23-40 $hasSubscriptionreturns booleanhasSubscription(feature) L23-40 returns boolVALID — but file header marks it @deprecated (prefer useSubscription) → new minor finding REV-8
mixpanel-events.ts (contants dir)[CHATBOT]-prefixed MIXPANEL_EVENTSmisspelled contants/ confirmed; MIXPANEL_EVENT_PREFIX="[CHATBOT]"VALID
ai-assist.vue referenced for component props/emits (REV-1)infer prop shapes from ai-assist.vueai-assist.vue is <script setup> with NO defineProps/defineEmits — it's a page-level view, not a prop-driven component; uses defineExpose onlyDRIFTED → sharpens REV-1 as REV-9 (the cited file has no prop contract to infer from)
ai-assist.vue:925 mixpanel.track(MIXPANEL_EVENTS.X, props)single call passing propsactual call at L926 passes an inline literal ({Threshold: params.reply_limit, …}), plus L933/L939 — three calls, not propsDRIFTED (cosmetic; the pattern — mixpanel.track(MIXPANEL_EVENTS.*, {...}) — is intact and re-usable)
FE reads state.reply_limit snake_case directly (REV-6)no transformation layerai-assist.vue template {{ state.reply_limit }} (L49), v-model="state.reply_limit" (L92); store seeds via ...preferencesOrigin.value, no case mapVALID

Bottom line: the RFC's anti-hallucination grounding survives a full re-verification at current HEADs. The two drift notes are about how the pattern reference is described, not whether the referenced code exists. None change the score.


PRD → RFC Traceability

Forward (PRD → RFC):

PRD itemRFC coverageVerdict
§7 CHG-001 (AI auto-score + threshold)§2.3 new cols, §2.4 row 1, §2.A AutoScoreToggle
§7 CHG-002 (wire prompt rubric)§2.3 widen, §2.4 row 2, §2.A CustomParamEditor
§8 New (editor + default viewer)§2.4 row 3 (new endpoint), §2.A DefaultRubricViewer
§9 Behavior 1/2§2.4 rows 1–2 (HTTP/path/schema/errors resolved)
§10 stories UASC-S01/S02/S03§1.A.4 every AC/ERR/NEG mapped; §2.1a sequences
§11 Rollout§4 + Cross-Layer Rollout matrix
§12 Observability (6 events + alert)§3 Monitoring; all 6 events present
§13 Success metrics§1 Success Criteria + §4.D adoption signals
§16 Decisions/Alternatives§1.B + 8 ADRs
§17 Open Questions§5
App. A 9 metrics§2.4 row 3 payload (descriptions seeded)

Reverse (RFC → PRD): every endpoint, column, and component in §1.A.5 / §1.C cites a PRD driver. No orphan artifacts.

Full-stack contradiction check: no FE/BE scope or behavior conflicts found (§2.G all Match? = yes).

PRT verdict: 11/11 PRD sections covered, 0 missing. The RFC corrects the PRD where the PRD contradicts code (roles, passing_grade range) rather than copying it — a strength re-confirmed this cycle (the human range message is literally "0 - 99", guard < 1 || > 99).


Scorecard

#CatScoreEvidence (re-grounded R3)
1PRT9.0Five traceability matrices (§1.A.1–1.A.5) + per-story map (§1.C), forward+reverse. All PRD-to-Schema rows verified against chatbot_gpt_schema.rb.
2TDC9.08 ADRs with options/consequences/reversibility + min-coverage checklist; cross-layer consistency §2.G. Every grounding citation re-verified.
3CNT (FE)7.5§2.A component table + §2.B store/service TS shapes. Component props/emits still pattern-referenced, and R3 confirms the referenced ai-assist.vue is <script setup> with no props/emits to infer from (REV-9). Held at 7.5 — typed store/service shapes (§2.B) carry the score; missing prop contracts cap it below 8.5.
4SCB (FE)8.5§2.D in/out scope + §4.C names exact FE file paths per chunk; pages/settings/scorecard/ confirmed absent (genuinely new).
5DEP (FE)8.0§1 Dependencies table; new BE endpoint availability in §2.4; Figma dep flagged as blocker. botSubscriptionFeature.ts deprecation surfaced (REV-8, minor).
6NFS (FE)7.5§3 Performance (500ms) + §3.C a11y + §3.D browser/perf budget.
7TPS (FE)7.5§4.B FE commands + §4.C per-chunk acceptance + §4.B cross-boundary contract test; package.json scripts (lint/test/test:e2e/build) verified L10-22.
8DMS (BE)9.0§2.3 ER + exact DDL; migration dialect (20241113041150) + chatbot_gpt_record connection re-verified; no-backfill.
9ACV (BE)8.0§2.4 three endpoints with request/response JSON + error codes + optional-field backward compat + dual mount surfaces (mounts re-verified gpt_api.rb:28,33 / gpt_service/api.rb:21,24); casing pinned §2.B.
10DIC (BE)8.0Strong consistency, org-unique upsert (upsert.rb:13-25 + model validates_uniqueness_of :organization_id with deleted_at scope), single-row tx, last-write-wins (§3.A); custom-param create dedup (NameUniquenessValidator#validate_create) re-verified.
11FMC (merged)8.5§3.A/§3.B catalogs + 3 failure-path sequences; FE handles the specific BE codes/messages from §3.B.
12CSS (BE)7.0Concurrency addressed; relies on platform rate-limiting. Low-volume config surface — capacity numbers advisory (rubric §RCS).
13SAS (BE)9.0§3 Security: authz matrix (re-verified set_role(%w[owner admin supervisor]) on all three existing surfaces), token-only tenancy (current_user[:organization_id] / current_user.try(:[],'company_id')), input coercion/length/control-char strip, v-html ban, prompt-injection forward note, whodunnit, log scrubbing.
14ROL (merged)8.5Deploy order specified (BE→FE; rollback FE→BE), Cross-Layer compat matrix, numbered rollback, flag contract.
15OBS (merged)7.56 events (FE+BE), alert, Rollbar/lograge; distributed-trace continuity scoped out for Phase 1 (REV-3, ddtrace/Aegis exist in BE).
16SBC (BE)8.0§2.1 per-service responsibilities; ADR-6 sync/async; ADR-1 column boundary.
17CPA (merged)8.5§2.0 Patterns-to-Follow cites real files both layers (all re-opened); naming consistent with existing snake_case FE consumption (state.reply_limit).
18CDG (BE, cond.)7.5Triggered by rubric = org IP. Handled: no-log-prompt, paper_trail audit (re-verified both models), soft-delete retention; retention/DSAR scope stated.

Caps applied: none triggered — no category < 5.0; ACV ≥ 5; ROL ≥ 5 with deploy order specified; no cross-layer contract mismatch (§2.G, re-verified). 9.0+ band not reached because CNT/DIC are < 8.5 and several FE categories sit at 7.0–7.5.


Decision Closure (8 ADRs)

ADRDecisionCheck 1 (resolve/alts)Check 2 (spec/failure)Check 3 (challenge)Verdict
1New AI columns✅ 3 options, chosen, files cited✅ DDL + contract + read defaultsReversible (drop cols); scales fineResolved
2Widen prompt text✅ vs new column✅ migration up/down + truncation warningForward-only flaggedResolved
3Derived auto_scorable✅ vs stored bool✅ computed in entityNo driftResolved
4New read-only rubric endpoint✅ 3 options (endpoint absent in code — re-confirmed)✅ payload seeded with 9 metricsReversibleResolved
5Feature gate✅ reuse vs new✅ BE (FindFeature) + FE ($hasSubscription) mechanism citedToggle offResolved
6Sync writes / async analytics✅ honest "no alternative"✅ best-effort events (SendMixpanelEventWorker)Holds at 10x (single-row)Resolved
7Reuse role enum✅ vs new roles (enum re-verified)✅ role matrixRevisit if QA role landsResolved
8ai_passing_grade 0–100✅ vs 1–99 (human rule re-verified)✅ coercion-then-range (§2.4 r1)Divergence documentedResolved

8 of 8 resolved, 0 dangling. This is the RFC's strongest dimension, re-confirmed at current HEAD.


Full-Stack Deep-Dives

Cross-Layer Contract Verification

EndpointBE responseFE expectsMatch?
PATCH preference{is_auto_score,passing_grade,is_ai_auto_score,ai_passing_grade} (snake_case)same snake_case (FE consumes API shape directly, per ai-assist reply_limit — re-verified state.reply_limit direct)Yes
POST/PATCH custom param{id,name,prompt,auto_scorable}same; auto_scorable boolean drives chipYes
GET default rubric{status,group,metrics[{code,name,description,veto}]}list + veto badge + PROPOSED noteYes

No casing mismatch (FE consumes snake_case directly — verified against existing store/ai-assist). No nullability traps (AI fields optional with read defaults). No blocker.

Cross-Layer Rollout Compatibility

ScenarioFEBEWorks?
Pre-deployOldOldYes
Backend firstOldNewYes (new cols inert; AI fields optional)
Frontend firstNewOldN/A — deploy order mandates BE first; if it occurred, FE no-ops behind flag
BothNewNewYes
BE rollbackNewOldYes (FE behind flag; AI fields optional)
FE rollbackOldNewYes (BE inert without FE)

Deploy order specified (§4 "Deploy BE before FE. Rollback FE before BE"). No blocker.

End-to-End Flow (traceable in one place?)

Yes — §2.1a sequences S01/S02/S03 trace User → FE → LB → API → hub-service(auth) → chatbot_gpt DB → response → FE state → Mixpanel, including failure branches. Side effects (analytics, paper_trail) shown. An agent can implement each step in order.


Strengths (top 3)

  1. Decision closure (TDC 9.0) — 8 fully-resolved ADRs with reversibility; zero dangling. Re-confirmed at HEAD.
  2. Anti-hallucination grounding (DMS/SAS/PRT) — every claim has a file:line Source-Verification row; R3 re-opened all of them and found zero broken citations. The RFC corrects the PRD where it contradicts code (roles, the 1–99 human range whose message is literally "0 - 99", the auto-scorer that does exist).
  3. Security depth for a config layer (SAS 9.0) — token-only tenancy (re-verified), control-char stripping, v-html ban, log scrubbing, whodunnit, prompt-injection forward note.

Gaps (top 3, specific)

  1. FE component contracts are pattern-referenced, not field-typed (CNT) — and the reference is weaker than stated. §2.A names components + backing endpoints; §2.B types store/service. Component props/emits are deferred to §5 #7. R3 finds the cited ai-assist.vue is <script setup> with no defineProps/defineEmits — so "infer prop shapes from ai-assist.vue" gives an agent nothing to copy; it would design the three components' prop contracts from scratch. (REV-1 carry-forward, sharpened by REV-9.)
  2. botSubscriptionFeature.ts is @deprecated (REV-8, new minor). ADR-5 / §2.0 cite $hasSubscription from plugins/botSubscriptionFeature.ts; the file header marks it deprecated in favor of useSubscription. An agent following the RFC verbatim would extend a deprecated path.
  3. Observability stops at events (OBS). No distributed trace correlating an FE error to BE root cause; explicitly scoped out for Phase 1 (REV-3). Acceptable for a config surface.

Top 3 improvement actions

  1. REV-1/REV-9: add minimal props/emits TS interfaces for AutoScoreToggle, CustomParamEditor, DefaultRubricViewer directly in §2.A — don't point at ai-assist.vue, which exposes no props. Low cost; removes the only real agent-guessing surface left.
  2. REV-8: in ADR-5 / §2.0, note that botSubscriptionFeature.ts is deprecated and state whether the new code uses $hasSubscription (parity) or the preferred useSubscription composable, so the agent doesn't extend a deprecated plugin by accident.
  3. Keep REV-3 trace continuity scoped to Phase 2 (no action; confirmed appropriate).

Findings Ledger (carry-forward)

IDSeverityFindingRFC section to fixfirst_seenstatusresolved_in
REV-1majorFE contracts not typed: store state, service req/resp, component props pattern-referenced only§2.A / §2.BR1partially-fixed (still open)R2 (store+service typed; props deferred → RFC §5 #7); R3 confirmed still open
REV-2majorNo FE↔BE contract/integration test in the plan§4.B / §4.CR1fixedR2 (re-confirmed R3: §4.B cross-boundary test + §4.C ch.11)
REV-3minorNo distributed-trace correlation FE→API→BE§3 MonitoringR1fixed (scoped-out)R2 (continuity noted + scoped; re-confirmed R3)
REV-4minorNo browser-support matrix / FE performance budget§3 / §3.CR1fixedR2 (§3.D; re-confirmed R3)
REV-5minorCustom-param create duplicate handling / idempotency not explicit§2.4 r2 / §3.AR1fixedR2 (NameUniquenessValidator#validate_create cited; R3 re-verified the validator exists at the cited path with the exact message)
REV-6minorAPI response casing convention vs FE consumption not stated§2.B / §2.GR1fixedR2 (§2.B; R3 re-verified state.reply_limit snake_case consumption)
REV-7minorRubric-text data governance (retention/DSAR scope) not stated§3 SecurityR1fixedR2 (re-confirmed R3)
REV-8minorbotSubscriptionFeature.ts ($hasSubscription) cited by ADR-5/§2.0 is marked @deprecated (prefer useSubscription)§2.0 / ADR-5R3open— (promoted to RFC §5 #8)
REV-9minorREV-1's reference target ai-assist.vue is <script setup> with no defineProps/defineEmits, so "infer component props from ai-assist.vue" gives the agent no contract to copy — strengthens the case to type the 3 components' props in §2.A§2.A / §2.BR3open— (folded into RFC §5 #7)

Ledger summary: 3 open (0 blocker / 1 major REV-1 / 2 minor REV-8, REV-9), 0 newly fixed this cycle (R2 fixes re-confirmed against code), 0 accepted-risk. REV-1 carried forward (still open); REV-8 and REV-9 minted this cycle from re-grounding. All R2 fixes (REV-2..REV-7) re-verified as still holding in current code/RFC. Still-open material findings promoted to RFC §5.

Review History

CycleDateReviewed revision (last_updated / commit)ScoreVerdictFindings open → fixedNotes
R12026-06-20last_updated 2026-06-20 / working tree8.0PROCEED7 open, 0 fixedInitial full-stack score; 7 findings raised.
R22026-06-20last_updated 2026-06-20 (in-session revision) / working tree8.5PROCEED1 open (REV-1 partial), 6 fixedRFC revised in-session; REV-2..REV-7 fixed, REV-1 partially.
R32026-06-20last_updated 2026-06-20 / chatbot fa6dd8b79 · chatbot-fe f5b80d5a · hub-service 8cfe79c61 · hub-core 68586c45ee8.5PROCEED3 open (REV-1, REV-8, REV-9), 0 newly fixedRefresh/confirm cycle. Re-grounded every citation at current HEADs: zero broken, all R2 fixes hold. Minted REV-8 (deprecated $hasSubscription plugin) + REV-9 (cited ai-assist.vue exposes no props). Score held — drift is cosmetic, not contract-breaking.

Dangling Decisions

None. All 8 ADRs resolved.

Open Questions (promoted to RFC §5)

#QuestionCategorySeverity
1REV-1 / REV-9 (carry-forward): type the three components' props/emits in §2.A rather than deferring to ai-assist.vue, which is <script setup> and exposes no prop contract.CNTImportant
2REV-8 (new): plugins/botSubscriptionFeature.ts is @deprecated; state whether new FE uses $hasSubscription (parity) or useSubscription (preferred).DEP/CPANice-to-have
3Pre-existing RFC items: Figma frames (§5 #1, blocker for FE pixel fidelity), DSAI 9-metric confirmation (§5 #2), plan-tier provisioning owner (§5 #5) — none block agent execution of BE + FE logic.(tracked in RFC §5)

Evidence Notes

  • R3 re-grounding — every Source-Verification row + Patterns-to-Follow citation re-opened at the four current HEADs; results in the "R3 Re-Grounding Summary" table. Zero broken citations; this is what holds the score at 8.5 rather than dropping it.
  • name_uniqueness_validator.rb — REV-5's fix re-verified: file exists at chatbot/app/core/repositories/gpt/scorecard_custom_parameter/name_uniqueness_validator.rb, validate_create (L12), invoked create.rb:74, exact 422 message create.rb:75.
  • ai-assist.vue — re-opened: <script setup>, no defineProps/defineEmits (uses defineExpose); useVuelidate/$toast/$hasSubscription all present; mixpanel.track(MIXPANEL_EVENTS.*, {...}) at L926/933/939 (inline literals). Drove REV-9 and the cosmetic-drift note on §2.0's mixpanel pattern line.
  • Role enumhub-core user.rb:38-44: same 5 values as cited; only declaration order differs (admin,supervisor,agent,owner,member). Not load-bearing — ADR-7 maps personas onto the set, not the order.