Skip to content

More resiliance and observability to EMMA#57

Merged
GelatoGenesis merged 4 commits intomainfrom
b-17736642073
Mar 16, 2026
Merged

More resiliance and observability to EMMA#57
GelatoGenesis merged 4 commits intomainfrom
b-17736642073

Conversation

@GelatoGenesis
Copy link
Copy Markdown
Collaborator

@GelatoGenesis GelatoGenesis commented Mar 16, 2026

Summary by CodeRabbit

  • New Features

    • Retry endpoint for token-pool downloads; start/retry workflows for stalled downloads
    • Expanded API responses with timestamps, stages, progress, attempt/failure counts, error reasons, stale/retryable flags
    • Progress tracking and requeue support for token-pool download processing
  • Database

    • Migration to add observability columns (timestamps, status/stage, progress, retry/failure metadata)
  • Tests

    • Comprehensive tests covering retry flows, stale detection, and error/history handling

Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Introduces observability and retry for token pool downloads: DB schema adds observability columns, new stage enum, repository methods for progress/failures, service retry and staleness logic, async downloader requeueing, API retry endpoint, and related tests and utility adjustments.

Changes

Cohort / File(s) Summary
Database Migration
migrations/20260316120000-token-pool-download-observability.js, migrations/sqls/20260316120000-token-pool-download-observability-up.sql, migrations/sqls/20260316120000-token-pool-download-observability-down.sql
Adds 13 observability/retry-related columns to token_pool_download (timestamps, progress, stage, error/failure tracking, attempt/failure counters). Migration script reads and runs up/down SQL files.
Domain Model & Enum
src/repository/token-pool-download/token-pool-download-stage.ts, src/repository/token-pool-download/token-pool-download.entity.ts
Adds TokenPoolDownloadStage enum and extends TokenPoolDownloadEntity with new optional fields matching DB columns (timestamps, stage, progress, error/failure metadata, counters).
Repository Layer
src/repository/token-pool-download/token-pool-download.repository.ts
Centralizes selectColumns; expands save/claim to include new columns; adds getByTokenPoolId, requeue, updateProgress, recordFailureHistory, changeStatusToCompleted, changeStatusToError; updates selects for consistency.
Service & Downloader
src/api/token-pool-download/token-pool-download.service.ts, src/token-pool/token-pool-downloader.service.ts, src/token-pool/token-pool-async-downloader.ts
Adds retry flow, staleness detection, failure-history recording, create-operation lookup, and async downloader invocation; downloader gains requeue, richer progress/stage tracking, executionPath handling, transaction-scoped claim and enhanced error handling; async-downloader chooses prepare vs requeue by state.
API Layer
src/api/token-pool-download/token-pool-download.controller.ts, src/api/token-pool-download/model/token-pool-download-response-api.model.ts
Adds POST retry endpoint and expands response model with rawStatus, timestamps, stage, progress, attempt/failure counts, failure/error details, stale and retryable flags.
Tests & Utilities
src/api/token-pool-download/token-pool-download.service.spec.ts, src/app.utils.ts, src/app.utils.spec.ts
Adds unit tests for stale detection, legacy rows, retry behavior, and app.utils; changes bigInt2Number signature/behavior and improves stringifyError formatting tests.
Package
package.json
Bumps @6529-collections/allowlist-lib from 0.0.130 → 0.0.131.

Sequence Diagram

sequenceDiagram
    actor User
    participant Controller as TokenPoolDownloadController
    participant Service as TokenPoolDownloadService
    participant Repo as TokenPoolDownloadRepository
    participant OpRepo as AllowlistOperationRepository
    participant Downloader as TokenPoolAsyncDownloader
    participant DB as Database

    User->>Controller: POST /allowlists/:allowlistId/token-pool-downloads/token-pool/:tokenPoolId/retry
    Controller->>Service: retry(allowlistId, tokenPoolId)

    Service->>Repo: getByTokenPoolId(allowlistId, tokenPoolId)
    Repo->>DB: SELECT token_pool_download row
    DB-->>Repo: row
    Repo-->>Service: entity

    alt entity exists and retryable
        Service->>Repo: recordFailureHistory(tokenPoolId, reason)
        Repo->>DB: UPDATE failure_count, last_failure_at, last_failure_reason
        DB-->>Repo: OK

        Service->>OpRepo: getCreateTokenPoolOperation(allowlistId, tokenPoolId)
        OpRepo->>DB: SELECT operation
        DB-->>OpRepo: operation data
        OpRepo-->>Service: params

        Service->>Downloader: start(config, state) or start async with params
        Downloader->>Repo: updateProgress / changeStatus...
        Repo->>DB: UPDATE progress, stage, timestamps
        DB-->>Repo: OK
        Downloader-->>Service: { entity, state }

        Service->>Service: entityToApiModel(entity) (compute stale, retryable, rawStatus)
        Service-->>Controller: TokenPoolDownloadResponseApiModel
    else missing or not retryable
        Service-->>Controller: throw NotFoundException or BadRequestException
    end

    Controller-->>User: 200 OK or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A little hop to retry and mend,

Timestamps and stages to track every trend,
Failures logged, progress kept near,
Stale ones retried with a hopeful cheer,
Observability carrots for troubles to end.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'More resiliance and observability to EMMA' is directly related to the main changes in the PR, which add extensive observability features (new columns, tracking fields, error handling) and retry/resilience logic to the token pool download system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch b-17736642073
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
migrations/20260316120000-token-pool-download-observability.js (1)

22-23: Avoid logging full SQL migration content.

Logging full SQL payloads adds noise and can expose internal schema details in logs; this is safe to remove.

Proposed change
-      console.log('received data: ' + data);

Also applies to: 37-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/20260316120000-token-pool-download-observability.js` around lines
22 - 23, Remove the full SQL content being printed in
migrations/20260316120000-token-pool-download-observability.js by deleting the
console.log('received data: ' + data) statement (and the similar logging at the
later occurrence around lines 37-38); if visibility is needed, replace with a
minimal, non-sensitive message (e.g., indicate migration step name or "received
data (truncated)" and/or a byte/row count) rather than printing the entire SQL
payload to logs.
src/api/token-pool-download/token-pool-download.service.ts (2)

259-273: Double JSON.parse on the same operation.params.

The params are parsed once inside find() (line 261) to match the tokenPoolId, and then parsed again at line 272 for the return value. Consider caching the parsed result.

♻️ Proposed refactor to avoid double parsing
-    const operation = operations.find((candidate) => {
+    let parsedParams: {
+      id?: string;
+      tokenIds?: string;
+      contract: string;
+      blockNo: number;
+      consolidateBlockNo?: number | null;
+    } | null = null;
+    const operation = operations.find((candidate) => {
       try {
-        return JSON.parse(candidate.params)?.id === tokenPoolId;
+        const params = JSON.parse(candidate.params);
+        if (params?.id === tokenPoolId) {
+          parsedParams = params;
+          return true;
+        }
+        return false;
       } catch (e) {
         return false;
       }
     });
     if (!operation) {
       throw new NotFoundException(
         `Create token pool operation for token pool ${tokenPoolId} does not exist`,
       );
     }
     return {
-      params: JSON.parse(operation.params),
+      params: parsedParams!,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/token-pool-download/token-pool-download.service.ts` around lines 259
- 273, The code double-parses operation.params when locating the matching
operation and again when returning params; update the logic around
operations.find (the callback that currently JSON.parse(candidate.params) to
compare to tokenPoolId) to parse once and cache the parsed value (e.g., store
parsed object on the matched operation variable or a local map) so that after
you check for !operation and throw the NotFoundException you return the cached
parsed params instead of calling JSON.parse(operation.params) a second time;
ensure the comparison still guards parse errors (catching exceptions) as before.

199-203: Consider logging the JSON parse failure for observability.

The graceful fallback to { raw: progress } is appropriate, but silently swallowing the exception makes debugging harder if progress data is consistently malformed.

📝 Proposed improvement to add logging
     try {
       return JSON.parse(progress);
     } catch (e) {
+      // Log at debug level - malformed progress JSON shouldn't alarm but is useful for debugging
+      this.logger?.debug?.(`Failed to parse progress JSON: ${e.message}`);
       return { raw: progress };
     }

Note: You'd need to inject a Logger instance if not already available.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/token-pool-download/token-pool-download.service.ts` around lines 199
- 203, The try/catch that calls JSON.parse(progress) should log the parse
failure for observability instead of silently swallowing it: inject or obtain a
Logger instance in the TokenPoolDownloadService (or the service class containing
the JSON.parse(progress) call), and inside the catch block call
logger.warn/error with a clear message including the raw progress value and the
caught exception (e.g., logger.error('Failed to parse progress JSON', {
progress, err })). Keep the existing fallback return { raw: progress } after
logging.
migrations/sqls/20260316120000-token-pool-download-observability-up.sql (1)

1-14: LGTM! Schema extension aligns with observability requirements.

The column types are appropriate:

  • BIGINT for millisecond timestamps
  • INT NOT NULL DEFAULT 0 for counters to avoid null handling
  • LONGTEXT for potentially large error messages and progress JSON
  • VARCHAR(64) for bounded stage enum values

Optional consideration: If you anticipate frequent queries filtering by stage (e.g., finding all jobs in a particular stage for monitoring dashboards), consider adding an index:

CREATE INDEX idx_token_pool_download_stage ON token_pool_download(stage);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/sqls/20260316120000-token-pool-download-observability-up.sql`
around lines 1 - 14, The migration adds a new nullable VARCHAR(64) column stage
to token_pool_download but doesn't add an index; if you expect frequent
filtering by stage, add a separate migration step to create an index on
token_pool_download(stage) (e.g., run a CREATE INDEX
idx_token_pool_download_stage ON token_pool_download(stage) in this migration or
an immediately subsequent one) to improve query performance and monitoring
dashboards.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/token-pool-download/token-pool-download.service.ts`:
- Around line 147-152: After calling
tokenPoolDownloadRepository.getByTokenPoolId the returned variable refreshed may
be null; update the code that calls getByTokenPoolId (the logic surrounding
tokenPoolAsyncDownloader.start and the subsequent return
this.entityToApiModel(refreshed)) to explicitly handle a null result—either
throw a descriptive error (e.g., NotFound) or return an appropriate null/404
response—before calling entityToApiModel so entityToApiModel is never invoked
with null; reference tokenPoolDownloadRepository.getByTokenPoolId and
entityToApiModel when implementing the guard.

In `@src/app.utils.ts`:
- Around line 5-11: The bigInt2Number helper currently returns Number(value)
which can yield NaN or Infinity and leak into downstream logic; update
bigInt2Number to coerce only after the null/undefined guard, then compute const
n = Number(value) and return undefined unless Number.isFinite(n) is true (i.e.,
return n only for finite numeric results); reference the function name
bigInt2Number when making this change so callers continue to receive undefined
for non-finite coercions.

---

Nitpick comments:
In `@migrations/20260316120000-token-pool-download-observability.js`:
- Around line 22-23: Remove the full SQL content being printed in
migrations/20260316120000-token-pool-download-observability.js by deleting the
console.log('received data: ' + data) statement (and the similar logging at the
later occurrence around lines 37-38); if visibility is needed, replace with a
minimal, non-sensitive message (e.g., indicate migration step name or "received
data (truncated)" and/or a byte/row count) rather than printing the entire SQL
payload to logs.

In `@migrations/sqls/20260316120000-token-pool-download-observability-up.sql`:
- Around line 1-14: The migration adds a new nullable VARCHAR(64) column stage
to token_pool_download but doesn't add an index; if you expect frequent
filtering by stage, add a separate migration step to create an index on
token_pool_download(stage) (e.g., run a CREATE INDEX
idx_token_pool_download_stage ON token_pool_download(stage) in this migration or
an immediately subsequent one) to improve query performance and monitoring
dashboards.

In `@src/api/token-pool-download/token-pool-download.service.ts`:
- Around line 259-273: The code double-parses operation.params when locating the
matching operation and again when returning params; update the logic around
operations.find (the callback that currently JSON.parse(candidate.params) to
compare to tokenPoolId) to parse once and cache the parsed value (e.g., store
parsed object on the matched operation variable or a local map) so that after
you check for !operation and throw the NotFoundException you return the cached
parsed params instead of calling JSON.parse(operation.params) a second time;
ensure the comparison still guards parse errors (catching exceptions) as before.
- Around line 199-203: The try/catch that calls JSON.parse(progress) should log
the parse failure for observability instead of silently swallowing it: inject or
obtain a Logger instance in the TokenPoolDownloadService (or the service class
containing the JSON.parse(progress) call), and inside the catch block call
logger.warn/error with a clear message including the raw progress value and the
caught exception (e.g., logger.error('Failed to parse progress JSON', {
progress, err })). Keep the existing fallback return { raw: progress } after
logging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fed74a62-0531-4bc5-9672-18350cdf60ba

📥 Commits

Reviewing files that changed from the base of the PR and between 4601d1a and 8c2f586.

📒 Files selected for processing (13)
  • migrations/20260316120000-token-pool-download-observability.js
  • migrations/sqls/20260316120000-token-pool-download-observability-down.sql
  • migrations/sqls/20260316120000-token-pool-download-observability-up.sql
  • src/api/token-pool-download/model/token-pool-download-response-api.model.ts
  • src/api/token-pool-download/token-pool-download.controller.ts
  • src/api/token-pool-download/token-pool-download.service.spec.ts
  • src/api/token-pool-download/token-pool-download.service.ts
  • src/app.utils.ts
  • src/repository/token-pool-download/token-pool-download-stage.ts
  • src/repository/token-pool-download/token-pool-download.entity.ts
  • src/repository/token-pool-download/token-pool-download.repository.ts
  • src/token-pool/token-pool-async-downloader.ts
  • src/token-pool/token-pool-downloader.service.ts

Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
4.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/api/token-pool-download/token-pool-download.service.spec.ts (2)

184-241: Add assertion that recordFailureHistory is not called for FAILED status.

When the entity status is FAILED, the service skips recording failure history (line 121-126 in service). The test should verify this behavior to catch regressions.

💚 Suggested enhancement
     ).rejects.toThrow(
       'Token pool download with ID pool-1 no longer exists after retry',
     );
+    expect(tokenPoolDownloadRepository.recordFailureHistory).not.toHaveBeenCalled();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/token-pool-download/token-pool-download.service.spec.ts` around lines
184 - 241, Add an assertion in the test that
tokenPoolDownloadRepository.recordFailureHistory is not called when the
entity.status is TokenPoolDownloadStatus.FAILED: after calling
service.retry(...)
expect(tokenPoolDownloadRepository.recordFailureHistory).not.toHaveBeenCalled().
This references the mocked tokenPoolDownloadRepository.recordFailureHistory and
the TokenPoolDownloadService.retry behavior for FAILED status to ensure
recording failure history is skipped.

160-161: Strengthen assertion to verify recordFailureHistory arguments.

The test only verifies that recordFailureHistory was called, but doesn't validate the arguments. This could miss bugs where the wrong tokenPoolId or failureReason is passed.

💚 Suggested enhancement
-    expect(tokenPoolDownloadRepository.recordFailureHistory).toHaveBeenCalled();
+    expect(tokenPoolDownloadRepository.recordFailureHistory).toHaveBeenCalledWith({
+      tokenPoolId: 'pool-1',
+      failureReason: expect.stringContaining('has not reported progress for more than 20 minutes'),
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/token-pool-download/token-pool-download.service.spec.ts` around lines
160 - 161, The test currently only checks that
tokenPoolDownloadRepository.recordFailureHistory was called; update the
assertion to verify the actual arguments passed by replacing or adding an
expectation that tokenPoolDownloadRepository.recordFailureHistory was called
with the correct tokenPoolId and failure details (e.g., use the test's
tokenPool.id and the expected failureReason or pattern) — target the
tokenPoolDownloadRepository.recordFailureHistory call and assert e.g.
.toHaveBeenCalledWith(tokenPool.id, expect.objectContaining({ failureReason:
expectedFailureReason })) so the test fails if the wrong id or reason is sent;
you can still keep the existing expectation verifying
tokenPoolAsyncDownloader.start was called with the expected payload.
src/api/token-pool-download/token-pool-download.service.ts (1)

264-278: Avoid double JSON.parse on the same string.

The params string is parsed once in the find callback (line 266) to locate the operation, then parsed again at line 277. This is redundant.

♻️ Suggested refactor to parse once
-    const operation = operations.find((candidate) => {
+    let parsedParams: Record<string, unknown> | undefined;
+    const operation = operations.find((candidate) => {
       try {
-        return JSON.parse(candidate.params)?.id === tokenPoolId;
+        const parsed = JSON.parse(candidate.params);
+        if (parsed?.id === tokenPoolId) {
+          parsedParams = parsed;
+          return true;
+        }
+        return false;
       } catch (e) {
         return false;
       }
     });
-    if (!operation) {
+    if (!operation || !parsedParams) {
       throw new NotFoundException(
         `Create token pool operation for token pool ${tokenPoolId} does not exist`,
       );
     }
     return {
-      params: JSON.parse(operation.params),
+      params: parsedParams as {
+        tokenIds?: string;
+        contract: string;
+        blockNo: number;
+        consolidateBlockNo?: number | null;
+      },
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/token-pool-download/token-pool-download.service.ts` around lines 264
- 278, The code redundantly calls JSON.parse on operation.params twice; change
the operations.find usage to parse once and preserve the parsed result for
reuse—e.g., when using operations.find((candidate) => { const parsed =
safeParse(candidate.params); if (parsed?.id === tokenPoolId) {
candidate.__parsedParams = parsed; return true } return false }), then after the
NotFoundException check return { params: operation.__parsedParams } (or
alternatively have find return an object like { operation, parsedParams }) so
you only parse operation.params a single time; update references to use the
stored parsed object instead of calling JSON.parse again (referencing
operations, operation, tokenPoolId).
src/app.utils.spec.ts (1)

17-40: Add resilience tests for non-serializable error payloads.

Given this utility is used in catch paths, add cases ensuring stringifyError does not fail on problematic inputs (e.g., BigInt in metadata, circular cause graphs, Symbol values).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app.utils.spec.ts` around lines 17 - 40, Add resilience tests and harden
stringifyError to handle non-serializable metadata and cyclic/odd causes: write
unit tests feeding stringifyError with metadata containing BigInt, Symbol, and a
cause graph with a cycle and ensure it returns a string (not throw); then modify
stringifyError to (1) safely serialize metadata by converting BigInt to string
and Symbols to their description or toString, (2) guard against circular
references when traversing causes using a WeakSet/Set of seen objects to break
cycles, and (3) wrap metadata and cause processing in try/catch and fall back to
a safe placeholder like "<unserializable>" for values that still throw — target
the stringifyError function and any helper used to format metadata/cause.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 28: The package.json dependency "@6529-collections/allowlist-lib" is set
to "0.0.131" but package-lock.json still references "0.0.124"; update the
lockfile to match by either running npm install (or npm ci after installing) to
regenerate package-lock.json so it reflects 0.0.131, or if 0.0.131 isn't
available, revert the version in package.json back to "0.0.124" so both files
are synchronized.

In `@src/app.utils.ts`:
- Around line 71-73: The Prettier violation is caused by the multiline ternary
that handles error.cause; locate the code that calls stringifyError and
references error.cause (the ternary producing `? \` Cause:
${stringifyError(error.cause)}\` : ''`) and reformat it to satisfy
Prettier—either collapse the ternary onto a single line or replace it with a
short if/else or concatenation so the expression is formatted consistently
(preserve the same output string but ensure the expression around error.cause
and stringifyError is on one line).
- Around line 57-86: The current stringifyError implementation uses raw
JSON.stringify on unknown values (metadata entries, message fallback, and nested
cause) which can throw or return non-strings; update stringifyError to always
return a string by wrapping all JSON.stringify calls in try/catch and falling
back to String(value) when JSON fails or returns undefined, and add simple
circular-reference protection for recursive stringifyError calls (e.g., accept
an optional WeakSet seen param, add objects to it and detect cycles to return
"[Circular]"). Ensure all places that build code, metadata, message and cause
use the safe stringify helper so stringifyError never throws and always returns
a plain string.

---

Nitpick comments:
In `@src/api/token-pool-download/token-pool-download.service.spec.ts`:
- Around line 184-241: Add an assertion in the test that
tokenPoolDownloadRepository.recordFailureHistory is not called when the
entity.status is TokenPoolDownloadStatus.FAILED: after calling
service.retry(...)
expect(tokenPoolDownloadRepository.recordFailureHistory).not.toHaveBeenCalled().
This references the mocked tokenPoolDownloadRepository.recordFailureHistory and
the TokenPoolDownloadService.retry behavior for FAILED status to ensure
recording failure history is skipped.
- Around line 160-161: The test currently only checks that
tokenPoolDownloadRepository.recordFailureHistory was called; update the
assertion to verify the actual arguments passed by replacing or adding an
expectation that tokenPoolDownloadRepository.recordFailureHistory was called
with the correct tokenPoolId and failure details (e.g., use the test's
tokenPool.id and the expected failureReason or pattern) — target the
tokenPoolDownloadRepository.recordFailureHistory call and assert e.g.
.toHaveBeenCalledWith(tokenPool.id, expect.objectContaining({ failureReason:
expectedFailureReason })) so the test fails if the wrong id or reason is sent;
you can still keep the existing expectation verifying
tokenPoolAsyncDownloader.start was called with the expected payload.

In `@src/api/token-pool-download/token-pool-download.service.ts`:
- Around line 264-278: The code redundantly calls JSON.parse on operation.params
twice; change the operations.find usage to parse once and preserve the parsed
result for reuse—e.g., when using operations.find((candidate) => { const parsed
= safeParse(candidate.params); if (parsed?.id === tokenPoolId) {
candidate.__parsedParams = parsed; return true } return false }), then after the
NotFoundException check return { params: operation.__parsedParams } (or
alternatively have find return an object like { operation, parsedParams }) so
you only parse operation.params a single time; update references to use the
stored parsed object instead of calling JSON.parse again (referencing
operations, operation, tokenPoolId).

In `@src/app.utils.spec.ts`:
- Around line 17-40: Add resilience tests and harden stringifyError to handle
non-serializable metadata and cyclic/odd causes: write unit tests feeding
stringifyError with metadata containing BigInt, Symbol, and a cause graph with a
cycle and ensure it returns a string (not throw); then modify stringifyError to
(1) safely serialize metadata by converting BigInt to string and Symbols to
their description or toString, (2) guard against circular references when
traversing causes using a WeakSet/Set of seen objects to break cycles, and (3)
wrap metadata and cause processing in try/catch and fall back to a safe
placeholder like "<unserializable>" for values that still throw — target the
stringifyError function and any helper used to format metadata/cause.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc2088ab-5238-4eaf-88a9-07ef8d3a1f1d

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2f586 and 3e714d4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • package.json
  • src/api/token-pool-download/token-pool-download.service.spec.ts
  • src/api/token-pool-download/token-pool-download.service.ts
  • src/app.utils.spec.ts
  • src/app.utils.ts

},
"dependencies": {
"@6529-collections/allowlist-lib": "0.0.130",
"@6529-collections/allowlist-lib": "0.0.131",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the library version exists on npm and check for any changelog or breaking changes.

# Check if version 0.0.131 exists on npm
echo "=== Checking if version 0.0.131 exists on npm ==="
npm view `@6529-collections/allowlist-lib`@0.0.131 version 2>/dev/null || echo "Version not found on npm registry"

# Show recent versions for context
echo ""
echo "=== Recent versions of the package ==="
npm view `@6529-collections/allowlist-lib` versions --json 2>/dev/null | jq '.[-5:]' || echo "Could not fetch versions"

# List all imports from this library in the codebase to identify dependencies
echo ""
echo "=== Imports from `@6529-collections/allowlist-lib` in codebase ==="
rg -n "from '@6529-collections/allowlist-lib" --type ts | head -30

Repository: 6529-Collections/allowlist-api

Length of output: 4737


🏁 Script executed:

# Check if there's npm registry configuration
echo "=== Checking for .npmrc ==="
cat .npmrc 2>/dev/null || echo "No .npmrc file found"

echo ""
echo "=== Current package.json dependencies (lines around 28) ==="
sed -n '20,35p' package.json

echo ""
echo "=== Checking for lock files ==="
ls -la package-lock.json yarn.lock 2>/dev/null | head -5

echo ""
echo "=== Git log for package.json (recent changes) ==="
git log --oneline -n 10 -- package.json 2>/dev/null || echo "Git history not available"

echo ""
echo "=== Check if this is in a monorepo ==="
find . -maxdepth 3 -name "package.json" -type f | grep -v node_modules | head -10

Repository: 6529-Collections/allowlist-api

Length of output: 1456


🏁 Script executed:

#!/bin/bash
# Check package-lock.json for the library version
echo "=== Checking package-lock.json for `@6529-collections/allowlist-lib` ==="
grep -A 10 '"@6529-collections/allowlist-lib"' package-lock.json | head -20

echo ""
echo "=== Checking if the package resolves in lock file ==="
jq '.packages["node_modules/@6529-collections/allowlist-lib"].version' package-lock.json 2>/dev/null || echo "Could not extract version from package-lock.json"

echo ""
echo "=== Attempting to fetch from GitHub Package Registry ==="
# Try to fetch package info from GitHub registry (requires auth token, but let's check if it's discoverable)
curl -s -H "Accept: application/vnd.npm.install-v1+json" \
  "https://npm.pkg.github.com/@6529-collections/allowlist-lib" 2>/dev/null | jq '.versions | keys | .[-5:]' 2>/dev/null || echo "Could not access GitHub Package Registry (authentication may be required)"

echo ""
echo "=== Check if node_modules has the package installed ==="
ls -la node_modules/@6529-collections/allowlist-lib/ 2>/dev/null | head -5 || echo "Package not in node_modules (expected - sandbox environment)"

Repository: 6529-Collections/allowlist-api

Length of output: 848


Synchronize package.json and package-lock.json for @6529-collections/allowlist-lib.

The package.json declares version 0.0.131, but package-lock.json is still locked to 0.0.124. This mismatch will cause inconsistent dependency resolution across environments. Regenerate the lock file by running npm install (or npm ci after install) to ensure package-lock.json reflects the version specified in package.json, or revert package.json to 0.0.124 if 0.0.131 is not yet available on GitHub Package Registry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 28, The package.json dependency
"@6529-collections/allowlist-lib" is set to "0.0.131" but package-lock.json
still references "0.0.124"; update the lockfile to match by either running npm
install (or npm ci after installing) to regenerate package-lock.json so it
reflects 0.0.131, or if 0.0.131 isn't available, revert the version in
package.json back to "0.0.124" so both files are synchronized.

Comment on lines +57 to 86
if (error instanceof Error || (error && typeof error === 'object')) {
const code =
typeof error.code === 'string' && error.code.length
? `[${error.code}] `
: '';
const metadata =
error.metadata && typeof error.metadata === 'object'
? Object.entries(error.metadata)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join(', ')
: '';
const details = metadata ? ` (${metadata})` : '';
const cause =
error.cause !== undefined
? ` Cause: ${stringifyError(error.cause)}`
: '';
const message =
typeof error.message === 'string' && error.message.length
? error.message
: JSON.stringify(error);

return `${code}${message}${details}${cause}`;
}

if (error === undefined) {
return 'undefined';
} else {
return JSON.stringify(error);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does JSON.stringify throw on BigInt and circular references, and can it return undefined for Symbol/function/undefined values?

💡 Result:

  • BigInt: Yes. JSON.stringify() throws a TypeError if it encounters a BigInt (unless you convert it yourself via toJSON/replacer). (tc39.es)
  • Circular (cyclic) references: Yes. JSON.stringify() throws a TypeError when the value is or contains a cyclic structure. (tc39.es)
  • Return value undefined (top-level): Yes. If the top-level input is (or is transformed into) an unrepresentable value like undefined or a function (and per the algorithm, non-JSON primitives fall through to “return undefined”), then JSON.stringify(...) returns undefined. (tc39.es)
  • Object vs array handling for undefined/function: In objects, such properties are omitted; in arrays, they become null. (tc39.es)

Citations:


🏁 Script executed:

# First, let's find the stringifyError function and confirm line numbers
cd /tmp && find . -name "app.utils.ts" -type f 2>/dev/null | head -5

Repository: 6529-Collections/allowlist-api

Length of output: 56


🏁 Script executed:

# List all TypeScript files to locate the correct path
git ls-files | grep -E "(app\.utils|token-pool-downloader|runner\.service)" | head -20

Repository: 6529-Collections/allowlist-api

Length of output: 225


🏁 Script executed:

# Check the stringifyError function in app.utils.ts
wc -l src/app.utils.ts

Repository: 6529-Collections/allowlist-api

Length of output: 93


🏁 Script executed:

# Read the stringifyError function
cat -n src/app.utils.ts | sed -n '50,90p'

Repository: 6529-Collections/allowlist-api

Length of output: 1398


🏁 Script executed:

# Check the usage in runner.service.ts around lines 78-85
sed -n '70,95p' src/runner/runner.service.ts

Repository: 6529-Collections/allowlist-api

Length of output: 810


🏁 Script executed:

# Check the usage in token-pool-downloader.service.ts around lines 206-220
sed -n '200,230p' src/token-pool/token-pool-downloader.service.ts

Repository: 6529-Collections/allowlist-api

Length of output: 773


Harden stringifyError so error serialization never throws or returns non-string.

Lines 66, 77, and 85 use raw JSON.stringify(...) on unknown values. This can throw (e.g., BigInt, circular refs) or return undefined (e.g., Symbol), breaking error persistence at src/token-pool/token-pool-downloader.service.ts:206 and src/runner/runner.service.ts:82, where callers expect a string for errorReason.

Wrap JSON.stringify calls in try-catch, return String(value) as fallback, and add circular reference detection for recursive calls.

🧰 Tools
🪛 ESLint

[error] 71-73: Replace ⏎········?··Cause:·${stringifyError(error.cause)}⏎······· with ·?··Cause:·${stringifyError(error.cause)}``

(prettier/prettier)

🪛 GitHub Check: SonarCloud Code Analysis

[warning] 71-71: Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=6529-Collections_allowlist-api&issues=AZz3NTPeIT_xPcmk7UWA&open=AZz3NTPeIT_xPcmk7UWA&pullRequest=57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app.utils.ts` around lines 57 - 86, The current stringifyError
implementation uses raw JSON.stringify on unknown values (metadata entries,
message fallback, and nested cause) which can throw or return non-strings;
update stringifyError to always return a string by wrapping all JSON.stringify
calls in try/catch and falling back to String(value) when JSON fails or returns
undefined, and add simple circular-reference protection for recursive
stringifyError calls (e.g., accept an optional WeakSet seen param, add objects
to it and detect cycles to return "[Circular]"). Ensure all places that build
code, metadata, message and cause use the safe stringify helper so
stringifyError never throws and always returns a plain string.

Comment on lines +71 to +73
error.cause !== undefined
? ` Cause: ${stringifyError(error.cause)}`
: '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the Prettier violation in the cause ternary.

Static analysis reports a formatting error at Line 71-Line 73 (prettier/prettier). This can fail lint/CI for the PR.

Minimal formatting fix
-    const cause =
-      error.cause !== undefined
-        ? ` Cause: ${stringifyError(error.cause)}`
-        : '';
+    const cause = error.cause !== undefined ? ` Cause: ${stringifyError(error.cause)}` : '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
error.cause !== undefined
? ` Cause: ${stringifyError(error.cause)}`
: '';
const cause = error.cause !== undefined ? ` Cause: ${stringifyError(error.cause)}` : '';
🧰 Tools
🪛 ESLint

[error] 71-73: Replace ⏎········?··Cause:·${stringifyError(error.cause)}⏎······· with ·?··Cause:·${stringifyError(error.cause)}``

(prettier/prettier)

🪛 GitHub Check: SonarCloud Code Analysis

[warning] 71-71: Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=6529-Collections_allowlist-api&issues=AZz3NTPeIT_xPcmk7UWA&open=AZz3NTPeIT_xPcmk7UWA&pullRequest=57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app.utils.ts` around lines 71 - 73, The Prettier violation is caused by
the multiline ternary that handles error.cause; locate the code that calls
stringifyError and references error.cause (the ternary producing `? \` Cause:
${stringifyError(error.cause)}\` : ''`) and reformat it to satisfy
Prettier—either collapse the ternary onto a single line or replace it with a
short if/else or concatenation so the expression is formatted consistently
(preserve the same output string but ensure the expression around error.cause
and stringifyError is on one line).

@GelatoGenesis GelatoGenesis merged commit 3e714d4 into main Mar 16, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant