
Sf Industry Commoncore Callable Apex
- 1 installs
- 423 repo stars
- Updated April 27, 2026
- jaganpro/claude-code-sfskills
Generates and reviews Salesforce Industries Common Core System.Callable Apex classes for OmniStudio extension points, scored against a 120-point rubric.
About
Builds and audits callable Apex classes that integrate with OmniStudio, Integration Procedures, and DataRaptors. A Salesforce Industries developer uses it to produce secure, deterministic callable extensions or migrate legacy VlocityOpenInterface code.
- Defines versioned action lists and a consistent response envelope
- Enforces CRUD/FLS rules and 120-point scoring
Sf Industry Commoncore Callable Apex by the numbers
- 1 all-time installs (skills.sh)
- Ranked #3,836 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jaganpro/claude-code-sfskills --skill sf-industry-commoncore-callable-apexAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 423 |
| Last updated | April 27, 2026 |
| Repository | jaganpro/claude-code-sfskills ↗ |
What it does
Generates and reviews Salesforce Industries Common Core System.Callable Apex classes for OmniStudio extension points, scored against a 120-point rubric.
Files
sf-industry-commoncore-callable-apex: Callable Apex for Salesforce Industries Common Core
Specialist for Salesforce Industries Common Core callable Apex implementations. Produce secure, deterministic, and configurable Apex that cleanly integrates with OmniStudio and Industries extension points.
Core Responsibilities
1. Callable Generation: Build System.Callable classes with safe action dispatch 2. Callable Review: Audit existing callable implementations for correctness and risks 3. Validation & Scoring: Evaluate against the 120-point rubric 4. Industries Fit: Ensure compatibility with OmniStudio/Industries extension points
---
Workflow (4-Phase Pattern)
Phase 1: Requirements Gathering
Ask for:
- Entry point (OmniScript, Integration Procedure, DataRaptor, or other Industries hook)
- Action names (strings passed into
call) - Input/output contract (required keys, types, and response shape)
- Data access needs (objects/fields, CRUD/FLS rules)
- Side effects (DML, callouts, async requirements)
Then: 1. Scan for existing callable classes: Glob: **/*Callable*.cls 2. Identify shared utilities or base classes used for Industries extensions 3. Create a task list
---
Phase 2: Design & Contract Definition
Define the callable contract:
- Action list (explicit, versioned strings)
- Input schema (required keys + types)
- Output schema (consistent response envelope)
Recommended response envelope:
{
"success": true|false,
"data": {...},
"errors": [ { "code": "...", "message": "..." } ]
}Action dispatch rules:
- Use
switch on action - Default case throws a typed exception
- No dynamic method invocation or reflection
VlocityOpenInterface / VlocityOpenInterface2 contract mapping:
When designing for legacy Open Interface extensions (or dual Callable + Open Interface support), map the signature:
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options)| Parameter | Role | Callable equivalent |
|---|---|---|
methodName | Action selector (same semantics as action) | action in call(action, args) |
inputMap | Primary input data (required keys, types) | args.get('inputMap') |
outputMap | Mutable map where results are written (out-by-reference) | Return value; Callable returns envelope instead |
options | Additional context (parent DataRaptor/OmniScript context, invocation metadata) | args.get('options') |
Design rules for Open Interface contracts:
- Treat
inputMapandoptionsas the combined input schema - Define what keys must be written to
outputMapper action (success and error cases) - Preserve
methodNamestrings so they align with Callableactionstrings - Document whether
optionsis required, optional, or unused for each action
---
Phase 3: Implementation Pattern
Vanilla System.Callable (flat args, no Open Interface coupling):
public with sharing class Industries_OrderCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
switch on action {
when 'createOrder' {
return createOrder(args != null ? args : new Map<String, Object>());
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> createOrder(Map<String, Object> args) {
// Validate input (e.g. args.get('orderId')), run business logic, return response envelope
return new Map<String, Object>{ 'success' => true };
}
}Use the vanilla pattern when callers pass flat args and no VlocityOpenInterface integration is required.
Callable skeleton (same inputs as VlocityOpenInterface):
Use inputMap and options keys in args when integrating with Open Interface or when callers pass that structure:
public with sharing class Industries_OrderCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'createOrder' {
return createOrder(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
// Validate input, run business logic, return response envelope
return new Map<String, Object>{ 'success' => true };
}
}Input format: Callers pass args as { 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> }. For backward compatibility with flat callers, if args lacks 'inputMap', treat args itself as inputMap and use an empty map for options.
Implementation rules: 1. Keep call() thin; delegate to private methods or service classes 2. Validate and coerce input types early (null-safe) 3. Enforce CRUD/FLS and sharing (with sharing, Security.stripInaccessible()) 4. Bulkify when args include record collections 5. Use WITH USER_MODE for SOQL when appropriate
VlocityOpenInterface / VlocityOpenInterface2 implementation:
When implementing omnistudio.VlocityOpenInterface or omnistudio.VlocityOpenInterface2, use the signature:
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
Map<String, Object> outputMap, Map<String, Object> options)Open Interface skeleton:
global with sharing class Industries_OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
Map<String, Object> outputMap, Map<String, Object> options) {
switch on methodName {
when 'createOrder' {
Map<String, Object> result = createOrder(inputMap, options);
outputMap.putAll(result);
return true;
}
when else {
outputMap.put('success', false);
outputMap.put('errors', new List<Map<String, Object>>{
new Map<String, Object>{ 'code' => 'UNSUPPORTED_ACTION', 'message' => 'Unsupported action: ' + methodName }
});
return false;
}
}
}
private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
// Validate input, run business logic, return response envelope
return new Map<String, Object>{ 'success' => true, 'data' => new Map<String, Object>() };
}
}Open Interface implementation rules:
- Write results into
outputMapviaputAll()or individualput()calls; do not return the envelope frominvokeMethod - Return
truefor success,falsefor unsupported or failed actions - Use the same internal private methods as the Callable (same
inputMapandoptionsparameters); only the entry point differs - Populate
outputMapwith the same envelope shape (success,data,errors) for consistency
Both Callable and Open Interface accept the same inputs (inputMap, options) and delegate to identical private method signatures for shared logic.
---
Phase 4: Testing & Validation
Minimum tests:
- Positive: Supported action executes successfully
- Negative: Unsupported action throws expected exception
- Contract: Missing/invalid inputs return error envelope
- Bulk: Handles list inputs without hitting limits
Example test class:
@IsTest
private class Industries_OrderCallableTest {
@IsTest
static void testCreateOrder() {
System.Callable svc = new Industries_OrderCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'orderId' => '001000000000001' },
'options' => new Map<String, Object>()
};
Map<String, Object> result =
(Map<String, Object>) svc.call('createOrder', args);
Assert.isTrue((Boolean) result.get('success'));
}
@IsTest
static void testUnsupportedAction() {
try {
System.Callable svc = new Industries_OrderCallable();
svc.call('unknownAction', new Map<String, Object>());
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'));
}
}
}---
Migration: VlocityOpenInterface to System.Callable
When modernizing Industries extensions, move VlocityOpenInterface or VlocityOpenInterface2 implementations to System.Callable and keep the action contract stable. Use the Salesforce guidance as the source of truth. Salesforce Help
Guidance:
- Preserve action names (
methodName) asactionstrings incall() - Pass
inputMapandoptionsas keys inargs:{ 'inputMap' => inputMap, 'options' => options } - Return a consistent response envelope instead of mutating
outMap - Keep
call()thin; delegate to the same internal methods with(inputMap, options)signature - Add tests for each action and unsupported action
Example migration (pattern):
// BEFORE: VlocityOpenInterface2
global class OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
global Boolean invokeMethod(String methodName, Map<String, Object> input,
Map<String, Object> output,
Map<String, Object> options) {
if (methodName == 'createOrder') {
output.putAll(createOrder(input, options));
return true;
}
return false;
}
}
// AFTER: System.Callable (same inputs: inputMap, options)
public with sharing class OrderCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = args != null ? (Map<String, Object>) args.get('inputMap') : new Map<String, Object>();
Map<String, Object> options = args != null ? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'createOrder' {
return createOrder(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
}---
Best Practices (120-Point Scoring)
| Category | Points | Key Rules |
|---|---|---|
| Contract & Dispatch | 20 | Explicit action list; switch on; versioned action strings |
| Input Validation | 20 | Required keys validated; types coerced safely; null guards |
| Security | 20 | with sharing; CRUD/FLS checks; Security.stripInaccessible() |
| Error Handling | 15 | Typed exceptions; consistent error envelope; no empty catch |
| Bulkification & Limits | 20 | No SOQL/DML in loops; supports list inputs |
| Testing | 15 | Positive/negative/contract/bulk tests |
| Documentation | 10 | ApexDoc for class and action methods |
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)
---
⛔ Guardrails (Mandatory)
Stop and ask the user if any of these would be introduced:
- Dynamic method execution based on user input (no reflection)
- SOQL/DML inside loops
without sharingon callable classes- Silent failures (empty catch, swallowed exceptions)
- Inconsistent response shapes across actions
---
Common Anti-Patterns
call()contains business logic instead of delegating- Action names are unversioned or not documented
- Input maps assumed to have keys without checks
- Mixed response types (sometimes Map, sometimes String)
- No tests for unsupported actions
---
Cross-Skill Integration
| Skill | When to Use | Example |
|---|---|---|
| sf-apex | General Apex work beyond callable implementations | "Create trigger for Account" |
| sf-metadata | Verify object/field availability before coding | "Describe Product2" |
| sf-deploy | Validate/deploy callable classes | "Deploy to sandbox" |
---
Reference Skill
Use the core Apex standards, testing patterns, and guardrails in:
- skills/sf-apex/SKILL.md
---
Bundled Examples
- examples/Test_QuoteByProductCallable/ — read-only query example with
WITH USER_MODE - examples/Test_VlocityOpenInterfaceConversion/ — migration from legacy
VlocityOpenInterface - examples/Test_VlocityOpenInterface2Conversion/ — migration from
VlocityOpenInterface2
Notes
- Prefer deterministic, side-effect-aware callable actions
- Keep action contracts stable; introduce new actions for breaking changes
- Avoid long-running work in synchronous callables; use async when needed
---
Credits & Acknowledgments
This skill was built upon the collective wisdom of the Salesforce Industries and Salesforce Apex communities. We gratefully acknowledge the following authors and resources whose ideas, patterns, and best practices have shaped this skill.
---
Authors & Contributors
Primary Contributor
Shreyas Dhond (CTA)
Key contributions:
- Contributed the
sf-industry-commoncore-callable-apexskill - Strengthened callable guidance, migration patterns, and bundled examples
- Expanded coverage for
System.Callableand legacyVlocityOpenInterface/VlocityOpenInterface2scenarios
Salesforce Industries Documentation Team
[Callable Implementations](https://help.salesforce.com/s/articleView?id=ind.v_dev_t_callable_implementations_651821.htm&type=5)
Key contributions:
- Recommended patterns for Industries callable implementations
- Migration guidance from
VlocityOpenInterface/VlocityOpenInterface2 - Action contract and response design guidance
---
Frameworks & Libraries
OmniStudio / Vlocity Open Interface (Legacy)
- Provider: Salesforce Industries
- Interface signatures:
VlocityOpenInterfaceandVlocityOpenInterface2withinvokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options) - Reference: https://help.salesforce.com/s/articleView?id=ind.v_dev_t_callable_implementations_651821.htm&type=5
System.Callable (Salesforce Platform)
- Provider: Salesforce
- Reference: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/
---
Official Salesforce Resources
- Salesforce Help: Callable Implementations: https://help.salesforce.com/s/articleView?id=ind.v_dev_t_callable_implementations_651821.htm&type=5
- Apex Developer Guide: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/
- Trailhead: https://trailhead.salesforce.com
---
Community Resources
Salesforce Stack Exchange
[salesforce.stackexchange.com](https://salesforce.stackexchange.com/)
- Community Q&A and solutions
- Industry-specific callable implementation discussions
---
/**
* @description Industries callable that finds Quotes on an Account that have at least one
* QuoteLineItem with a specified Product2.ProductCode.
* Integrates with OmniStudio/Industries extension points.
* @author sf-industry-commoncore-callable-apex
*
* Actions:
* - findQuotesByProductCode: Returns Quotes for an Account with QuoteLineItems matching productCode
*
* Input (findQuotesByProductCode):
* - accountId (String, required): Id of the Account
* - productCode (String, required): Product2.ProductCode to match on QuoteLineItems
*
* Output envelope:
* - success (Boolean)
* - data (Map): quotes (List<Map> with Id, Name, QuoteNumber)
* - errors (List<Map>)
*/
public with sharing class Industries_QuoteByProductCallable implements System.Callable {
private static final String ACTION_FIND_QUOTES = 'findQuotesByProductCode';
/**
* @description Dispatches to the appropriate action handler.
* @param action Action name (e.g. 'findQuotesByProductCode')
* @param args Input map with accountId and productCode
* @return Response envelope: success, data, errors
*/
public Object call(String action, Map<String, Object> args) {
switch on action {
when ACTION_FIND_QUOTES {
return findQuotesByProductCode(args);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
/**
* @description Finds all Quotes on an Account that have at least one QuoteLineItem
* with the specified Product2.ProductCode.
* @param args Map with keys: accountId (String), productCode (String)
* @return Response envelope with quotes list or errors
*/
private Map<String, Object> findQuotesByProductCode(Map<String, Object> args) {
List<Map<String, Object>> errors = new List<Map<String, Object>>();
Object accountIdObj = args != null ? args.get('accountId') : null;
Object productCodeObj = args != null ? args.get('productCode') : null;
String accountId = accountIdObj != null ? String.valueOf(accountIdObj).trim() : null;
String productCode = productCodeObj != null ? String.valueOf(productCodeObj).trim() : null;
if (String.isBlank(accountId)) {
errors.add(new Map<String, Object>{
'code' => 'MISSING_ACCOUNT_ID',
'message' => 'accountId is required'
});
}
if (String.isBlank(productCode)) {
errors.add(new Map<String, Object>{
'code' => 'MISSING_PRODUCT_CODE',
'message' => 'productCode is required'
});
}
if (!errors.isEmpty()) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>{ 'quotes' => new List<Map<String, Object>>() },
'errors' => errors
};
}
try {
List<Quote> quotes = [
SELECT Id, Name, QuoteNumber, OpportunityId
FROM Quote
WITH USER_MODE
WHERE Opportunity.AccountId = :accountId
AND Id IN (
SELECT QuoteId
FROM QuoteLineItem
WHERE Product2.ProductCode = :productCode
)
];
List<Map<String, Object>> quoteMaps = new List<Map<String, Object>>();
for (Quote q : quotes) {
quoteMaps.add(new Map<String, Object>{
'Id' => q.Id,
'Name' => q.Name,
'QuoteNumber' => q.QuoteNumber
});
}
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'quotes' => quoteMaps },
'errors' => new List<Map<String, Object>>()
};
} catch (Exception e) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>{ 'quotes' => new List<Map<String, Object>>() },
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'QUERY_ERROR',
'message' => e.getMessage()
}
}
};
}
}
}
/**
* @description Test class for Industries_QuoteByProductCallable.
* Covers positive, negative, contract, and unsupported action scenarios.
* @author sf-industry-commoncore-callable-apex
*/
@IsTest
private class Industries_QuoteByProductCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// TEST DATA SETUP
// ═══════════════════════════════════════════════════════════════════════════
@TestSetup
static void setupTestData() {
Account acc = new Account(Name = 'Test Account');
insert acc;
Pricebook2 pb = [SELECT Id FROM Pricebook2 WHERE IsStandard = true LIMIT 1];
Opportunity opp = new Opportunity(
Name = 'Test Opp',
AccountId = acc.Id,
Pricebook2Id = pb.Id,
StageName = 'Prospecting',
CloseDate = Date.today().addMonths(1)
);
insert opp;
Product2 prod = new Product2(
Name = 'Test Product',
ProductCode = 'PROD-001',
IsActive = true
);
insert prod;
PricebookEntry pbe = new PricebookEntry(
Pricebook2Id = pb.Id,
Product2Id = prod.Id,
UnitPrice = 100,
IsActive = true
);
insert pbe;
Quote q = new Quote(
Name = 'Test Quote',
OpportunityId = opp.Id,
Pricebook2Id = pb.Id
);
insert q;
QuoteLineItem qli = new QuoteLineItem(
QuoteId = q.Id,
PricebookEntryId = pbe.Id,
Quantity = 1,
UnitPrice = 100
);
insert qli;
}
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests findQuotesByProductCode returns Quotes with matching productCode
*/
@IsTest
static void testFindQuotesByProductCode_Success() {
Account acc = [SELECT Id FROM Account LIMIT 1];
System.Callable svc = new Industries_QuoteByProductCallable();
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id,
'productCode' => 'PROD-001'
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
List<Object> quotes = (List<Object>) data.get('quotes');
Assert.isNotNull(quotes, 'quotes list should not be null');
Assert.areEqual(1, quotes.size(), 'Should return 1 Quote');
}
/**
* @description Tests findQuotesByProductCode returns empty list when no match
*/
@IsTest
static void testFindQuotesByProductCode_NoMatch_ReturnsEmpty() {
Account acc = [SELECT Id FROM Account LIMIT 1];
System.Callable svc = new Industries_QuoteByProductCallable();
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id,
'productCode' => 'NONEXISTENT'
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed with empty result');
Map<String, Object> data = (Map<String, Object>) result.get('data');
List<Object> quotes = (List<Object>) data.get('quotes');
Assert.areEqual(0, quotes.size(), 'Should return 0 Quotes');
}
// ═══════════════════════════════════════════════════════════════════════════
// CONTRACT / INPUT VALIDATION TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests missing accountId returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_MissingAccountId_ReturnsError() {
Map<String, Object> args = new Map<String, Object>{
'productCode' => 'PROD-001'
};
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isTrue(errors.size() >= 1, 'Should have at least one error');
}
/**
* @description Tests missing productCode returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_MissingProductCode_ReturnsError() {
Account acc = [SELECT Id FROM Account LIMIT 1];
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id
};
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isTrue(errors.size() >= 1, 'Should have at least one error');
}
/**
* @description Tests null args returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_NullArgs_ReturnsError() {
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', null);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE TESTS (Unsupported Action)
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_ThrowsException() {
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
try {
svc.call('unknownAction', new Map<String, Object>());
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(
e.getMessage().contains('Unsupported action'),
'Error message should mention unsupported action: ' + e.getMessage()
);
}
Test.stopTest();
}
}
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author sf-industry-commoncore-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
Reasoning Transcript: Industries Quote-by-Product Callable
This document records the reasoning process and skills used to generate Industries_QuoteByProductCallable.cls and Industries_QuoteByProductCallableTest.cls.
---
Task Summary
Create a callable Apex class that finds all Quotes on an Account that have at least one QuoteLineItem with a specified Product2.ProductCode, plus a supporting test class.
---
Skills Applied
1. sf-industry-commoncore-callable-apex (Primary)
Path: ~/.claude/skills/sf-industry-commoncore-callable-apex/SKILL.md Trigger: Callable implementations, OmniStudio, Vlocity, Industries Apex extensions
Application:
- Phase 1 – Requirements Gathering:
Entry point: Integration Procedure / OmniScript. Action: findQuotesByProductCode. Inputs: accountId (String), productCode (String). Data: Quote, QuoteLineItem, Product2. No side effects (read-only).
- Phase 2 – Contract Definition:
Response envelope: { success, data: { quotes }, errors }. Action dispatch via switch on action with typed exception for unsupported actions.
- Phase 3 – Implementation Pattern:
call()kept thin; logic delegated tofindQuotesByProductCode(args)- Input validation for
accountIdandproductCode(blank checks) with sharingon the callable class- SOQL with
WITH USER_MODEfor CRUD/FLS - No SOQL/DML in loops
- Consistent envelope shape
- Phase 4 – Testing:
Positive, negative, contract, and unsupported-action tests per skill requirements.
- Exception Class:
IndustriesCallableException used per skill pattern for unsupported actions.
---
2. sf-apex (Reference)
Path: ~/.claude/skills/sf-apex/SKILL.md Trigger: Apex classes, code quality
Application:
- ApexDoc on the class and action method
- Naming:
Industries_QuoteByProductCallable(callable pattern) - Null-safe input handling
- Clear separation of concerns
---
3. sf-testing (Reference)
Path: ~/.claude/skills/sf-testing/SKILL.md Trigger: Apex tests
Application:
@TestSetupfor shared data (Account, Opportunity, Product2, PricebookEntry, Quote, QuoteLineItem)- Positive: success case and empty-result case
- Contract: missing
accountId, missingproductCode, null args - Negative: unsupported action throws
IndustriesCallableException - GIVEN/WHEN/THEN comments (adapted from basic-test template)
---
4. sf-soql / sf-data (Reference)
Paths: skills/sf-data/assets/soql/, skills/sf-diagram-mermaid/assets/datamodel/ Trigger: SOQL and data model
Application:
- Quote–Opportunity–Account:
Quote.Opportunity.AccountId - QuoteLineItem–Product2:
QuoteLineItem.Product2.ProductCode - Subquery:
Id IN (SELECT QuoteId FROM QuoteLineItem WHERE Product2.ProductCode = :productCode) - Confirmation that Product2.ProductCode is queryable from QuoteLineItem
---
Design Decisions
| Decision | Rationale |
|---|---|
| Response envelope | Aligns with Industries pattern: success, data, errors for consistent handling by Integration Procedures |
| Error envelope for validation | Return structured errors instead of throwing; callers can handle without try/catch |
| Exception for unsupported action | Follows skill pattern; IndustriesCallableException makes misuse explicit |
| `IndustriesCallableException` in separate class | Reusable across callable classes, matches skill examples |
| `WITH USER_MODE` on SOQL | Enforces CRUD/FLS; requires API 59+ |
| Quote ↔ Account path | Standard model: Quote.Opportunity.AccountId (no direct Quote → Account) |
| Product code on Product2 | Standard Product2.ProductCode via QuoteLineItem.Product2 relationship |
---
Artifacts Produced
1. IndustriesCallableException.cls – Custom exception for unsupported actions 2. Industries_QuoteByProductCallable.cls – Callable implementation 3. Industries_QuoteByProductCallableTest.cls – Test class (6 methods) 4. TRANSCRIPT.md – This reasoning transcript
---
Deployment Notes
Copy the .cls files into your Salesforce project under force-app/main/default/classes/ and add the corresponding -meta.xml files with apiVersion 59.0 or higher (for WITH USER_MODE). If your org uses an older API, remove WITH USER_MODE from the SOQL query.
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author sf-industry-commoncore-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
/**
* @description Callable implementation for calculateTotal (converted from VlocityOpenInterface2).
* Accepts price and quantity from inputMap, returns total in response envelope.
* Original: MyCustomRemoteClass implements omnistudio.VlocityOpenInterface2.
* @author sf-industry-commoncore-callable-apex
*/
public with sharing class MyCustomCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'calculateTotal' {
return calculateTotal(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> calculateTotal(Map<String, Object> inputMap, Map<String, Object> options) {
Decimal price = toDecimal(inputMap.get('price'));
Integer qty = toInteger(inputMap.get('quantity'));
if (price == null || qty == null) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>(),
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'INVALID_INPUT',
'message' => 'price and quantity are required and must be numeric'
}
}
};
}
Decimal total = price * qty;
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'total' => total },
'errors' => new List<Map<String, Object>>()
};
}
private Decimal toDecimal(Object o) {
if (o == null) return null;
if (o instanceof Decimal) return (Decimal) o;
if (o instanceof Integer) return Decimal.valueOf((Integer) o);
if (o instanceof Double) return Decimal.valueOf((Double) o);
if (o instanceof String) {
try { return Decimal.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
private Integer toInteger(Object o) {
if (o == null) return null;
if (o instanceof Integer) return (Integer) o;
if (o instanceof Long) return ((Long) o).intValue();
if (o instanceof Decimal) return ((Decimal) o).intValue();
if (o instanceof String) {
try { return Integer.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
}
/**
* @description Test class for MyCustomCallable (converted from MyCustomRemoteClass VlocityOpenInterface2).
* Covers positive, negative, contract, and unsupported action scenarios.
* @author sf-industry-commoncore-callable-apex
*/
@IsTest
private class MyCustomCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests calculateTotal returns total with inputMap/options format
*/
@IsTest
static void testCalculateTotal_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'price' => 10.50,
'quantity' => 3
},
'options' => new Map<String, Object>()
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(31.50, (Decimal) data.get('total'), 'total should be 10.50 * 3');
}
/**
* @description Tests calculateTotal with flat args (backward compatibility)
*/
@IsTest
static void testCalculateTotal_FlatArgs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'price' => 100,
'quantity' => 5
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(500, (Decimal) data.get('total'), 'total should be 100 * 5');
}
/**
* @description Tests calculateTotal with string inputs (type coercion from OmniScript)
*/
@IsTest
static void testCalculateTotal_StringInputs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'price' => '25.00',
'quantity' => '4'
},
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(100, (Decimal) data.get('total'), 'total should be 25 * 4');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE / CONTRACT TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_Throws() {
try {
System.Callable svc = new MyCustomCallable();
svc.call('unknownAction', new Map<String, Object>{
'inputMap' => new Map<String, Object>(),
'options' => new Map<String, Object>()
});
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'), 'Message should mention unsupported action');
}
}
/**
* @description Tests missing price returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingPrice_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'quantity' => 2 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
Assert.areEqual(1, errors.size(), 'Should have one error');
}
/**
* @description Tests missing quantity returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingQuantity_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'price' => 50 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
}
/**
* @description Tests null args handled gracefully
*/
@IsTest
static void testCalculateTotal_NullArgs_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', null);
Assert.isFalse((Boolean) result.get('success'), 'Should fail with null inputMap');
}
}
global with sharing class MyCustomRemoteClass implements omnistudio.VlocityOpenInterface2 {
global Boolean invokeMethod(String methodName, Map<String,Object> inputMap, Map<String,Object> outputMap, Map<String,Object> options) {
if (methodName.equals('calculateTotal')) {
calculateTotal(inputMap, outputMap);
return true;
}
return false;
}
private void calculateTotal(Map<String,Object> inputMap, Map<String,Object> outputMap) {
Decimal price = (Decimal)inputMap.get('price');
Decimal qty = (Decimal)inputMap.get('quantity');
outputMap.put('total', price * qty);
}
}Reasoning Transcript: VlocityOpenInterface2 → Callable Conversion (MyCustomRemoteClass)
This document records the reasoning process and skills used to convert MyCustomRemoteClass.cls (VlocityOpenInterface2) to MyCustomCallable.cls and create MyCustomCallableTest.cls.
---
Task Summary
Convert a VlocityOpenInterface2 implementation (MyCustomRemoteClass) to System.Callable, add a test class, and document reasoning and skills used.
---
Source Analysis
Original class: MyCustomRemoteClass.cls
- Implements:
omnistudio.VlocityOpenInterface2 - Action:
calculateTotal - Input contract:
inputMapwith keysprice(Decimal),quantity(Decimal) - Output contract:
outputMap.put('total', price * qty) - Behavior: Single action; returns
trueon success,falsefor unsupported methods
---
Skills Applied
1. sf-industry-commoncore-callable-apex (Primary)
Path: skills/sf-industry-commoncore-callable-apex/SKILL.md Trigger: Callable implementations, VlocityOpenInterface, migration to System.Callable
Application:
- Migration guidance (SKILL § Migration: VlocityOpenInterface to System.Callable):
- Preserved action name:
calculateTotal→actionincall() - Pass
inputMapandoptionsas keys inargs:{ 'inputMap' => inputMap, 'options' => options } - Return response envelope instead of mutating
outputMap - Keep
call()thin; delegate tocalculateTotal(inputMap, options) - Backward compatibility: if
argslacksinputMap, treatargsasinputMap
- Callable skeleton (SKILL § Phase 3 – Callable skeleton):
- Extracted
inputMapandoptionsfromargswith null guards switch on actionwithwhen elsethrowingIndustriesCallableException
- Response envelope:
{ success, data: { total }, errors }- Input validation: Added
toDecimal()andtoInteger()type coercion (OmniScript often passes strings); return error envelope whenpriceorquantityis null or invalid.
- Contract & Dispatch: Explicit action list; typed exception for unsupported actions.
---
2. sf-apex (Reference)
Path: ~/.claude/skills/sf-apex/SKILL.md Trigger: Apex classes, code quality
Application:
- ApexDoc on class and key methods
with sharingon the callable class- Null-safe input handling; no direct casts without validation
- Naming:
MyCustomCallable(callable pattern; preserves "MyCustom" from source)
---
3. sf-testing (Reference)
Path: ~/.claude/skills/sf-testing/SKILL.md Trigger: Apex tests
Application:
- Positive: Success with
inputMap/options, flat args, and string inputs (type coercion) - Contract: Missing
price, missingquantity, null args → error envelope - Negative: Unsupported action throws
IndustriesCallableException - GIVEN/WHEN/THEN implied via clear assertions and method names
---
Design Decisions
| Decision | Rationale |
|---|---|
Preserve lowercase keys (price, quantity, total) | Original MyCustomRemoteClass used lowercase; maintains contract for existing Integration Procedures / OmniScripts |
| Response envelope | Aligns with Industries pattern; callers get consistent success, data, errors shape |
| Error envelope for validation | Return structured errors instead of throwing; callers can handle without try/catch |
| Exception for unsupported action | Skill pattern; IndustriesCallableException makes misuse explicit |
| Type coercion helpers | OmniScript/Integration Procedures often pass strings; toDecimal/toInteger allow '25.00', '4' etc. |
| Flat args fallback | When args lacks inputMap, treat args as inputMap for backward compatibility |
---
Artifacts Produced
1. IndustriesCallableException.cls – Custom exception for unsupported actions 2. MyCustomCallable.cls – Callable implementation (converted from MyCustomRemoteClass) 3. MyCustomCallableTest.cls – Test class (7 methods) 4. TRANSCRIPT.md – This reasoning transcript
---
Contract Mapping (Before → After)
| VlocityOpenInterface2 | System.Callable |
|---|---|
methodName = 'calculateTotal' | action = 'calculateTotal' |
inputMap.get('price'), inputMap.get('quantity') | Same keys in args.inputMap or flat args |
outputMap.put('total', ...) | return { success => true, data => { total => ... } } |
return false for unsupported | throw IndustriesCallableException |
options (unused in original) | Passed through for future use |
---
Deployment Notes
Copy the .cls files into your Salesforce project under force-app/main/default/classes/ and add the corresponding -meta.xml files. No SOQL/DML; no special API version requirements.
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author sf-industry-commoncore-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
/**
* @description Callable implementation for calculateTotal (converted from VlocityOpenInterface).
* Accepts Price and Quantity, returns TotalAmount in response envelope.
* @author sf-industry-commoncore-callable-apex
*/
public with sharing class MyCustomCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'calculateTotal' {
return calculateTotal(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> calculateTotal(Map<String, Object> inputMap, Map<String, Object> options) {
Decimal price = toDecimal(inputMap.get('Price'));
Integer qty = toInteger(inputMap.get('Quantity'));
if (price == null || qty == null) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>(),
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'INVALID_INPUT',
'message' => 'Price and Quantity are required and must be numeric'
}
}
};
}
Decimal total = price * qty;
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'TotalAmount' => total },
'errors' => new List<Map<String, Object>>()
};
}
private Decimal toDecimal(Object o) {
if (o == null) return null;
if (o instanceof Decimal) return (Decimal) o;
if (o instanceof Integer) return Decimal.valueOf((Integer) o);
if (o instanceof Double) return Decimal.valueOf((Double) o);
if (o instanceof String) {
try { return Decimal.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
private Integer toInteger(Object o) {
if (o == null) return null;
if (o instanceof Integer) return (Integer) o;
if (o instanceof Long) return ((Long) o).intValue();
if (o instanceof Decimal) return ((Decimal) o).intValue();
if (o instanceof String) {
try { return Integer.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
}
/**
* @description Test class for MyCustomCallable.
* Covers positive, negative, contract, and unsupported action scenarios.
* @author sf-industry-commoncore-callable-apex
*/
@IsTest
private class MyCustomCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests calculateTotal returns TotalAmount with inputMap/options format
*/
@IsTest
static void testCalculateTotal_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'Price' => 10.50,
'Quantity' => 3
},
'options' => new Map<String, Object>()
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(31.50, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 10.50 * 3');
}
/**
* @description Tests calculateTotal with flat args (backward compatibility)
*/
@IsTest
static void testCalculateTotal_FlatArgs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'Price' => 100,
'Quantity' => 5
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(500, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 100 * 5');
}
/**
* @description Tests calculateTotal with string inputs (type coercion from OmniScript)
*/
@IsTest
static void testCalculateTotal_StringInputs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'Price' => '25.00',
'Quantity' => '4'
},
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(100, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 25 * 4');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE / CONTRACT TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_Throws() {
try {
System.Callable svc = new MyCustomCallable();
svc.call('unknownAction', new Map<String, Object>{
'inputMap' => new Map<String, Object>(),
'options' => new Map<String, Object>()
});
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'), 'Message should mention unsupported action');
}
}
/**
* @description Tests missing Price returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingPrice_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'Quantity' => 2 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
Assert.areEqual(1, errors.size(), 'Should have one error');
}
/**
* @description Tests null args handled gracefully
*/
@IsTest
static void testCalculateTotal_NullArgs_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', null);
Assert.isFalse((Boolean) result.get('success'), 'Should fail with null inputMap');
}
}
global class MyCustomClass implements vlocity_cmt.VlocityOpenInterface {
global Boolean invokeMethod(String methodName, Map<String, Object> inputs,
Map<String, Object> output, Map<String, Object> options) {
if (methodName == 'calculateTotal') {
calculateTotal(inputs, output);
}
return true;
}
private void calculateTotal(Map<String, Object> inputs, Map<String, Object> output) {
// Retrieve inputs from OmniScript/IP
Decimal price = (Decimal)inputs.get('Price');
Integer qty = (Integer)inputs.get('Quantity');
// Perform calculation
Decimal total = price * qty;
// Return result to JSON
output.put('TotalAmount', total);
}
}TRANSCRIPT: VlocityOpenInterface → System.Callable Conversion
Task
Convert MyCustomVlocityOpenInterface2.cls (VlocityOpenInterface) to System.Callable and create a test class. Keep a transcript of reasoning and skills used.
---
Skills Used
| Skill | Purpose |
|---|---|
| sf-industry-commoncore-callable-apex | Migration pattern, response envelope, action dispatch, inputMap/options contract, test patterns |
| sf-apex (implicit) | ApexDoc, type coercion, null safety, exception handling |
---
Reasoning
1. Source Analysis
Original class: MyCustomClass implements vlocity_cmt.VlocityOpenInterface
- Action:
calculateTotal - Inputs:
Price(Decimal),Quantity(Integer) frominputsmap - Output: Mutates
outputmap withTotalAmount - Observation: Original returns
truefor all method names (no unsupported-action handling)
2. Design Decisions
| Decision | Rationale |
|---|---|
| Open Interface–compatible args | Use inputMap and options keys per skill Phase 3 so OmniScript/IP callers can pass the same structure |
| Flat args fallback | Skill allows flat args when inputMap is absent; supports both calling styles |
| Response envelope | Return { success, data, errors } instead of mutating outputMap per migration guidance |
| Input validation | Skill requires null-safe type coercion; OmniScript can send numbers as String |
| Unsupported action | Skill: use switch on action and throw IndustriesCallableException |
| `with sharing` | Skill guardrail: enforce sharing on callable classes |
3. Implementation Pattern
- Entry point: Extract
inputMapandoptionsfromargs(or treat flatargsasinputMap) - Action dispatch:
switch on actionwithwhen 'calculateTotal'andwhen elsethrowing - Business logic:
calculateTotal(inputMap, options)returns envelope - Type coercion:
toDecimal()andtoInteger()handle Decimal, Integer, Long, String (OmniScript casts) - Error envelope: Missing/invalid inputs →
success: falsewithINVALID_INPUTerror code
4. Test Coverage (Phase 4)
| Test | Category | Purpose |
|---|---|---|
testCalculateTotal_Success | Positive | InputMap/options format, validates TotalAmount |
testCalculateTotal_FlatArgs_Success | Positive | Flat args backward compatibility |
testCalculateTotal_StringInputs_Success | Contract | String coercion from OmniScript |
testUnsupportedAction_Throws | Negative | Unsupported action throws IndustriesCallableException |
testCalculateTotal_MissingPrice_ReturnsError | Contract | Missing required key returns error envelope |
testCalculateTotal_NullArgs_ReturnsError | Contract | Null args handled without NPE |
5. Files Produced
| File | Role |
|---|---|
MyCustomCallable.cls | Converted System.Callable |
MyCustomCallableTest.cls | Test class |
IndustriesCallableException.cls | Custom exception (skill pattern) |
TRANSCRIPT.md | This transcript |
---
Skill References
- Phase 2 (Design): Contract mapping (
methodName→action,inputMap/options→args) - Phase 3 (Implementation): Callable skeleton (inputMap/options), implementation rules (validate early, null-safe)
- Phase 4 (Testing): Positive, negative, contract tests
- Migration: Preserve action names, return envelope instead of mutating output
sf-industry-commoncore-callable-apex
Generates and reviews Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable implementations. Build secure, deterministic System.Callable classes with a 120-point scoring rubric and migration guidance from legacy VlocityOpenInterface implementations.
Features
- Callable Generation: Create
System.Callableclasses with safe action dispatch - Callable Review: Analyze existing callable implementations for risks and fixes
- 120-Point Scoring: Validation across 7 callable-specific categories
- VlocityOpenInterface / VlocityOpenInterface2 Support: Phase 2 contract mapping and Phase 3 implementation patterns for
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options) - Migration Guidance: Patterns for moving from
VlocityOpenInterface/VlocityOpenInterface2toSystem.Callable - Testing Examples: Test class patterns for actions, errors, and bulk inputs
Installation
# Install as part of sf-skills
npx skills add Jaganpro/sf-skills
# Or install just this skill
npx skills add Jaganpro/sf-skills --skill sf-industry-commoncore-callable-apexQuick Start
1. Invoke the skill
Skill: sf-industry-commoncore-callable-apex
Request: "Create a callable implementation for Order actions with createOrder and cancelOrder"2. Answer requirements questions
The skill will ask about:
- Industries entry point (OmniScript, Integration Procedure, DataRaptor)
- Action names (strings passed into
call) - Input/output contract (required keys, response shape)
- Data access needs and security expectations
3. Review generated code
The skill generates:
- Callable class with explicit
switch on action - Consistent response envelope
- Test class examples for action coverage and error paths
Bundled Examples
- examples/Test_QuoteByProductCallable/ — read-only callable example with SOQL and test coverage
- examples/Test_VlocityOpenInterfaceConversion/ — migration pattern from legacy
VlocityOpenInterface - examples/Test_VlocityOpenInterface2Conversion/ — migration pattern from
VlocityOpenInterface2
Scoring System (120 Points)
| Category | Points | Focus |
|---|---|---|
| Contract & Dispatch | 20 | Explicit actions, switch on, versioned strings |
| Input Validation | 20 | Required keys, type coercion, null guards |
| Security | 20 | CRUD/FLS checks, with sharing, stripInaccessible |
| Error Handling | 15 | Typed exceptions, consistent errors |
| Bulkification & Limits | 20 | No SOQL/DML in loops, list inputs |
| Testing | 15 | Positive/negative/contract/bulk tests |
| Documentation | 10 | ApexDoc for class and action methods |
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)
Cross-Skill Integration
| Related Skill | When to Use |
|---|---|
| sf-apex | General Apex work beyond callable implementations |
| sf-metadata | Verify object/field availability before coding |
| sf-testing | Run tests and analyze coverage |
| sf-deploy | Deploy callable classes to an org |
Documentation
- Skill Instructions
- Callable Implementations (Salesforce Help)
VlocityOpenInterface / VlocityOpenInterface2
The skill includes design (Phase 2) and implementation (Phase 3) guidance for the Open Interface signature invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options). Use this when extending legacy OmniStudio/Vlocity integration points or building dual Callable + Open Interface implementations.
Requirements
- sf CLI v2
- Target Salesforce org
License
MIT License. See LICENSE file. Copyright (c) 2024-2025 Jag Valaiyapathy