From 0b7d4f4cc1643789858045102884aa5fc156bcb8 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 21:00:29 +0100 Subject: [PATCH 01/18] fix(backend): degrade gracefully when Soroban RPC is unreachable at startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EventPollingService.start() let a failed startup event replay abort before the continuous polling loop was armed, so a transient Soroban RPC outage during boot would permanently disable event polling until the process was restarted. Catch the replay failure, log a clear warning, and continue starting the poll loop — the next successful poll re-derives the same missed range from the persisted cursor, so nothing is lost. --- .../src/__tests__/eventPollingService.test.ts | 44 +++++++++++++++++++ backend/src/eventPollingService.ts | 13 +++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/backend/src/__tests__/eventPollingService.test.ts b/backend/src/__tests__/eventPollingService.test.ts index fceae8fb..a287eb4c 100644 --- a/backend/src/__tests__/eventPollingService.test.ts +++ b/backend/src/__tests__/eventPollingService.test.ts @@ -329,6 +329,50 @@ describe('EventPollingService', () => { expect(true).toBe(true); }); + it('starts the continuous polling loop even when startup replay fails outright', async () => { + (prisma.eventCursor.findUnique as jest.Mock).mockResolvedValue({ + id: 1, + lastLedgerSeq: 1000, + }); + + // getLatestLedger succeeds (there are missed events to replay), but the + // subsequent getEvents call for the replay fails outright — simulating + // an RPC outage mid-replay rather than a clean "nothing missed" case. + (fetch as jest.Mock).mockImplementation((_url, options) => { + const body = JSON.parse((options as RequestInit).body as string); + if (body.method === 'getLatestLedger') { + return Promise.resolve({ + json: async () => ({ result: { sequence: 1050 } }), + }); + } + return Promise.reject(new Error('RPC unavailable')); + }); + + // start() must not reject: a failed replay should degrade gracefully + // instead of aborting before the continuous polling loop is armed. + await expect(service.start()).resolves.toBeUndefined(); + + // The continuous polling loop should now be armed for this leader — + // subsequent successful polls pick up where the failed replay left off. + (fetch as jest.Mock).mockReset(); + (fetch as jest.Mock).mockImplementation((_url, options) => { + const body = JSON.parse((options as RequestInit).body as string); + if (body.method === 'getLatestLedger') { + return Promise.resolve({ json: async () => ({ result: { sequence: 1050 } }) }); + } + return Promise.resolve({ json: async () => ({ result: { events: [] } }) }); + }); + (prisma.eventCursor.upsert as jest.Mock).mockResolvedValue({}); + + await (service as any).pollEvents(); + + expect(prisma.eventCursor.upsert).toHaveBeenCalledWith({ + where: { id: 1 }, + update: { lastLedgerSeq: 1050 }, + create: { id: 1, lastLedgerSeq: 1050 }, + }); + }); + it('should continue polling after transient errors', async () => { (prisma.eventCursor.findUnique as jest.Mock).mockResolvedValue({ id: 1, diff --git a/backend/src/eventPollingService.ts b/backend/src/eventPollingService.ts index 8cd6593e..b52017a8 100644 --- a/backend/src/eventPollingService.ts +++ b/backend/src/eventPollingService.ts @@ -48,8 +48,17 @@ export class EventPollingService { // Acquire leader lock before starting polling await this.acquireLeaderLock(); - // Replay missed events on startup - let errors propagate - await this.replayMissedEvents(); + // Replay missed events on startup. A Soroban RPC outage here must not + // prevent continuous polling from ever starting — the next successful + // poll will re-derive the same missed range from the persisted cursor, + // so a failed replay is a delayed catch-up rather than lost data. + try { + await this.replayMissedEvents(); + } catch (error) { + logger.log('warn', 'Event replay on startup failed — external dependency outage suspected, continuing in degraded mode', { + error: error instanceof Error ? error.message : 'Unknown error', + }); + } // Start continuous polling only if we are the leader if (this.isLeader) { From dd293cd12a8d914d74b5b7aa5786b1b68d0fb797 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 21:14:58 +0100 Subject: [PATCH 02/18] fix(backend): sync package-lock.json for the api-schemas file dependency npm ci failed on main with "Missing: @yieldvault/api-schemas@1.0.0 from lock file" because the lockfile's link-package entry for the local ../packages/api-schemas file dependency was missing the lockfileVersion 3 "link" metadata npm ci requires. Regenerated via `npm install --package-lock-only`; no dependency versions changed. --- backend/package-lock.json | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 32f50aed..b6fcf8ad 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -57,6 +57,17 @@ "typescript": "^5.1.0" } }, + "../packages/api-schemas": { + "name": "@yieldvault/api-schemas", + "version": "1.0.0", + "dependencies": { + "zod": "^4.3.6" + }, + "devDependencies": { + "typescript": "~5.9.3", + "vitest": "^4.1.5" + } + }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", @@ -3966,11 +3977,8 @@ "license": "ISC" }, "node_modules/@yieldvault/api-schemas": { - "version": "1.0.0", - "resolved": "file:../packages/api-schemas", - "dependencies": { - "zod": "^4.3.6" - } + "resolved": "../packages/api-schemas", + "link": true }, "node_modules/accepts": { "version": "1.3.8", From bd520deae671313ba6d4cd4bf41962ca69fd60b4 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 22:00:38 +0100 Subject: [PATCH 03/18] fix(backend): resolve eslint errors blocking npm run lint - eventOutbox.test.ts / sloMetrics.test.ts: use const for never-reassigned bindings (prefer-const) - optimisticConcurrency.ts / walletAliasService.ts: rewrite while(true) retry loops as for(;;), which is not flagged by no-constant-condition --- backend/src/__tests__/eventOutbox.test.ts | 2 +- backend/src/__tests__/sloMetrics.test.ts | 2 +- backend/src/optimisticConcurrency.ts | 2 +- backend/src/walletAliasService.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/__tests__/eventOutbox.test.ts b/backend/src/__tests__/eventOutbox.test.ts index 81607318..0949eff3 100644 --- a/backend/src/__tests__/eventOutbox.test.ts +++ b/backend/src/__tests__/eventOutbox.test.ts @@ -448,7 +448,7 @@ describe('EventOutboxService', () => { describe('end-to-end flow', () => { it('completes the full outbox lifecycle', async () => { - let deliveredPayloads: unknown[] = []; + const deliveredPayloads: unknown[] = []; global.fetch = jest.fn(async (_url, init) => { if (init?.body && String(init.body).includes('webhook.verification')) { const body = JSON.parse(String(init.body)); diff --git a/backend/src/__tests__/sloMetrics.test.ts b/backend/src/__tests__/sloMetrics.test.ts index 89ea64ee..621143b1 100644 --- a/backend/src/__tests__/sloMetrics.test.ts +++ b/backend/src/__tests__/sloMetrics.test.ts @@ -33,7 +33,7 @@ describe('endpoint SLO Prometheus metrics', () => { latencyMonitoringService.syncSloMetrics(); syncJobGovernanceMetrics(); - let metrics = await register.metrics(); + const metrics = await register.metrics(); expect(metrics).toContain('backend_slo_breach'); expect(metrics).toContain('backend_slo_p95_latency_ms'); expect(metrics).toContain('tier="critical"'); diff --git a/backend/src/optimisticConcurrency.ts b/backend/src/optimisticConcurrency.ts index b2c89582..1187bcc7 100644 --- a/backend/src/optimisticConcurrency.ts +++ b/backend/src/optimisticConcurrency.ts @@ -40,7 +40,7 @@ export async function executeWithOptimisticConcurrency( const maxDelayMs = options?.maxDelayMs ?? DEFAULT_OPTIONS.maxDelayMs; let attempt = 0; - while (true) { + for (;;) { attempt++; try { return await operation(attempt); diff --git a/backend/src/walletAliasService.ts b/backend/src/walletAliasService.ts index 1ea9503c..8c046d80 100644 --- a/backend/src/walletAliasService.ts +++ b/backend/src/walletAliasService.ts @@ -354,7 +354,7 @@ export class WalletAliasMappingService { } private async createCanonicalId(tx: PrismaTransaction): Promise { - while (true) { + for (;;) { this.canonicalIdCounter += 1; const id = `wallet-alias:${this.canonicalIdCounter}`; const existing = await tx.walletCanonicalIdentity.findUnique({ where: { id } }); From 1b66d2b01d89a579a8401c6f34eb47707a972335 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 22:00:59 +0100 Subject: [PATCH 04/18] fix(backend): add missing EventOutbox/AdminConfigChange/FeatureFlagOverride migration schema.prisma had three models (EventOutbox, AdminConfigChange, FeatureFlagOverride) and a `version` column on BulkExportJob/VaultState with no corresponding migration, so `prisma migrate deploy` against a fresh database left those tables missing. This is what made most of the backend test suite fail with "table does not exist" errors. Generated the reconciling migration via `prisma migrate dev --create-only` and dropped its one cosmetic index-rename step (dropping an index that backs a UNIQUE constraint isn't supported directly on SQLite; the existing autoindex already enforces the same constraint). Regenerated dev.db from the full, now-complete migration history. --- backend/prisma/dev.db | Bin 741376 -> 806912 bytes .../migration.sql | 117 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 backend/prisma/migrations/20260824204600_add_event_outbox/migration.sql diff --git a/backend/prisma/dev.db b/backend/prisma/dev.db index b94640da05dc139a2ef7d963fb1a7970040d7741..b01a3f153bdf1ee7e4eb2a9d9e486e6cb2df74ee 100644 GIT binary patch literal 806912 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 literal 741376 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 Date: Mon, 24 Aug 2026 22:01:29 +0100 Subject: [PATCH 05/18] fix(backend): fix crashing scoped-admin-token test and off-by-zero TTL bug ScopedAdminTokenStore is fully async/Prisma-backed, but the Issue #723 tests in issues705-707-719-723.test.ts called create()/revoke()/rotate()/ list()/authenticate()/clear() without awaiting them and asserted on the unresolved promises. Two of those tests wrapped the async create() call in a synchronous expect(() => ...).toThrow(), which turned validation errors into unhandled promise rejections that crashed the whole Jest worker process. Made the describe block properly async/await throughout and switched the two throw assertions to expect(...).rejects.toThrow(). Separately, getStalePendingTtlMs() in writeAheadAuditLog.ts treated an explicit WAL_STALE_PENDING_TTL_MS=0 (used by a test to mean "everything is immediately stale") as unset and fell back to the 15-minute default, because the guard was `parsed > 0` instead of `parsed >= 0`. --- .../__tests__/issues705-707-719-723.test.ts | 88 +++++++++---------- backend/src/writeAheadAuditLog.ts | 2 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/backend/src/__tests__/issues705-707-719-723.test.ts b/backend/src/__tests__/issues705-707-719-723.test.ts index f12375d7..b35612de 100644 --- a/backend/src/__tests__/issues705-707-719-723.test.ts +++ b/backend/src/__tests__/issues705-707-719-723.test.ts @@ -408,12 +408,12 @@ describe('Issue #719: Health probe decomposition for dependencies', () => { // ─── Issue #723: Permission-scoped admin tokens ───────────────────────────── describe('Issue #723: Permission-scoped admin tokens with rotating key identifiers', () => { - beforeEach(() => { - scopedAdminTokenStore.clear(); + beforeEach(async () => { + await scopedAdminTokenStore.clear(); }); - it('creates a scoped token with permissions', () => { - const { token, secret } = scopedAdminTokenStore.create({ + it('creates a scoped token with permissions', async () => { + const { token, secret } = await scopedAdminTokenStore.create({ label: 'CI Pipeline', permissions: ['read:metrics', 'read:audit'], createdBy: 'admin-1', @@ -427,56 +427,56 @@ describe('Issue #723: Permission-scoped admin tokens with rotating key identifie expect(secret.length).toBe(64); }); - it('authenticates a valid token', () => { - const { token, secret } = scopedAdminTokenStore.create({ + it('authenticates a valid token', async () => { + const { token, secret } = await scopedAdminTokenStore.create({ label: 'Test Token', permissions: ['read:metrics'], createdBy: 'admin-1', }); - const authenticated = scopedAdminTokenStore.authenticate(token.keyId, secret); + const authenticated = await scopedAdminTokenStore.authenticate(token.keyId, secret); expect(authenticated).not.toBeNull(); expect(authenticated!.keyId).toBe(token.keyId); }); - it('rejects authentication with wrong secret', () => { - const { token } = scopedAdminTokenStore.create({ + it('rejects authentication with wrong secret', async () => { + const { token } = await scopedAdminTokenStore.create({ label: 'Test Token', permissions: ['read:metrics'], createdBy: 'admin-1', }); - const result = scopedAdminTokenStore.authenticate(token.keyId, 'wrong-secret'); + const result = await scopedAdminTokenStore.authenticate(token.keyId, 'wrong-secret'); expect(result).toBeNull(); }); - it('rejects authentication for revoked tokens', () => { - const { token, secret } = scopedAdminTokenStore.create({ + it('rejects authentication for revoked tokens', async () => { + const { token, secret } = await scopedAdminTokenStore.create({ label: 'Revokable', permissions: ['read:metrics'], createdBy: 'admin-1', }); - scopedAdminTokenStore.revoke(token.keyId); + await scopedAdminTokenStore.revoke(token.keyId); - const result = scopedAdminTokenStore.authenticate(token.keyId, secret); + const result = await scopedAdminTokenStore.authenticate(token.keyId, secret); expect(result).toBeNull(); }); - it('rejects authentication for expired tokens', () => { - const { token, secret } = scopedAdminTokenStore.create({ + it('rejects authentication for expired tokens', async () => { + const { token, secret } = await scopedAdminTokenStore.create({ label: 'Short-lived', permissions: ['read:metrics'], expiresInSeconds: -1, createdBy: 'admin-1', }); - const result = scopedAdminTokenStore.authenticate(token.keyId, secret); + const result = await scopedAdminTokenStore.authenticate(token.keyId, secret); expect(result).toBeNull(); }); - it('checks individual permissions', () => { - const { token } = scopedAdminTokenStore.create({ + it('checks individual permissions', async () => { + const { token } = await scopedAdminTokenStore.create({ label: 'Partial', permissions: ['read:metrics', 'read:audit'], createdBy: 'admin-1', @@ -487,8 +487,8 @@ describe('Issue #723: Permission-scoped admin tokens with rotating key identifie expect(scopedAdminTokenStore.hasPermission(token, 'write:config')).toBe(false); }); - it('admin:* permission grants access to all permissions', () => { - const { token } = scopedAdminTokenStore.create({ + it('admin:* permission grants access to all permissions', async () => { + const { token } = await scopedAdminTokenStore.create({ label: 'Super', permissions: ['admin:*'], createdBy: 'admin-1', @@ -499,74 +499,74 @@ describe('Issue #723: Permission-scoped admin tokens with rotating key identifie expect(scopedAdminTokenStore.hasPermission(token, 'write:maintenance')).toBe(true); }); - it('rotates token secret', () => { - const { token, secret: oldSecret } = scopedAdminTokenStore.create({ + it('rotates token secret', async () => { + const { token, secret: oldSecret } = await scopedAdminTokenStore.create({ label: 'Rotatable', permissions: ['read:metrics'], createdBy: 'admin-1', }); - const result = scopedAdminTokenStore.rotate(token.keyId); + const result = await scopedAdminTokenStore.rotate(token.keyId); expect(result).not.toBeNull(); expect(result!.newSecret).not.toBe(oldSecret); expect(result!.rotatedAt).toBeDefined(); // Old secret no longer works - expect(scopedAdminTokenStore.authenticate(token.keyId, oldSecret)).toBeNull(); + expect(await scopedAdminTokenStore.authenticate(token.keyId, oldSecret)).toBeNull(); // New secret works - expect(scopedAdminTokenStore.authenticate(token.keyId, result!.newSecret)).not.toBeNull(); + expect(await scopedAdminTokenStore.authenticate(token.keyId, result!.newSecret)).not.toBeNull(); }); - it('cannot rotate revoked token', () => { - const { token } = scopedAdminTokenStore.create({ + it('cannot rotate revoked token', async () => { + const { token } = await scopedAdminTokenStore.create({ label: 'Revoked', permissions: ['read:metrics'], createdBy: 'admin-1', }); - scopedAdminTokenStore.revoke(token.keyId); - const result = scopedAdminTokenStore.rotate(token.keyId); + await scopedAdminTokenStore.revoke(token.keyId); + const result = await scopedAdminTokenStore.rotate(token.keyId); expect(result).toBeNull(); }); - it('lists active tokens excluding revoked by default', () => { - scopedAdminTokenStore.create({ + it('lists active tokens excluding revoked by default', async () => { + await scopedAdminTokenStore.create({ label: 'Active', permissions: ['read:metrics'], createdBy: 'admin-1', }); - const { token: revoked } = scopedAdminTokenStore.create({ + const { token: revoked } = await scopedAdminTokenStore.create({ label: 'Revoked', permissions: ['read:audit'], createdBy: 'admin-1', }); - scopedAdminTokenStore.revoke(revoked.keyId); + await scopedAdminTokenStore.revoke(revoked.keyId); - const activeOnly = scopedAdminTokenStore.list(); + const activeOnly = await scopedAdminTokenStore.list(); expect(activeOnly).toHaveLength(1); - const all = scopedAdminTokenStore.list({ includeRevoked: true }); + const all = await scopedAdminTokenStore.list({ includeRevoked: true }); expect(all).toHaveLength(2); }); - it('rejects invalid permissions', () => { - expect(() => + it('rejects invalid permissions', async () => { + await expect( scopedAdminTokenStore.create({ label: 'Invalid', permissions: ['invalid:perm' as any], createdBy: 'admin-1', }), - ).toThrow('Invalid permission'); + ).rejects.toThrow('Invalid permission'); }); - it('rejects empty permissions array', () => { - expect(() => + it('rejects empty permissions array', async () => { + await expect( scopedAdminTokenStore.create({ label: 'Empty', permissions: [], createdBy: 'admin-1', }), - ).toThrow('At least one permission is required'); + ).rejects.toThrow('At least one permission is required'); }); it('returns valid permissions list', () => { @@ -577,8 +577,8 @@ describe('Issue #723: Permission-scoped admin tokens with rotating key identifie expect(perms.length).toBeGreaterThan(5); }); - it('hasAnyPermission checks multiple permissions', () => { - const { token } = scopedAdminTokenStore.create({ + it('hasAnyPermission checks multiple permissions', async () => { + const { token } = await scopedAdminTokenStore.create({ label: 'Multi', permissions: ['read:metrics'], createdBy: 'admin-1', diff --git a/backend/src/writeAheadAuditLog.ts b/backend/src/writeAheadAuditLog.ts index 93e0487c..71e61b70 100644 --- a/backend/src/writeAheadAuditLog.ts +++ b/backend/src/writeAheadAuditLog.ts @@ -46,7 +46,7 @@ const DEFAULT_STALE_PENDING_TTL_MS = 15 * 60 * 1000; function getStalePendingTtlMs(): number { const parsed = parseInt(process.env.WAL_STALE_PENDING_TTL_MS || '', 10); - return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STALE_PENDING_TTL_MS; + return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_STALE_PENDING_TTL_MS; } /** From 195c5d8d499d4a4e1b5a1c9e6fbf0e7fbf8e82b9 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 22:09:48 +0100 Subject: [PATCH 06/18] fix(backend): apply non-breaking npm audit fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumped axios, body-parser, brace-expansion, fast-uri, js-yaml, and protobufjs to their patched versions within existing semver ranges (package.json unchanged). Reduces npm audit findings from 31 (6 high) to 25 (2 high) with no code changes required. The remaining 2 high-severity findings are all in the @opentelemetry/* dependency chain, which has no non-breaking fix available — resolving those needs a major-version upgrade of the OpenTelemetry SDK and is left out of scope here since it would require re-validating the tracing instrumentation in tracing.ts / index.ts. --- backend/package-lock.json | 52 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index b6fcf8ad..e3314f8e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1972,9 +1972,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "dev": true, "license": "MIT", "dependencies": { @@ -4171,13 +4171,13 @@ } }, "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } @@ -4417,9 +4417,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -4462,9 +4462,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -5737,9 +5737,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", + "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", "funding": [ { "type": "github", @@ -7362,9 +7362,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", @@ -8427,9 +8427,9 @@ } }, "node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -9293,15 +9293,15 @@ } }, "node_modules/swagger-jsdoc/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/swagger-jsdoc/node_modules/glob": { From 00c95b28e1eb37f945a946c2a441ef64a505c14e Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 23:24:32 +0100 Subject: [PATCH 07/18] fix(backend): upgrade OpenTelemetry packages to clear remaining audit findings Forced the last 2 high-severity npm audit findings (both rooted in @opentelemetry/core <2.8.0) by bumping @opentelemetry/sdk-node, exporter-trace-otlp-http, and instrumentation-http from ^0.218.0 to ^0.221.0, which pulls in a patched @opentelemetry/core transitively. `npm audit` now reports 0 vulnerabilities. Verified: `npm run build` and `npm run lint` are clean, all previously affected test suites still pass, and a runtime smoke test of initTracing()/withSpan()/shutdownTracing() from tracing.ts behaves identically to before the bump (the only error observed is an expected ECONNREFUSED from the exporter trying to flush spans to a real OTLP collector, which isn't running in this environment and is unrelated to the version change). --- backend/package-lock.json | 471 ++++++++++++++++++++++---------------- backend/package.json | 8 +- 2 files changed, 279 insertions(+), 200 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index e3314f8e..92e607e5 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,11 +10,11 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.1058.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.221.0", "@opentelemetry/instrumentation-express": "^0.66.0", - "@opentelemetry/instrumentation-http": "^0.218.0", + "@opentelemetry/instrumentation-http": "^0.221.0", "@opentelemetry/resources": "^2.7.1", - "@opentelemetry/sdk-node": "^0.218.0", + "@opentelemetry/sdk-node": "^0.221.0", "@opentelemetry/semantic-conventions": "^1.41.1", "@prisma/client": "^5.10.0", "@prisma/instrumentation": "^7.8.0", @@ -2484,13 +2484,13 @@ } }, "node_modules/@opentelemetry/configuration": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.218.0.tgz", - "integrity": "sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.221.0.tgz", + "integrity": "sha512-uE9y56Zdi9Gt/RdxYnVOo3YmFZkKJJMA0gqtBe8wh8gdtF5Asqe+Oh/TWiDtFb1s+31jNY4CWgnfIB1KOITfFA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "yaml": "^2.0.0" + "@opentelemetry/core": "2.10.0", + "yaml": "^2.8.3" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2500,9 +2500,9 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", - "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.10.0.tgz", + "integrity": "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==", "license": "Apache-2.0", "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2512,9 +2512,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", - "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2527,17 +2527,15 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.218.0.tgz", - "integrity": "sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.221.0.tgz", + "integrity": "sha512-txG1G0IrYSsKKMeiWZfj/i5cQmWB+h+hf3HzPpF3RqZVwp+iQQEIsv8Vtmzy6RWVdHdJZfygmVrBI39YTBvWcw==", "license": "Apache-2.0", "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/sdk-logs": "0.218.0" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-logs": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2547,16 +2545,14 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz", - "integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.221.0.tgz", + "integrity": "sha512-nKXkr4Tomi6fjYVOf+ytcW3dZAVr4v4Bv5gsT6dr2gvpUPJpKgHB4XbMufMsPotRE3g0XH2GwVVCkN2w6SON+Q==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/sdk-logs": "0.218.0" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-logs": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2566,18 +2562,14 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.218.0.tgz", - "integrity": "sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.221.0.tgz", + "integrity": "sha512-AH6EY+47gXFaWYgG3hfeOneGiE9xIZGtDBk+9g0sM8NZWzsQhhmqPbQQXJzS7pyCh5jRRr2nYNXVrkCmoojRvQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-logs": "0.218.0", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-logs": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2587,19 +2579,14 @@ } }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.218.0.tgz", - "integrity": "sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.221.0.tgz", + "integrity": "sha512-KOgCtO15FC6C1T/xOqBcr7EyUs7B+7yomGNb5Y97d3s38rPbCCk5sewkmE2b0/itOkQ/PptX8CLlD+kn2mEtTg==", "license": "Apache-2.0", "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" + "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2609,16 +2596,16 @@ } }, "node_modules/@opentelemetry/exporter-metrics-otlp-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.218.0.tgz", - "integrity": "sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.221.0.tgz", + "integrity": "sha512-sRfCKbOzgy8xZQV2as0RzIZlnCmCseCKZGLfRcrpo2CBngJDr+rPtX0zkG0+oUCV5kfQPUoW3W3C96Ag3Y/Clg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" + "@opentelemetry/core": "2.10.0", + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-metrics": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2628,17 +2615,14 @@ } }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.218.0.tgz", - "integrity": "sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.221.0.tgz", + "integrity": "sha512-YMF4LveY2I3yhw61rn6nmC9FE8U24IZHPeKU1Duc5+sbwjMd8FwZAwba318ImdThCg/HuVQvhm2y6bfgNPnfYg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" + "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2648,14 +2632,14 @@ } }, "node_modules/@opentelemetry/exporter-prometheus": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.218.0.tgz", - "integrity": "sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.221.0.tgz", + "integrity": "sha512-kW79a20qWESIuAdDrxzg9WKM98twV/NBWBFRAH57ap/+ssZhiCo0hckzKT0zpuwR/gSHrFAQhJL0bYDrnEM34g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2666,18 +2650,15 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.218.0.tgz", - "integrity": "sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.221.0.tgz", + "integrity": "sha512-zXminlZedtq9LvOW64CnNkOqk15zV75k8JgtdTuWFge6+jk2m4GmAUm6L2eIiG1o2a2bZxXw2PDrszm+bps0IA==", "license": "Apache-2.0", "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-trace": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2687,16 +2668,14 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", - "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.221.0.tgz", + "integrity": "sha512-AySXiKoC+meiWm6zdVj5T2LnPDZuatveBby1cMOeQteIWsYXAUxs8Sru13G2pVSPrUXz6vF+og7QVBX6GdC/oQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-trace": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2706,16 +2685,14 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.218.0.tgz", - "integrity": "sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.221.0.tgz", + "integrity": "sha512-Z9i2T7vgZbWe9rSLYxXVIbeW+XyzUq4rZanW3ZyVNwVDqCsh0EJKUgBWWQ0CZfeuUA+RQPzKgJQHMuWAUnKqXw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-trace": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2725,14 +2702,14 @@ } }, "node_modules/@opentelemetry/exporter-zipkin": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.1.tgz", - "integrity": "sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.10.0.tgz", + "integrity": "sha512-7gsvgf0UDoJ4l9ObrwBmz5G/ZogiPk+lq+g5GpLp24YQF/vPM/BSsnOfcLnfinast5ASUgLo78uSC/ObjlnXgg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2777,13 +2754,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.218.0.tgz", - "integrity": "sha512-x9djaqdzpT8WAboep1H9nCAQ1E+MMsm08TNfA02TqM3bNNddZeiim+E3KMWVQFaX6JpUy7V0nm/wfN/K2Em+Zw==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.221.0.tgz", + "integrity": "sha512-oIP91CPIANuYr09tGFElPFKAh6JUar+awJf1kBRYlaeo9b0gDwZHEB2zBfFlvdNFHm0wAVutMZODVi5smKT30g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/instrumentation": "0.218.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/instrumentation": "0.221.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, @@ -2794,14 +2771,43 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/api-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", + "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/instrumentation": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.221.0.tgz", + "integrity": "sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.221.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", - "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.221.0.tgz", + "integrity": "sha512-UFPIq80OH3Ns/oPFHRj14d4DTOxUo+MUFU8hUiCq5jTqFhdeJnfVSANHT+xp92409cA+oxzvlZCe6NM1wvCuBA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-transformer": "0.218.0" + "@opentelemetry/core": "2.10.0", + "@opentelemetry/otlp-transformer": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2811,15 +2817,15 @@ } }, "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.218.0.tgz", - "integrity": "sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.221.0.tgz", + "integrity": "sha512-rQDmNgyiGCTrescjnzH2ntVyUKVIq6I2UjuK8+stT/Xg0ZOT71FVJqwjFdspQl6Yol/Yqsut9bDo+ame8oTmDQ==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0" + "@opentelemetry/core": "2.10.0", + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2829,17 +2835,17 @@ } }, "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", - "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.221.0.tgz", + "integrity": "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-logs": "0.218.0", - "@opentelemetry/sdk-metrics": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/api-logs": "0.221.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-logs": "0.221.0", + "@opentelemetry/sdk-metrics": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2848,13 +2854,25 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", + "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@opentelemetry/propagator-b3": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.7.1.tgz", - "integrity": "sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.10.0.tgz", + "integrity": "sha512-GnA5B24H+1w8BO21J0q+IWNB0z1v+AGbcquTdIt/dufibhnhgxaA8YKvz0I3akRZhB1jHT+/tlzK+qlAjEDybQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1" + "@opentelemetry/core": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2864,12 +2882,12 @@ } }, "node_modules/@opentelemetry/propagator-jaeger": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.1.tgz", - "integrity": "sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.10.0.tgz", + "integrity": "sha512-yw/IX8DL470dSMZJoE82ScfYGp7JWZ/G8kFJo35ZILUVTB2jFPTOaioN+8s09pH0RHsWNhweVZb+ZnjJJpCChg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1" + "@opentelemetry/core": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2879,12 +2897,12 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", + "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2895,14 +2913,14 @@ } }, "node_modules/@opentelemetry/sdk-logs": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", - "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.221.0.tgz", + "integrity": "sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", + "@opentelemetry/api-logs": "0.221.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2912,14 +2930,26 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/api-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", + "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", - "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.10.0.tgz", + "integrity": "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1" + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2929,35 +2959,83 @@ } }, "node_modules/@opentelemetry/sdk-node": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.218.0.tgz", - "integrity": "sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==", + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.221.0.tgz", + "integrity": "sha512-UbYuvtBrQQB5Prsh9KOKy4kxzexFxfMs5MkteHeWMoswsEB7kiNhyUVkAOFW/qsEzNHtrkgyghrD2ilZJa+5YA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/configuration": "0.218.0", - "@opentelemetry/context-async-hooks": "2.7.1", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/exporter-logs-otlp-grpc": "0.218.0", - "@opentelemetry/exporter-logs-otlp-http": "0.218.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.218.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "0.218.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", - "@opentelemetry/exporter-metrics-otlp-proto": "0.218.0", - "@opentelemetry/exporter-prometheus": "0.218.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.218.0", - "@opentelemetry/exporter-trace-otlp-http": "0.218.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.218.0", - "@opentelemetry/exporter-zipkin": "2.7.1", - "@opentelemetry/instrumentation": "0.218.0", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/propagator-b3": "2.7.1", - "@opentelemetry/propagator-jaeger": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-logs": "0.218.0", - "@opentelemetry/sdk-metrics": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1", - "@opentelemetry/sdk-trace-node": "2.7.1", + "@opentelemetry/api-logs": "0.221.0", + "@opentelemetry/configuration": "0.221.0", + "@opentelemetry/context-async-hooks": "2.10.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.221.0", + "@opentelemetry/exporter-logs-otlp-http": "0.221.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.221.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.221.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.221.0", + "@opentelemetry/exporter-prometheus": "0.221.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.221.0", + "@opentelemetry/exporter-trace-otlp-http": "0.221.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.221.0", + "@opentelemetry/exporter-zipkin": "2.10.0", + "@opentelemetry/instrumentation": "0.221.0", + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", + "@opentelemetry/propagator-b3": "2.10.0", + "@opentelemetry/propagator-jaeger": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-logs": "0.221.0", + "@opentelemetry/sdk-metrics": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", + "@opentelemetry/sdk-trace-base": "2.10.0", + "@opentelemetry/sdk-trace-node": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", + "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/instrumentation": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.221.0.tgz", + "integrity": "sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.221.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2968,13 +3046,14 @@ } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2985,14 +3064,14 @@ } }, "node_modules/@opentelemetry/sdk-trace-node": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz", - "integrity": "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.10.0.tgz", + "integrity": "sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/context-async-hooks": "2.7.1", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" + "@opentelemetry/context-async-hooks": "2.10.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/sdk-trace-base": "2.10.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -3193,9 +3272,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, "node_modules/@scarf/scarf": { diff --git a/backend/package.json b/backend/package.json index a592bcba..a223ae15 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,18 +30,18 @@ "author": "", "license": "MIT", "dependencies": { - "@yieldvault/api-schemas": "file:../packages/api-schemas", "@aws-sdk/client-s3": "^3.1058.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.221.0", "@opentelemetry/instrumentation-express": "^0.66.0", - "@opentelemetry/instrumentation-http": "^0.218.0", + "@opentelemetry/instrumentation-http": "^0.221.0", "@opentelemetry/resources": "^2.7.1", - "@opentelemetry/sdk-node": "^0.218.0", + "@opentelemetry/sdk-node": "^0.221.0", "@opentelemetry/semantic-conventions": "^1.41.1", "@prisma/client": "^5.10.0", "@prisma/instrumentation": "^7.8.0", "@stellar/stellar-base": "^13.1.0", "@stellar/stellar-sdk": "^13.0.0", + "@yieldvault/api-schemas": "file:../packages/api-schemas", "cors": "^2.8.6", "decimal.js": "^10.6.0", "dotenv": "^16.3.1", From fb6abdb42f545492f127ef33710353db8d09d337 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Mon, 24 Aug 2026 23:45:59 +0100 Subject: [PATCH 08/18] fix(backend): stop crashing when DATABASE_URL is a non-sqlite connection string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit schema.prisma's datasource provider is "sqlite", so its generated client can only accept file: URLs no matter what override is passed in. The backend-governance CI job sets DATABASE_URL to a Postgres connection string for the separate raw `pg` pool in database.ts (which has its own migrations under scripts/postgres-migrations.js), but prisma.ts was also reading DATABASE_URL and passing it straight through to PrismaClient's datasource override, which Prisma rejects outright ("the URL must start with the protocol file:") — crashing every Prisma-backed query in that job before a single test could run. buildDatasourceUrl() now falls back to the schema-declared sqlite file whenever DATABASE_URL isn't itself a file: URL, instead of forwarding an override the generated client can never accept. fix(ci): pin cargo-audit to a version compatible with the pinned Rust toolchain rust-toolchain.toml pins the workspace to rustc 1.85.0 (for reproducible Soroban/WASM contract builds), but `cargo install cargo-audit` was installing the latest release (0.22.2), which requires rustc 1.88+. Pinned to cargo-audit 0.22.1, which the error message itself confirms supports 1.85, instead of bumping the toolchain pin and risking a change in contract build output. --- .github/workflows/rust-security.yml | 2 +- backend/src/prisma.ts | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rust-security.yml b/.github/workflows/rust-security.yml index 61ef5eda..29942321 100644 --- a/.github/workflows/rust-security.yml +++ b/.github/workflows/rust-security.yml @@ -49,7 +49,7 @@ jobs: working-directory: frontend - name: Install cargo-audit - run: cargo install cargo-audit + run: cargo install cargo-audit --version 0.22.1 - name: Run cargo audit run: | diff --git a/backend/src/prisma.ts b/backend/src/prisma.ts index a66c948d..3a353dc1 100644 --- a/backend/src/prisma.ts +++ b/backend/src/prisma.ts @@ -24,23 +24,21 @@ function buildDatasourceUrl(): string | undefined { return rawUrl; } + // schema.prisma's datasource provider is "sqlite" — its generated client + // can only ever accept file: URLs, regardless of what DATABASE_URL holds. + // DATABASE_URL pointing at Postgres/MySQL is for the separate raw `pg` + // pool in database.ts (its own migrations, its own tables); overriding + // Prisma's datasource with that URL here would fail schema validation + // outright, so fall back to the schema-declared sqlite file instead of + // crashing every Prisma-backed query. try { const url = new URL(rawUrl); - if (url.protocol.startsWith('postgres')) { - url.searchParams.set('connection_limit', String(POOL_MAX)); - url.searchParams.set('pool_timeout', String(Math.round(POOL_TIMEOUT_MS / 1000))); - return url.toString(); + if (url.protocol.startsWith('postgres') || url.protocol.startsWith('mysql')) { + return undefined; } - - if (url.protocol.startsWith('mysql')) { - url.searchParams.set('connection_limit', String(POOL_MAX)); - url.searchParams.set('pool_timeout', String(POOL_TIMEOUT_MS)); - return url.toString(); - } - return rawUrl; } catch { - return rawUrl; + return undefined; } } From dc3de2deed41918f11828d2db3d4e59f565b6c34 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:41:08 +0100 Subject: [PATCH 09/18] fix(backend): avoid SQLite table rebuild in migration, fix migration-safety false positive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EventOutbox migration's auto-generated SQLite table rebuild (drop + recreate) for adding a version column to BulkExportJob/VaultState tripped the repo's migration-safety check, which hard-bans any DROP TABLE/COLUMN/ INDEX pattern with no annotation opt-out. Rewrote it as plain ALTER TABLE ADD COLUMN statements (fully supported by SQLite for a column with a constant DEFAULT), which achieves the same schema without ever dropping the table or its existing indexes. Also fixed a real false positive in the migration-safety script itself: its ADD COLUMN NOT NULL / no DEFAULT check anchored the regex match at "NOT NULL", so a trailing "DEFAULT x" (Prisma's own convention — "TYPE NOT NULL DEFAULT x") was invisible to it, incorrectly flagging an already-safe pre-existing migration (webhook_verification) as risky. Widened the match to the whole ADD COLUMN statement so DEFAULT is detected regardless of which side of NOT NULL it's on. Regenerated dev.db from the corrected migration. --- backend/prisma/dev.db | Bin 806912 -> 778240 bytes .../migration.sql | 45 ++++-------------- backend/scripts/check-migrations.js | 13 +++-- 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/backend/prisma/dev.db b/backend/prisma/dev.db index b01a3f153bdf1ee7e4eb2a9d9e486e6cb2df74ee..e7010fdf0de30aaed93629e90b9ae7bedcfbcff3 100644 GIT binary patch delta 3716 zcma)-dvF!i9mjX?-TT_TH+#wJCLxd;0U^L4XV0E}oK{OOXbJ`p7&4Zsqx+0F)mlJ@ zmZ{15K*f$tx`nOwk1`Ars!`B5yfJ%UswOAzFe@Lx@&`)oEj0cMS6c=|Kqr%kd>OS#i(?d>x3)(i}~xG(B%%;@)UUD@SZ&2#wew@el!CVEO<%KBA{}4`_yg2y}H@3s8J#3rE}?|F{J* z#O~!YXr(1v4bHz%hFuShQOeG05}XVAfaOd zOo;DUzD+dEj(gfU;+8mQHyC1DbcxCl*E0l76+;910EH}y0MedXNC)(!9_pcE5#5Y` zIgPj}ZmTS1h%j2x#1XtBkhr?(=nxRwreo<^E^xz81D>ftnhep5fW$j1Jc=;>L4|k3 zi+W2OaV?L+4;|u%2-nuYAP@<#WjYvOOflFGAaHbB z2ei*FH0Vnd5;qPzc@STSCp-ICDYir%GDoasc_1?gT2r0Q#oUp1RlvuB|~}(e7GK zXwbfBu`!P$w0QTt#jF-E>XbNQkRdRA5j+!nATS9<;LrgMod;@RG&_O~N3+cM^faPB zo;SajAy!1)X^!Bgh3vozU5KdT(9;-l+rt6yolr;Ea!lk{@s9aC3J^a#pPSb5s53Pi zh2dH#)NK=(2BxEh3(!&h&80O>ZKa;3F;!82SKM9ta?|U@Lru@BcxukZ?&8Rj^pM!dSEs^+ z4SXJOI}Ghri^R$#)98`sDuIai)38}X+ce|7^d zl;zL<1g{zSxY=ZwvJp#>n6MG!NHgKZ?16qjXZ<0yI6c&m3zVkB(f{-x94j9@R(*O* z+BhYRp6_}3m;|S!1Mjb#BkvPh1YIb-tWFi*Egll|qQ1pP*5BDK&M8ev8{b#! z;+UAekv`;n4}JKB2cBO!vtXMCkCmTE=f|e<2i{vr*L(-kF!#Tm%PxIUP#LH z@nXAjkNi)0TmDRbUp}9^Gy8`4V)lvbg3QC2Ug?0;F;%NREXwz+8^~1_FBb2=rvBm; z()`rLBTdfMqz6(XgUQdHRB-adDrqov$5qMtql#8vwp!XMX36KWmGz60r_$+;13AQna+Fq&W9OKFRI`CtdwpO zYf0|y+;VYvyw;JddpkEb*?M(hg01xXYUgtG+PVD64VN`d3a!FXp;RxG3t!Bi$d2bn zvaOj%GVRiSNk~1PJ}MkdJx~;t6G~Ygm&?jK%1!cbp{;LEKWm@?37CcyP2z^PVZrg(lB5c6Q$^<#nMV42#7f<&3gM{-NBLkMfsg zznXhfQnR)6uGC*r*NHEPX<=Bst?7fNn@h(_tBXI0YgJ8(I-(WHL*G-rBZ6eqYa^~@RhHZeKzdwFe{7O+771YuRQFk2bw z=cTKE>kslT#QL9pE%%7^uha?)#N@ZH$ZFD6D=5ixugI&?yCR$n9agmc6UyA=mcuNN zBTB34Z4aECfnVEsNuP6jq$j5iD=^v1G;=2uAfjaLgi=f35xVi{gtA%GlJ_PQTRfg< eN0hB1OddR literal 806912 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 diff --git a/backend/prisma/migrations/20260824204600_add_event_outbox/migration.sql b/backend/prisma/migrations/20260824204600_add_event_outbox/migration.sql index d2799a33..35e61951 100644 --- a/backend/prisma/migrations/20260824204600_add_event_outbox/migration.sql +++ b/backend/prisma/migrations/20260824204600_add_event_outbox/migration.sql @@ -42,43 +42,14 @@ CREATE TABLE "EventOutbox" ( "relayedAt" DATETIME ); --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_BulkExportJob" ( - "id" TEXT NOT NULL PRIMARY KEY, - "status" TEXT NOT NULL DEFAULT 'pending', - "format" TEXT NOT NULL, - "generatedBy" TEXT NOT NULL, - "filters" TEXT NOT NULL, - "totalRows" INTEGER NOT NULL DEFAULT 0, - "processedRows" INTEGER NOT NULL DEFAULT 0, - "errorRows" INTEGER NOT NULL DEFAULT 0, - "artifactId" TEXT, - "errorMessage" TEXT, - "version" INTEGER NOT NULL DEFAULT 1, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "completedAt" DATETIME -); -INSERT INTO "new_BulkExportJob" ("artifactId", "completedAt", "createdAt", "errorMessage", "errorRows", "filters", "format", "generatedBy", "id", "processedRows", "status", "totalRows", "updatedAt") SELECT "artifactId", "completedAt", "createdAt", "errorMessage", "errorRows", "filters", "format", "generatedBy", "id", "processedRows", "status", "totalRows", "updatedAt" FROM "BulkExportJob"; -DROP TABLE "BulkExportJob"; -ALTER TABLE "new_BulkExportJob" RENAME TO "BulkExportJob"; -CREATE INDEX "BulkExportJob_status_idx" ON "BulkExportJob"("status"); -CREATE INDEX "BulkExportJob_createdAt_idx" ON "BulkExportJob"("createdAt"); -CREATE INDEX "BulkExportJob_generatedBy_idx" ON "BulkExportJob"("generatedBy"); -CREATE TABLE "new_VaultState" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT DEFAULT 1, - "totalAssets" TEXT NOT NULL, - "totalShares" TEXT NOT NULL, - "version" INTEGER NOT NULL DEFAULT 1, - "updatedAt" DATETIME NOT NULL -); -INSERT INTO "new_VaultState" ("id", "totalAssets", "totalShares", "updatedAt") SELECT "id", "totalAssets", "totalShares", "updatedAt" FROM "VaultState"; -DROP TABLE "VaultState"; -ALTER TABLE "new_VaultState" RENAME TO "VaultState"; -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; +-- AlterTable: add optimistic-concurrency version column. +-- A plain ADD COLUMN with a constant DEFAULT is fully supported by SQLite +-- and preserves the existing table (and its indexes) in place, unlike +-- Prisma's default drop-and-rebuild diff for SQLite. +ALTER TABLE "BulkExportJob" ADD COLUMN "version" INTEGER DEFAULT 1 NOT NULL; + +-- AlterTable: add optimistic-concurrency version column (see above). +ALTER TABLE "VaultState" ADD COLUMN "version" INTEGER DEFAULT 1 NOT NULL; -- CreateIndex CREATE INDEX "AdminConfigChange_configType_idx" ON "AdminConfigChange"("configType"); diff --git a/backend/scripts/check-migrations.js b/backend/scripts/check-migrations.js index 727bb567..a087d9b7 100644 --- a/backend/scripts/check-migrations.js +++ b/backend/scripts/check-migrations.js @@ -80,14 +80,17 @@ function checkFile(file) { // Safe opt-out: add `-- migration-safety: canary-safe` at the top of the // file to acknowledge the risk (e.g. zero-downtime step 2 of 3). // - // The regex looks for ADD COLUMN ... NOT NULL within a 300-char window and - // checks that DEFAULT does NOT appear in that window. - const addColumnNotNullPattern = /\badd\s+column\b[^;]{0,300}\bnot\s+null\b/gi; + // The regex captures the whole ADD COLUMN statement (up to 300 chars or the + // next semicolon) and checks that whole window for NOT NULL and DEFAULT, + // since either can come first — "NOT NULL DEFAULT x" (Prisma's own + // convention) and "DEFAULT x NOT NULL" are equally valid SQL. + const addColumnStatementPattern = /\badd\s+column\b[^;]{0,300}/gi; let match; - while ((match = addColumnNotNullPattern.exec(content)) !== null) { + while ((match = addColumnStatementPattern.exec(content)) !== null) { const snippet = match[0]; + const hasNotNull = /\bnot\s+null\b/i.test(snippet); const hasDefault = /\bdefault\b/i.test(snippet); - if (!hasDefault && !isCanarySafeOptIn) { + if (hasNotNull && !hasDefault && !isCanarySafeOptIn) { results.push({ file, severity: 'error', From 6e69c99a836d3d68fe065a209b59040ee3d33ae7 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:41:20 +0100 Subject: [PATCH 10/18] fix(backend): stop crashing test /health checks against a database no test provisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DatabaseManager's no-args constructor always built a real PostgresDatabasePool, falling back to a hardcoded postgres://postgres:postgres@localhost:5432/yieldvault connection string when DATABASE_URL was unset. No test suite runs a real Postgres at that address, so every isHealthy() check against the singleton `db` export failed for a reason entirely unrelated to what a given test was actually exercising — most visibly, /health always reported databasePrimary/databaseReplica as "down" and returned 503. Added NoopDatabasePool (resolves queries to empty results, reports healthy) and use it instead of a real Postgres pool when NODE_ENV=test and DATABASE_URL is unset — mirroring the existing fallback pattern this codebase already uses for Redis and the deposits rate limiter. Real production/dev behavior (DATABASE_URL set, or NODE_ENV=production) is unchanged. --- backend/src/database.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/backend/src/database.ts b/backend/src/database.ts index 23985de3..75057250 100644 --- a/backend/src/database.ts +++ b/backend/src/database.ts @@ -53,6 +53,28 @@ export class PostgresDatabasePool implements IDatabasePool { } } +/** + * No-op pool used in place of a real Postgres connection when running tests + * without a provisioned database (DATABASE_URL unset). Queries resolve to an + * empty result set instead of attempting — and failing — a real network + * connection, and health checks report healthy so `/health`/`/ready` reflect + * the actual state of the test environment rather than a database no test + * suite ever provisions. + */ +export class NoopDatabasePool implements IDatabasePool { + constructor(private readonly name: string) {} + + async query(_text: string, _params?: any[]): Promise<{ rows: T[] }> { + return { rows: [] }; + } + + async end(): Promise {} + + async isHealthy(): Promise { + return true; + } +} + /** * DatabaseManager handles routing queries between a primary write database * and a read-only replica, with automatic failover to primary for reads. @@ -66,6 +88,15 @@ export class DatabaseManager { if (primaryPool) { this.primaryPool = primaryPool; this.replicaPool = replicaPool || primaryPool; + } else if (process.env.NODE_ENV === 'test' && !process.env.DATABASE_URL) { + // No test suite provisions a real Postgres instance, and unlike Prisma + // (which falls back to its own sqlite dev.db) this raw pool had no + // test-mode fallback at all — every query attempted a real connection + // to the hardcoded dev default below, which no test environment + // actually runs. That made /health and anything touching `db` fail + // for a reason entirely unrelated to what the test was exercising. + this.primaryPool = new NoopDatabasePool('primary'); + this.replicaPool = this.primaryPool; } else { const primaryUrl = requireDatabaseUrl(); this.primaryPool = new PostgresDatabasePool(primaryUrl, 'primary'); From 97eaf54822f0a097007dd577ff478757b17942cf Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:41:31 +0100 Subject: [PATCH 11/18] fix(backend): sync API contract schemas with the actual response shapes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VaultSummaryResponseSchema declared totalAssets/totalShares as z.number() and had no field for sharePrice, but the real GET /api/v1/vault/summary handler (buildVaultSummaryResponseFromDb in index.ts) returns both as Decimal-backed strings — this API's established convention for money fields, to avoid floating-point precision loss (see TransactionItemSchema. amount in the same file, already z.string()) — and always includes sharePrice. HealthResponseSchema was similarly missing lastIndexedLedger, which GET /health has always returned. Both schemas are .strict(), so every real response failed validation: missing fields as "unrecognized keys", and totalAssets/totalShares as wrong-type errors. Fixed the schemas, the fixture payloads in issues711.test.ts that encoded the same stale shape, and the inline assertions in openApiContractTests.test.ts (numeric-finite check now Number()-coerces the string fields; the additionalProperties allowlist now includes sharePrice). Regenerated the committed schema-snapshots/*.json files to match. --- backend/schema-snapshots/get-_api_v1_vault_summary.json | 8 ++++++-- backend/schema-snapshots/get-_health.json | 4 ++++ backend/src/__tests__/issues711.test.ts | 6 ++++-- backend/src/__tests__/openApiContractTests.test.ts | 9 ++++++--- backend/src/apiContractSnapshots.ts | 9 +++++++-- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/backend/schema-snapshots/get-_api_v1_vault_summary.json b/backend/schema-snapshots/get-_api_v1_vault_summary.json index 090a1be9..f261b9c7 100644 --- a/backend/schema-snapshots/get-_api_v1_vault_summary.json +++ b/backend/schema-snapshots/get-_api_v1_vault_summary.json @@ -2,10 +2,13 @@ "type": "object", "properties": { "totalAssets": { - "type": "number" + "type": "string" }, "totalShares": { - "type": "number" + "type": "string" + }, + "sharePrice": { + "type": "string" }, "apy": { "type": "number" @@ -17,6 +20,7 @@ "required": [ "totalAssets", "totalShares", + "sharePrice", "apy", "timestamp" ], diff --git a/backend/schema-snapshots/get-_health.json b/backend/schema-snapshots/get-_health.json index f0dd64c6..31d8f6b4 100644 --- a/backend/schema-snapshots/get-_health.json +++ b/backend/schema-snapshots/get-_health.json @@ -13,6 +13,9 @@ "environment": { "type": "string" }, + "lastIndexedLedger": { + "type": "number" + }, "checks": { "type": "object", "properties": { @@ -117,6 +120,7 @@ "timestamp", "uptime", "environment", + "lastIndexedLedger", "checks", "sorobanCircuitBreaker" ], diff --git a/backend/src/__tests__/issues711.test.ts b/backend/src/__tests__/issues711.test.ts index 2700516a..6bdaa11d 100644 --- a/backend/src/__tests__/issues711.test.ts +++ b/backend/src/__tests__/issues711.test.ts @@ -52,6 +52,7 @@ describe('#711 API contract schema snapshots', () => { timestamp: new Date().toISOString(), uptime: 12.5, environment: 'test', + lastIndexedLedger: 0, checks: { api: 'up', cache: 'up', @@ -82,8 +83,9 @@ describe('#711 API contract schema snapshots', () => { it('validates a conforming vault summary payload', () => { const result = validateResponseAgainstSchema('GET /api/v1/vault/summary', { - totalAssets: 1000, - totalShares: 500, + totalAssets: '1000', + totalShares: '500', + sharePrice: '1.000000', apy: 8.5, timestamp: new Date().toISOString(), }); diff --git a/backend/src/__tests__/openApiContractTests.test.ts b/backend/src/__tests__/openApiContractTests.test.ts index f38946e7..a280e737 100644 --- a/backend/src/__tests__/openApiContractTests.test.ts +++ b/backend/src/__tests__/openApiContractTests.test.ts @@ -146,8 +146,11 @@ describe('OpenAPI contract: GET /api/v1/vault/summary', () => { it('numeric fields are finite numbers', async () => { const res = await request(app).get('/api/v1/vault/summary'); - expect(Number.isFinite(res.body.totalAssets)).toBe(true); - expect(Number.isFinite(res.body.totalShares)).toBe(true); + // totalAssets/totalShares/sharePrice are Decimal-backed strings (to avoid + // floating-point precision loss), not numbers — verify they parse cleanly. + expect(Number.isFinite(Number(res.body.totalAssets))).toBe(true); + expect(Number.isFinite(Number(res.body.totalShares))).toBe(true); + expect(Number.isFinite(Number(res.body.sharePrice))).toBe(true); expect(Number.isFinite(res.body.apy)).toBe(true); }); @@ -159,7 +162,7 @@ describe('OpenAPI contract: GET /api/v1/vault/summary', () => { it('does not contain unexpected extra fields (additionalProperties: false)', async () => { const res = await request(app).get('/api/v1/vault/summary'); - const allowedKeys = new Set(['totalAssets', 'totalShares', 'apy', 'timestamp']); + const allowedKeys = new Set(['totalAssets', 'totalShares', 'sharePrice', 'apy', 'timestamp']); for (const key of Object.keys(res.body)) { expect(allowedKeys.has(key)).toBe(true); } diff --git a/backend/src/apiContractSnapshots.ts b/backend/src/apiContractSnapshots.ts index 8918cc5e..e818138c 100644 --- a/backend/src/apiContractSnapshots.ts +++ b/backend/src/apiContractSnapshots.ts @@ -29,6 +29,7 @@ export const HealthResponseSchema = z timestamp: z.string(), uptime: z.number(), environment: z.string(), + lastIndexedLedger: z.number(), checks: z.object({ api: HealthCheckValueSchema, cache: HealthCheckValueSchema, @@ -61,8 +62,12 @@ export const ReadyResponseSchema = z export const VaultSummaryResponseSchema = z .object({ - totalAssets: z.number(), - totalShares: z.number(), + // Money fields are strings throughout this API (Decimal-backed, to avoid + // floating-point precision loss) — see buildVaultSummaryResponseFromDb in + // index.ts, which returns totalAssets/totalShares/sharePrice as strings. + totalAssets: z.string(), + totalShares: z.string(), + sharePrice: z.string(), apy: z.number(), timestamp: z.string(), }) From 964ca9b197340a6a7e8fa94d1369a1ba58255840 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:42:43 +0100 Subject: [PATCH 12/18] fix(backend): missing 404 handler, missing await, stale impersonation summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.ts had no catch-all 404 handler, so an unmatched route fell through to Express's default plain-text 404 instead of this API's JSON error format. Added one as the final middleware. - buildImpersonatedVaultState() built its "summary" field from the old buildVaultSummaryResponse() mock (always zeroed numbers) instead of the DB-backed buildVaultSummaryResponseFromDb() the real GET /api/v1/vault/summary route serves, so an admin impersonating a wallet saw stale/wrong balances instead of what that wallet's own dashboard shows. Removed the now-unused mock function. - transactionEndpoints.ts's GET /api/v1/transactions handler called the async buildTransactionsResponse() without awaiting it in the no-walletAddress branch, so res.json() serialized the pending Promise — which has no enumerable own properties — as "{}", and the DateRangeParseError catch below it could never actually catch anything thrown inside that promise. --- backend/src/index.ts | 28 +++++++++++++++++----------- backend/src/transactionEndpoints.ts | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index cbb48f4d..70e2c0c9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -240,15 +240,6 @@ void walletAliasMappingService.loadFromDatabase().catch((error) => { // Health check cache to track dependency status const cache = new NodeCache({ stdTTL: 30 }); -function buildVaultSummaryResponse() { - return { - totalAssets: 0, - totalShares: 0, - apy: 0, - timestamp: new Date().toISOString(), - }; -} - /** * Reads the vault summary from the VaultState table and the most recent * SharePriceSnapshot for the share price. Falls back to zeroed values when @@ -410,7 +401,10 @@ async function buildImpersonatedVaultState(wallet: string) { const normalizedWallet = normalizeWalletAddress(wallet); return { walletAddress: normalizedWallet, - summary: buildVaultSummaryResponse(), + // Impersonation must show an admin exactly what the wallet's own + // dashboard would show, so read the same DB-backed summary + // GET /api/v1/vault/summary serves. + summary: await buildVaultSummaryResponseFromDb(), transactions: await buildWalletTransactionsSnapshot(normalizedWallet), portfolioHoldings: buildPortfolioHoldingsResponse({ walletAddress: normalizedWallet }), vaultHistory: buildVaultHistoryResponse({}), @@ -5133,9 +5127,21 @@ app.post('/admin/withdrawals/recovery/sweep', validateApiKey, async (req: Reques // suites drive the sweeper explicitly. if (process.env.NODE_ENV !== 'test') { withdrawalRecoveryCoordinator.startSweeper(); - + // Initialize job governance from persisted dead-letter records void initializeJobGovernance(); } +// Catch-all 404 handler. Must be the last middleware registered so it only +// fires for requests no route above matched, instead of Express's default +// plain-text/HTML 404 page. +app.use((req: Request, res: Response) => { + res.status(404).json({ + error: 'Not Found', + status: 404, + path: req.originalUrl, + message: `Cannot ${req.method} ${req.originalUrl}`, + }); +}); + export default app; diff --git a/backend/src/transactionEndpoints.ts b/backend/src/transactionEndpoints.ts index eaf24235..ace2d71f 100644 --- a/backend/src/transactionEndpoints.ts +++ b/backend/src/transactionEndpoints.ts @@ -63,7 +63,7 @@ router.get('/', if (!walletAddress) { try { - const response = buildTransactionsResponse({ + const response = await buildTransactionsResponse({ limit: typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : undefined, cursor: typeof req.query.cursor === 'string' ? req.query.cursor : undefined, page: typeof req.query.page === 'string' ? parseInt(req.query.page, 10) : undefined, From b3855181d1ae25111a8efb86eb64a72dbf01acfd Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:43:11 +0100 Subject: [PATCH 13/18] fix(backend): replace invalid shared test wallet fixtures with real Ed25519 keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VALID_TEST_WALLET / SECOND_TEST_WALLET / THIRD_TEST_WALLET (and the identical MOCK_WALLET_ADDRESS in listEndpoints.ts, backing MOCK_TRANSACTIONS) were hand-typed, regex-shaped strings — correct character set, but not a real Ed25519 public key, so they failed StrKey.isValidEd25519PublicKey. Endpoints validated only via the looser @yieldvault/api-schemas regex (e.g. deposits) tolerated them; anything using the stricter check (walletAddressSchema in middleware/validate.ts — login, nonce, signed actions) rejected every test using these constants outright. Replaced all four with real keypair public keys, keeping listEndpoints.ts's copy in sync with setup.ts's since MOCK_TRANSACTIONS's wallet ownership is asserted against VALID_TEST_WALLET in several tests. fix(test-env): relax rate-limit/adaptive-throttle defaults broadly, restore true values where a test specifically exercises them setup.ts already relaxed RATE_LIMIT_ADMIN_MAX/RATE_LIMIT_WRITES_MAX for tests; extended the same treatment to RATE_LIMIT_AUTH_MAX, DEPOSITS_RATE_LIMIT_MAX, RATE_LIMIT_READS_MAX, SUMMARY_RATE_LIMIT_MAX, API_RATE_LIMIT_MAX_REQUESTS, and ADAPTIVE_THROTTLE_SCORE_THRESHOLD — any integration test file exercising real auth/read/write/deposit routes across several `it` blocks, or several validation-error responses, could exhaust these production-tight defaults or trip the adaptive-throttle block well before its assertions were about rate limiting at all. Two dedicated suites test these mechanisms at their real defaults and restore them locally (before importing the modules that read them once at load time): rateLimiter.test.ts's "depositsLimiter on vault deposit/ withdrawal routes" block and rateLimiter.auth_transfer.test.ts (both need the true DEPOSITS_RATE_LIMIT_MAX=10), and api.test.ts (has its own adaptive-throttle escalation test and already resets that middleware's state per test, so needs the true ADAPTIVE_THROTTLE_SCORE_THRESHOLD=6 restored rather than the relaxed global default). --- backend/src/__tests__/api.test.ts | 19 +++++++- .../rateLimiter.auth_transfer.test.ts | 8 ++++ backend/src/__tests__/rateLimiter.test.ts | 6 +++ backend/src/__tests__/setup.ts | 44 +++++++++++++++++-- backend/src/listEndpoints.ts | 5 ++- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/backend/src/__tests__/api.test.ts b/backend/src/__tests__/api.test.ts index 3fdb5c4b..9b5cc5cc 100644 --- a/backend/src/__tests__/api.test.ts +++ b/backend/src/__tests__/api.test.ts @@ -1,3 +1,11 @@ +// This file has its own dedicated adaptive-throttle escalation test and +// already resets that middleware's state before every test (below), so it +// needs the real threshold restored here — setup.ts globally raises +// ADAPTIVE_THROTTLE_SCORE_THRESHOLD for every other integration test file, +// most of which have no such per-test reset. Must run before importing +// '../index', which reads this value once at module-load time. +process.env.ADAPTIVE_THROTTLE_SCORE_THRESHOLD = '6'; + import request from 'supertest'; import app from '../index'; import { resetAdaptiveThrottleStateForTests } from '../middleware/adaptiveThrottle'; @@ -141,12 +149,21 @@ describe('Backend API', () => { it('should adaptively throttle repeated 4xx abuse patterns per IP', async () => { const clientIp = '198.51.100.33'; - for (let i = 0; i < 8; i++) { + // 404s are scored at half weight (0.5) vs. other 4xx (1) in + // scoreForStatus — deliberately less suspicious than e.g. repeated + // 401/403s — so crossing the default threshold of 6 needs at least 12 + // hits, not 8. + for (let i = 0; i < 20; i++) { await request(app) .get('/api/not-found-abuse') .set('x-forwarded-for', clientIp); } + // The middleware scores each response in a fire-and-forget res.on('finish') + // handler, not before the response is sent, so the priming loop above can + // outrun its own bookkeeping. Give it a beat to settle before relying on it. + await new Promise((resolve) => setTimeout(resolve, 100)); + const throttled = await request(app) .get('/api/not-found-abuse') .set('x-forwarded-for', clientIp); diff --git a/backend/src/__tests__/rateLimiter.auth_transfer.test.ts b/backend/src/__tests__/rateLimiter.auth_transfer.test.ts index 245fac21..bfef8e68 100644 --- a/backend/src/__tests__/rateLimiter.auth_transfer.test.ts +++ b/backend/src/__tests__/rateLimiter.auth_transfer.test.ts @@ -3,6 +3,14 @@ * Integration tests for request-level rate limiting on Auth and Transfer APIs (#887). */ +// This file tests the deposits tier's real, tight default (10 req/min), so it +// must restore that default here — setup.ts globally raises +// DEPOSITS_RATE_LIMIT_MAX for every other integration test file, since most +// of them just exercise deposit/withdrawal routes incidentally and aren't +// testing rate limiting at all. Must run before importing rateLimiter, whose +// singleton limiters read this value once at module-load time. +process.env.DEPOSITS_RATE_LIMIT_MAX = '10'; + import request from 'supertest'; import express, { Request, Response } from 'express'; import { authLimiter, depositsLimiter } from '../rateLimiter'; diff --git a/backend/src/__tests__/rateLimiter.test.ts b/backend/src/__tests__/rateLimiter.test.ts index d5d3fa27..ccf2d62e 100644 --- a/backend/src/__tests__/rateLimiter.test.ts +++ b/backend/src/__tests__/rateLimiter.test.ts @@ -291,6 +291,12 @@ describe('depositsLimiter on vault deposit/withdrawal routes', () => { let app: ReturnType; beforeEach(() => { + // This suite tests the real, tight default (10 req/min) — setup.ts + // globally raises DEPOSITS_RATE_LIMIT_MAX for every other integration + // test file, so it must be restored here before the fresh require picks + // it up (this describe block reads process.env fresh on every test via + // jest.resetModules()). + process.env.DEPOSITS_RATE_LIMIT_MAX = '10'; jest.resetModules(); // eslint-disable-next-line @typescript-eslint/no-require-imports const { depositsLimiter } = require('../rateLimiter'); diff --git a/backend/src/__tests__/setup.ts b/backend/src/__tests__/setup.ts index 9a0c6640..37b1a2d5 100644 --- a/backend/src/__tests__/setup.ts +++ b/backend/src/__tests__/setup.ts @@ -23,8 +23,37 @@ process.env.ALLOWLIST_ENABLED = process.env.ALLOWLIST_ENABLED || 'false'; // Disable rate limiting for RBAC/security tests by using very high limits. // The admin limiter defaults to 20 req/min which is too low for comprehensive // endpoint-level security tests that fire 100+ requests in rapid succession. +// Same reasoning extends to every other tier: any integration test file that +// exercises real auth/read/write/summary/deposit routes across several `it` +// blocks can easily exceed the tight production defaults (e.g. auth: 5/min, +// deposits: 10/min) within a single file's run, well before its assertions +// are about rate limiting at all. +// +// rateLimiter.auth_transfer.test.ts intentionally restores the true tight +// DEPOSITS_RATE_LIMIT_MAX default itself (before importing the limiter +// singletons) because it specifically tests deposits/withdrawals throttling +// behavior at that default — everything else is safe to relax globally since +// no other test relies on hitting these defaults via a real, non-harness +// route (rateLimiter.tiers.test.ts's own /auth,/write,/read,/admin routes are +// exempted from config.max entirely in NODE_ENV=test, see +// createInMemoryLimiter's testHarnessDefaults in rateLimiter.ts). process.env.RATE_LIMIT_ADMIN_MAX = process.env.RATE_LIMIT_ADMIN_MAX || '10000'; process.env.RATE_LIMIT_WRITES_MAX = process.env.RATE_LIMIT_WRITES_MAX || '10000'; +process.env.RATE_LIMIT_AUTH_MAX = process.env.RATE_LIMIT_AUTH_MAX || '10000'; +process.env.DEPOSITS_RATE_LIMIT_MAX = process.env.DEPOSITS_RATE_LIMIT_MAX || '10000'; +process.env.RATE_LIMIT_READS_MAX = process.env.RATE_LIMIT_READS_MAX || '10000'; +process.env.SUMMARY_RATE_LIMIT_MAX = process.env.SUMMARY_RATE_LIMIT_MAX || '10000'; +process.env.API_RATE_LIMIT_MAX_REQUESTS = process.env.API_RATE_LIMIT_MAX_REQUESTS || '10000'; + +// Adaptive throttle (src/middleware/adaptiveThrottle.ts) tracks a per-IP +// abuse score across every response in the app (any 4xx adds to it) and, +// once it crosses this threshold, blocks that IP with an escalating delay. +// Integration test files that deliberately exercise several validation-error +// paths (400s, 401s, 403s) accumulate this score exactly like production +// abuse would, then get 429s in later, unrelated test cases in the same +// file. Raise the threshold sky-high so the mechanism stays exercised by its +// own dedicated tests (if any) without tripping incidentally. +process.env.ADAPTIVE_THROTTLE_SCORE_THRESHOLD = process.env.ADAPTIVE_THROTTLE_SCORE_THRESHOLD || '1000000'; // CRITICAL: Patch PrismaClient constructor BEFORE any code tries to instantiate it // This intercepts the instrumentation hooks and prevents the panic @@ -52,14 +81,21 @@ const defaultAdminKey = process.env.ADMIN_API_KEY || 'test-admin-key'; registerApiKey(defaultAdminKey); registerApiKey('super-admin-test-key', { role: 'super-admin' }); -/** Valid 56-character Stellar test wallet (G + 55 base32 chars). */ +/** + * Valid Stellar test wallets — real Ed25519 public keys (correct StrKey + * checksum), not just regex-shaped strings. Endpoints validated via the + * character-class-only regex in @yieldvault/api-schemas tolerated the old + * hand-typed placeholders, but anything validated via the stricter + * StrKey.isValidEd25519PublicKey check (login, nonce, signed actions — + * walletAddressSchema in middleware/validate.ts) rejected them outright. + */ export const VALID_TEST_WALLET = - 'G234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQ'; + 'GBF2VOZSQLF2BZRW6NIDETV2L3I6GHXZUEFSIESELEAGATV2FYTOHOHI'; /** Second valid wallet for multi-wallet test scenarios. */ export const SECOND_TEST_WALLET = - 'G345678ABCDEFGHIJKLMNOPQRSTUVWXYZ345678ABCDEFGHIJKLMNOPQR'; + 'GAB5ZIMWXPZBBTCYQYICI4LJJW2F5PQ3Z3KJJB3SSY66B45KC2KSLIFD'; /** Third valid wallet for multi-wallet test scenarios. */ export const THIRD_TEST_WALLET = - 'G456789ABCDEFGHIJKLMNOPQRSTUVWXYZ456789ABCDEFGHIJKLMNOPQR'; + 'GASEUS2QGJBV5OX2J6AKIORVEA242PQLMRZXCJYN5CZR5G5A65ONOI3N'; diff --git a/backend/src/listEndpoints.ts b/backend/src/listEndpoints.ts index 406083bb..257007e2 100644 --- a/backend/src/listEndpoints.ts +++ b/backend/src/listEndpoints.ts @@ -158,7 +158,10 @@ export interface TransactionExportArtifact { // ─── Mock Data ────────────────────────────────────────────────────────────── -const MOCK_WALLET_ADDRESS = 'G234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQ'; +// Kept in sync with VALID_TEST_WALLET in src/__tests__/setup.ts — both must be +// a real, checksum-valid Ed25519 public key (not just regex-shaped) since +// some endpoints validate wallet addresses via StrKey.isValidEd25519PublicKey. +const MOCK_WALLET_ADDRESS = 'GBF2VOZSQLF2BZRW6NIDETV2L3I6GHXZUEFSIESELEAGATV2FYTOHOHI'; const MOCK_TRANSACTIONS: Transaction[] = Array.from({ length: 100 }, (_, i) => ({ id: `tx-${i + 1}`, From 5c0c35a68dd37310783372f7fb3edd95e0a51519 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:43:19 +0100 Subject: [PATCH 14/18] fix(backend): correct wrong auth header and invalid webhook event type in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit webhookInputValidation.test.ts sent its admin API key via a plain 'x-api-key' header on every request; validateApiKey only recognizes 'Authorization: ApiKey ' (as every other admin test file already does), so every request in this file was rejected with 401 regardless of what it was actually testing. rbac.test.ts registered webhooks with eventTypes: ['transaction.created'], which isn't one of the two valid event types (transaction.deposit.created, transaction.withdrawal.created — see WEBHOOK_EVENT_TYPES), so registration itself failed and later assertions dereferenced the (absent) created.body.endpoint.id. --- backend/src/__tests__/rbac.test.ts | 4 +- .../__tests__/webhookInputValidation.test.ts | 46 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/backend/src/__tests__/rbac.test.ts b/backend/src/__tests__/rbac.test.ts index b25742b2..948649d3 100644 --- a/backend/src/__tests__/rbac.test.ts +++ b/backend/src/__tests__/rbac.test.ts @@ -81,7 +81,7 @@ describe('RBAC', () => { const create = await request(app) .post('/admin/webhooks') .set('Authorization', `ApiKey ${adminKey}`) - .send({ url: 'https://example.com/hook', eventTypes: ['transaction.created'] }); + .send({ url: 'https://example.com/hook', eventTypes: ['transaction.deposit.created'] }); expect(create.status).toBe(201); const id = create.body.endpoint.id; @@ -99,7 +99,7 @@ describe('RBAC', () => { const create = await request(app) .post('/admin/webhooks') .set('Authorization', `ApiKey ${adminKey}`) - .send({ url: 'https://example.com/hook2', eventTypes: ['transaction.created'] }); + .send({ url: 'https://example.com/hook2', eventTypes: ['transaction.deposit.created'] }); const id = create.body.endpoint.id; diff --git a/backend/src/__tests__/webhookInputValidation.test.ts b/backend/src/__tests__/webhookInputValidation.test.ts index 15858701..7fd0ac19 100644 --- a/backend/src/__tests__/webhookInputValidation.test.ts +++ b/backend/src/__tests__/webhookInputValidation.test.ts @@ -259,7 +259,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 201 for a minimal valid payload', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook' }); expect(res.status).toBe(201); @@ -271,7 +271,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 201 for a full valid payload', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', eventTypes: ['transaction.deposit.created'], @@ -288,7 +288,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when url is missing', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: true }); expect(res.status).toBe(400); @@ -300,7 +300,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when url is not a valid URL', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'not-a-url' }); expect(res.status).toBe(400); @@ -310,7 +310,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 for an unsupported protocol', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'ftp://example.com/hook' }); expect(res.status).toBe(400); @@ -319,7 +319,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 for an unknown event type', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', eventTypes: ['transaction.unknown'] }); expect(res.status).toBe(400); @@ -329,7 +329,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when eventTypes is an empty array', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', eventTypes: [] }); expect(res.status).toBe(400); @@ -338,7 +338,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when secret is too short', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', secret: 'tiny' }); expect(res.status).toBe(400); @@ -348,7 +348,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 for unknown extra fields', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', injected: 'bad' }); expect(res.status).toBe(400); @@ -357,7 +357,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when enabled is not a boolean', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook', enabled: 'yes' }); expect(res.status).toBe(400); @@ -366,7 +366,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('returns 400 when url is a number', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 12345 }); expect(res.status).toBe(400); @@ -375,7 +375,7 @@ describe('POST /admin/webhooks – input validation via HTTP', () => { it('includes structured errors array in validation failure response', async () => { const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: 'bad-value' }); expect(res.status).toBe(400); @@ -396,7 +396,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { // Create a fresh endpoint to update in each test const res = await request(app) .post('/admin/webhooks') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'https://example.com/hook' }); endpointId = res.body.endpoint.id as string; @@ -405,7 +405,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 200 for a valid enabled update', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: false }); expect(res.status).toBe(200); @@ -415,7 +415,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 200 for a valid eventTypes update', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ eventTypes: ['transaction.deposit.created'] }); expect(res.status).toBe(200); @@ -425,7 +425,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for an empty body', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({}); expect(res.status).toBe(400); @@ -435,7 +435,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for an invalid url in update', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ url: 'javascript://xss' }); expect(res.status).toBe(400); @@ -444,7 +444,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for an empty eventTypes array in update', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ eventTypes: [] }); expect(res.status).toBe(400); @@ -453,7 +453,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for an unknown event type in update', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ eventTypes: ['transaction.bogus'] }); expect(res.status).toBe(400); @@ -462,7 +462,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for a secret shorter than 8 chars', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ secret: 'abc' }); expect(res.status).toBe(400); @@ -471,7 +471,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 400 for unknown extra fields', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: true, injected: 'bad' }); expect(res.status).toBe(400); @@ -480,7 +480,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('returns 404 for a non-existent endpoint id', async () => { const res = await request(app) .patch('/admin/webhooks/wh_nonexistent') - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: false }); expect(res.status).toBe(404); @@ -489,7 +489,7 @@ describe('PATCH /admin/webhooks/:id – input validation via HTTP', () => { it('includes structured errors in validation failure response', async () => { const res = await request(app) .patch(`/admin/webhooks/${endpointId}`) - .set('x-api-key', ADMIN_KEY) + .set('Authorization', `ApiKey ${ADMIN_KEY}`) .send({ enabled: 'not-a-bool' }); expect(res.status).toBe(400); From cf9454f4d546e2a9d13b4045e0d1a6915240111a Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:43:36 +0100 Subject: [PATCH 15/18] fix(backend): fix remaining isolated test bugs (bad fixture, missing mock, stale regex) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adminFeatures.test.ts: the deposit test's walletAddress had lowercase letters, which VaultDepositBodySchema's regex rejects (Stellar addresses are uppercase base32); and its webhook-delivery assertion raced the outbox's background poller with a fixed 30ms wait instead of draining it explicitly via eventOutboxService.processOutbox(). - withdrawalRecoveryEndpoint.test.ts's deposit test 500'd because referralService.recordDeposit() (called on every deposit) resolves the wallet's canonical identity and opens its own Prisma transaction for referral bookkeeping, neither of which this file's minimal Prisma mock supports — and neither of which this suite (withdrawal saga recovery) is testing. Mocked referralService directly instead of expanding the Prisma mock's surface for an unrelated subsystem. - jobGovernanceMetrics.test.ts's three metric-matching regexes anchored the closing brace immediately after job_name="...", but the actual Prometheus output has an additional app="yieldvault-backend" default label (register.setDefaultLabels) before the brace closes. Widened the regexes to tolerate any additional labels. --- backend/src/__tests__/adminFeatures.test.ts | 7 ++++++- backend/src/__tests__/jobGovernanceMetrics.test.ts | 6 +++--- .../src/__tests__/withdrawalRecoveryEndpoint.test.ts | 10 ++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/backend/src/__tests__/adminFeatures.test.ts b/backend/src/__tests__/adminFeatures.test.ts index 34447ffc..ff0343ab 100644 --- a/backend/src/__tests__/adminFeatures.test.ts +++ b/backend/src/__tests__/adminFeatures.test.ts @@ -5,6 +5,7 @@ import { resetWebhookState } from '../webhookDelivery'; import { resetAuditLogs } from '../auditLog'; import { resetTransactionBackfillJobsForTests } from '../transactionBackfill'; import { resetExportManifestsForTests } from '../exportManifest'; +import { eventOutboxService } from '../eventOutbox'; describe('Admin backend features', () => { const adminKey = 'admin-feature-test-key'; @@ -52,7 +53,7 @@ describe('Admin backend features', () => { .send({ amount: '125.00', asset: 'USDC', - walletAddress: 'GABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz234567', + walletAddress: 'G234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQ', }); if (typeof previousAllowlistEnabled === 'string') { @@ -63,7 +64,11 @@ describe('Admin backend features', () => { expect(depositResponse.status).toBe(201); + // The deposit handler writes the event to the outbox fire-and-forget, so + // wait for that write to land before explicitly draining it — the + // background poller isn't guaranteed to run within a fixed test delay. await new Promise((resolve) => setTimeout(resolve, 30)); + await eventOutboxService.processOutbox(10); const deliveriesResponse = await request(app) .get('/admin/webhooks/deliveries') diff --git a/backend/src/__tests__/jobGovernanceMetrics.test.ts b/backend/src/__tests__/jobGovernanceMetrics.test.ts index 414411ec..06e72188 100644 --- a/backend/src/__tests__/jobGovernanceMetrics.test.ts +++ b/backend/src/__tests__/jobGovernanceMetrics.test.ts @@ -24,7 +24,7 @@ describe('syncJobGovernanceMetrics', () => { syncJobGovernanceMetrics(); const metricsText = await register.metrics(); - expect(metricsText).toMatch(/job_dead_letter_count\{job_name="priceRefresh"\}\s+1/); + expect(metricsText).toMatch(/job_dead_letter_count\{[^}]*job_name="priceRefresh"[^}]*\}\s+1/); }); it('sets job_health_status=0 for jobs with recurring failures', async () => { @@ -44,7 +44,7 @@ describe('syncJobGovernanceMetrics', () => { syncJobGovernanceMetrics(); const metricsText = await register.metrics(); - expect(metricsText).toMatch(/job_health_status\{job_name="positionReconciliation"\}\s+0/); + expect(metricsText).toMatch(/job_health_status\{[^}]*job_name="positionReconciliation"[^}]*\}\s+0/); }); it('sets job_health_status=1 for jobs below recurring failure threshold', async () => { @@ -62,6 +62,6 @@ describe('syncJobGovernanceMetrics', () => { // 1 failure < threshold of 3, so health should still be up (1) const metricsText = await register.metrics(); - expect(metricsText).toMatch(/job_health_status\{job_name="priceRefresh"\}\s+1/); + expect(metricsText).toMatch(/job_health_status\{[^}]*job_name="priceRefresh"[^}]*\}\s+1/); }); }); diff --git a/backend/src/__tests__/withdrawalRecoveryEndpoint.test.ts b/backend/src/__tests__/withdrawalRecoveryEndpoint.test.ts index 3ad8386f..fd878796 100644 --- a/backend/src/__tests__/withdrawalRecoveryEndpoint.test.ts +++ b/backend/src/__tests__/withdrawalRecoveryEndpoint.test.ts @@ -24,6 +24,16 @@ jest.mock('../prismaClient', () => ({ disconnectPrismaClient: jest.fn().mockResolvedValue(undefined), })); +// Deposits call referralService.recordDeposit(), which resolves the wallet's +// canonical identity and opens its own Prisma transaction for referral +// bookkeeping — neither of which this file's minimal Prisma mock supports, +// and neither of which this suite (withdrawal saga recovery) is testing. +jest.mock('../referralService', () => ({ + referralService: { + recordDeposit: jest.fn().mockResolvedValue(undefined), + }, +})); + // The signed-action middleware pulls in the wallet nonce service, which is not // exercised here (signature enforcement is off in tests). jest.mock('../walletNonce', () => ({ From 1507fc9b9fa58b79ebce1733e1e54b97b76590f2 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:47:10 +0100 Subject: [PATCH 16/18] fix(ci): missing shared-schemas build step, unlocked cargo-audit install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend-governance.yml never installed/built packages/api-schemas before running backend commands, so every step that imports @yieldvault/api-schemas (most of the backend, transitively via middleware/validate.ts) failed with "Cannot find module 'zod'" — that package's own dependency, never installed because its package.json/package-lock.json were never touched. Added the same "Build shared API schemas" step the other two workflows (Monorepo CI, Dependency Vulnerability Audit) already have. The cargo-audit 0.22.1 pin (previous commit) still failed to install: an unlocked `cargo install` re-resolves that crate's dependency tree against whatever's newest on crates.io today, and several of those transitive deps have since raised their own MSRV past the workspace's pinned rustc 1.85.0 — the exact error message suggested the fix. Added --locked to install against the dependency versions actually validated for 0.22.1's release, instead of the latest crates.io allows. --- .github/workflows/backend-governance.yml | 4 ++++ .github/workflows/rust-security.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backend-governance.yml b/.github/workflows/backend-governance.yml index cee52c55..58567cd6 100644 --- a/.github/workflows/backend-governance.yml +++ b/.github/workflows/backend-governance.yml @@ -49,6 +49,10 @@ jobs: cache: npm cache-dependency-path: backend/package-lock.json + - name: Build shared API schemas + run: npm ci && npm run build + working-directory: packages/api-schemas + - name: Install dependencies run: npm ci diff --git a/.github/workflows/rust-security.yml b/.github/workflows/rust-security.yml index 29942321..f554eb59 100644 --- a/.github/workflows/rust-security.yml +++ b/.github/workflows/rust-security.yml @@ -49,7 +49,7 @@ jobs: working-directory: frontend - name: Install cargo-audit - run: cargo install cargo-audit --version 0.22.1 + run: cargo install cargo-audit --version 0.22.1 --locked - name: Run cargo audit run: | From f2aa34baf4db22de55a98699ccf95d11c12c0eb3 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 00:54:25 +0100 Subject: [PATCH 17/18] fix(backend): correct wrong table name in apySnapshot's raw APY read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetchCurrentApy() queried a table named "vault_metrics", but the actual raw-SQL migration (migrations/001_initial_storage.sql) creates "vault_metrics_snapshots" — every other query in this codebase referencing that table already uses the correct name (see scripts/postgres-migrations.js's drift check, which explicitly checks for "vault_metrics_snapshots"). Against a real Postgres database (e.g. the backend-governance CI job), this raised "relation vault_metrics does not exist" and crashed the entire APY snapshot job and every endpoint that depends on it, even though the query's own result is discarded (this function returns a synthetic value pending a real Soroban RPC integration). --- backend/src/apySnapshot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/apySnapshot.ts b/backend/src/apySnapshot.ts index ad7a5e31..87f972c4 100644 --- a/backend/src/apySnapshot.ts +++ b/backend/src/apySnapshot.ts @@ -77,7 +77,7 @@ function datePlusDays(date: string, days: number): string { */ async function fetchCurrentApy(): Promise { // Mock: query the DB / RPC for the live APY gauge - await db.query('SELECT apy FROM vault_metrics ORDER BY recorded_at DESC LIMIT 1'); + await db.query('SELECT apy FROM vault_metrics_snapshots ORDER BY recorded_at DESC LIMIT 1'); // Return a realistic mock value between 6% and 12% const base = 8.5; const jitter = (Math.random() - 0.5) * 0.4; From 1b91debd51b664be3b19d540f7dc9fa4b5b9db03 Mon Sep 17 00:00:00 2001 From: Awosdot Date: Tue, 25 Aug 2026 01:00:12 +0100 Subject: [PATCH 18/18] fix(ci): install cargo-fuzz under the nightly toolchain, not the pinned stable one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Set up Rust nightly (cargo-fuzz)" step only installs the nightly toolchain as an available option — it doesn't change the active default, which stays pinned to rust-toolchain.toml's 1.85.0 (rustup directory overrides don't apply just because a toolchain was installed). The very next step, "Run vault share-price fuzz", already accounts for this by invoking `cargo +nightly fuzz run ...` explicitly, but "Install cargo-fuzz" ran plain `cargo install`, resolving under 1.85.0 and failing outright: cargo-fuzz 0.13.2's own locked dependencies (cargo-platform, cargo_metadata) require rustc 1.86–1.91. Added the same `+nightly` override to the install step so it builds under the toolchain it's actually meant for. --- .github/workflows/rust-security.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-security.yml b/.github/workflows/rust-security.yml index f554eb59..da46133f 100644 --- a/.github/workflows/rust-security.yml +++ b/.github/workflows/rust-security.yml @@ -96,7 +96,7 @@ jobs: uses: dtolnay/rust-toolchain@nightly - name: Install cargo-fuzz - run: cargo install cargo-fuzz --locked + run: cargo +nightly install cargo-fuzz --locked - name: Run vault share-price fuzz (60s) run: |