@reproapp/node-sdk 0.0.52 → 0.0.53

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.
package/dist/index.js CHANGED
@@ -2340,12 +2340,15 @@ function coerceMongoId(value) {
2340
2340
  if (typeof value !== 'object') {
2341
2341
  return null;
2342
2342
  }
2343
+ // Argument lists and query-result arrays are business data, not BSON ids.
2344
+ if (Array.isArray(value))
2345
+ return null;
2343
2346
  const bsonType = typeof value?._bsontype === 'string' ? String(value._bsontype).toLowerCase() : '';
2344
2347
  const ctorName = typeof value?.constructor?.name === 'string' ? String(value.constructor.name).toLowerCase() : '';
2345
2348
  const looksLikeObjectId = bsonType.includes('objectid') ||
2346
2349
  ctorName === 'objectid' ||
2347
2350
  typeof value.toHexString === 'function';
2348
- const fromBufferShape = coerceMongoIdFromBufferShape(value, looksLikeObjectId);
2351
+ const fromBufferShape = looksLikeObjectId ? coerceMongoIdFromBufferShape(value, true) : null;
2349
2352
  if (fromBufferShape) {
2350
2353
  return fromBufferShape;
2351
2354
  }
@@ -2422,12 +2425,11 @@ function coerceMongoIdFromBufferShape(value, includeIdCandidate = false) {
2422
2425
  if (!Array.isArray(bytes) || bytes.length !== 12) {
2423
2426
  return null;
2424
2427
  }
2425
- const normalized = bytes.map((byte) => Number(byte));
2426
- if (normalized.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2428
+ if (bytes.some((byte) => typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2427
2429
  return null;
2428
2430
  }
2429
2431
  try {
2430
- return Buffer.from(normalized).toString('hex');
2432
+ return Buffer.from(bytes).toString('hex');
2431
2433
  }
2432
2434
  catch {
2433
2435
  return null;
package/dist/privacy.js CHANGED
@@ -2891,12 +2891,14 @@ function coercePrivacyObjectId(value) {
2891
2891
  if (typeof value !== 'object') {
2892
2892
  return null;
2893
2893
  }
2894
+ if (Array.isArray(value))
2895
+ return null;
2894
2896
  const bsonType = typeof value?._bsontype === 'string' ? String(value._bsontype).toLowerCase() : '';
2895
2897
  const ctorName = typeof value?.constructor?.name === 'string' ? String(value.constructor.name).toLowerCase() : '';
2896
2898
  const looksLikeObjectId = bsonType.includes('objectid') ||
2897
2899
  ctorName === 'objectid' ||
2898
2900
  typeof value.toHexString === 'function';
2899
- const fromBufferShape = coercePrivacyObjectIdFromBufferShape(value, looksLikeObjectId);
2901
+ const fromBufferShape = looksLikeObjectId ? coercePrivacyObjectIdFromBufferShape(value, true) : null;
2900
2902
  if (fromBufferShape) {
2901
2903
  return fromBufferShape;
2902
2904
  }
@@ -2970,12 +2972,11 @@ function coercePrivacyObjectIdFromBufferShape(value, includeIdCandidate = false)
2970
2972
  if (!Array.isArray(bytes) || bytes.length !== 12) {
2971
2973
  return null;
2972
2974
  }
2973
- const normalized = bytes.map((byte) => Number(byte));
2974
- if (normalized.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2975
+ if (bytes.some((byte) => typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2975
2976
  return null;
2976
2977
  }
2977
2978
  try {
2978
- return Buffer.from(normalized).toString('hex');
2979
+ return Buffer.from(bytes).toString('hex');
2979
2980
  }
2980
2981
  catch {
2981
2982
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,13 +10,13 @@
10
10
  },
11
11
  "scripts": {
12
12
  "test:latency": "npm run build && node test/privacy-fetch-timeout.test.js && node test/capture-latency.testcontainers.test.js",
13
- "test:live-capture": "npm run build && node test/live-capture-limits.test.js && node test/ingest-throttle-retention.test.js && node test/live-capture-limits.testcontainers.test.js",
13
+ "test:live-capture": "npm run build && node test/twelve-element-capture.testcontainers.test.js && node test/live-capture-limits.test.js && node test/ingest-throttle-retention.test.js && node test/live-capture-limits.testcontainers.test.js",
14
14
  "test:live-brokers": "npm run build && node test/live-broker-limits.testcontainers.test.js",
15
15
  "build": "tsc -p tsconfig.json",
16
16
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
17
17
  "prepublishOnly": "npm run build",
18
- "test": "npm run build && node test/semantic-serialization.test.js && node test/semantic-serialization-trace.test.js && node test/content-addressed-trace-values.test.js && node test/trace-observation-snapshots.test.js && node test/session-storage-budget.test.js && node test/trace-source-exclude.test.js && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/sdk-background-timeout-isolation.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-high-priority-fairness.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-policy-index.test.js && node test/policy-capture-attestation.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
19
- "test:containers": "npm run build && node test/background-continuous-traffic.testcontainers.test.js && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/content-addressed-mongodb.testcontainers.test.js && node test/policy-session-completion.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
18
+ "test": "npm run build && node test/semantic-serialization.test.js && node test/semantic-serialization-trace.test.js && node test/content-addressed-trace-values.test.js && node test/trace-observation-snapshots.test.js && node test/session-storage-budget.test.js && node test/trace-source-exclude.test.js && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/sdk-background-timeout-isolation.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-high-priority-fairness.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-policy-index.test.js && node test/policy-capture-attestation.test.js && node test/object-id-shapes.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
19
+ "test:containers": "npm run build && node test/twelve-element-capture.testcontainers.test.js && node test/background-continuous-traffic.testcontainers.test.js && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/content-addressed-mongodb.testcontainers.test.js && node test/policy-session-completion.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "express": "^5.1.0",
package/src/index.ts CHANGED
@@ -2906,6 +2906,8 @@ function coerceMongoId(value: any): string | null {
2906
2906
  if (typeof value !== 'object') {
2907
2907
  return null;
2908
2908
  }
2909
+ // Argument lists and query-result arrays are business data, not BSON ids.
2910
+ if (Array.isArray(value)) return null;
2909
2911
 
2910
2912
  const bsonType = typeof (value as any)?._bsontype === 'string' ? String((value as any)._bsontype).toLowerCase() : '';
2911
2913
  const ctorName = typeof value?.constructor?.name === 'string' ? String(value.constructor.name).toLowerCase() : '';
@@ -2914,7 +2916,7 @@ function coerceMongoId(value: any): string | null {
2914
2916
  ctorName === 'objectid' ||
2915
2917
  typeof (value as any).toHexString === 'function';
2916
2918
 
2917
- const fromBufferShape = coerceMongoIdFromBufferShape(value, looksLikeObjectId);
2919
+ const fromBufferShape = looksLikeObjectId ? coerceMongoIdFromBufferShape(value, true) : null;
2918
2920
  if (fromBufferShape) {
2919
2921
  return fromBufferShape;
2920
2922
  }
@@ -2994,13 +2996,12 @@ function coerceMongoIdFromBufferShape(value: any, includeIdCandidate: boolean =
2994
2996
  return null;
2995
2997
  }
2996
2998
 
2997
- const normalized = bytes.map((byte) => Number(byte));
2998
- if (normalized.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2999
+ if (bytes.some((byte) => typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255)) {
2999
3000
  return null;
3000
3001
  }
3001
3002
 
3002
3003
  try {
3003
- return Buffer.from(normalized).toString('hex');
3004
+ return Buffer.from(bytes).toString('hex');
3004
3005
  } catch {
3005
3006
  return null;
3006
3007
  }
package/src/privacy.ts CHANGED
@@ -3685,6 +3685,7 @@ function coercePrivacyObjectId(value: any): string | null {
3685
3685
  if (typeof value !== 'object') {
3686
3686
  return null;
3687
3687
  }
3688
+ if (Array.isArray(value)) return null;
3688
3689
 
3689
3690
  const bsonType = typeof (value as any)?._bsontype === 'string' ? String((value as any)._bsontype).toLowerCase() : '';
3690
3691
  const ctorName = typeof value?.constructor?.name === 'string' ? String(value.constructor.name).toLowerCase() : '';
@@ -3693,7 +3694,7 @@ function coercePrivacyObjectId(value: any): string | null {
3693
3694
  ctorName === 'objectid' ||
3694
3695
  typeof value.toHexString === 'function';
3695
3696
 
3696
- const fromBufferShape = coercePrivacyObjectIdFromBufferShape(value, looksLikeObjectId);
3697
+ const fromBufferShape = looksLikeObjectId ? coercePrivacyObjectIdFromBufferShape(value, true) : null;
3697
3698
  if (fromBufferShape) {
3698
3699
  return fromBufferShape;
3699
3700
  }
@@ -3774,13 +3775,12 @@ function coercePrivacyObjectIdFromBufferShape(value: any, includeIdCandidate: bo
3774
3775
  return null;
3775
3776
  }
3776
3777
 
3777
- const normalized = bytes.map((byte) => Number(byte));
3778
- if (normalized.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
3778
+ if (bytes.some((byte) => typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255)) {
3779
3779
  return null;
3780
3780
  }
3781
3781
 
3782
3782
  try {
3783
- return Buffer.from(normalized).toString('hex');
3783
+ return Buffer.from(bytes).toString('hex');
3784
3784
  } catch {
3785
3785
  return null;
3786
3786
  }
@@ -0,0 +1,36 @@
1
+ const assert = require('node:assert/strict');
2
+ const { ObjectId } = require('mongodb');
3
+ const { __reproTestHooks } = require('../dist');
4
+ const { applyRuntimePrivacyPolicy, applyRuntimePrivacyPolicyAsync } = require('../dist/privacy');
5
+
6
+ async function main() {
7
+ const serializers = {
8
+ trace: value => __reproTestHooks.sanitizeTraceValueForPrivacyForTest(value),
9
+ policy: value => applyRuntimePrivacyPolicy(null, 'trace.args', value, { appId: 'APP_test' }),
10
+ asyncPolicy: value => applyRuntimePrivacyPolicyAsync(null, 'db.resultMeta', value, { appId: 'APP_test' }),
11
+ };
12
+ const bytes = Array.from({ length: 12 }, (_, i) => i);
13
+ const hex = '000102030405060708090a0b';
14
+ for (const [name, serialize] of Object.entries(serializers)) {
15
+ const rows = bytes.map(ordinal => Object.assign(Object.create(null), { ordinal, status: 'active' }));
16
+ const numericKeys = Object.fromEntries(bytes.map(i => [i, i]));
17
+ for (const input of [bytes, rows, numericKeys, { buffer: bytes }, { type: 'Buffer', data: bytes }]) {
18
+ const result = await serialize(input);
19
+ assert.deepEqual(JSON.parse(JSON.stringify(result)), JSON.parse(JSON.stringify(input)),
20
+ `${name}: ordinary data must not be collapsed to a BSON id`);
21
+ }
22
+ for (const input of [new ObjectId(hex), { $oid: hex }, { _bsontype: 'ObjectId', buffer: bytes }]) {
23
+ assert.equal(await serialize(input), hex, `${name}: genuine ObjectId representation must remain supported`);
24
+ }
25
+ let coercions = 0;
26
+ const invalidByte = { valueOf() { coercions += 1; throw new Error('must not coerce application objects'); } };
27
+ for (const invalid of [invalidByte, Object.create(null), '1', -1, 256, 1.5, NaN]) {
28
+ const input = { _bsontype: 'ObjectId', buffer: [...bytes.slice(0, 11), invalid] };
29
+ assert.equal(await serialize(input), '[mongo-id]', `${name}: invalid bytes must not become a fabricated ObjectId`);
30
+ }
31
+ assert.equal(coercions, 0);
32
+ }
33
+ console.log('ObjectId discrimination passed for trace, synchronous and asynchronous privacy serialization');
34
+ }
35
+
36
+ main().catch(error => { console.error(error); process.exitCode = 1; });
@@ -0,0 +1,116 @@
1
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '0';
2
+ process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
3
+ process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '30';
4
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
5
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
6
+ const assert = require('node:assert/strict');
7
+ const http = require('node:http');
8
+ const express = require('express');
9
+ const mongoose = require('mongoose');
10
+ const { GenericContainer, Wait } = require('testcontainers');
11
+ const { initReproTracing, reproMiddleware, reproMongoosePlugin } = require('../dist');
12
+ const { flushIngestQueue } = require('../dist/ingest/client');
13
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
14
+
15
+ async function main() {
16
+ let container, connection, server, sink;
17
+ const posts = [];
18
+ try {
19
+ sink = http.createServer(async (req, res) => {
20
+ const parts = []; for await (const part of req) parts.push(part);
21
+ posts.push(JSON.parse(Buffer.concat(parts).toString()));
22
+ res.writeHead(202); res.end('{}');
23
+ });
24
+ await new Promise(resolve => sink.listen(0, '127.0.0.1', resolve));
25
+ container = await new GenericContainer('mongo:7').withExposedPorts(27017)
26
+ .withWaitStrategy(Wait.forListeningPorts()).start();
27
+ connection = await mongoose.createConnection(`mongodb://${container.getHost()}:${container.getMappedPort(27017)}/twelve_elements`).asPromise();
28
+ initReproTracing({ instrument: false, logFunctionCalls: false, fullValueCapture: true });
29
+ const app = express();
30
+ const policy = { environment: 'dev', statements: [{ sid: 'drop-secret',
31
+ when: { scope: 'FIELD', selector: 'field:internalSecret', target: '**.internalSecret' },
32
+ apply: { action: 'DROP' }, meta: { priority: 1 } }] };
33
+ for (const privacy of ['policy', 'raw']) {
34
+ const config = { tenantId: 'TENANT_twelve', appId: 'APP_twelve', appSecret: 'test',
35
+ serviceName: `depot-${privacy}`, captureHeaders: false,
36
+ privacy: { environment: 'dev', captureRaw: privacy === 'raw', ...(privacy === 'policy' ? { policy } : {}) },
37
+ ingestBase: `http://127.0.0.1:${sink.address().port}` };
38
+ const schema = new mongoose.Schema({ ordinal: Number, location: String, internalSecret: String });
39
+ schema.plugin(reproMongoosePlugin(config));
40
+ const Depot = connection.model(`Depot_${privacy}`, schema);
41
+ await Depot.insertMany(Array.from({ length: 13 }, (_, ordinal) => ({ ordinal, location: `Depot ${ordinal}`, internalSecret: 'private-fixture-secret' })));
42
+ app.use(`/${privacy}`, reproMiddleware(config));
43
+ app.get(`/${privacy}/:count`, async (req, res, next) => {
44
+ try {
45
+ const rows = await Depot.find({}).sort({ ordinal: 1 }).limit(Number(req.params.count)).lean().exec();
46
+ function projectDepots(input) { return input; }
47
+ const observed = global.__repro_call(projectDepots, null, [rows], __filename, 1, 'projectDepots', false);
48
+ function numericArguments(...values) { return values; }
49
+ const numbers = global.__repro_call(numericArguments, null, Array.from({ length: 12 }, (_, i) => i), __filename, 2, 'numericArguments', false);
50
+ res.json({ rows: observed, numbers });
51
+ } catch (error) { next(error); }
52
+ });
53
+ }
54
+ server = await new Promise(resolve => { const listener = app.listen(0, '127.0.0.1', () => resolve(listener)); });
55
+ const scenarios = [];
56
+ for (const mode of ['live', 'staging']) for (const privacy of ['policy', 'raw']) for (const count of [12, 11, 13]) {
57
+ const sid = `S_${mode}_${privacy}_${count}`;
58
+ const response = await fetch(`http://127.0.0.1:${server.address().port}/${privacy}/${count}`, { headers: {
59
+ 'x-bug-session-id': sid, 'x-bug-action-id': `A_${sid}`, 'x-repro-capture-mode': mode,
60
+ } });
61
+ assert.equal(response.status, 200);
62
+ const data = await response.json();
63
+ assert.equal(data.rows.length, count);
64
+ assert.equal(data.rows[0].internalSecret, 'private-fixture-secret', 'SDK must not mutate business results');
65
+ assert.deepEqual(data.numbers, Array.from({ length: 12 }, (_, i) => i));
66
+ scenarios.push({ sid, mode, privacy, count });
67
+ }
68
+ let events;
69
+ const deadline = Date.now() + 30000;
70
+ do {
71
+ await flushIngestQueue();
72
+ events = posts.flatMap(post => post.events || []);
73
+ if (events.filter(e => e.event_type === 'request_capture_manifest').length >= scenarios.length) break;
74
+ await sleep(50);
75
+ } while (Date.now() < deadline);
76
+ for (const { sid, privacy, count } of scenarios) {
77
+ const scoped = events.filter(e => e.session_id === sid);
78
+ const manifest = scoped.find(e => e.event_type === 'request_capture_manifest')?.payload.requestCaptureManifest;
79
+ assert(manifest, `missing manifest ${sid}`);
80
+ assert.equal(manifest.captureFailures, undefined, `${sid}: ${JSON.stringify(manifest.captureFailures)}`);
81
+ if (privacy === 'policy') assert.equal(manifest.privacy.status, 'applied');
82
+ const values = scoped.flatMap(e => [...(e.payload.traceValues || []), ...(e.payload.requestValues || []), ...(e.payload.dbValues || [])]);
83
+ const resolve = (record, ref, field) => values.find(v => v.id === record?.[ref]?.valueId)?.value ?? record?.[field];
84
+ const trace = scoped.flatMap(e => e.payload.trace || []);
85
+ const args = resolve(trace.find(e => e.fn === 'projectDepots' && e.type === 'enter'), 'argsValueCapture', 'args');
86
+ const result = resolve(trace.find(e => e.fn === 'projectDepots' && e.type === 'exit'), 'returnValueCapture', 'returnValue');
87
+ for (const rows of [args?.[0], result]) {
88
+ assert.equal(rows?.length, count, `missing business array ${sid}`);
89
+ assert.equal(rows.at(-1).ordinal, count - 1);
90
+ assert.equal(rows[0].internalSecret, privacy === 'policy' ? '[dropped]' : 'private-fixture-secret');
91
+ }
92
+ const numbers = resolve(trace.find(e => e.fn === 'numericArguments' && e.type === 'enter'), 'argsValueCapture', 'args');
93
+ assert.deepEqual(numbers, Array.from({ length: 12 }, (_, i) => i), 'numeric argument list must not become an ObjectId');
94
+ const request = scoped.find(e => e.event_type === 'backend_request')?.payload.request;
95
+ const body = resolve(request, 'respBodyValueCapture', 'respBody');
96
+ assert.equal(body.rows.length, count);
97
+ const dbRead = scoped.flatMap(e => e.payload.db || []).find(e => e.op === 'find');
98
+ assert(dbRead, `missing Mongo read ${sid}`);
99
+ const dbResult = resolve(dbRead, 'resultMetaValueCapture', 'resultMeta');
100
+ const rows = Array.isArray(dbResult) ? dbResult : dbResult?.result;
101
+ assert.equal(rows?.length, count, `missing Mongo result ${sid}: ${JSON.stringify({ dbRead, dbResult })}`);
102
+ assert.equal(rows[0].internalSecret, privacy === 'policy' ? '[dropped]' : 'private-fixture-secret');
103
+ assert.equal(scoped.filter(e => e.event_type === 'trace_batch').length, manifest.expected.traceBatch);
104
+ assert.equal(scoped.flatMap(e => e.payload.db || []).length, manifest.expected.dbChange);
105
+ if (privacy === 'policy') assert(!JSON.stringify(scoped).includes('private-fixture-secret'), 'secret leaked into captured evidence');
106
+ }
107
+ console.log('12 live/staging policy/raw scenarios: business arrays, numeric arguments, Mongo results and manifests passed');
108
+ } finally {
109
+ if (server) await new Promise(resolve => server.close(resolve));
110
+ if (connection) await connection.close();
111
+ await flushIngestQueue();
112
+ if (sink) await new Promise(resolve => sink.close(resolve));
113
+ if (container) await container.stop();
114
+ }
115
+ }
116
+ main().catch(error => { console.error(error); process.exitCode = 1; });