@reproapp/node-sdk 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,264 @@
1
+ process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
2
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '0';
3
+ process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
4
+ process.env.REPRO_SDK_TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = '2048';
5
+ process.env.REPRO_TRACE_BATCH_MAX_SERIALIZED_BYTES = '65536';
6
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
7
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
8
+
9
+ const assert = require('node:assert/strict');
10
+ const { EventEmitter } = require('node:events');
11
+ const { initReproTracing, reproMiddleware, __reproTestHooks } = require('../dist');
12
+ const { flushIngestQueue } = require('../dist/ingest/client');
13
+ const { normalizeRuntimePrivacyPolicy } = require('../dist/privacy');
14
+
15
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
16
+
17
+ function makeReqRes(sessionId, actionId) {
18
+ const req = new EventEmitter();
19
+ req.method = 'POST';
20
+ req.url = '/content-addressed-values';
21
+ req.originalUrl = req.url;
22
+ req.headers = {
23
+ 'x-bug-session-id': sessionId,
24
+ 'x-bug-action-id': actionId,
25
+ 'x-bug-request-start': String(Date.now()),
26
+ };
27
+ req.body = {};
28
+ req.params = {};
29
+ req.query = {};
30
+
31
+ const res = new EventEmitter();
32
+ res.statusCode = 204;
33
+ res.getHeader = () => undefined;
34
+ res.getHeaders = () => ({});
35
+ res.setHeader = () => {};
36
+ res.write = () => true;
37
+ res.end = function end() {
38
+ this.emit('finish');
39
+ return this;
40
+ };
41
+ return { req, res };
42
+ }
43
+
44
+ function makePayload(changed = false, internalSecret) {
45
+ const payload = {
46
+ eventType: 'clinical.shipment.observed',
47
+ shipmentNumber: changed ? 'SHIP-CONTENT-CHANGED' : 'SHIP-CONTENT-STABLE',
48
+ rows: Array.from({ length: 300 }, (_, index) => ({
49
+ index,
50
+ depot: `DEPOT-${String(index).padStart(4, '0')}`,
51
+ site: {
52
+ number: `SITE-${index % 20}`,
53
+ country: { code: 'US', region: 'FDA' },
54
+ },
55
+ status: index % 2 === 0 ? 'available' : 'released',
56
+ })),
57
+ };
58
+ if (internalSecret !== undefined) {
59
+ payload.internalSecret = internalSecret;
60
+ }
61
+ return payload;
62
+ }
63
+
64
+ function observePayload(payload) {
65
+ return payload.shipmentNumber;
66
+ }
67
+
68
+ async function runRequest(config, sessionId, repeatedPayload, changedPayload = makePayload(true)) {
69
+ const posts = [];
70
+ const previousFetch = global.fetch;
71
+ global.fetch = async (_url, init = {}) => {
72
+ posts.push(JSON.parse(String(init.body || '{}')));
73
+ return {
74
+ ok: true,
75
+ status: 202,
76
+ json: async () => ({ ok: true }),
77
+ text: async () => '{"ok":true}',
78
+ };
79
+ };
80
+
81
+ const middleware = reproMiddleware(config);
82
+ const { req, res } = makeReqRes(sessionId, `A_${sessionId}`);
83
+ try {
84
+ await new Promise((resolve, reject) => {
85
+ middleware(req, res, async (error) => {
86
+ if (error) {
87
+ reject(error);
88
+ return;
89
+ }
90
+ try {
91
+ for (let index = 0; index < 60; index += 1) {
92
+ const result = global.__repro_call(
93
+ observePayload,
94
+ null,
95
+ [repeatedPayload],
96
+ '/app/shipments.service.ts',
97
+ 70,
98
+ 'observePayload',
99
+ false,
100
+ );
101
+ assert.equal(result, repeatedPayload.shipmentNumber);
102
+ }
103
+ const changedResult = global.__repro_call(
104
+ observePayload,
105
+ null,
106
+ [changedPayload],
107
+ '/app/shipments.service.ts',
108
+ 75,
109
+ 'observePayload',
110
+ false,
111
+ );
112
+ assert.equal(changedResult, changedPayload.shipmentNumber);
113
+ res.end();
114
+ resolve();
115
+ } catch (traceError) {
116
+ reject(traceError);
117
+ }
118
+ });
119
+ });
120
+
121
+ const deadline = Date.now() + 5000;
122
+ while (Date.now() < deadline) {
123
+ const events = posts.flatMap((post) => post.events || []);
124
+ if (events.some((event) => event.event_type === 'backend_request') &&
125
+ events.some((event) => event.event_type === 'trace_batch')) {
126
+ break;
127
+ }
128
+ await sleep(20);
129
+ }
130
+ await __reproTestHooks.drainSdkBackgroundQueueForTest();
131
+ await flushIngestQueue();
132
+ return posts;
133
+ } finally {
134
+ global.fetch = previousFetch;
135
+ }
136
+ }
137
+
138
+ function inspectCapture(posts) {
139
+ const ingestEvents = posts.flatMap((post) => post.events || []);
140
+ const traceBatches = ingestEvents.filter((event) => event.event_type === 'trace_batch');
141
+ const traceEvents = traceBatches.flatMap((event) => event.payload?.trace || []);
142
+ const observedEvents = traceEvents.filter((event) => event.fn === 'observePayload');
143
+ const definitions = traceBatches.flatMap((event) => event.payload?.traceValues || []);
144
+ const refs = observedEvents
145
+ .map((event) => event.argsValueCapture?.valueId)
146
+ .filter((valueId) => typeof valueId === 'string');
147
+ return { ingestEvents, traceBatches, observedEvents, definitions, refs };
148
+ }
149
+
150
+ async function main() {
151
+ const config = {
152
+ tenantId: 'TENANT_content_addressed',
153
+ appId: 'APP_content_addressed',
154
+ appSecret: 'content-addressed-test-secret',
155
+ captureHeaders: false,
156
+ privacy: { environment: 'dev', captureRaw: true },
157
+ ingestBase: 'http://127.0.0.1:65535',
158
+ };
159
+
160
+ initReproTracing({ instrument: false, fullValueCapture: true, logFunctionCalls: false });
161
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
162
+
163
+ const repeatedPayload = makePayload(false);
164
+ const first = inspectCapture(await runRequest(config, 'S_content_addressed_one', repeatedPayload));
165
+
166
+ assert.equal(first.observedEvents.length, 122, 'each invocation must retain enter and exit evidence');
167
+ assert.equal(first.refs.length, 122, 'every large argument occurrence must retain a resolvable ref');
168
+ assert.equal(new Set(first.refs).size, 2, 'only stable and changed values should receive IDs');
169
+ assert.equal(first.definitions.length, 2, 'each distinct value must be defined once per request');
170
+ assert.equal(new Set(first.definitions.map((entry) => entry.id)).size, 2);
171
+
172
+ for (const ref of first.refs) {
173
+ assert(first.definitions.some((entry) => entry.id === ref), `unresolved captured value ref ${ref}`);
174
+ }
175
+ const stableDefinition = first.definitions.find((entry) =>
176
+ entry.value?.[0]?.shipmentNumber === 'SHIP-CONTENT-STABLE'
177
+ );
178
+ assert(stableDefinition, JSON.stringify(first.definitions));
179
+ assert.equal(stableDefinition.value[0].rows.length, 300);
180
+ assert.equal(stableDefinition.value[0].rows[299].depot, 'DEPOT-0299');
181
+
182
+ const serializedBytes = Buffer.byteLength(JSON.stringify(first.traceBatches));
183
+ assert(serializedBytes < 250000, `repeated values inflated trace transport to ${serializedBytes} bytes`);
184
+
185
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
186
+ const second = inspectCapture(await runRequest(config, 'S_content_addressed_two', repeatedPayload));
187
+ assert.equal(second.definitions.length, 2);
188
+ assert.notEqual(
189
+ first.definitions[0].id,
190
+ second.definitions[0].id,
191
+ 'content IDs must be session-scoped to prevent cross-session correlation',
192
+ );
193
+
194
+ const secretPolicy = normalizeRuntimePrivacyPolicy({
195
+ environment: 'dev',
196
+ groups: [
197
+ {
198
+ gid: 'content-addressed-secret-policy',
199
+ scope: 'FIELD',
200
+ selector: 'field:internalSecret',
201
+ statements: [
202
+ {
203
+ sid: 'drop-trace-argument-secret',
204
+ when: {
205
+ scope: 'FIELD',
206
+ selector: 'field:internalSecret',
207
+ target: 'trace.args[].internalSecret',
208
+ },
209
+ apply: { action: 'DROP' },
210
+ meta: { priority: 1 },
211
+ },
212
+ ],
213
+ },
214
+ ],
215
+ });
216
+ assert(secretPolicy);
217
+
218
+ const policyConfig = {
219
+ ...config,
220
+ appId: 'APP_content_addressed_policy',
221
+ privacy: { environment: 'dev' },
222
+ };
223
+ __reproTestHooks.setRuntimePrivacyPolicyForTest(policyConfig, secretPolicy);
224
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
225
+ const policyCapture = inspectCapture(await runRequest(
226
+ policyConfig,
227
+ 'S_content_addressed_policy',
228
+ makePayload(false, 'policy-secret-alpha'),
229
+ makePayload(false, 'policy-secret-beta'),
230
+ ));
231
+ assert.equal(policyCapture.refs.length, 122);
232
+ assert.equal(new Set(policyCapture.refs).size, 1);
233
+ assert.equal(policyCapture.definitions.length, 1);
234
+ const policySerialized = JSON.stringify(policyCapture.definitions);
235
+ assert(!policySerialized.includes('policy-secret-alpha'), policySerialized);
236
+ assert(!policySerialized.includes('policy-secret-beta'), policySerialized);
237
+ assert(policySerialized.includes('[dropped]'), policySerialized);
238
+
239
+ const rawConfig = {
240
+ ...config,
241
+ appId: 'APP_content_addressed_raw',
242
+ privacy: { environment: 'dev', captureRaw: true },
243
+ };
244
+ __reproTestHooks.setRuntimePrivacyPolicyForTest(rawConfig, secretPolicy);
245
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
246
+ const rawCapture = inspectCapture(await runRequest(
247
+ rawConfig,
248
+ 'S_content_addressed_raw',
249
+ makePayload(false, 'raw-secret-alpha'),
250
+ makePayload(false, 'raw-secret-beta'),
251
+ ));
252
+ assert.equal(new Set(rawCapture.refs).size, 2);
253
+ assert.equal(rawCapture.definitions.length, 2);
254
+ const rawSerialized = JSON.stringify(rawCapture.definitions);
255
+ assert(rawSerialized.includes('raw-secret-alpha'), rawSerialized);
256
+ assert(rawSerialized.includes('raw-secret-beta'), rawSerialized);
257
+
258
+ console.log('content-addressed trace value deduplication OK');
259
+ }
260
+
261
+ main().catch((error) => {
262
+ console.error(error);
263
+ process.exit(1);
264
+ });
@@ -0,0 +1,85 @@
1
+ class Logger {
2
+ constructor(context) {
3
+ this.context = context;
4
+ this.internalBuffer = Array.from({ length: 200 }, () => 'logger-runtime-state');
5
+ }
6
+
7
+ log() {}
8
+ }
9
+
10
+ class ConfigService {
11
+ constructor() {
12
+ this.cache = {
13
+ DATABASE_URL: 'mongodb://runtime-user:runtime-password@database/repro',
14
+ SENDGRID_API_KEY: 'sendgrid-runtime-secret',
15
+ };
16
+ this.internalConfig = {
17
+ modules: Array.from({ length: 800 }, (_, index) => ({
18
+ index,
19
+ secret: `config-secret-${index}`,
20
+ nested: { host: `database-${index}.internal`, port: 27017 },
21
+ })),
22
+ };
23
+ }
24
+
25
+ get(key) {
26
+ return this.cache[key];
27
+ }
28
+ }
29
+
30
+ class ApiConfigService {
31
+ constructor(configService) {
32
+ this.configService = configService;
33
+ }
34
+ }
35
+
36
+ class ShipmentGenerator {
37
+ constructor(configService) {
38
+ this.apiConfigService = new ApiConfigService(configService);
39
+ this.logger = new Logger('ShipmentGenerator');
40
+ this.triggerAction = 'ShipmentCreated';
41
+ }
42
+ }
43
+
44
+ class ShipmentDto {
45
+ constructor() {
46
+ this.shipmentNumber = 'SHIP-TRACE-1001';
47
+ this.site = {
48
+ number: 'SITE-0042',
49
+ address: {
50
+ country: {
51
+ code: 'US',
52
+ regulatory: { region: 'FDA', enabled: true },
53
+ },
54
+ },
55
+ };
56
+ this.kits = Array.from({ length: 160 }, (_, index) => ({
57
+ kitNumber: `KIT-${String(index).padStart(4, '0')}`,
58
+ status: index % 2 === 0 ? 'available' : 'released',
59
+ temperature: { minC: 2, maxC: 8 },
60
+ }));
61
+ }
62
+ }
63
+
64
+ function recordShipment(generator, shipment) {
65
+ return {
66
+ accepted: true,
67
+ originalGeneratorObserved: generator instanceof ShipmentGenerator,
68
+ originalShipmentObserved: shipment instanceof ShipmentDto,
69
+ runtimeConfigStillReadable:
70
+ generator.apiConfigService.configService.cache.DATABASE_URL.startsWith('mongodb://'),
71
+ shipmentNumber: shipment.shipmentNumber,
72
+ lastKit: shipment.kits[shipment.kits.length - 1].kitNumber,
73
+ };
74
+ }
75
+
76
+ async function handleSemanticTrace(_req, res) {
77
+ const configService = new ConfigService();
78
+ const generator = new ShipmentGenerator(configService);
79
+ const shipment = new ShipmentDto();
80
+ res.json(recordShipment(generator, shipment));
81
+ }
82
+
83
+ module.exports = {
84
+ handleSemanticTrace,
85
+ };
@@ -0,0 +1,19 @@
1
+ const express = require('express');
2
+ const { reproMiddleware } = require('../../dist');
3
+ const { handleSemanticTrace } = require('./semantic-serialization-trace-controller');
4
+
5
+ async function startServer(config) {
6
+ const app = express();
7
+ app.use(reproMiddleware(config));
8
+ app.get('/semantic-trace', handleSemanticTrace);
9
+
10
+ return new Promise((resolve, reject) => {
11
+ const server = app.listen(0, '127.0.0.1');
12
+ server.once('listening', () => resolve(server));
13
+ server.once('error', reject);
14
+ });
15
+ }
16
+
17
+ module.exports = {
18
+ startServer,
19
+ };
@@ -145,10 +145,19 @@ async function testKafkaConsumerTraceUsesConsumeTraceId() {
145
145
  privacy: { environment: 'dev' },
146
146
  ingestBase: 'http://127.0.0.1:65535',
147
147
  };
148
+ const consumerPayload = {
149
+ marker: 'before-consumer-work',
150
+ repeated: 'consumer-trace-value'.repeat(1024),
151
+ };
148
152
  const wrapped = __reproTestHooks.wrapKafkaEachMessageHandlerForTest(
149
153
  async () => {
150
- tracer.tracer.enter('consumerWork', { file: '/app/consumer.ts', line: 10 }, { args: ['ok'] });
151
- tracer.tracer.exit({ fn: 'consumerWork', file: '/app/consumer.ts', line: 10 }, { returnValue: 'done' });
154
+ tracer.tracer.enter('consumerWork', { file: '/app/consumer.ts', line: 10 }, { args: [consumerPayload] });
155
+ consumerPayload.marker = 'at-consumer-exit';
156
+ tracer.tracer.exit(
157
+ { fn: 'consumerWork', file: '/app/consumer.ts', line: 10 },
158
+ { args: [consumerPayload], returnValue: consumerPayload },
159
+ );
160
+ consumerPayload.marker = 'after-consumer-exit';
152
161
 
153
162
  tracer.withTrace('parent-http-trace', () => {
154
163
  tracer.tracer.enter('parentWork', { file: '/app/controller.ts', line: 20 }, { args: ['wrong'] });
@@ -207,6 +216,21 @@ async function testKafkaConsumerTraceUsesConsumeTraceId() {
207
216
  .map((traceEvent) => traceEvent.fn);
208
217
  assert(traceFns.includes('consumerWork'), traceFns.join(','));
209
218
  assert(!traceFns.includes('parentWork'), traceFns.join(','));
219
+
220
+ const traceBatches = events.filter((event) => event.event_type === 'trace_batch');
221
+ const traceEvents = traceBatches.flatMap((event) => event.payload?.trace || []);
222
+ const definitions = traceBatches.flatMap((event) => event.payload?.traceValues || []);
223
+ const definitionsById = new Map(definitions.map((definition) => [definition.id, definition]));
224
+ const consumerEnter = traceEvents.find((event) => event.fn === 'consumerWork' && event.type === 'enter');
225
+ const consumerExit = traceEvents.find((event) => event.fn === 'consumerWork' && event.type === 'exit');
226
+ assert(consumerEnter && consumerExit, JSON.stringify(traceEvents));
227
+ const enterValue = definitionsById.get(consumerEnter.argsValueCapture?.valueId)?.value;
228
+ const exitArgsValue = definitionsById.get(consumerExit.argsValueCapture?.valueId)?.value;
229
+ const returnValue = definitionsById.get(consumerExit.returnValueCapture?.valueId)?.value;
230
+ assert.equal(enterValue?.[0]?.marker, 'before-consumer-work');
231
+ assert.equal(exitArgsValue?.[0]?.marker, 'at-consumer-exit');
232
+ assert.equal(returnValue?.marker, 'at-consumer-exit');
233
+ assert(!JSON.stringify(definitions).includes('after-consumer-exit'));
210
234
  } finally {
211
235
  global.fetch = originalFetch;
212
236
  }
@@ -79,6 +79,7 @@ function requestNames(port, index) {
79
79
  'x-bug-session-id': 'S_policy_completion',
80
80
  'x-bug-action-id': `A_policy_completion_${index}`,
81
81
  'x-bug-request-start': String(Date.now()),
82
+ 'x-demo-actor-id': 'policy-completion-actor',
82
83
  },
83
84
  }, (response) => {
84
85
  const chunks = [];
@@ -152,10 +153,26 @@ async function main() {
152
153
  captureHeaders: false,
153
154
  privacy: {
154
155
  environment: 'dev',
155
- policy: largePolicy(),
156
+ },
157
+ liveMode: {
158
+ enabled: true,
159
+ backendOnly: true,
160
+ defineActorId: ({ req }) => req.headers['x-demo-actor-id'],
161
+ shouldCapture: () => true,
162
+ actorType: 'policy-test-user',
156
163
  },
157
164
  ingestBase: 'http://127.0.0.1:65535',
158
165
  };
166
+ __reproTestHooks.shareRuntimePrivacyStateForTest(
167
+ {
168
+ ...cfg,
169
+ privacy: {
170
+ environment: 'dev',
171
+ policy: largePolicy(),
172
+ },
173
+ },
174
+ cfg,
175
+ );
159
176
 
160
177
  const depotSchema = new mongoose.Schema({
161
178
  location: String,
@@ -0,0 +1,152 @@
1
+ const assert = require('node:assert/strict');
2
+ const http = require('node:http');
3
+
4
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
5
+ process.env.REPRO_SDK_TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = '2048';
6
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
7
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
8
+
9
+ const originalFetch = global.fetch;
10
+ const capturedBodies = [];
11
+
12
+ global.fetch = async (url, init = {}) => {
13
+ assert.match(String(url), /\/v1\/ingest\/events$/);
14
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
15
+ return {
16
+ ok: true,
17
+ status: 202,
18
+ json: async () => ({ ok: true }),
19
+ text: async () => '{"ok":true}',
20
+ };
21
+ };
22
+
23
+ const { initRepro } = require('../dist');
24
+ const { flushIngestQueue } = require('../dist/ingest/client');
25
+
26
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
27
+
28
+ async function waitFor(predicate, timeoutMs = 4000) {
29
+ const deadline = Date.now() + timeoutMs;
30
+ while (Date.now() < deadline) {
31
+ if (predicate()) return;
32
+ await sleep(20);
33
+ }
34
+ assert(predicate(), 'timed out waiting for semantic trace capture');
35
+ }
36
+
37
+ function eventsOf(type) {
38
+ return capturedBodies
39
+ .flatMap((body) => Array.isArray(body?.events) ? body.events : [])
40
+ .filter((event) => event?.event_type === type);
41
+ }
42
+
43
+ function get(url) {
44
+ return new Promise((resolve, reject) => {
45
+ const request = http.get(url, (response) => {
46
+ let body = '';
47
+ response.setEncoding('utf8');
48
+ response.on('data', (chunk) => { body += chunk; });
49
+ response.on('end', () => resolve({ statusCode: response.statusCode, body }));
50
+ });
51
+ request.on('error', reject);
52
+ });
53
+ }
54
+
55
+ async function main() {
56
+ const config = {
57
+ tenantId: 'TENANT_semantic_trace',
58
+ appId: 'APP_semantic_trace',
59
+ appSecret: 'secret',
60
+ appName: 'semantic-trace',
61
+ serviceName: 'semantic-trace',
62
+ ingestBase: 'http://127.0.0.1:65535',
63
+ tracing: {
64
+ disableFunctionTypes: ['constructor'],
65
+ fullValueCapture: true,
66
+ logFunctionCalls: false,
67
+ },
68
+ };
69
+
70
+ await initRepro(config);
71
+ const { startServer } = require('./fixtures/semantic-serialization-trace-server');
72
+ const server = await startServer(config);
73
+
74
+ try {
75
+ const port = server.address().port;
76
+ const response = await get(
77
+ `http://127.0.0.1:${port}/semantic-trace` +
78
+ `?__repro_sid=S_semantic_trace&__repro_aid=A_semantic_trace&__repro_start=${Date.now()}`,
79
+ );
80
+ assert.equal(response.statusCode, 200);
81
+ assert.deepEqual(JSON.parse(response.body), {
82
+ accepted: true,
83
+ originalGeneratorObserved: true,
84
+ originalShipmentObserved: true,
85
+ runtimeConfigStillReadable: true,
86
+ shipmentNumber: 'SHIP-TRACE-1001',
87
+ lastKit: 'KIT-0159',
88
+ });
89
+
90
+ await waitFor(() => eventsOf('backend_request').length === 1);
91
+ await waitFor(() => eventsOf('trace_batch').some((event) =>
92
+ event.payload?.trace?.some((traceEvent) => traceEvent?.fn === 'recordShipment')
93
+ ));
94
+ await flushIngestQueue();
95
+
96
+ const traceBatches = eventsOf('trace_batch');
97
+ const traceEvents = traceBatches.flatMap((event) => event.payload?.trace || []);
98
+ const enter = traceEvents.find((event) => event?.type === 'enter' && event?.fn === 'recordShipment');
99
+ assert(enter, JSON.stringify(traceEvents));
100
+
101
+ const traceValues = traceBatches.flatMap((event) => event.payload?.traceValues || []);
102
+ const argsValue = traceValues.find((entry) =>
103
+ entry?.id === enter.argsValueCapture?.valueId && entry?.target === 'trace.args'
104
+ );
105
+ const capturedArgs = argsValue?.value || enter.args;
106
+ assert(Array.isArray(capturedArgs), JSON.stringify({ enter, traceValues }));
107
+
108
+ assert.equal(capturedArgs[0].__class, 'ShipmentGenerator');
109
+ assert.equal(capturedArgs[0].triggerAction, 'ShipmentCreated');
110
+ assert.deepEqual(capturedArgs[0].logger, {
111
+ __kind: 'repro-semantic',
112
+ category: 'logger',
113
+ className: 'Logger',
114
+ context: 'ShipmentGenerator',
115
+ });
116
+ assert.deepEqual(capturedArgs[0].apiConfigService.configService, {
117
+ __kind: 'repro-semantic',
118
+ category: 'configuration-service',
119
+ className: 'ConfigService',
120
+ });
121
+
122
+ assert.equal(capturedArgs[1].shipmentNumber, 'SHIP-TRACE-1001');
123
+ assert.equal(capturedArgs[1].site.address.country.regulatory.region, 'FDA');
124
+ assert.equal(capturedArgs[1].kits.length, 160);
125
+ assert.equal(capturedArgs[1].kits[159].kitNumber, 'KIT-0159');
126
+
127
+ const allCapturedJson = JSON.stringify(capturedBodies);
128
+ assert(!allCapturedJson.includes('sendgrid-runtime-secret'));
129
+ assert(!allCapturedJson.includes('runtime-password'));
130
+ assert(!allCapturedJson.includes('config-secret-799'));
131
+ assert(!allCapturedJson.includes('logger-runtime-state'));
132
+
133
+ const argsBytes = Buffer.byteLength(JSON.stringify(capturedArgs));
134
+ assert(argsBytes < 30000, `semantic trace args unexpectedly large: ${argsBytes} bytes`);
135
+
136
+ console.log('semantic serialization real trace pipeline OK');
137
+ } finally {
138
+ await new Promise((resolve, reject) => {
139
+ server.close((error) => error ? reject(error) : resolve());
140
+ });
141
+ }
142
+ }
143
+
144
+ main()
145
+ .catch((error) => {
146
+ console.error(error);
147
+ process.exitCode = 1;
148
+ })
149
+ .finally(async () => {
150
+ await flushIngestQueue().catch(() => {});
151
+ global.fetch = originalFetch;
152
+ });