RFC Review: Midtrans Native Integration — Phase 1 (P0 Core Actions)
Companion review for
midtrans-phase-1-p0-core-actions.md, produced by therfc-reviewerskill. Lives beside the RFC; valid only for the RFC revision inreviewed_rfc_last_updated. This file records three cycles: R1 (initial draft) and R2 (post-iteration; reviewed against working tree at HEAD392ffaa) in one prior session, and R3 — a cumulative DELTA re-review that re-grounds every citation against the MOVEDchatbotHEADfa6dd8b79(top commit literallyfix(service): skip actions with unresolved argument types).
Executive Summary
- Overall Score:
7.0/10(R3; R2 was7.5, R16.5) - Rating:
Strong(R3; R2Strong, R1Needs Work) - RFC Type:
full-stack - Sub-Type: frontend
new-feature· backendnew-feature - Assessment Confidence:
High(every RFC anchor re-opened against currentchatbot fa6dd8b79/chatbot-fe f5b80d5a; drift recorded per-row in §Evidence Notes and the citation table below) - Applied Caps/Gates: No hard cap triggers (no category < 5.0; the one cross-layer
partialcarries a documented mapper mitigation, so the 6.5 cross-layer-mismatch cap does not apply). The −0.5 vs R2 is a judgment move, not a cap: a newly-discovered, code-introduced execution gap (REV-9) lowers DMS/CNT/ACV confidence. - Implementation Readiness Verdict:
PROCEED with notes— an agent can still build all 9 chunks against the shipped default-Bearer path; but one new pre-seed step is now mandatory (REV-9: eachnode_registriesproperty must carry a non-blanktype, else the action is silently dropped at AI-Service sync). The two prior external confirmations (REV-1, REV-7) remain open. - Report Path:
chatbot/native-integration/rfcs/midtrans-phase-1-p0-core-actions-review.md - RFC Author: Dimas Fauzi Hidayat | Reviewed: 2026-06-20 (R3)
This RFC remains unusually well-grounded: after re-opening every anchor against the moved codebase,
the executor framework (ActionExecutorFactory, NodeTypeRegistry::GROUP, NodeExecutorInterface),
AuthorizedHttpClient, the shipped midtrans_oauth2.yml, the api_connections/node_registries/
ai_agent_tools mounts, the CTA-URL send path, events, flag, and encryption all still verify
exactly as the RFC describes — citation health is high. The single material consequence of the
codebase move is the top commit fa6dd8b79 "skip actions with unresolved argument types":
FrontendService::V2::AiAgent::Repositories::SyncToAiService now filters out any action whose
AI-resolvable arguments have a blank type, resolving that type for non-api actions from the
matching node_registries row's properties[].type. The RFC's §2.3 properties[] spec lists fields
only as field → html.element and omits the per-property type — so a Midtrans action seeded as
written would be silently skipped from the agent's skill pack. That is the new R3 finding (REV-9) and
the one thing that must be fixed before chunk 1 seeds the registry rows. Two minor citation paths
drifted (FE actionIconConfig.ts moved under modules/bot-automation/utils/; the input-text
shorthand in §2.3 must render as html.element:'input' + html.type:'text') — both recorded below.
Quick Verdict
Why this RFC can be implemented agentically:
- Every execution chunk (§4.D) still has exact, re-verified file paths and repo-sourced commands; the executor pattern is copied from a real, still-present precedent (
GoogleSheets::Execute). - The auth/HTTP seam, registry wiring, CTA-URL delivery, events, flag/plan gating, encryption, and rollback are all re-grounded to verified code at
fa6dd8b79— minimal guessing surface. - Failure handling is thorough and includes the 429
rate_limitedpath and pinned per-call request/response schemas (REV-2/REV-3 fixes confirmed to still hold).
Why this RFC will still cause agent guessing or a pre-merge pause:
- NEW (REV-9): the seeded
properties[]lack a per-propertytype, which the moved code (fa6dd8b79) now requires — actions would be silently dropped at AI-Service sync. This must be fixed in the §2.3 schema before chunk 1. - The live auth header for
/v2/*is confirmed only after a Midtrans-docs check (REV-1) — both outcomes pre-specified. - The Admin credential field set depends on REV-1/REV-7; chunk 7 form copy is provisional.
properties[]field names remain provisional pending Midtrans docs (OQ-6 / REV-8).
Findings Ledger (carry-forward)
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | major (was blocker) | Auth header for Midtrans v2 (Bearer vs Basic) unconfirmed — gated chunks 3–6 | §2.4 external calls, OQ-3 | open (de-risked) | R1 | — (mitigated R2) | R2 added §2 Decision 8: both Bearer (shipped) + midtrans_basic fallback specified; executor auth-agnostic via AuthorizedHttpClient → config swap, not code change. R3 re-verified: midtrans_oauth2.yml (header_strategy: bearer_token, oauth2_client_credentials, ttl_seconds:3300) + AuthorizedHttpClient.call(...max_retries:1, call_options:{}) + AUTH_FAILURE_CODES=%w[401 403] all still present. Residual = 1-line doc confirmation. |
REV-2 | major | Per-call Midtrans request/response schemas not pinned (ACV) | §2.4 | fixed | R1 | R2 | R3 confirms §2.4 still carries concrete JSON request/response per call incl. consumed fields + error shapes. Fix holds. |
REV-3 | major | Midtrans rate-limit / 429 handling unspecified | §3 Perf, §3.A, §3.B | fixed | R1 | R2 | R3 confirms 429 → no-auto-retry policy + distinct rate_limited failure_reason + copy + catalog rows still present; AuthorizedHttpClient retries only on 401/403 (verified AUTH_FAILURE_CODES). Fix holds. |
REV-4 | minor | No FE browser-support matrix / perf budget rationale (NFS) | §3 Performance | fixed | R1 | R2 | R3 confirms browser matrix + perf n/a rationale still present. Fix holds. |
REV-5 | minor | FE config-panel analytics not defined (OBS) | §3 Monitoring | fixed | R1 | R2 | R3 confirms reuse-existing-analytics note + optional midtrans_action_configured event still present. Fix holds. |
REV-6 | minor | api_connections → connect-form casing/derivation gap (cross-layer ACV) | §2.G | accepted-risk | R1 | R2 | R3 re-verified: POST /v1/api_connections mounted (api.rb:28); FE-side mapper deriving environment/connected still the specified mitigation; no BE change. Acceptable. |
REV-7 | major | PRD auth fields/TTL/endpoint mismatch needs PM reconciliation | grounding note, OQ-2 | open | R1 | — | External: PM must confirm field set; coupled to REV-1. R3 re-verified the grounding-note table is still accurate vs shipped midtrans_oauth2.yml (environment/client_id/client_secret, /v1/oauth/token, 3300 s). |
REV-8 | minor | properties[] field names provisional | §2.3, OQ-6 | open | R1 | — | External: confirm against Midtrans docs; schemas pinned in §2.4 reduce gap. (Distinct from REV-9, which is about the missing type key, not the names.) |
REV-9 | major | properties[] rows omit the per-property type that fa6dd8b79 now requires — non-api actions whose AI-resolvable args resolve to a blank type are silently filtered out of the AI-Service sync (args_with_unresolved_type?), so the Midtrans actions would never reach the agent. | §2.3 properties[] table; §2.D | fixed | R3 | RFC rev 2026-06-21 | NEW (codebase moved 392ffaa→fa6dd8b79). app/api/frontend_service/v2/ai_agent/repositories/sync_to_ai_service.rb: build_skill_actions now uses filter_map + next if args_with_unresolved_type?(args); build_non_api_args sets type: map_param_type(registry_prop&.dig('type')) from the matching node_registries properties[].type. Factory with_properties shows canonical shape {name,type,required}; FE PropertiesItem also requires non-optional type:string. RFC §2.3 listed only field → html.element. FIXED (RFC last_updated 2026-06-21): §2.3 rewritten with a per-property type column (string/integer/array/object), the grounded canonical JSON shape, and the SyncToAiService drop rationale cited to :404-467; §4.D chunk 1 acceptance now asserts a use_ai Midtrans action survives build_skill_actions (args_with_unresolved_type? ⇒ false); OQ-8 → resolved. (Field names still tracked by REV-8/OQ-6.) |
Ledger summary: 2 open (REV-1, REV-7 — both external confirmations), 1 newly fixed (REV-9, fixed in RFC rev 2026-06-21; the R2 fixes REV-2..REV-5 also re-confirmed to hold), 1 accepted-risk (REV-6), 1 minor open (REV-8). Still-open material findings (REV-1, REV-7) remain promoted to the RFC's §5 Open-Questions table by id. Note: REV-9 was fixed by an RFC edit made after the R3 scoring pass (RFC last_updated advanced 2026-06-20 → 2026-06-21); a fresh R4 pass should re-score DMS/CNT/ACV (R3 had docked them −0.5 for REV-9) and confirm the chunk-1 sync-survival rspec lands during implementation.
Citation Re-Grounding (R3 — codebase moved 392ffaa → fa6dd8b79)
The core of this cycle. Every anchor re-opened against current HEAD. ✓ valid / ⚠ drifted / ✗ broken.
| Anchor (RFC cite) | Current code | Status |
|---|---|---|
config/auth_providers/midtrans_oauth2.yml (bearer_token, oauth2_client_credentials, ttl_seconds:3300, sandbox/prod /v1/oauth/token, fields environment/client_id/client_secret) | identical | ✓ valid |
OrganizationConnection#auth_type_enum contains midtrans_oauth2 | %w[… midtrans_oauth2 google_oauth2] | ✓ valid |
lib/auth/authorized_http_client.rb — call(credential:, request:, http_client:, max_retries:1, call_options:{}); AUTH_FAILURE_CODES=%w[401 403] | identical | ✓ valid |
lib/auth/token_manager.rb + token_stores/redis_store.rb + payload_encryptor.rb | all present | ✓ valid |
NodeTypeRegistry::GROUP (RFC quotes {'api','mekari_qontak_crm','google_sheets',…}) | {'api','mekari_qontak_crm','mekari_qontak_chat','google_sheets'} — same shape; midtrans correctly absent (to be added by chunk 1) | ✓ valid |
ActionExecutorFactory.for resolves NodeRegistry.find_by_type_and_version(action_type)&.node_type_group → GROUP[group], constantize under PREFIX | identical logic (api special-cased) | ✓ valid |
NodeExecutorInterface keyword init (organization_id:, credential:, parameters:, arguments:, room_id:, node_type:) | present at nodes/node_executor_interface.rb | ✓ valid |
GoogleSheets::Execute group executor dispatching on @node_type | include NodeExecutorInterface/ArgumentInterpolation; case @node_type; credential[:id] = OrganizationConnection id | ✓ valid |
db/schema.rb node_registries (node_type, node_type_group, properties jsonb, enabled, unique (node_type, version)) | present (schema comment "Maps to an executor class via NodeTypeRegistry") | ✓ valid |
GET /v1/node_registries / POST /v1/api_connections / /v1/ai_agent_tools mounts | api.rb:51 / :28 / :46 — exact lines match | ✓ valid |
send_message_interactive.rb send_button_message(..., link:) | present | ✓ valid |
feature_flag.rb FeatureFlag.enabled?(group, code, default:) | present | ✓ valid |
NodeRegistry.find_by_type_and_version | app/models/node_registry.rb:8 | ✓ valid |
Action arguments/argument-type resolution (RFC Assumption 3: args channel like API/Sheets) | CHANGED by fa6dd8b79: SyncToAiService#build_non_api_args resolves arg type from node_registries properties[].type; build_skill_actions now drops actions with any blank arg type (args_with_unresolved_type?). RFC §2.3 omits property type. | ⚠ drifted → REV-9 (new major) |
FE utils/actionIconConfig.ts SOURCE_ICON["Google Calendar"] (Source Verification row) | file is at modules/bot-automation/utils/actionIconConfig.ts (not repo-root utils/); content confirms SOURCE_ICON["Google Calendar"] → /icons/google-calendar.svg + a default fallback | ⚠ minor path drift (claim otherwise correct) |
FE ActionIntegrationForm.vue loops requiredItem.properties[] by prop.html.element | present; but text inputs key on html.element === 'input' && html.type === 'text' (not a single 'input-text' token) | ⚠ §2.3 input-text shorthand must map to html.element:'input' + html.type:'text' |
FE NewActionDrawer.vue / CreateActionForm.vue / ActionListItem / ai-agents.ts (fetchActionList → node_registries) / ai-agent-tools.ts / nlp-feature.ts | all present; ActionListItem/PropertiesItem shapes match (properties?: PropertiesItem[], PropertiesItem.type:string + html{}) | ✓ valid |
FE: "no Midtrans MidtransAuthBlock.vue in production" (net-new) | absent (correct — net-new) | ✓ valid |
Net: 16 anchors ✓, 1 ✗-equivalent drift that is genuinely material (REV-9), 2 minor path/shorthand drifts. The "unresolved argument types" fix does impact the RFC's action-argument assumptions — see REV-9.
PRD → RFC Traceability Matrix
Standard format (PRD exists)
| PRD Element | RFC Section | Coverage |
|---|---|---|
| §6 four P0 action types | §1 PRD-to-Schema rows 1–8, §2.3 seed rows, §2.4 | Full (but see REV-9 — seeded properties[] need type to actually sync) |
| MIDTRANS-S01 (configure action) | §1 Detail 1.A/1.C, §2.A, §2.4 | Full |
| MIDTRANS-S02 (check status) | §2.2 seq-1, §4.D chunk 3 | Full |
| MIDTRANS-S03 (payment link + CTA-URL) | §2.2 seq-2, §4.D chunk 4 | Full |
| MIDTRANS-S04 (cancel + confirm) | §2.1 branch, §2.2 seq-4, §4.D chunk 5 | Full |
| MIDTRANS-S05 (refund + confirm + idempotency) | §2.D, §2.2 seq-4, §4.D chunk 6 | Full |
| §7 idempotency / rate-limit / partial-refund | §2 Decision 4, §3 (rate-limit), §5 OQ-5 | Full (partial deferred with documented default) |
| §10 observability events | §3 Monitoring (5 events incl. rate_limited) | Full |
| NEG-1..4 | §1 Detail 1.A, §3 | Full |
| §12 launch stage gates | §4 (signals) | Partial — schedule intentionally deferred to delivery/ (RFC scope rule) |
Summary: 9 of 9 core PRD areas fully covered; 1 intentionally-partial. Reverse: every new artifact traces to a PRD AC; refund_key dedupe is RFC-introduced and justified vs PRD §7. No scope-creep. The only new caveat is execution-time (REV-9), not coverage.
PRT score: 9.0 (unchanged).
Scorecard
Full-Stack Scorecard (18 categories) — R3
| # | Category | Source | Score (R2→R3) | Evidence-Based Rationale |
|---|---|---|---|---|
| 1 | PRT — PRD Traceability | Merged | 9.0 | FE: Detail 1.A surface/role coverage · BE: PRD-to-Schema + per-section coverage; both directions present. Re-verified. |
| 2 | TDC — Technical Decisions | Merged | 8.0 | 8 ADR blocks w/ options+reversibility; Decision 8 closes auth strategy. Re-grounded; still resolved. |
| 3 | CNT — Contract Specificity | FE | 7.0→6.5 | MidtransAuthBlock props/events typed (§2.A); but the registry-driven properties[] (§2.3) omit per-property type that FE PropertiesItem requires and BE sync now enforces (REV-9); input-text shorthand drifts from html.element:'input'+html.type:'text'. |
| 4 | SCB — Scope Boundaries | FE | 8.0 | §2.I lists FE create/modify/NOT-touched explicitly. Re-verified. |
| 5 | DEP — Dependencies | FE+BE avail | 8.5 | §1 Dependencies table tags each as exists/needs-confirming; every "exists" row re-opened and confirmed at fa6dd8b79. |
| 6 | NFS — Non-Functional | FE | 7.5 | Browser matrix + perf n/a rationale + a11y (§3.E). Unchanged. |
| 7 | TPS — Test Plan | FE | 7.0 | §4.C commands repo-sourced (vitest/rspec/build); cross-layer manual sandbox. Gap surfaced: no test asserts the seeded actions survive SyncToAiService filtering (REV-9). FE unit specifics still light. |
| 8 | DMS — Data Model & Schema | BE | 7.5→7.0 | No new tables; seed rows + schema ref + per-status note. Seed properties[] are now functionally incomplete (missing type) given the moved code — would sync-skip the action (REV-9). |
| 9 | ACV — API Contract & Versioning | BE | 7.5 | §2.4 per-call request/response schemas + Decision 8 dual-auth re-verified; external contract concrete. The internal ActionExecute→sync contract gained a new precondition (REV-9) but the HTTP contracts themselves are intact. |
| 10 | DIC — Data Integrity & Consistency | BE | 8.5 | §2.D integrity matrix; refund refund_key + redis dedupe; cancel natural idempotency. Re-verified. |
| 11 | FMC — Failure Mode Coverage | Merged | 8.5 | BE failure+branch+error catalogs; FE error catalog; 429 rate_limited; FE↔BE code-shape check. Re-verified. |
| 12 | CSS — Concurrency & Scaling | BE | 7.5 | §2.E collision map (token herd, refund dedupe); explicit 429 policy. Unchanged. |
| 13 | SAS — Security & Authorization | BE | 8.5 | Role×endpoint matrix, SSRF mitigation (no free-form URL), Lockbox encryption (payload_encryptor.rb verified), masked reads, brakeman. |
| 14 | ROL — Rollout & Rollback | Merged | 8.5 | Deploy order (BE-first) + cross-layer compat matrix + flag + ordered rollback recipe. Re-verified. |
| 15 | OBS — Observability | Merged | 7.5 | BE events/logs/alerts thorough; FE analytics note lifts the BE-only ceiling. Unchanged. |
| 16 | SBC — Service Boundary & Coupling | BE | 8.0 | Sync-vs-async (Decision 5), single-squad boundary, executor seam. Re-verified. |
| 17 | CPA — Pattern Alignment | Merged | 8.5 | Patterns-to-Follow table both layers, each citing a read reference file; all reference files re-opened and present. |
| 18 | CDG — Compliance & Data Governance | BE | 8.0 | Triggered (payment); §3.D — no PAN stored, only encrypted merchant credential; scope bounded. |
Resource & Cost Advisory
- Synchronous execution holds a pod ≤60 s per in-flight action; fine at P0 volume (RFC §4.F / §5). Non-blocking.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| 1 | Single Midtrans::Execute group executor | Resolved | none — mirrors GoogleSheets::Execute (re-verified present) |
| 2 | Outbound via AuthorizedHttpClient | Resolved | none (signature re-verified) |
| 3 | 60 s timeout via call_options | Resolved | confirm call_options forwards to ::Http#call (noted in RFC; call_options param re-verified present on AuthorizedHttpClient) |
| 4 | Refund idempotency (refund_key + dedupe) | Resolved | none |
| 5 | Synchronous execution | Resolved | none |
| 6 | Feature flag + plan gating | Resolved | flag name proposed, confirm (OQ-1) |
| 7 | Multi-tenancy isolation | Resolved | none |
| 8 | Auth header (Bearer + Basic fallback) | Resolved (with 1 external confirmation) | REV-1 doc check before live wiring; both paths specified |
Aggregate: 8 of 8 decisions Resolved, 0 Partial, 0 Dangling. R3 note: no decision regressed, but a new implicit precondition (every registry property needs a type so the action survives SyncToAiService) is not captured by any decision and surfaced as REV-9.
Decision: 8 — Auth header strategy (the R1→R2 keystone; R3 re-grounded)
Status: Resolved (re-verified against fa6dd8b79)
What was decided — Default to Bearer via the shipped midtrans_oauth2 descriptor; specify a drop-in midtrans_basic descriptor as the fallback, selected by a single confirmation. The executor is auth-agnostic because it calls AuthorizedHttpClient.call(credential:, request:).
R3 grounding — config/auth_providers/midtrans_oauth2.yml (header_strategy: bearer_token, token_flow.strategy: oauth2_client_credentials, ttl_seconds: 3300, env→/v1/oauth/token map), lib/auth/authorized_http_client.rb (call(... max_retries:1, call_options:{}), AUTH_FAILURE_CODES=%w[401 403]) — all present and unchanged. The fallback YAML is fully written out in the RFC.
Failure handling — 401/403 retry-once (verified existing); 429 → rate_limited (no retry); auth failure → auth_error.
Challenge results — Scale: read-heavy, low volume — holds. Reversibility: trivial (auth_type swap). Consistency: aligns with grounding note + OQ-2/OQ-3. Agent implementability: an agent can build against the default Bearer path immediately; only the live-header wiring waits on REV-1.
Gaps / suggested resolution — Get the Midtrans-docs confirmation (REV-1); if Basic, confirm server_key (REV-7).
(Decisions 1–7 are all Resolved with grounded references re-opened at fa6dd8b79; abbreviated per the rubric's ≥7.0 optimization.)
Cross-Layer Contract Verification
| Endpoint | Backend Response Schema | Frontend Expected Schema | Match? | Gaps |
|---|---|---|---|---|
GET /v1/node_registries | rows {node_type,name,categories,properties,settings,version} | ActionListItem {node_type,name,categories[],version,inputs[],outputs[],properties?:PropertiesItem[],settings?} (re-verified L49–66) | Yes | snake_case already consumed; but PropertiesItem.type is non-optional (L4) and the seeded rows must populate it — same root as REV-9 |
POST /v1/api_connections | {id,auth_type,auth_data(masked),name,code} | connect form expects {id,environment,connected} | Partial | FE-side mapper derives environment/connected — specified in §2.G (REV-6, accepted) |
POST /v1/ai_agent_tools | {id,action_type,name,parameters,credentials} | create payload mirror | Yes | existing CRUD |
(internal) ActionExecute → SyncToAiService | action synced only if every AI-resolvable arg has a non-blank type (fa6dd8b79) | n/a (agent-side) | No (new) | REV-9 — registry properties[].type must be set or the action is filtered out before reaching the agent |
Mismatches found: 1 prior partial (mapper-mitigated, no cap) + 1 new functional mismatch (REV-9) between the seeded property schema and the post-fa6dd8b79 sync contract. REV-9 is a schema-completeness gap fixable in the RFC, not a backend-vs-frontend shape contradiction, so it does not trip the 6.5 cross-layer-mismatch cap — but it is a blocker for chunk 1 correctness.
Cross-Layer Rollout Compatibility Matrix
| Scenario | Frontend | Backend | Works? | Notes |
|---|---|---|---|---|
| Pre-deploy | Old | Old | Yes | baseline |
| Backend first | Old | New | Yes | registry rows inert behind flag OFF |
| Frontend first | New | Old | No | avoided by deploy order (BE first) |
| Both deployed | New | New | Yes | target |
| Backend rollback | New | Old | Partial | flag OFF hides actions |
| Frontend rollback | Old | New | Yes | BE inert behind flag |
Deploy order: Backend first (specified, §4 + Detail 4.A). The one "No" is explicitly avoided by deploy order → ROL not capped. Re-verified unchanged.
End-to-End Data Flow (spot-check: create payment link)
Customer message → AI Agent identifies payment intent
→ [config-time] action synced to AI Service ONLY if every arg has a non-blank type (fa6dd8b79 — REV-9)
→ internal ActionExecute (flag + plan gate)
→ Midtrans::Execute#midtrans_create_payment_link
→ AuthorizedHttpClient.call(POST /v1/payment-links {transaction_details,…}, Bearer/Basic)
→ Midtrans 201 { payment_url }
→ SendMessageInteractive#send_button_message(link: payment_url) [WhatsApp] / text [other]
→ SendMixpanelEventWorker(midtrans_payment_link_created {payment_link_url, channel_type})
Gaps in flow: the runtime hops still name real, re-verified files. The new gap is one step earlier, at config/sync time: REV-9 (a property without type ⇒ the action is dropped from the synced skill pack, so the agent never invokes the executor at all). Same caveat applies to status/cancel/refund.
Agentic Readiness Deep-Dive
Vague Word Audit
Spec sections are typed/measured; "TBD" appears only inside explicitly-labelled Open Questions. Vague words in spec sections: ~0.
Dangling Alternatives
0 — Decision 8 resolves the only R1 dangling alternative.
Task Decomposition Assessment
9 ordered chunks (§4.D), each with files + repo-sourced commands + assertable criteria. Verifiable. R3 addition: chunk 1's acceptance criteria should be extended to assert the seeded rows carry a type per property and that SyncToAiService does not skip the Midtrans actions (REV-9).
Strengths
- Citation health survived a codebase move — re-opening every anchor at
fa6dd8b79(vs prior392ffaa), the entire BE execution/auth framework and all fourapi.rbmounts still verify line-for-line. This is rare and is the RFC's biggest asset. - Intellectual honesty on the PRD-vs-code gap — the §1 grounding note still matches the shipped
midtrans_oauth2.ymlexactly (environment/client_id/client_secret,/v1/oauth/token, 3300 s). - Strong safety design for irreversible actions — status pre-check + confirmation + refund
refund_key/dedupe (§2.D, Decision 4), with webmock-assertable "endpoint NOT called" criteria.
Biggest Gaps
- REV-9 (NEW, major): the §2.3
properties[]rows omit the per-propertytypethe moved code now requires; a Midtrans action seeded as written is silently filtered out of the AI-Service sync (args_with_unresolved_type?insync_to_ai_service.rb). Highest-impact gap this cycle — it would make the feature appear configured but non-functional. - REV-1 / OQ-3 (de-risked, external): the live auth header on
/v2/*still needs a one-line Midtrans-docs confirmation; default Bearer is shipped, fallback is specified. - REV-7 / OQ-2 (external): PM must reconcile the PRD's
server_key-era field names with the chosen descriptor so chunk 7's credential form is built once.
Priority Actions
- REV-9 / §2.3
properties[]— add an explicit non-blanktype(e.g.string/integer) to every seeded property, matching the factory shape{name,type,required}and the FEPropertiesItem.type. Extend chunk 1 acceptance criteria with an rspec assertingSyncToAiServicedoes NOT skip the seeded Midtrans actions (args_with_unresolved_type?returns false). Do before chunk 1 seeds the rows. - REV-1 / §2.4 + Decision 8 — confirm against Midtrans docs whether
/v2/*accepts the OAuth2 Bearer token; else point the credential atmidtrans_basic(already specified). Unblocks live wiring on chunks 3–6. - REV-7 / OQ-2 — PM confirms the credential field set so the FE form copy (chunk 7) is final.
- §2.3
input-textshorthand / §2.0 FE Source Verification path — clarify thatorder_id → input-textrenders ashtml.element:'input' + html.type:'text'(perActionIntegrationForm.vue), and fix theactionIconConfig.tspath tomodules/bot-automation/utils/actionIconConfig.ts. - OQ-6 (REV-8) — confirm
properties[]field names/optionality against Midtrans docs. - OQ-1 — confirm feature-flag name
rollout / midtrans_native_action.
Backend Contract Addendum
Endpoint Contract Details (external Midtrans calls — pinned in §2.4, re-verified)
| Endpoint | Method/Path | AuthZ | Request Contract | Response Contract | Error Contract | Idempotency/Versioning | Status |
|---|---|---|---|---|---|---|---|
| Check status | GET /v2/{order_id}/status | Bearer/Basic (Decision 8) | no body | {order_id, transaction_status, status_code,…} | 404 not-found, 429 rate_limited | read; n/a | Complete (confirm field casing OQ-6) |
| Create link | POST /v1/payment-links | Bearer/Basic | {transaction_details{order_id,gross_amount}, item_details?, customer_details?,…} | {order_id, payment_url} | 400 error_messages | n/a | Complete |
| Cancel | POST /v2/{order_id}/cancel | Bearer/Basic | no body | {status_code, transaction_status} | 412 invalid status | natural (status pre-check) | Complete |
| Refund | POST /v2/{order_id}/refund | Bearer/Basic | {refund_key, amount?, reason?} | {status_code, transaction_status, refund_key} | 412/400 | refund_key + redis dedupe | Complete |
Database / Seed Changes Details
| Change | Table/Entity | DDL / Shape Diff | Data Migration Plan | Rollback Plan | Compatibility Window | Status |
|---|---|---|---|---|---|---|
| Seed 4 catalog rows | node_registries | rows only (no DDL); each properties[] entry MUST include a non-blank type (REV-9) plus the html block FE renders | idempotent data migration on (node_type, version) | set enabled=false / db:rollback | inert behind flag | Missing type in seeded properties (REV-9) |
Implementation Readiness Checklist
Unblocked (agent can proceed)
- PRD → RFC traceability complete (forward + reverse)
- All technical decisions resolved (8/8; Decision 8 with 1 external confirmation)
- Failure modes handled with error catalog (incl. 429
rate_limited) - Configuration contract (flag + env + per-org credential) documented
- Rollout plan with deploy order, flag, ordered rollback
- Observability events + alerts defined (BE) + FE analytics note
- Cross-layer contract verified (1 partial w/ mapper mitigation)
- Deploy order specified (BE-first); compat matrix has no unaddressed "No"
- End-to-end data flow traceable for each action
- Security: authZ matrix, SSRF mitigation, tenancy isolation, secret encryption
- Compliance handled (payment-adjacent; no PAN stored)
Blocked (must fix first)
- REV-9 — seed
properties[]must carry a per-propertytype, else the Midtrans actions are silently dropped at AI-Service sync (fa6dd8b79). Fix the §2.3 schema + add the sync-survival assertion to chunk 1 before seeding. - Before live auth wiring (chunks 3–6): REV-1 doc confirmation.
- Before FE credential form (chunk 7): REV-7 PM field reconciliation.
Verdict: Ready to implement against the default Bearer path AFTER closing REV-9 in the §2.3 seed schema; close REV-1/REV-7 before finalizing the live auth header and credential form.
Task Manifest
The RFC's §4.D Agent Execution Plan is complete and well-ordered (9 chunks, files + commands +
acceptance criteria) and its file paths re-verified at fa6dd8b79. One amendment: chunk 1 must
seed properties[] with a per-property type and assert the seeded Midtrans actions survive
SyncToAiService filtering (REV-9) before the rest proceeds. Chunks 3–6 default to Bearer and
need only REV-1 before live wiring; chunk 7 (FE credential form) needs REV-7 for final field copy.
Dangling Decisions Log
| # | Decision | Location | Owner | Deadline |
|---|---|---|---|---|
| — | None dangling. REV-1 (auth confirmation) and REV-7 (field reconciliation) are external confirmations; REV-9 is a seed-schema correction, not an unresolved design. | §2.4 / §2.3 / OQ-2/OQ-3 | BOT Eng + PM | 2026-06-27 |
Open Questions
| # | Question | Category | Severity |
|---|---|---|---|
| REV-9 | Do the seeded node_registries properties[] carry a non-blank type per property, so SyncToAiService (fa6dd8b79) does not silently skip the Midtrans actions? | DMS/CNT/ACV | Blocking (for chunk 1) |
| REV-1 | Does the OAuth2 Bearer token authenticate Midtrans /v2/* + /v1/payment-links? (else use midtrans_basic) | ACV/TDC | Important |
| REV-7 | Reconcile PRD auth field names/TTL/endpoint with the chosen descriptor; finalise FE form copy | TDC/CNT | Important |
| OQ-6 (REV-8) | Confirm properties[] field names/optionality vs Midtrans docs | DMS/CNT | Important |
| OQ-1 | Confirm feature-flag name rollout / midtrans_native_action | ROL | Nice-to-have |
| OQ-5 | Partial vs full refund scope for P0 | DIC | Nice-to-have |
Evidence Notes
app/api/frontend_service/v2/ai_agent/repositories/sync_to_ai_service.rb(NEW atfa6dd8b79) —build_skill_actionsnowfilter_maps withnext if args_with_unresolved_type?(args);build_non_api_argsderivestype: map_param_type(registry_prop&.dig('type'))fromnode_registriesproperties[].type. This is the single material consequence of the codebase move and the basis for REV-9. Lowered DMS 7.5→7.0 and CNT 7.0→6.5.config/auth_providers/midtrans_oauth2.yml,lib/auth/authorized_http_client.rb,node_type_registry.rb,action_executor_factory.rb,nodes/node_executor_interface.rb,google_sheets/execute.rb,app/api/frontend_service/api.rb(mounts 28/46/51),node_registry.rb— all re-opened atfa6dd8b79; match the RFC anchors. Sustained the high PRT/CPA/SAS/DEP scores.spec/factories/node_registry.rb—with_propertiestrait shows canonical property shape{name, type, required}, confirmingtypeis an expected property key (corroborates REV-9).chatbot-femodules/bot-automation/utils/actionIconConfig.ts,ActionIntegrationForm.vue,bot-automation-actions-constants.ts—actionIconConfig.tspath drifted from the RFC'sutils/...;PropertiesItem.typeis non-optional and text inputs render viahtml.element:'input'+html.type:'text'(not'input-text'). Minor §2.3/§2.0 corrections.- §2.4 / §2 Decision 8 / §3 (R2 fixes) — re-confirmed present and consistent; REV-2/3/4/5 remain fixed.
Review History
| Cycle | Date | Reviewed RFC revision (last_updated / commit) | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
R1 | 2026-06-20 | last_updated 2026-06-20 (initial draft) | 6.5 | HOLD | 8 open | Strong grounding; ACV weak (5.5) + auth mechanism a self-declared blocker → HOLD |
R2 | 2026-06-20 | last_updated 2026-06-20 (post-iteration; HEAD 392ffaa working tree) | 7.5 | PROCEED with notes | 4 fixed, 1 de-risked, 1 accepted-risk, 2 open (external) | Decision 8 dual-auth, pinned §2.4 schemas, 429 policy, FE perf/analytics |
R3 | 2026-06-20 | last_updated 2026-06-20 (re-grounded vs MOVED HEAD fa6dd8b79; FE f5b80d5a) | 7.0 | PROCEED with notes | 0 newly fixed (R2 fixes re-confirmed), 3 open (REV-1/REV-7 external + new REV-9 major), 1 accepted, 1 minor open | Re-grounded every citation: 16 ✓, REV-9 drift from fix(service): skip actions with unresolved argument types — seeded properties[] need a per-property type or actions are silently sync-skipped; 2 minor FE path/shorthand drifts. −0.5 vs R2. |