From f0d9757aca9431a3e92462c8bfade7801d214a5e Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Tue, 2 Jun 2026 12:26:18 +0530 Subject: [PATCH 1/6] added segment_traits_by_field and segment_identifiers_by_field in upsertProfile Memora Sync --- .../destinations/memora/upsertProfile/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts index c428d65b372..a858306ee01 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts @@ -54,6 +54,24 @@ const action: ActionDefinition = { additionalProperties: true, dynamic: true, defaultObjectUI: 'keyvalue' + }, + segment_traits_by_field: { + label: 'Segment Traits Used', + description: + 'Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually.', + type: 'object', + required: false, + additionalProperties: true, + unsafe_hidden: true + }, + segment_identifiers_by_field: { + label: 'Segment Identifiers Used', + description: + 'Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually.', + type: 'object', + required: false, + additionalProperties: true, + unsafe_hidden: true } }, dynamicFields: { From 235fd5b1e5d2aa18164426bfc788b453d0e723d4 Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Thu, 4 Jun 2026 12:17:05 +0530 Subject: [PATCH 2/6] add JSDoc to segment_traits/identifiers_by_field and include generated types Documents that both fields are used exclusively by the Segment control plane (actions-memora-internal destination) for the Conversation Memory Sync feature, and explains why they are marked unsafe_hidden. Also includes generated-types updates that were missing from the previous commit. Co-Authored-By: Claude Sonnet 4.6 --- .../memora-internal/upsertProfile.types.ts | 12 ++++++++++++ .../memora/upsertProfile/generated-types.ts | 12 ++++++++++++ .../src/destinations/memora/upsertProfile/index.ts | 14 ++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/packages/destination-actions/src/destinations/memora-internal/upsertProfile.types.ts b/packages/destination-actions/src/destinations/memora-internal/upsertProfile.types.ts index 0fcaadddded..a17761c640d 100644 --- a/packages/destination-actions/src/destinations/memora-internal/upsertProfile.types.ts +++ b/packages/destination-actions/src/destinations/memora-internal/upsertProfile.types.ts @@ -25,4 +25,16 @@ export interface Payload { profile_traits?: { [k: string]: unknown } + /** + * Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually. + */ + segment_traits_by_field?: { + [k: string]: unknown + } + /** + * Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually. + */ + segment_identifiers_by_field?: { + [k: string]: unknown + } } diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts index 0fcaadddded..a17761c640d 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts @@ -25,4 +25,16 @@ export interface Payload { profile_traits?: { [k: string]: unknown } + /** + * Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually. + */ + segment_traits_by_field?: { + [k: string]: unknown + } + /** + * Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually. + */ + segment_identifiers_by_field?: { + [k: string]: unknown + } } diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts index a858306ee01..a5d6a89ee52 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts @@ -55,6 +55,13 @@ const action: ActionDefinition = { dynamic: true, defaultObjectUI: 'keyvalue' }, + /** + * Used exclusively by the Segment control plane (actions-memora-internal destination) as part of the + * Conversation Memory Sync feature. The control plane writes this map when creating or updating a mapping, + * and reads it back to build the event-emitter enrichment mapping that ensures the referenced Segment traits + * are present in event payloads at runtime. Not consumed by this action itself — marked unsafe_hidden so it + * round-trips through subscription settings without being exposed or stripped. + */ segment_traits_by_field: { label: 'Segment Traits Used', description: @@ -64,6 +71,13 @@ const action: ActionDefinition = { additionalProperties: true, unsafe_hidden: true }, + /** + * Used exclusively by the Segment control plane (actions-memora-internal destination) as part of the + * Conversation Memory Sync feature. The control plane writes this map when creating or updating a mapping, + * and reads it back to populate id_sync so that the declared Segment identifiers are available as external + * IDs at runtime. Not consumed by this action itself — marked unsafe_hidden so it round-trips through + * subscription settings without being exposed or stripped. + */ segment_identifiers_by_field: { label: 'Segment Identifiers Used', description: From e29f620099ee39180806f71b19192917bf711f7e Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Thu, 4 Jun 2026 12:34:46 +0530 Subject: [PATCH 3/6] test: assert segment_traits/identifiers_by_field not sent to Memora API Verifies that the unsafe_hidden control-plane metadata fields are never forwarded in the outbound Bulk upsert request body. Co-Authored-By: Claude Sonnet 4.6 --- .../upsertProfile/__tests__/index.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts index 67a460eff1a..4000e65c947 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts @@ -766,6 +766,38 @@ describe('Memora.upsertProfile', () => { }) }) + it('should not include segment_traits_by_field or segment_identifiers_by_field in the outbound request', async () => { + const event = createTestEvent({ + type: 'identify', + userId: 'user-123', + traits: { email: 'john@example.com', first_name: 'John' } + }) + + let capturedBody: Record = {} + + nock(BASE_URL) + .put(`/${API_VERSION}/Stores/test-store-id/Profiles/Bulk`, (body) => { + capturedBody = body as Record + return true + }) + .reply(202) + + await testDestination.testAction('upsertProfile', { + event, + settings: defaultSettings, + mapping: { + ...defaultMapping, + segment_traits_by_field: { 'Contact.$.first_name': ['first_name'] }, + segment_identifiers_by_field: { 'Contact.$.email': ['email'] } + }, + useDefaultMappings: true + }) + + const requestBodyStr = JSON.stringify(capturedBody) + expect(requestBodyStr).not.toContain('segment_traits_by_field') + expect(requestBodyStr).not.toContain('segment_identifiers_by_field') + }) + describe('performBatch (multiple profiles)', () => { it('should upsert multiple profiles in a single bulk request', async () => { const events = [ From 43ebc5894a4b4e394f2762d8c815095c7ed80234 Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Thu, 4 Jun 2026 22:16:49 +0530 Subject: [PATCH 4/6] refactor: move segment_traits/identifiers_by_field to memora-internal only These fields are exclusively used by the Segment control plane for the Conversation Memory Sync feature and have no place in the public memora destination. memora-internal now overrides upsertProfile to spread the base action fields and append the two unsafe_hidden fields. Co-Authored-By: Claude Sonnet 4.6 --- .../src/destinations/memora-internal/index.ts | 42 ++++++++++++++++++- .../memora/upsertProfile/generated-types.ts | 12 ------ .../memora/upsertProfile/index.ts | 32 -------------- 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/packages/destination-actions/src/destinations/memora-internal/index.ts b/packages/destination-actions/src/destinations/memora-internal/index.ts index e8aa734b498..e9b5a4fe5a4 100644 --- a/packages/destination-actions/src/destinations/memora-internal/index.ts +++ b/packages/destination-actions/src/destinations/memora-internal/index.ts @@ -5,7 +5,47 @@ import memoraDestination from '../memora' const destination: DestinationDefinition = { ...memoraDestination, name: 'Memora Internal', - slug: 'actions-memora-internal' + slug: 'actions-memora-internal', + actions: { + upsertProfile: { + ...memoraDestination.actions.upsertProfile, + fields: { + ...memoraDestination.actions.upsertProfile.fields, + /** + * Used by the Segment control plane as part of the Conversation Memory Sync feature. + * The control plane writes this map when creating or updating a mapping, and reads it back + * to build the event-emitter enrichment mapping that ensures the referenced Segment traits + * are present in event payloads at runtime. Not consumed by this action itself — marked + * unsafe_hidden so it round-trips through subscription settings without being exposed or stripped. + */ + segment_traits_by_field: { + label: 'Segment Traits Used', + description: + 'Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually.', + type: 'object' as const, + required: false, + additionalProperties: true, + unsafe_hidden: true + }, + /** + * Used by the Segment control plane as part of the Conversation Memory Sync feature. + * The control plane writes this map when creating or updating a mapping, and reads it back + * to populate id_sync so that the declared Segment identifiers are available as external IDs + * at runtime. Not consumed by this action itself — marked unsafe_hidden so it round-trips + * through subscription settings without being exposed or stripped. + */ + segment_identifiers_by_field: { + label: 'Segment Identifiers Used', + description: + 'Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually.', + type: 'object' as const, + required: false, + additionalProperties: true, + unsafe_hidden: true + } + } + } + } } export default destination diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts index a17761c640d..0fcaadddded 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/generated-types.ts @@ -25,16 +25,4 @@ export interface Payload { profile_traits?: { [k: string]: unknown } - /** - * Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually. - */ - segment_traits_by_field?: { - [k: string]: unknown - } - /** - * Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually. - */ - segment_identifiers_by_field?: { - [k: string]: unknown - } } diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts index a5d6a89ee52..c428d65b372 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/index.ts @@ -54,38 +54,6 @@ const action: ActionDefinition = { additionalProperties: true, dynamic: true, defaultObjectUI: 'keyvalue' - }, - /** - * Used exclusively by the Segment control plane (actions-memora-internal destination) as part of the - * Conversation Memory Sync feature. The control plane writes this map when creating or updating a mapping, - * and reads it back to build the event-emitter enrichment mapping that ensures the referenced Segment traits - * are present in event payloads at runtime. Not consumed by this action itself — marked unsafe_hidden so it - * round-trips through subscription settings without being exposed or stripped. - */ - segment_traits_by_field: { - label: 'Segment Traits Used', - description: - 'Per-field map of Segment trait names referenced by each directive (keyed by stored field key). Used by the control plane to wire up event-emitter enrichment. Set automatically — do not edit manually.', - type: 'object', - required: false, - additionalProperties: true, - unsafe_hidden: true - }, - /** - * Used exclusively by the Segment control plane (actions-memora-internal destination) as part of the - * Conversation Memory Sync feature. The control plane writes this map when creating or updating a mapping, - * and reads it back to populate id_sync so that the declared Segment identifiers are available as external - * IDs at runtime. Not consumed by this action itself — marked unsafe_hidden so it round-trips through - * subscription settings without being exposed or stripped. - */ - segment_identifiers_by_field: { - label: 'Segment Identifiers Used', - description: - 'Per-field map of Segment identifier names referenced by each directive (keyed by stored field key). Used by the control plane to wire up id_sync. Set automatically — do not edit manually.', - type: 'object', - required: false, - additionalProperties: true, - unsafe_hidden: true } }, dynamicFields: { From 113d908e3b2c79cc6b897e6a614f4cca388c9bbb Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Thu, 4 Jun 2026 22:25:15 +0530 Subject: [PATCH 5/6] test: move hidden-fields outbound test to memora-internal The test was using the base memora destination where the fields are not declared, so the framework stripped them trivially. Moved to memora-internal where segment_traits_by_field and segment_identifiers_by_field are declared, making the assertion meaningful. Co-Authored-By: Claude Sonnet 4.6 --- .../memora-internal/__tests__/index.test.ts | 62 +++++++++++++++++++ .../upsertProfile/__tests__/index.test.ts | 32 ---------- 2 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts diff --git a/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts b/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts new file mode 100644 index 00000000000..fc4cf54428e --- /dev/null +++ b/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts @@ -0,0 +1,62 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Destination from '../index' +import { API_VERSION } from '../../memora/versioning-info' +import { BASE_URL } from '../../memora/constants' + +const testDestination = createTestIntegration(Destination) + +const defaultSettings = { + username: 'test-api-key', + password: 'test-api-secret', + twilioAccount: 'AC1234567890' +} + +const defaultMapping = { + memora_store: 'test-store-id', + profile_identifiers: { + 'Contact.$.email': { '@path': '$.traits.email' }, + 'Contact.$.phone': { '@path': '$.traits.phone' } + }, + profile_traits: { + 'Contact.$.firstName': { '@path': '$.traits.first_name' } + } +} + +describe('Memora Internal (actions-memora-internal)', () => { + beforeEach(() => { + nock.cleanAll() + }) + + it('should not include segment_traits_by_field or segment_identifiers_by_field in the outbound request', async () => { + const event = createTestEvent({ + type: 'identify', + userId: 'user-123', + traits: { email: 'john@example.com', phone: '+1-555-0100', first_name: 'John' } + }) + + let capturedBody: Record = {} + + nock(BASE_URL) + .put(`/${API_VERSION}/Stores/test-store-id/Profiles/Bulk`, (body) => { + capturedBody = body as Record + return true + }) + .reply(202) + + await testDestination.testAction('upsertProfile', { + event, + settings: defaultSettings, + mapping: { + ...defaultMapping, + segment_traits_by_field: { 'Contact.$.firstName': ['first_name'] }, + segment_identifiers_by_field: { 'Contact.$.email': ['email'] } + }, + useDefaultMappings: true + }) + + const requestBodyStr = JSON.stringify(capturedBody) + expect(requestBodyStr).not.toContain('segment_traits_by_field') + expect(requestBodyStr).not.toContain('segment_identifiers_by_field') + }) +}) diff --git a/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts b/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts index 4000e65c947..67a460eff1a 100644 --- a/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts @@ -766,38 +766,6 @@ describe('Memora.upsertProfile', () => { }) }) - it('should not include segment_traits_by_field or segment_identifiers_by_field in the outbound request', async () => { - const event = createTestEvent({ - type: 'identify', - userId: 'user-123', - traits: { email: 'john@example.com', first_name: 'John' } - }) - - let capturedBody: Record = {} - - nock(BASE_URL) - .put(`/${API_VERSION}/Stores/test-store-id/Profiles/Bulk`, (body) => { - capturedBody = body as Record - return true - }) - .reply(202) - - await testDestination.testAction('upsertProfile', { - event, - settings: defaultSettings, - mapping: { - ...defaultMapping, - segment_traits_by_field: { 'Contact.$.first_name': ['first_name'] }, - segment_identifiers_by_field: { 'Contact.$.email': ['email'] } - }, - useDefaultMappings: true - }) - - const requestBodyStr = JSON.stringify(capturedBody) - expect(requestBodyStr).not.toContain('segment_traits_by_field') - expect(requestBodyStr).not.toContain('segment_identifiers_by_field') - }) - describe('performBatch (multiple profiles)', () => { it('should upsert multiple profiles in a single bulk request', async () => { const events = [ From a53e3f6774cc5185cc7596be31e44d6efb1b1ac8 Mon Sep 17 00:00:00 2001 From: Monu Kumar Date: Thu, 4 Jun 2026 22:43:00 +0530 Subject: [PATCH 6/6] test: assert nock interceptor was called and response received Adds nockIntercepted flag and response assertions so the test fails meaningfully if the HTTP interceptor is never hit, not just on body content. Co-Authored-By: Claude Sonnet 4.6 --- .../destinations/memora-internal/__tests__/index.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts b/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts index fc4cf54428e..e70d756c833 100644 --- a/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/memora-internal/__tests__/index.test.ts @@ -36,15 +36,17 @@ describe('Memora Internal (actions-memora-internal)', () => { }) let capturedBody: Record = {} + let nockIntercepted = false nock(BASE_URL) .put(`/${API_VERSION}/Stores/test-store-id/Profiles/Bulk`, (body) => { capturedBody = body as Record + nockIntercepted = true return true }) .reply(202) - await testDestination.testAction('upsertProfile', { + const responses = await testDestination.testAction('upsertProfile', { event, settings: defaultSettings, mapping: { @@ -55,6 +57,10 @@ describe('Memora Internal (actions-memora-internal)', () => { useDefaultMappings: true }) + expect(nockIntercepted).toBe(true) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + const requestBodyStr = JSON.stringify(capturedBody) expect(requestBodyStr).not.toContain('segment_traits_by_field') expect(requestBodyStr).not.toContain('segment_identifiers_by_field')