Skip to main content

Codex Review Fixes — Integration Builder v2

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Fix 5 critical/high issues found by Codex adversarial review so generated code matches the actual Avenia API contract.

Architecture: Each fix is isolated to 1-2 files in the engine layer. FlowEngine handles data transformation (payment method stripping, ticket body nesting). CodeGenerator handles code output (binding variables, Go encoding). One block JSON config gets new fields.

Tech Stack: TypeScript, Vitest, React Flow (no React components touched — all engine-layer)


File Map

FileActionResponsibility
src/engine/FlowEngine.tsModifyStrip payment method prefixes, build nested ticket bodies
src/engine/CodeGenerator.tsModifyFix Go double-encoding, wire binding variables into all languages
src/configs/blocks/beneficiary-usd-bank.jsonModifyAdd beneficiaryAddress fields
src/engine/__tests__/FlowEngine.test.tsModifyTests for stripping + nesting
src/engine/__tests__/CodeGenerator.test.tsModifyTests for Go fix + binding wiring

Task 1: Fix Go double-encoding

Files:

  • Modify: src/engine/__tests__/CodeGenerator.test.ts
  • Modify: src/engine/CodeGenerator.ts:251-254

The Go generator does json.Marshal(JSON.stringify(body)) which double-encodes the JSON. The fix: emit raw JSON bytes directly instead of marshaling a pre-stringified string.

  • Step 1: Write failing test

Add to the Go describe block in src/engine/__tests__/CodeGenerator.test.ts:

it('does not double-encode JSON body', () => {
const output = gen.generate([makeStep()], 'go');
// Should use json.RawMessage or direct byte literal, not json.Marshal on a string
expect(output).not.toMatch(/json\.Marshal\("[{]/);
// Should contain the actual JSON as a byte literal
expect(output).toContain('[]byte(`{');
});
  • Step 2: Run test to verify it fails

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/CodeGenerator.test.ts --reporter=verbose Expected: FAIL — current code emits json.Marshal("...")

  • Step 3: Fix Go body emission

In src/engine/CodeGenerator.ts, replace the Go body emission (lines 251-254) from:

if (body !== null) {
const bodyJson = JSON.stringify(body);
lines.push(`\tbody${step.order}, _ := json.Marshal(${JSON.stringify(bodyJson)})`);
lines.push(`\treq${step.order}, _ := http.NewRequest("${step.method}", "${urlStr}", bytes.NewBuffer(body${step.order}))`);
}

To:

if (body !== null) {
const bodyJson = JSON.stringify(body, null, 2)
.split('\n')
.map((l) => `\t${l}`)
.join('\n');
lines.push(`\tbody${step.order} := []byte(\`${JSON.stringify(body, null, 2)}\`)`);
lines.push(`\treq${step.order}, _ := http.NewRequest("${step.method}", "${urlStr}", bytes.NewBuffer(body${step.order}))`);
}

Also remove the now-unused "encoding/json" import only if no other step uses it. Actually, json.Unmarshal is still used for token extraction, so keep the import.

  • Step 4: Run test to verify it passes

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/CodeGenerator.test.ts --reporter=verbose Expected: PASS

  • Step 5: Commit
git add src/engine/CodeGenerator.ts src/engine/__tests__/CodeGenerator.test.ts
git commit -m "fix(codegen): eliminate Go double-encoding of JSON request bodies"

Task 2: Strip payment method currency prefixes

Files:

  • Modify: src/engine/__tests__/FlowEngine.test.ts
  • Modify: src/engine/FlowEngine.ts:153-167

The API expects inputPaymentMethod=PIX but the builder sends BRL-PIX. The UI uses CURRENCY-METHOD format for filtering logic; we need to strip the currency prefix when building query params.

The stripping rule: for inputPaymentMethod and outputPaymentMethod, find the matching currency in the config (inputCurrency/outputCurrency), and remove the {CURRENCY}- prefix. Examples:

  • BRL-PIX with currency BRLPIX

  • BRLA-POLYGON with currency BRLAPOLYGON

  • USD-ACH_SAME_DAY with currency USDACH_SAME_DAY

  • BRL-PIX-BRCODE with currency BRLPIX-BRCODE

  • USDCe-POLYGON with currency USDCePOLYGON

  • Step 1: Write failing test

Add to src/engine/__tests__/FlowEngine.test.ts:

it('strips currency prefix from payment method query params', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'BRL',
inputPaymentMethod: 'BRL-PIX',
outputCurrency: 'BRLA',
outputPaymentMethod: 'BRLA-POLYGON',
inputAmount: 100,
}),
];
const edges: Edge<BuilderEdgeData>[] = [];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
expect(steps[0].queryParams['inputPaymentMethod']).toBe('PIX');
expect(steps[0].queryParams['outputPaymentMethod']).toBe('POLYGON');
});

it('strips currency prefix for complex payment methods', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'USD',
inputPaymentMethod: 'USD-WIRE',
outputCurrency: 'BRL',
outputPaymentMethod: 'BRL-PIX-BRCODE',
inputAmount: 50,
}),
];
const edges: Edge<BuilderEdgeData>[] = [];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
expect(steps[0].queryParams['inputPaymentMethod']).toBe('WIRE');
expect(steps[0].queryParams['outputPaymentMethod']).toBe('PIX-BRCODE');
});
  • Step 2: Run test to verify it fails

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/FlowEngine.test.ts --reporter=verbose Expected: FAIL — currently passes through BRL-PIX verbatim

  • Step 3: Implement payment method stripping in FlowEngine

In src/engine/FlowEngine.ts, add a private helper method and use it in generateSteps:

// Add this private method to the FlowEngine class
private stripPaymentMethodPrefix(method: string, currency: string): string {
const prefix = `${currency}-`;
if (method.startsWith(prefix)) {
return method.slice(prefix.length);
}
return method;
}

Then in generateSteps, after the loop that populates queryParams from config fields (around line 166), add stripping logic:

// Strip currency prefix from payment methods for API compatibility
// UI uses CURRENCY-METHOD format (e.g. BRL-PIX) but API expects just METHOD (e.g. PIX)
const inputCurrency = String(config['inputCurrency'] ?? '');
const outputCurrency = String(config['outputCurrency'] ?? '');
if (queryParams['inputPaymentMethod'] && inputCurrency) {
queryParams['inputPaymentMethod'] = this.stripPaymentMethodPrefix(
queryParams['inputPaymentMethod'], inputCurrency
);
}
if (queryParams['outputPaymentMethod'] && outputCurrency) {
queryParams['outputPaymentMethod'] = this.stripPaymentMethodPrefix(
queryParams['outputPaymentMethod'], outputCurrency
);
}
  • Step 4: Run test to verify it passes

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/FlowEngine.test.ts --reporter=verbose Expected: PASS

  • Step 5: Commit
git add src/engine/FlowEngine.ts src/engine/__tests__/FlowEngine.test.ts
git commit -m "fix(engine): strip currency prefix from payment methods for API compatibility"

Task 3: Build nested ticket request bodies

Files:

  • Modify: src/engine/__tests__/FlowEngine.test.ts
  • Modify: src/engine/FlowEngine.ts:154-185

The API requires nested objects in ticket bodies (ticketBrlPixOutput, ticketUsdWireOutput, ticketBlockchainOutput, etc.) but the engine flattens everything to the root level.

The nesting rules (from docs):

  • PIX input fields → ticketBrlPixInput: { additionalData }

  • Blockchain input fields → ticketBlockchainInput: { walletAddress, permit/personal }

  • PIX output fields → ticketBrlPixOutput: { beneficiaryBrlBankAccountId, pixMessage }

  • USD WIRE output → ticketUsdWireOutput: { beneficiaryUsdBankAccountId }

  • USD ACH output → ticketUsdOutput: { beneficiaryUsdBankAccountId, achReference }

  • EUR SEPA output → ticketEurSepaOutput: { beneficiaryEurBankAccountId, sepaReference }

  • Blockchain output → ticketBlockchainOutput: { beneficiaryWalletId } or { walletChain, walletAddress, walletMemo }

  • Step 1: Write failing tests

Add to src/engine/__tests__/FlowEngine.test.ts:

describe('ticket body nesting', () => {
it('nests PIX output fields under ticketBrlPixOutput', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'USDC',
inputPaymentMethod: 'USDC-INTERNAL',
outputCurrency: 'BRL',
outputPaymentMethod: 'BRL-PIX',
}),
makeNode('n2', 'create-ticket', {
quoteToken: 'qt_test',
beneficiaryBrlBankAccountId: 'ben-uuid-123',
}),
];
const edges = [makeEdge('n1', 'n2')];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
const ticketStep = steps.find(s => s.blockId === 'create-ticket')!;
expect(ticketStep.body).toHaveProperty('quoteToken', 'qt_test');
expect(ticketStep.body).toHaveProperty('ticketBrlPixOutput');
expect((ticketStep.body as any).ticketBrlPixOutput.beneficiaryBrlBankAccountId).toBe('ben-uuid-123');
// Should NOT have flat beneficiaryBrlBankAccountId at root
expect(ticketStep.body).not.toHaveProperty('beneficiaryBrlBankAccountId');
});

it('nests USD WIRE output fields under ticketUsdWireOutput', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'USDC',
inputPaymentMethod: 'USDC-INTERNAL',
outputCurrency: 'USD',
outputPaymentMethod: 'USD-WIRE',
}),
makeNode('n2', 'create-ticket', {
quoteToken: 'qt_test',
beneficiaryUsdBankAccountId: 'usd-ben-uuid',
}),
];
const edges = [makeEdge('n1', 'n2')];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
const ticketStep = steps.find(s => s.blockId === 'create-ticket')!;
expect(ticketStep.body).toHaveProperty('ticketUsdWireOutput');
expect((ticketStep.body as any).ticketUsdWireOutput.beneficiaryUsdBankAccountId).toBe('usd-ben-uuid');
});

it('nests blockchain output fields under ticketBlockchainOutput', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'BRL',
inputPaymentMethod: 'BRL-PIX',
outputCurrency: 'USDC',
outputPaymentMethod: 'USDC-POLYGON',
}),
makeNode('n2', 'create-ticket', {
quoteToken: 'qt_test',
beneficiaryWalletId: 'wallet-uuid',
}),
];
const edges = [makeEdge('n1', 'n2')];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
const ticketStep = steps.find(s => s.blockId === 'create-ticket')!;
expect(ticketStep.body).toHaveProperty('ticketBlockchainOutput');
expect((ticketStep.body as any).ticketBlockchainOutput.beneficiaryWalletId).toBe('wallet-uuid');
});

it('nests EUR SEPA output fields under ticketEurSepaOutput', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'USDC',
inputPaymentMethod: 'USDC-INTERNAL',
outputCurrency: 'EUR',
outputPaymentMethod: 'EUR-SEPA',
}),
makeNode('n2', 'create-ticket', {
quoteToken: 'qt_test',
beneficiaryEurBankAccountId: 'eur-ben-uuid',
}),
];
const edges = [makeEdge('n1', 'n2')];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
const ticketStep = steps.find(s => s.blockId === 'create-ticket')!;
expect(ticketStep.body).toHaveProperty('ticketEurSepaOutput');
expect((ticketStep.body as any).ticketEurSepaOutput.beneficiaryEurBankAccountId).toBe('eur-ben-uuid');
});

it('nests blockchain input walletAddress under ticketBlockchainInput', () => {
const nodes = [
makeNode('n1', 'get-quote', {
inputCurrency: 'USDC',
inputPaymentMethod: 'USDC-POLYGON',
outputCurrency: 'BRL',
outputPaymentMethod: 'BRL-PIX',
}),
makeNode('n2', 'create-ticket', {
quoteToken: 'qt_test',
walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38',
beneficiaryBrlBankAccountId: 'ben-uuid',
}),
];
const edges = [makeEdge('n1', 'n2')];

const steps = engine.generateSteps(nodes, edges, 'sandbox');
const ticketStep = steps.find(s => s.blockId === 'create-ticket')!;
expect(ticketStep.body).toHaveProperty('ticketBlockchainInput');
expect((ticketStep.body as any).ticketBlockchainInput.walletAddress).toBe('0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38');
expect(ticketStep.body).not.toHaveProperty('walletAddress');
});
});
  • Step 2: Run tests to verify they fail

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/FlowEngine.test.ts --reporter=verbose Expected: FAIL — current code flattens all fields

  • Step 3: Implement ticket body nesting

In src/engine/FlowEngine.ts, add a private method that restructures the flat ticket body into the nested API schema. Call it right before pushing the step (before line 176):

private buildTicketBody(
flatBody: Record<string, unknown>,
nodes: Node<BuilderNodeData>[],
edges: Edge<BuilderEdgeData>[],
ticketNodeId: string,
): Record<string, unknown> {
// Find connected quote node to determine input/output methods
const incomingEdges = edges.filter(e => e.target === ticketNodeId);
let inputMethod = '';
let outputMethod = '';
for (const edge of incomingEdges) {
const sourceNode = nodes.find(n => n.id === edge.source);
if (sourceNode?.data.blockId === 'get-quote') {
inputMethod = String(sourceNode.data.config.inputPaymentMethod ?? '');
outputMethod = String(sourceNode.data.config.outputPaymentMethod ?? '');
break;
}
}

if (!inputMethod && !outputMethod) return flatBody;

// Base fields that stay at root level
const result: Record<string, unknown> = {};
const rootFields = new Set(['quoteToken', 'externalId']);

for (const [key, value] of Object.entries(flatBody)) {
if (rootFields.has(key)) {
result[key] = value;
}
}

// Determine nested wrappers based on payment methods
const inputNested: Record<string, unknown> = {};
const outputNested: Record<string, unknown> = {};

// Input nesting rules
const INPUT_FIELD_MAP: Record<string, string[]> = {
'additionalData': ['additionalData'],
'walletAddress': ['walletAddress'],
'senderCuit': ['senderCuit'],
};

// Output nesting rules
const OUTPUT_FIELD_MAP: Record<string, string[]> = {
'beneficiaryBrlBankAccountId': ['beneficiaryBrlBankAccountId'],
'pixKey': ['pixKey'],
'pixMessage': ['pixMessage'],
'bankCode': ['bankCode'],
'branchCode': ['branchCode'],
'accountNumber': ['accountNumber'],
'accountType': ['accountType'],
'taxId': ['taxId'],
'userName': ['userName'],
'beneficiaryUsdBankAccountId': ['beneficiaryUsdBankAccountId'],
'wireMessage': ['wireMessage'],
'achReference': ['achReference'],
'beneficiaryEurBankAccountId': ['beneficiaryEurBankAccountId'],
'sepaReference': ['sepaReference'],
'beneficiaryArsBankAccountId': ['beneficiaryArsBankAccountId'],
'beneficiaryCopBankAccountId': ['beneficiaryCopBankAccountId'],
'beneficiaryWalletId': ['beneficiaryWalletId'],
};

// Classify each non-root field
const isBlockchainInput = !['BRL-PIX', 'USD-WIRE', 'USD-ACH', 'EUR-SEPA', 'ARS-BANK-TRANSFER', 'COP-BANK-TRANSFER'].some(m => inputMethod === m) && inputMethod !== '';
const inputFields = new Set(['additionalData', 'walletAddress', 'senderCuit']);
const outputFields = new Set(Object.keys(OUTPUT_FIELD_MAP));

for (const [key, value] of Object.entries(flatBody)) {
if (rootFields.has(key)) continue;
if (value === undefined || value === null || value === '') continue;

if (inputFields.has(key)) {
inputNested[key] = value;
} else if (outputFields.has(key)) {
outputNested[key] = value;
}
}

// Determine input wrapper key
if (Object.keys(inputNested).length > 0) {
if (inputMethod === 'BRL-PIX') {
result['ticketBrlPixInput'] = inputNested;
} else if (inputMethod === 'EUR-SEPA') {
result['ticketBrlPixInput'] = inputNested; // SEPA also uses additionalData same wrapper? No.
// Actually SEPA doesn't have a documented input wrapper. Let docs guide:
// PIX input → ticketBrlPixInput, blockchain input → ticketBlockchainInput
} else if (isBlockchainInput) {
result['ticketBlockchainInput'] = inputNested;
}
}

// Determine output wrapper key
if (Object.keys(outputNested).length > 0) {
if (outputMethod.startsWith('BRL-PIX')) {
result['ticketBrlPixOutput'] = outputNested;
} else if (outputMethod === 'BRL-TED') {
result['ticketBrlPixOutput'] = outputNested;
} else if (outputMethod === 'USD-WIRE') {
result['ticketUsdWireOutput'] = outputNested;
} else if (outputMethod === 'USD-ACH' || outputMethod === 'USD-ACH_SAME_DAY') {
result['ticketUsdOutput'] = outputNested;
} else if (outputMethod === 'EUR-SEPA') {
result['ticketEurSepaOutput'] = outputNested;
} else {
// Blockchain output (any CURRENCY-CHAIN combo that isn't fiat)
result['ticketBlockchainOutput'] = outputNested;
}
}

return result;
}

Then in generateSteps, right before steps.push(...) (~line 176), wrap the body for ticket blocks:

const finalBody = (block.id === 'create-ticket' && isWriteMethod)
? this.buildTicketBody(body, nodes, edges, node.id)
: (isWriteMethod ? body : null);

And change the steps.push to use finalBody instead of the ternary.

  • Step 4: Run tests to verify they pass

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/FlowEngine.test.ts --reporter=verbose Expected: PASS

  • Step 5: Commit
git add src/engine/FlowEngine.ts src/engine/__tests__/FlowEngine.test.ts
git commit -m "fix(engine): nest ticket body fields per API schema (ticketBrlPixOutput, etc.)"

Task 4: Add beneficiaryAddress to USD bank block

Files:

  • Modify: src/configs/blocks/beneficiary-usd-bank.json

The API requires a beneficiaryAddress object with streetLine1, city, state, postalCode, country. The block config only has the 5 top-level banking fields.

Since the API expects a nested object but our config system uses flat fields, we add the address sub-fields with a beneficiaryAddress. prefix convention, then handle serialization in RequestBodies (the canonical example already has the correct shape).

Simplest approach: add the address fields as individual config fields. The CodeGenerator falls back to ENDPOINT_EXAMPLES which already has the nested beneficiaryAddress structure. The address fields in the config panel give users the ability to customize them. For the generated code, the canonical example in RequestBodies.ts is already correct.

  • Step 1: Add address config fields to the block

Update src/configs/blocks/beneficiary-usd-bank.json:

{
"id": "beneficiary-usd-bank",
"name": "Beneficiary (USD Bank)",
"category": "beneficiaries",
"icon": "landmark",
"description": "Register a USD bank account (WIRE/ACH)",
"method": "POST",
"endpoint": "/v2/account/beneficiaries/bank-accounts/usd/",
"guideLink": "/docs/Beneficiaries-Bank-Accounts/beneficiariesBankAccountsUsd",
"inputs": {
"authToken": { "type": "token", "required": true, "from": "validate-login.accessToken" }
},
"outputs": {
"id": { "type": "uuid", "description": "Beneficiary USD account ID" }
},
"configFields": [
{ "id": "alias", "label": "Alias", "type": "string", "required": true, "placeholder": "US Bank" },
{ "id": "bankAccountNumber", "label": "Account Number", "type": "string", "required": true },
{ "id": "bankRoutingNumber", "label": "Routing Number (ABA)", "type": "string", "required": true },
{ "id": "bankBeneficiaryName", "label": "Beneficiary Name", "type": "string", "required": true },
{ "id": "bankName", "label": "Bank Name", "type": "string", "required": true },
{ "id": "beneficiaryAddress.streetLine1", "label": "Street Address", "type": "string", "required": true, "placeholder": "123 Main Street" },
{ "id": "beneficiaryAddress.city", "label": "City", "type": "string", "required": true, "placeholder": "New York" },
{ "id": "beneficiaryAddress.state", "label": "State", "type": "string", "required": true, "placeholder": "NY" },
{ "id": "beneficiaryAddress.postalCode", "label": "Postal Code", "type": "string", "required": true, "placeholder": "10001" },
{ "id": "beneficiaryAddress.country", "label": "Country", "type": "string", "required": true, "placeholder": "USA" }
],
"dependencies": ["validate-login"]
}
  • Step 2: Handle dot-notation fields in FlowEngine body builder

In src/engine/FlowEngine.ts, in the body-building loop (around line 158-166), replace the flat assignment with dot-notation expansion:

for (const field of block.configFields) {
const value = config[field.id];
if (value === undefined || value === null || value === '') continue;

if (isWriteMethod) {
// Support dot-notation fields (e.g., beneficiaryAddress.city → { beneficiaryAddress: { city: value } })
if (field.id.includes('.')) {
const [parent, child] = field.id.split('.', 2);
if (!body[parent]) body[parent] = {};
(body[parent] as Record<string, unknown>)[child] = value;
} else {
body[field.id] = value;
}
} else {
queryParams[field.id] = String(value);
}
}
  • Step 3: Run all tests

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run --reporter=verbose Expected: PASS (no existing tests break; the POST body test in FlowEngine still works)

  • Step 4: Commit
git add src/configs/blocks/beneficiary-usd-bank.json src/engine/FlowEngine.ts
git commit -m "fix(blocks): add beneficiaryAddress fields to USD bank beneficiary block"

Task 5: Wire binding variables into generated code

Files:

  • Modify: src/engine/__tests__/CodeGenerator.test.ts
  • Modify: src/engine/CodeGenerator.ts

The generators already extract token from validate-login and quoteToken from get-quote. But subsequent steps still emit placeholder values instead of referencing these variables. The fix: when a step body contains quoteToken and its value is a placeholder, replace it with the variable reference. Same for beneficiary IDs from upstream steps.

The pragmatic approach: for create-ticket steps, replace the quoteToken field value with the language-appropriate variable reference. For beneficiary IDs, add a comment indicating they should come from a previous step.

  • Step 1: Write failing tests

Add to src/engine/__tests__/CodeGenerator.test.ts:

describe('binding wiring', () => {
function makeQuoteStep(): FlowStep {
return makeStep({
order: 1,
blockId: 'get-quote',
name: 'Get Quote',
method: 'GET',
body: null,
queryParams: { inputCurrency: 'BRL', inputPaymentMethod: 'PIX' },
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer $TOKEN' },
});
}

function makeTicketStep(): FlowStep {
return makeStep({
order: 2,
blockId: 'create-ticket',
name: 'Create Ticket',
method: 'POST',
endpoint: 'https://api.sandbox.avenia.io:10952/v2/account/tickets/',
body: { quoteToken: '{quoteToken from previous step}', externalId: 'ref-123' },
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer $TOKEN' },
bindings: [{ sourceNodeId: 'n1', sourceField: 'quoteToken', targetField: 'quoteToken', auto: true }],
});
}

it('Node: uses quoteToken variable in create-ticket body', () => {
const output = gen.generate([makeQuoteStep(), makeTicketStep()], 'node');
// Should reference the extracted quoteToken variable, not the placeholder
expect(output).toContain('quoteToken');
expect(output).not.toContain('{quoteToken from previous step}');
});

it('Python: uses quote_token variable in create-ticket body', () => {
const output = gen.generate([makeQuoteStep(), makeTicketStep()], 'python');
expect(output).toContain('quote_token');
expect(output).not.toContain('{quoteToken from previous step}');
});

it('cURL: uses $QUOTE_RESPONSE variable for quoteToken', () => {
const output = gen.generate([makeQuoteStep(), makeTicketStep()], 'curl');
// Should use jq or variable extraction
expect(output).not.toContain('{quoteToken from previous step}');
});

it('Go: uses quoteToken variable in create-ticket body', () => {
const output = gen.generate([makeQuoteStep(), makeTicketStep()], 'go');
expect(output).toContain('quoteToken');
expect(output).not.toContain('{quoteToken from previous step}');
});
});
  • Step 2: Run tests to verify they fail

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/CodeGenerator.test.ts --reporter=verbose Expected: FAIL — generated code contains the placeholder string

  • Step 3: Implement binding resolution in CodeGenerator

Add a method to resolve bindings and modify each generator:

// Add to CodeGenerator class
private resolveBindings(step: FlowStep, steps: FlowStep[]): {
body: Record<string, unknown> | null;
boundFields: Set<string>;
} {
const body = this.getBody(step);
if (!body || step.bindings.length === 0) return { body, boundFields: new Set() };

const resolved = { ...body };
const boundFields = new Set<string>();

for (const binding of step.bindings) {
if (binding.targetField in resolved) {
boundFields.add(binding.targetField);
}
}

return { body: resolved, boundFields };
}

Then in each generator, for steps with boundFields.has('quoteToken'), emit the variable reference instead of the body value:

Node generator — after building the body JSON, replace the quoteToken value with the variable:

// In generateNode, when building the body for a step:
const { body, boundFields } = this.resolveBindings(step, steps);
// ... existing body emission ...
// If quoteToken is bound, post-process the body to use the variable
if (boundFields.has('quoteToken') && body) {
// Build body manually with variable interpolation
const bodyEntries = Object.entries(body).map(([k, v]) => {
if (k === 'quoteToken') return ` quoteToken: quoteToken`;
return ` ${k}: ${JSON.stringify(v)}`;
});
lines.push(` body: JSON.stringify({`);
lines.push(bodyEntries.join(',\n'));
lines.push(` }),`);
}

Apply similar logic to Python (quote_token), cURL (use jq extraction from $QUOTE_RESPONSE), and Go (quoteToken variable).

Implementation note: The cleanest approach is to modify getBody to return a body + a set of fields that should be variable-referenced, then each language emitter handles variable interpolation for its syntax. Don't try to string-replace in the final output — do it at the data level before emitting.

  • Step 4: Run tests to verify they pass

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run src/engine/__tests__/CodeGenerator.test.ts --reporter=verbose Expected: PASS

  • Step 5: Run full test suite

Run: cd /home/odmrs/Projects/front/integration-guide && npx vitest run --reporter=verbose Expected: All tests PASS

  • Step 6: Commit
git add src/engine/CodeGenerator.ts src/engine/__tests__/CodeGenerator.test.ts
git commit -m "fix(codegen): wire quoteToken binding into generated code for all languages"