Skip to content

Feature/use native sqlite#1423

Merged
alexcos20 merged 3 commits into
next-4from
feature/use_native_sqlite
Jul 17, 2026
Merged

Feature/use native sqlite#1423
alexcos20 merged 3 commits into
next-4from
feature/use_native_sqlite

Conversation

@alexcos20

@alexcos20 alexcos20 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Closes #1407

Replace the sqlite3 native addon with Node's built-in node:sqlite

Drops the external sqlite3 npm dependency and moves every embedded SQLite database onto
Node's built-in node:sqlite module (DatabaseSync / StatementSync), behind one small
shared client. This is a drop-in engine swap: no on-disk format change, no schema
change, and no change to any public method signature of the database wrapper classes.

Motivation

sqlite3 is a native C++ addon (hasInstallScript: true → node-gyp / prebuilt-binary
download at install time) and the source of recurring pain documented in CLAUDE.md:
broken bindings after Node version switches (npm_config_build_from_source=true npm rebuild sqlite3), GLIBC mismatches, and a native compile step in the Docker builder.

node:sqlite ships inside the Node binary: zero install scripts, zero node-gyp, no
prebuilt-binary supply chain — identical behaviour in the slim Docker runner and on dev
machines. The synchronous API also removes ~50% of the code (callback→Promise plumbing) and
fixes one latent bug (see below). SQLite usage here is a perfect fit: small, local,
single-process key-value / job-tracking tables (nonce, config, C2D jobs, auth tokens,
persistent-storage registry) — no heavy queries, no multi-process contention.

Node version requirement (breaking for very old 22.x)

node:sqlite is available unflagged since Node 22.13.0. To fail fast instead of
crashing at runtime with ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite:

  • .nvmrc: 2222.22.2 (matches the Dockerfile and CI exactly; any >=22.13 works).
  • .github/workflows/ci.yml: v22.15.0v22.22.2 (all jobs).
  • package.json: added "engines": { "node": ">=22.13.0" }.

Dev (.nvmrc), CI, and the Dockerfile (node:22.22.2) now all run the same Node. No new
runtime flag is needed — the existing --experimental-specifier-resolution=node on start is
unchanged. A harmless ExperimentalWarning: SQLite is an experimental feature is printed on
stderr at startup (the start script already runs with --trace-warnings).

What changed

New shared client — src/components/database/sqliteClient.ts. A thin synchronous
wrapper around DatabaseSync that owns one handle per file and centralizes the
sqlite3-compatibility concerns:

  • run / get / all / exec mapping the old callback API to synchronous calls.
  • sanitize() on every binding: node:sqlite throws on undefined and boolean
    bindings where sqlite3 silently coerced them — so undefinedNULL, true/false
    1/0, applied centrally.
  • Eager mkdirSync(dirname) in the constructor: DatabaseSync opens eagerly and throws if
    the parent dir is missing (the old driver deferred opening), so directory creation moves
    into the client and the previously per-caller mkdir blocks are removed.
  • PRAGMA busy_timeout = 5000 for the long-running server process. WAL is intentionally
    not enabled in this PR
    (zero behaviour change / no new -wal/-shm sidecar files); it
    can be added later as a follow-up knob.

Engine swap (mechanical) — same public signatures, internals become
try { …sync… } catch (e) { log; throw }:

  • database/sqlite.ts (SQLiteProvider: nonce + config DBs).
  • database/sqliteAuthToken.ts (SQLiteAuthToken).
  • database/sqliteCompute.ts (SQLiteCompute: C2D jobs, docker images, service jobs) —
    the largest file.
  • persistentStorage/PersistentStorageFactory.ts (bucket registry; schema setup is now
    synchronous in the constructor, so the DB is ready before it returns).

Redundant mkdir removed from SQLLiteNonceDatabase.ts, SQLLiteConfigDatabase.ts,
and AuthTokenDatabase.ts (now handled by the client).

Dependency + docs: sqlite3 removed from package.json / package-lock.json
(−~756 lockfile lines; sqlite3 + its unique node-addon-api/bindings/prebuilds are
gone — node-addon-api remains for keccak/secp256k1). CLAUDE.md updated (drop the
rebuild sqlite3 recovery note; state the ≥22.13 requirement and the node:sqlite backing).

Behavioural fixes / gotchas handled

  • BLOBs come back as Uint8Array, not Buffer. Every read-side .toString() on a
    blob is now Buffer.from(blob).toString()generateJSONFromBlob and mapServiceRows
    in sqliteCompute.ts. This was the Tech Spike - Database evaluation #1 silent-breakage risk; write-side is unchanged
    (Buffer is a Uint8Array and binds as BLOB).
  • this.changes → returned value. deleteJob / updateJob / updateServiceJob
    (compute) and dbDeleteBucket / dbUpdateBucketLabel (persistent storage) read
    changes off the object returned by run(). Kept as a JS number (no readBigInts), so
    the existing Promise<number> signatures and === 1 comparisons are unaffected.
  • Latent bug fixed: sqliteAuthToken.createTable previously did await this.db.exec(…),
    but sqlite3's exec takes a callback and returns the Database object, so the await
    never waited — the CREATE TABLE raced the chainId ALTER TABLE (masked by sqlite3's
    internal statement queue). With synchronous exec this is now genuinely ordered; the
    migration is wrapped in a try/catch that swallows only the expected "duplicate column".
  • No code in src/ matched sqlite3 error codes (SQLITE_CONSTRAINT etc.), so the changed
    error shape (ERR_SQLITE_ERROR) is a non-issue; errors still land in DATABASE_LOGGER.

Tests

  • New unit (src/test/unit/sqliteClient.test.ts, no daemon): eager parent-dir
    creation, run changes on hit/miss, get undefined on no-row, bind sanitization
    (undefined→NULL, boolean→1/0), BLOB round-trip (Buffer in → Uint8Array out →
    Buffer.from(...).toString()), and INSERT … ON CONFLICT upsert.
  • Updated src/test/integration/imageCleanup.test.ts: its ~5 raw-handle probes
    ((sqliteProvider as any).db.get/run(…, cb)) rewritten to the synchronous SqliteClient
    API.
  • On-disk compatibility check (the key validation): booted the migrated providers
    against real databases/*.sqlite files written by the old sqlite3 driver — decoded
    5 pre-existing C2D jobs + 141 service jobs from old blobs, read nonces/config version, and
    validated a stored auth token. Confirms the BLOB-decode path and format are compatible in
    both directions (rollback = revert the PR; files stay compatible).

Summary by CodeRabbit

  • New Features
    • Added Service-on-Demand capabilities, including templates, asynchronous startup, status, logs, restart, stop, and duration extension.
    • Added sample CPU and GPU service templates, including LLM and embedding deployments.
    • Added cross-node authentication token validation and improved ownership handling.
    • Added support for escrow relock events.
  • Improvements
    • Simplified compute resource configuration with shared pools and discrete-resource tracking.
    • Improved Docker shutdown and service resource allocation.
    • Migrated SQLite storage to Node’s built-in SQLite support.
  • Documentation
    • Expanded API, GPU, compute pricing, configuration, and Service-on-Demand documentation.

@alexcos20
alexcos20 changed the base branch from main to next-4 July 15, 2026 10:08
@alexcos20 alexcos20 self-assigned this Jul 15, 2026
@alexcos20

Copy link
Copy Markdown
Member Author

/run-security-scan

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 45dbb146-8117-4ece-8d09-c23ced641fc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change introduces Docker Service-on-Demand APIs and lifecycle management, shared compute-resource pools, delegated JWT validation, native node:sqlite storage, escrow relock indexing, service templates, expanded tests, and related configuration, documentation, Docker, and CI updates.

Changes

Service-on-Demand and compute resources

Layer / File(s) Summary
Service contracts and configuration
src/@types/C2D/*, src/@types/commands.ts, src/utils/config/*, src/utils/constants.ts
Adds service jobs, templates, lifecycle statuses, commands, feature gates, resource references, and validation rules.
Docker engine lifecycle
src/components/c2d/compute_engine_*.ts, src/components/core/service/*
Adds asynchronous service start, escrow/image processing, container lifecycle operations, endpoint allocation, logs, expiry handling, restart, cleanup, and graceful shutdown.
Service persistence and exposure
src/components/database/C2DDatabase.ts, src/components/database/sqliteCompute.ts, src/components/httpRoutes/compute.ts
Persists service jobs and exposes template, start, stop, extend, restart, status, and log-streaming routes.
Templates and resource documentation
docs/serviceTemplates/*, docs/GPU.md, docs/env.md, docs/compute-pricing.md
Documents the template schema, example services, shared resource pools, GPU configuration, pricing, and migration rules.

Native SQLite and authentication

Layer / File(s) Summary
Native SQLite migration
src/components/database/sqlite*.ts, src/components/database/SqliteClient.ts, src/components/persistentStorage/PersistentStorageFactory.ts
Replaces callback-based sqlite3 access with synchronous SqliteClient operations backed by node:sqlite, including BLOB handling and directory creation.
Delegated token validation
src/components/Auth/index.ts, src/components/core/handler/authHandler.ts, src/components/core/utils/nonceHandler.ts
Adds issuer-aware JWT claims, remote token validation through P2P, bounded verdict parsing, and authenticated address propagation.
Escrow relock indexing
src/components/Indexer/*, src/@types/Escrow.ts, database schemas
Indexes relock events and stores token, amount, and expiry fields.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ServiceStartHandler
  participant C2DEngineDocker
  participant Escrow
  participant Docker
  Client->>ServiceStartHandler: SERVICE_START
  ServiceStartHandler->>C2DEngineDocker: createServiceJob
  C2DEngineDocker-->>Client: Starting service job
  C2DEngineDocker->>Escrow: create and claim lock
  C2DEngineDocker->>Docker: pull/build image and start container
  Docker-->>C2DEngineDocker: container endpoint
  C2DEngineDocker-->>Client: Running status and endpoint
Loading

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: bogdanfazakas, giurgiur99, dnsi0, andreip136

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also includes substantial service, auth, resource, docs, and workflow changes unrelated to the native SQLite migration. Split unrelated service/auth/resource/docs/workflow work into separate PRs so this change stays focused on the native sqlite migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: switching to native SQLite.
Linked Issues check ✅ Passed The PR replaces sqlite3 with Node's built-in node:sqlite via SqliteClient as requested by #1407.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/use_native_sqlite

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.

@alexcos20 alexcos20 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: medium

Summary:
Excellent architectural improvement migrating from the native sqlite3 dependency to Node's built-in node:sqlite. The abstraction in SqliteClient is extremely well-designed, seamlessly handling type sanitization and path initialization, and the synchronous refactoring correctly maintains backward compatibility. However, there is a critical typo in the pinned Node.js version (22.22.2) which does not exist and will break CI pipelines and local setups.

Comments:
• [ERROR][bug] Node.js version v22.22.2 does not exist (the v22 release line is currently at ~v22.14.x). Pinning to a nonexistent version will cause actions/setup-node to fail immediately. Please update to a valid version that satisfies your >=22.13.0 requirement (e.g., 22.13.1 or 22.14.0).

-          node-version: 'v22.22.2'
+          node-version: '22.13.1'

(Note: Ensure you apply this fix to the matrix strategy on line 45 as well)
• [ERROR][bug] Matches the nonexistent version from CI. Please update this to a valid Node.js version so that nvm use does not fail for developers.

-22.22.2
+22.13.1

• [INFO][style] Excellent design choice here. Eagerly ensuring the directory exists (fs.mkdirSync) and configuring PRAGMA busy_timeout in the central client wrapper removes repetitive boilerplate from the individual DB classes and guarantees consistency.
• [INFO][style] Centralizing the sanitize logic for node:sqlite's stricter bindings (gracefully mapping undefined and boolean) is a fantastic way to ensure a smooth migration without scattering type coercions throughout the consuming code.
• [INFO][other] Good catch handling the Uint8Array to string conversion here via Buffer.from(blob). This correctly accounts for the change in how node:sqlite returns BLOBs compared to the old sqlite3 addon.

@alexcos20

Copy link
Copy Markdown
Member Author

gemini is wrong: https://nodejs.org/en/download/archive/v22.22.2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

116-121: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable credential persistence for the external Barge checkout.

The checked-out repository's scripts run immediately afterward and can access the persisted GitHub token. Add persist-credentials: false to limit supply-chain exposure.

Proposed fix
         with:
           repository: 'oceanprotocol/barge'
           path: 'barge'
           ref: '43cfdfd21154a2bae00770b779e7c39390ff5043'
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 116 - 121, Update the “Checkout Barge”
actions/checkout step to set persist-credentials to false in its with
configuration, while preserving the existing repository, path, and ref values.

Source: Linters/SAST tools

🟠 Major comments (25)
docs/env.md-230-234 (1)

230-234: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve environment-specific constraints during migration.

Line 261 says to move all constraints to connection-level resources, but Line 234 documents them as valid environment overrides. Moving them unconditionally makes environment-specific requirements pool-wide and changes scheduling behavior. Only move constraints intended for every environment.

Also applies to: 243-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/env.md` around lines 230 - 234, Update the migration guidance around the
environment-level resources and constraints so environment-specific constraints
remain attached to their environment. Move constraints to connection-level
resources only when they are intended to apply to every environment; preserve
per-environment overrides, including empty constraints used to remove inherited
restrictions.
src/utils/config/schemas.ts-193-201 (1)

193-201: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject every legacy hardware field, not only two of them.

.passthrough() accepts platform, memoryTotal, type, kind, shareable, and other unknown fields, while the migration check only detects init and driverVersion. This contradicts the documented startup-error contract. Make resource references strict or explicitly reject the complete hardware-field set.

Proposed strict resource-reference schema
 export const EnvironmentResourceRefSchema = z
   .object({
     id: z.string(),
     total: z.number().optional(),
     min: z.number().optional(),
     max: z.number().optional(),
     constraints: z.array(ResourceConstraintSchema).optional()
   })
-  .passthrough()
+  .strict()

Also applies to: 437-450

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 193 - 201, Update
EnvironmentResourceRefSchema and the corresponding schema at the additional
resource-reference definition to reject unknown legacy hardware fields instead
of accepting them via passthrough. Replace the permissive object behavior with
strict validation, or explicitly forbid the complete legacy hardware-field set
including platform, memoryTotal, type, kind, shareable, init, and driverVersion,
while preserving the existing valid fields.
src/utils/config/schemas.ts-454-471 (1)

454-471: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate free-tier resource references against the pool.

Only env.resources is checked against poolIds; env.free.resources can reference an unknown resource and pass startup validation. Apply the same validation to both resource lists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 454 - 471, Extend the
resource-reference validation in the Docker configuration schema to also iterate
over each environment’s free-tier resources, such as env.free.resources, using
the existing poolIds set. Report unknown IDs through ctx.addIssue with the
corresponding free-resource path, while preserving the current env.resources
validation.
src/components/c2d/compute_engine_docker.ts-620-642 (1)

620-642: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop every cron and delayed startup task.

stop() drains the internal loop and service starts, but startCrons() also creates untracked startup timeouts and scanDBUpdateTimer. Those callbacks can continue Docker/database work after the engine is stopped.

Track and clear all three startup timeouts, and clear scanDBUpdateTimer alongside the image-cleanup and payment timers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 620 - 642, Update
startCrons() to track all three delayed startup timeout handles so stop() can
clear them and prevent callbacks from running after shutdown. In stop(), clear
and reset those startup handles, and also clear scanDBUpdateTimer alongside the
existing image-cleanup and payment timers. Preserve the existing internal-loop
and service-start draining behavior.
src/components/c2d/compute_engine_docker.ts-3104-3115 (1)

3104-3115: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when the image scan cannot complete.

checkImageVulnerability() returns vulnerable: false when scanning fails, so both new service paths accept an unscanned image even when scanImages is enabled. Treat a missing or invalid report as a scan failure and reject the image.

Also applies to: 3164-3176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3104 - 3115, Update
the image-scan handling around checkImageVulnerability in both affected service
paths to fail closed when the scan result is missing, invalid, or indicates
scanning could not complete, rather than relying only on vulnerable. When
scanImages is enabled, reject and remove the image for any such failure, while
preserving the existing vulnerability rejection behavior and error context.
src/components/c2d/compute_engine_docker.ts-3475-3478 (1)

3475-3478: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release reservations only after confirmed Docker teardown.

cleanupServiceDocker() swallows stop/removal errors while releasing CPU cores, and its caller then releases host ports. stopService() likewise releases CPUs before cleanup. A leaked running container can therefore overlap another service on the same cores or port.

Return cleanup success and release CPU/port reservations only after removal succeeds or Docker confirms the resource is already absent.

Also applies to: 3513-3525, 3586-3617

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3475 - 3478, Update
cleanupServiceDocker and its callers, including the stopService path and the
catch block around container creation, to return and check whether Docker
teardown succeeded or confirmed the container is absent. Only release CPU cores
and host ports after that confirmed outcome; do not swallow teardown failures
and release reservations unconditionally. Preserve the existing refund behavior
while ensuring all affected paths use the cleanup result before releasing
reservations.
src/components/c2d/compute_engine_base.ts-494-508 (1)

494-508: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Deduplicate discrete capacity as well as discrete usage.

Each environment carries the same globally aggregated discrete inUse, so the code takes its maximum. Summing each environment’s discrete total still multiplies the same physical pool and can permit over-allocation when physicalLimits has no entry.

Proposed fix
     let globalUsed = 0
     let globalTotal = 0
     let discreteInUse: number | undefined
+    let discreteTotal: number | undefined
     for (const e of allEnvironments) {
       const res = this.getResource(e.resources, resourceId)
       if (res) {
-        globalTotal += res.total || 0
         if (res.kind === 'discrete') {
+          discreteTotal = Math.max(discreteTotal ?? 0, res.total || 0)
           discreteInUse = Math.max(discreteInUse ?? 0, res.inUse || 0)
         } else {
+          globalTotal += res.total || 0
           globalUsed += res.inUse || 0
         }
       }
     }
+    if (discreteTotal !== undefined) globalTotal += discreteTotal
     if (discreteInUse !== undefined) globalUsed += discreteInUse
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 494 - 508, Update the
resource aggregation loop in the compute-engine method containing discreteInUse
so discrete total capacity is deduplicated like discrete usage: track the
maximum res.total for discrete resources, exclude it from globalTotal per
environment, and add the deduplicated value once after iteration. Preserve
summed totals for non-discrete resources and the existing discrete inUse
behavior.
src/components/core/utils/escrow.ts-50-59 (1)

50-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject reverted or timed-out escrow transactions before continuing. provider.waitForTransaction can return a receipt even when the tx reverted, and it can still return null on timeout. This helper should fail unless the receipt exists and status === 1; otherwise the dependent claim/extend flow can continue without a confirmed lock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/utils/escrow.ts` around lines 50 - 59, The
waitForTransaction helper must validate the provider result before returning.
Capture the receipt from provider.waitForTransaction and throw when it is null
or its status is not 1, while preserving the existing chain, confirmation, and
timeout parameters.
src/components/c2d/compute_engine_base.ts-87-143 (1)

87-143: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the lint suppressions on these service stubs. Return Promise.resolve(...) from the defaults and prefix unused params with _ instead of keeping async no-op methods here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 87 - 143, Remove the
eslint-disable comments from createServiceJob, processServiceStart, stopService,
restartService, getServiceStatus, and getServiceStreamableLogs. Rename unused
parameters in these stubs with an _ prefix, remove async from the no-op methods,
and return Promise.resolve with the existing default values (null, empty array,
or undefined as appropriate).

Source: Coding guidelines

src/test/unit/compute.test.ts-1385-1415 (1)

1385-1415: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep the escrow refundable until container.start() succeeds. processServiceStart claims the lock before container creation/start, so a failure after that point records claimTx and bypasses the refund path. The consumer is charged even though the service never reaches Running, which conflicts with the documented claim-on-success/refund-on-failure flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/compute.test.ts` around lines 1385 - 1415, Update
processServiceStart so the escrow lock remains refundable until
container.start() completes successfully; move claimTx creation/claimLock
handling after the successful start, while preserving cleanup and Error status
on failures. Update the related documentation in docs/API.md (1927-1930) if
needed to match this claim-on-success/refund-on-failure behavior; the test
scenarios in src/test/unit/compute.test.ts (1385-1415) require no direct change
beyond validating the corrected flow.
src/components/core/service/utils.ts-84-100 (1)

84-100: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Scan the full range before reporting port exhaustion.

Trying only 50 random candidates can reject service startup even when many ports remain free. Pick a random starting offset, then inspect each candidate once.

Proposed fix
   const size = rangeEnd - rangeStart + 1
-  for (let i = 0; i < Math.min(size, 50); i++) {
-    const candidate = rangeStart + Math.floor(Math.random() * size)
+  const offset = Math.floor(Math.random() * size)
+  for (let i = 0; i < size; i++) {
+    const candidate = rangeStart + ((offset + i) % size)
     if (allocatedPorts.has(candidate)) continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/utils.ts` around lines 84 - 100, Update
allocateHostPort to scan the entire range exactly once instead of limiting
attempts to 50 random candidates. Choose a random starting offset, iterate
through all range offsets with wraparound, and retain the existing
allocatedPorts reservation and release behavior while checking each candidate.
src/components/httpRoutes/compute.ts-480-485 (1)

480-485: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log the signature and nonce.

req.query includes authentication material from Lines 492-493. Serializing the complete query exposes it to application logs. Log only non-sensitive fields such as serviceId, node, and since.

Proposed fix
     HTTP_LOGGER.logMessage(
-      `ServiceGetStreamableLogsCommand request received with query: ${JSON.stringify(
-        req.query
-      )}`,
+      `ServiceGetStreamableLogsCommand request received: ${JSON.stringify({
+        serviceId: req.query.serviceId,
+        node: req.query.node,
+        since: req.query.since
+      })}`,
       true
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/httpRoutes/compute.ts` around lines 480 - 485, Update the
request logging around ServiceGetStreamableLogsCommand to avoid serializing the
full req.query, which contains the signature and nonce. Log only the
non-sensitive fields serviceId, node, and since while preserving the existing
HTTP_LOGGER.logMessage call behavior.
src/components/database/sqlite.ts-22-39 (1)

22-39: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Preserve the Promise API without suppressing require-await.

The synchronous migration leaves every method async without an await and adds repeated lint suppressions. Use a small helper that converts synchronous results or exceptions into resolved/rejected Promises, then remove async and the disables.

Proposed pattern
+  private execute<T>(operation: () => T): Promise<T> {
+    try {
+      return Promise.resolve(operation())
+    } catch (error) {
+      return Promise.reject(error)
+    }
+  }

-  // eslint-disable-next-line require-await
-  async createNonce(address: string, nonce: number) {
-    this.db.run(insertSQL, [address, nonce])
-    return { id: address, nonce }
+  createNonce(address: string, nonce: number) {
+    return this.execute(() => {
+      this.db.run(insertSQL, [address, nonce])
+      return { id: address, nonce }
+    })
   }

As per coding guidelines, “Treat require-await violations as errors; do not leave unnecessary async functions.”

Also applies to: 42-80, 83-102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqlite.ts` around lines 22 - 39, Update
createTableForNonce, createTableForConfig, and the other migration methods in
the same file to remove async and require-await suppressions while preserving
their Promise-based API. Add and reuse a small helper that executes synchronous
database operations and returns a resolved Promise with the result or a rejected
Promise when an exception occurs.

Source: Coding guidelines

src/components/core/service/getStreamableLogs.ts-84-87 (1)

84-87: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose raw engine errors to clients.

Docker/runtime errors can contain internal paths, container details, or command data. Keep the detailed message in server logs and return a stable generic 500 response.

       CORE_LOGGER.error(message)
-      return { stream: null, status: { httpStatus: 500, error: message } }
+      return {
+        stream: null,
+        status: { httpStatus: 500, error: 'Failed to stream service logs' }
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/getStreamableLogs.ts` around lines 84 - 87,
Update the catch block in getStreamableLogs to keep the detailed error message
in CORE_LOGGER.error, but replace the returned status error with a stable
generic 500-safe message. Preserve the null stream and HTTP 500 response while
ensuring raw engine details are never exposed to clients.
src/components/core/service/getStreamableLogs.ts-1-14 (1)

1-14: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use Readable for the shared stream response type. The cast here is masking a broader type mismatch: P2PCommandResponse.stream and the C2D log producer signatures still use Stream/NodeJS.ReadableStream. Switch the shared response type and engine methods to Readable, then return the stream directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/getStreamableLogs.ts` around lines 1 - 14, Update
the shared stream typing used by getStreamableLogs: replace
Stream/NodeJS.ReadableStream with Node’s Readable in P2PCommandResponse and the
related C2D engine log-producer methods, then remove the cast and return the
Readable stream directly from the handler. Preserve existing stream behavior and
imports while aligning all affected signatures.

Source: Coding guidelines

src/components/database/sqliteClient.ts-35-38 (1)

35-38: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Avoid a 5-second synchronous lock wait. DatabaseSync runs on the main thread, so PRAGMA busy_timeout = 5000 can block request handling for up to 5 seconds whenever SQLite is locked. Use a shorter timeout with retry/backoff, or move SQLite work off the main thread.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteClient.ts` around lines 35 - 38, Update the
SQLite initialization flow around DatabaseSync and the busy_timeout pragma to
avoid blocking the main thread for up to five seconds: use a substantially
shorter timeout and implement bounded retry/backoff for transient lock failures,
preserving normal database operations and propagating persistent errors.
src/components/database/sqliteClient.ts-27-59 (1)

27-59: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Add close() to SqliteClient and wire wrapper shutdown
SqliteClient owns a persistent DatabaseSync handle, but SQLiteProvider, SQLiteAuthToken, SQLiteCompute, and PersistentStorageFactory never release it. Expose a close method and call it from the owning teardown path so shutdown and tests don’t leak file handles or leave the DB locked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteClient.ts` around lines 27 - 59, Add a public
close() method to SqliteClient that closes its DatabaseSync handle, then update
the teardown or shutdown paths of SQLiteProvider, SQLiteAuthToken,
SQLiteCompute, and PersistentStorageFactory to invoke the client’s close method
when they release their resources. Ensure each owner closes the database exactly
once, including test cleanup paths.
src/test/unit/auth/token.test.ts-90-101 (1)

90-101: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the require-await suppression.

Return a resolved promise from the fake instead of declaring an unnecessary async function.

Proposed fix
-      // eslint-disable-next-line require-await
-      sendTo: async (_peer: string, _msg: string) => {
+      sendTo: (_peer: string, _msg: string) => {
         tracker.calls++
         const httpStatus = opts.httpStatus ?? 200
         if (httpStatus !== 200) {
-          return { status: { httpStatus } }
+          return Promise.resolve({ status: { httpStatus } })
         }
-        return {
+        return Promise.resolve({
           status: { httpStatus: 200 },
           stream: Readable.from(JSON.stringify(opts.verdict))
-        }
+        })
       }

As per coding guidelines, “Treat require-await violations as errors; do not leave unused variables or unnecessary async functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/auth/token.test.ts` around lines 90 - 101, Update the fake
sendTo implementation in the token test to remove the unnecessary async
declaration and its eslint-disable suppression, while preserving the existing
tracker, status, and stream response behavior by returning a resolved promise
explicitly.

Source: Coding guidelines

src/components/core/service/extendService.ts-20-31 (1)

20-31: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require a finite positive integer duration.

parseInt allows values such as NaN, Infinity, or partially numeric strings to bypass this check and later corrupt expiry, duration, or pricing calculations.

Proposed fix
-    if (commandValidation.valid) {
-      if (parseInt(String(command.additionalDuration)) <= 0)
+    if (commandValidation.valid) {
+      if (
+        !Number.isSafeInteger(command.additionalDuration) ||
+        command.additionalDuration <= 0
+      )
         return buildInvalidRequestMessage('Invalid additionalDuration')
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 20 - 31, Update
validate in the ServiceExtendCommand validation flow to require
additionalDuration to be a finite, strictly positive integer, rejecting NaN,
Infinity, fractional values, and partially numeric strings. Preserve the
existing required-parameter validation and InvalidRequest response for invalid
durations.
src/components/core/service/extendService.ts-54-72 (1)

54-72: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the authenticated address for service operations.

The auth result is ignored after validation, so a valid token can be presented for one address while lookup, ownership checks, escrow, and mutation still run against task.consumerAddress.

  • src/components/core/service/extendService.ts#L54-L72: use auth.consumerAddress for job lookup, ACL checks, escrow calls, and persistence.
  • src/components/core/service/stopService.ts#L41-L60: use auth.consumerAddress for lookup, ownership checks, and stopService.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 54 - 72, Use the
authenticated address from the auth result, auth.consumerAddress, throughout
service operations instead of task.consumerAddress. In
src/components/core/service/extendService.ts lines 54-72, update job lookup,
ownership/ACL checks, escrow calls, and persistence; in
src/components/core/service/stopService.ts lines 41-60, update lookup, ownership
checks, and stopService calls. Preserve token validation while ensuring every
operation targets the authenticated consumer.
src/components/database/sqliteAuthToken.ts-37-46 (1)

37-46: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not suppress every schema-migration failure.

The catch also hides permission, corruption, I/O, and malformed-schema errors. Startup then succeeds with no chainId column, and later inserts fail. Check the schema before altering it or rethrow anything other than “column already exists.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 37 - 46, Update the
migration try/catch around the synchronous ALTER TABLE exec to suppress only the
expected duplicate-column case. Check whether authTokens already contains
chainId before altering it, or inspect the caught error and rethrow any failure
that is not specifically “column already exists,” so permission, corruption,
I/O, and malformed-schema errors propagate during startup.
src/components/database/sqliteAuthToken.ts-24-25 (1)

24-25: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the require-await suppressions.

These provider operations are synchronous now. Make the internal methods return void; the public database wrapper can retain its asynchronous API.

As per coding guidelines, require-await violations are errors and unnecessary async functions must not be left in place.

Also applies to: 49-50, 104-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 24 - 25, Remove the
require-await suppression comments and async modifiers from the synchronous
internal provider methods, including createTable and the methods at the other
referenced locations, changing their return types to void. Keep the public
database wrapper’s asynchronous API unchanged and update any internal calls as
needed to work without awaiting these methods.

Source: Coding guidelines

src/components/database/sqliteAuthToken.ts-95-97 (1)

95-97: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not write authentication tokens to logs.

The token is a bearer credential; anyone with log access could replay it before invalidation completes or retrieve it from retained logs.

Proposed fix
-      DATABASE_LOGGER.info(`Auth token ${token} is invalid`)
+      DATABASE_LOGGER.info('Expired auth token is being invalidated')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 95 - 97, Remove the
bearer token value from the log message in the invalid-token branch of the token
validation method, while preserving the invalidation call and informational
logging with a non-sensitive message.

Source: Linters/SAST tools

src/components/core/service/startService.ts-111-123 (1)

111-123: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject decrypted userData unless it is a plain object.

Valid JSON can still be null, an array, or a primitive. Those values pass this pre-escrow check but cannot safely become the container environment map, causing a delayed background failure.

Proposed fix
       if (task.userData) {
         try {
-          await decryptUserData(task.userData, node.getKeyManager())
+          const userData = await decryptUserData(task.userData, node.getKeyManager())
+          if (
+            userData === null ||
+            typeof userData !== 'object' ||
+            Array.isArray(userData)
+          ) {
+            throw new Error('userData must decrypt to an object')
+          }
         } catch {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/startService.ts` around lines 111 - 123, Update
the userData validation in the startService flow around decryptUserData so it
captures the decrypted value and accepts it only when it is a non-null plain
object, rejecting arrays and primitive values through
buildInvalidParametersResponse/buildInvalidRequestMessage. Preserve the existing
ECIES decryption error response for values that cannot be decrypted.
src/components/core/service/startService.ts-36-37 (1)

36-37: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require a finite positive integer duration.

parseInt() accepts values such as "3600junk" and lets NaN pass because NaN <= 0 is false. Invalid durations can consequently reach pricing and job persistence.

Proposed fix
-      if (parseInt(String(command.duration)) <= 0)
+      if (
+        typeof command.duration !== 'number' ||
+        !Number.isSafeInteger(command.duration) ||
+        command.duration <= 0
+      )
         return buildInvalidRequestMessage('Invalid duration')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/startService.ts` around lines 36 - 37, Update the
duration validation in startService to require a finite positive integer: reject
non-numeric values, NaN, Infinity, decimals, and strings with trailing
characters such as “3600junk,” while preserving valid positive integer durations
before pricing or persistence.
🟡 Minor comments (6)
docs/env.md-198-198 (1)

198-198: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use level-three headings below ## Compute.

These level-four headings skip a hierarchy level and fail the reported Markdown lint rule.

Also applies to: 216-216, 243-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/env.md` at line 198, Update the “Connection-level fields” heading and
the other affected headings to level-three headings under “## Compute,”
preserving their existing titles and content while maintaining the required
Markdown heading hierarchy.

Source: Linters/SAST tools

src/utils/config/schemas.ts-285-293 (1)

285-293: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Detect split shell invocations.

Because command is an argument array, the normal form ['sh', '-c', payload] never matches /sh\s+-c/ within one argument. Check adjacent arguments as well, otherwise the intended injection warning is bypassed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 285 - 293, Update the
shell-injection detection in the tmpl.command iteration to also inspect adjacent
command arguments, so split invocations such as “sh” followed by “-c” trigger
the existing warning. Preserve detection of inline “sh -c” patterns and
backticks, and keep the current warning context and index behavior.
src/@types/OceanNode.ts-109-109 (1)

109-109: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the documented default path.

config.json uses docs/serviceTemplates/, while this comment claims the default is databases/serviceTemplates/. Keep the configuration contract unambiguous.

-  serviceTemplatesPath?: string // folder of *.json service templates; defaults to 'databases/serviceTemplates/'
+  serviceTemplatesPath?: string // folder of *.json service templates; defaults to 'docs/serviceTemplates/'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/`@types/OceanNode.ts at line 109, Update the documentation comment for
serviceTemplatesPath in OceanNode to state the configured default path
docs/serviceTemplates/ instead of databases/serviceTemplates/, keeping the
configuration contract unambiguous.
src/test/integration/services.test.ts-223-224 (1)

223-224: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the unnecessary async modifier and lint suppression

readLogsWithTimeout returns a Promise directly, so async is redundant here. Dropping it also removes the require-await disable.

Proposed fix
-  // eslint-disable-next-line require-await
-  async function readLogsWithTimeout(
+  function readLogsWithTimeout(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/services.test.ts` around lines 223 - 224, Update
readLogsWithTimeout to remove the unnecessary async modifier and return the
existing Promise directly; also delete the accompanying require-await eslint
suppression.

Source: Coding guidelines

docs/Ocean Node.postman_collection.json-5-20 (1)

5-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the issuer peer ID required when creating auth tokens.

CREATE_AUTH_TOKEN signs consumerAddress + nonce + command + issuerPeerId, but the collection describes only the first three fields and provides no peer-ID variable. Following this example therefore produces an invalid signature. Add the node peer ID and the command-specific signing instructions.

Also applies to: 785-799

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/Ocean` Node.postman_collection.json around lines 5 - 20, Update the
collection description and variables around CREATE_AUTH_TOKEN to document that
its signature includes issuerPeerId in addition to consumerAddress, nonce, and
command. Add a dedicated issuer peer ID collection variable with an appropriate
placeholder, and include command-specific signing guidance so generated
signatures use the required four fields.
src/test/unit/service/serviceUtils.test.ts-99-121 (1)

99-121: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Release successful allocations when any concurrent allocation fails.

Promise.all() can reject after some ports were reserved, before ports is assigned or the finally block is entered. Those reservations then leak into subsequent tests.

Proposed fix
-      const ports = await Promise.all(
+      const results = await Promise.allSettled(
         Array.from({ length: count }, () => allocateHostPort(rangeStart, rangeEnd))
       )
+      const ports = results.flatMap((result) =>
+        result.status === 'fulfilled' ? [result.value] : []
+      )
       try {
+        const failure = results.find((result) => result.status === 'rejected')
+        if (failure?.status === 'rejected') throw failure.reason
+
         expect(new Set(ports).size).to.equal(count) // all unique
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceUtils.test.ts` around lines 99 - 121, Update the
allocateHostPort concurrency test to track successfully resolved ports
independently of the Promise.all result, and release those ports in cleanup even
when another allocation rejects before ports is assigned. Preserve the
uniqueness and range assertions for successful allocations while ensuring every
reserved port is passed to releaseHostPort.
🧹 Nitpick comments (6)
src/components/database/sqliteCompute.ts (1)

149-167: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Index the service lifecycle queries.

The two-second engine loop filters service_jobs by clusterHash, status, and expiresAt. Without a matching index, every pending/running/expiry check scans the growing history table.

Proposed fix
       CREATE TABLE IF NOT EXISTS service_jobs (
         serviceId TEXT PRIMARY KEY,
         owner TEXT,
         clusterHash TEXT,
         status INTEGER,
         expiresAt INTEGER,
         dateCreated TEXT,
         body BLOB
       );
+
+      CREATE INDEX IF NOT EXISTS service_jobs_lifecycle_idx
+      ON service_jobs (clusterHash, status, expiresAt);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteCompute.ts` around lines 149 - 167, Update
createServiceTable to add an index on service_jobs covering the lifecycle query
filters clusterHash, status, and expiresAt, preferably alongside the table
creation using CREATE INDEX IF NOT EXISTS. Keep the existing table schema and
error handling intact.
docs/compute-pricing.md (1)

44-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to the fenced code block.

As highlighted by static analysis, this fenced code block lacks a language specifier. Using text will resolve the linting warning and improve rendering.

📝 Proposed fix
-```
+```text
 DOCKER_COMPUTE_ENVIRONMENTS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/compute-pricing.md` at line 44, Update the fenced code block containing
DOCKER_COMPUTE_ENVIRONMENTS to specify the text language, using a ```text fence
while preserving the block’s contents.

Source: Linters/SAST tools

docs/serviceTemplates/vllm-dual-lite-gpu.json (1)

6-6: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the vLLM images used by these templates to tested tags or digests. latest is used across all vLLM service templates here, so the container content can drift independently of the memory and CLI assumptions in these commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/serviceTemplates/vllm-dual-lite-gpu.json` at line 6, Update the vLLM
image tag in the service templates, including the configuration containing the
"tag" field, to use a tested, immutable version tag or digest instead of
"latest"; apply the same pinned image version consistently across all vLLM
templates while preserving the existing memory and CLI settings.
src/test/unit/service/serviceJobsDatabase.test.ts (2)

289-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make each resource-accounting test self-contained.

Lines 304-310 depend on the CPU job created by the preceding test, and Lines 336-360 depend on the GPU job created earlier. Running either test alone or changing execution order produces different results. Seed and clean up each test’s required jobs in lifecycle hooks or within the test itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceJobsDatabase.test.ts` around lines 289 - 360,
Make the resource-accounting tests around getUsedResources and
checkIfResourcesAreAvailable self-contained by creating their required CPU or
GPU service jobs within each test or a scoped setup hook. Remove reliance on
jobs seeded by preceding tests, and clean up created jobs afterward so execution
order and isolated runs produce the same results.

27-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the require-await suppression here

These overrides don’t await anything. Return Promise.resolve(...) from each method instead of keeping async and disabling require-await for the whole helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceJobsDatabase.test.ts` around lines 27 - 67,
Remove the require-await suppression around SharedAccountingEngine and make each
override non-async by returning Promise.resolve(...) with the existing values.
Update getComputeEnvironments, checkDockerImage, startComputeJob,
stopComputeJob, getComputeJobStatus, getComputeJobResult, and
cleanupExpiredStorage while preserving their current return types and results.

Source: Coding guidelines

src/test/integration/escrow.test.ts (1)

227-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the ReLock test create its own lock.

This test relies on state created by the preceding test, so running it with --grep, retrying it, or continuing after an earlier failure cannot reliably exercise ReLock. Create the prerequisite lock in this test or a lifecycle hook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/escrow.test.ts` around lines 227 - 258, Update the
“indexes a ReLock event” test to create its prerequisite lock before calling
reLock, rather than relying on state from the preceding test. Reuse the existing
lock-creation setup and ensure the locally created lock uses the same jobId,
payer, token, and initial amount values needed by the assertions; keep the
ReLock indexing and event validation unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b7e68224-98a7-4c94-bf93-b1734519f79b

📥 Commits

Reviewing files that changed from the base of the PR and between 83b5cf9 and 703a642.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (83)
  • .dockerignore
  • .env.example
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
  • .nvmrc
  • CLAUDE.md
  • Dockerfile
  • README.md
  • config.json
  • docs/API.md
  • docs/GPU.md
  • docs/Ocean Node.postman_collection.json
  • docs/compute-pricing.md
  • docs/env.md
  • docs/serviceTemplates/README.md
  • docs/serviceTemplates/llamacpp-phi4-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-gpu.json
  • docs/serviceTemplates/vllm-hf-model.json
  • docs/serviceTemplates/vllm-nomic-embed.json
  • docs/serviceTemplates/vllm-qwen-0_5b.json
  • docs/services.md
  • package.json
  • scripts/ocean-node-quickstart.sh
  • src/@types/C2D/C2D.ts
  • src/@types/C2D/ServiceOnDemand.ts
  • src/@types/Escrow.ts
  • src/@types/OceanNode.ts
  • src/@types/commands.ts
  • src/components/Auth/index.ts
  • src/components/Indexer/processor.ts
  • src/components/Indexer/processors/EscrowEventProcessor.ts
  • src/components/c2d/compute_engine_base.ts
  • src/components/c2d/compute_engine_docker.ts
  • src/components/c2d/compute_engines.ts
  • src/components/c2d/serviceResourceMatching.ts
  • src/components/core/compute/startCompute.ts
  • src/components/core/handler/authHandler.ts
  • src/components/core/handler/coreHandlersRegistry.ts
  • src/components/core/handler/handler.ts
  • src/components/core/handler/persistentStorage.ts
  • src/components/core/service/extendService.ts
  • src/components/core/service/getStatus.ts
  • src/components/core/service/getStreamableLogs.ts
  • src/components/core/service/getTemplates.ts
  • src/components/core/service/index.ts
  • src/components/core/service/restartService.ts
  • src/components/core/service/startService.ts
  • src/components/core/service/stopService.ts
  • src/components/core/service/templateLoader.ts
  • src/components/core/service/utils.ts
  • src/components/core/utils/escrow.ts
  • src/components/core/utils/nonceHandler.ts
  • src/components/database/AuthTokenDatabase.ts
  • src/components/database/C2DDatabase.ts
  • src/components/database/ElasticSchemas.ts
  • src/components/database/SQLLiteConfigDatabase.ts
  • src/components/database/SQLLiteNonceDatabase.ts
  • src/components/database/TypesenseSchemas.ts
  • src/components/database/sqlite.ts
  • src/components/database/sqliteAuthToken.ts
  • src/components/database/sqliteClient.ts
  • src/components/database/sqliteCompute.ts
  • src/components/httpRoutes/compute.ts
  • src/components/persistentStorage/PersistentStorageFactory.ts
  • src/test/integration/algorithmsAccess.test.ts
  • src/test/integration/auth.test.ts
  • src/test/integration/escrow.test.ts
  • src/test/integration/imageCleanup.test.ts
  • src/test/integration/services.test.ts
  • src/test/unit/auth/token.test.ts
  • src/test/unit/c2d/serviceResourceMatching.test.ts
  • src/test/unit/compute.test.ts
  • src/test/unit/service/serviceHandlers.test.ts
  • src/test/unit/service/serviceJobsDatabase.test.ts
  • src/test/unit/service/serviceSchemas.test.ts
  • src/test/unit/service/serviceUtils.test.ts
  • src/test/unit/service/templateLoader.test.ts
  • src/test/unit/sqliteClient.test.ts
  • src/test/utils/signature.ts
  • src/utils/config/constants.ts
  • src/utils/config/schemas.ts
  • src/utils/constants.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

116-121: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable credential persistence for the external Barge checkout.

The checked-out repository's scripts run immediately afterward and can access the persisted GitHub token. Add persist-credentials: false to limit supply-chain exposure.

Proposed fix
         with:
           repository: 'oceanprotocol/barge'
           path: 'barge'
           ref: '43cfdfd21154a2bae00770b779e7c39390ff5043'
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 116 - 121, Update the “Checkout Barge”
actions/checkout step to set persist-credentials to false in its with
configuration, while preserving the existing repository, path, and ref values.

Source: Linters/SAST tools

🟠 Major comments (25)
docs/env.md-230-234 (1)

230-234: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve environment-specific constraints during migration.

Line 261 says to move all constraints to connection-level resources, but Line 234 documents them as valid environment overrides. Moving them unconditionally makes environment-specific requirements pool-wide and changes scheduling behavior. Only move constraints intended for every environment.

Also applies to: 243-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/env.md` around lines 230 - 234, Update the migration guidance around the
environment-level resources and constraints so environment-specific constraints
remain attached to their environment. Move constraints to connection-level
resources only when they are intended to apply to every environment; preserve
per-environment overrides, including empty constraints used to remove inherited
restrictions.
src/utils/config/schemas.ts-193-201 (1)

193-201: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject every legacy hardware field, not only two of them.

.passthrough() accepts platform, memoryTotal, type, kind, shareable, and other unknown fields, while the migration check only detects init and driverVersion. This contradicts the documented startup-error contract. Make resource references strict or explicitly reject the complete hardware-field set.

Proposed strict resource-reference schema
 export const EnvironmentResourceRefSchema = z
   .object({
     id: z.string(),
     total: z.number().optional(),
     min: z.number().optional(),
     max: z.number().optional(),
     constraints: z.array(ResourceConstraintSchema).optional()
   })
-  .passthrough()
+  .strict()

Also applies to: 437-450

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 193 - 201, Update
EnvironmentResourceRefSchema and the corresponding schema at the additional
resource-reference definition to reject unknown legacy hardware fields instead
of accepting them via passthrough. Replace the permissive object behavior with
strict validation, or explicitly forbid the complete legacy hardware-field set
including platform, memoryTotal, type, kind, shareable, init, and driverVersion,
while preserving the existing valid fields.
src/utils/config/schemas.ts-454-471 (1)

454-471: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate free-tier resource references against the pool.

Only env.resources is checked against poolIds; env.free.resources can reference an unknown resource and pass startup validation. Apply the same validation to both resource lists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 454 - 471, Extend the
resource-reference validation in the Docker configuration schema to also iterate
over each environment’s free-tier resources, such as env.free.resources, using
the existing poolIds set. Report unknown IDs through ctx.addIssue with the
corresponding free-resource path, while preserving the current env.resources
validation.
src/components/c2d/compute_engine_docker.ts-620-642 (1)

620-642: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop every cron and delayed startup task.

stop() drains the internal loop and service starts, but startCrons() also creates untracked startup timeouts and scanDBUpdateTimer. Those callbacks can continue Docker/database work after the engine is stopped.

Track and clear all three startup timeouts, and clear scanDBUpdateTimer alongside the image-cleanup and payment timers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 620 - 642, Update
startCrons() to track all three delayed startup timeout handles so stop() can
clear them and prevent callbacks from running after shutdown. In stop(), clear
and reset those startup handles, and also clear scanDBUpdateTimer alongside the
existing image-cleanup and payment timers. Preserve the existing internal-loop
and service-start draining behavior.
src/components/c2d/compute_engine_docker.ts-3104-3115 (1)

3104-3115: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when the image scan cannot complete.

checkImageVulnerability() returns vulnerable: false when scanning fails, so both new service paths accept an unscanned image even when scanImages is enabled. Treat a missing or invalid report as a scan failure and reject the image.

Also applies to: 3164-3176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3104 - 3115, Update
the image-scan handling around checkImageVulnerability in both affected service
paths to fail closed when the scan result is missing, invalid, or indicates
scanning could not complete, rather than relying only on vulnerable. When
scanImages is enabled, reject and remove the image for any such failure, while
preserving the existing vulnerability rejection behavior and error context.
src/components/c2d/compute_engine_docker.ts-3475-3478 (1)

3475-3478: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release reservations only after confirmed Docker teardown.

cleanupServiceDocker() swallows stop/removal errors while releasing CPU cores, and its caller then releases host ports. stopService() likewise releases CPUs before cleanup. A leaked running container can therefore overlap another service on the same cores or port.

Return cleanup success and release CPU/port reservations only after removal succeeds or Docker confirms the resource is already absent.

Also applies to: 3513-3525, 3586-3617

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3475 - 3478, Update
cleanupServiceDocker and its callers, including the stopService path and the
catch block around container creation, to return and check whether Docker
teardown succeeded or confirmed the container is absent. Only release CPU cores
and host ports after that confirmed outcome; do not swallow teardown failures
and release reservations unconditionally. Preserve the existing refund behavior
while ensuring all affected paths use the cleanup result before releasing
reservations.
src/components/c2d/compute_engine_base.ts-494-508 (1)

494-508: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Deduplicate discrete capacity as well as discrete usage.

Each environment carries the same globally aggregated discrete inUse, so the code takes its maximum. Summing each environment’s discrete total still multiplies the same physical pool and can permit over-allocation when physicalLimits has no entry.

Proposed fix
     let globalUsed = 0
     let globalTotal = 0
     let discreteInUse: number | undefined
+    let discreteTotal: number | undefined
     for (const e of allEnvironments) {
       const res = this.getResource(e.resources, resourceId)
       if (res) {
-        globalTotal += res.total || 0
         if (res.kind === 'discrete') {
+          discreteTotal = Math.max(discreteTotal ?? 0, res.total || 0)
           discreteInUse = Math.max(discreteInUse ?? 0, res.inUse || 0)
         } else {
+          globalTotal += res.total || 0
           globalUsed += res.inUse || 0
         }
       }
     }
+    if (discreteTotal !== undefined) globalTotal += discreteTotal
     if (discreteInUse !== undefined) globalUsed += discreteInUse
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 494 - 508, Update the
resource aggregation loop in the compute-engine method containing discreteInUse
so discrete total capacity is deduplicated like discrete usage: track the
maximum res.total for discrete resources, exclude it from globalTotal per
environment, and add the deduplicated value once after iteration. Preserve
summed totals for non-discrete resources and the existing discrete inUse
behavior.
src/components/core/utils/escrow.ts-50-59 (1)

50-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject reverted or timed-out escrow transactions before continuing. provider.waitForTransaction can return a receipt even when the tx reverted, and it can still return null on timeout. This helper should fail unless the receipt exists and status === 1; otherwise the dependent claim/extend flow can continue without a confirmed lock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/utils/escrow.ts` around lines 50 - 59, The
waitForTransaction helper must validate the provider result before returning.
Capture the receipt from provider.waitForTransaction and throw when it is null
or its status is not 1, while preserving the existing chain, confirmation, and
timeout parameters.
src/components/c2d/compute_engine_base.ts-87-143 (1)

87-143: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the lint suppressions on these service stubs. Return Promise.resolve(...) from the defaults and prefix unused params with _ instead of keeping async no-op methods here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 87 - 143, Remove the
eslint-disable comments from createServiceJob, processServiceStart, stopService,
restartService, getServiceStatus, and getServiceStreamableLogs. Rename unused
parameters in these stubs with an _ prefix, remove async from the no-op methods,
and return Promise.resolve with the existing default values (null, empty array,
or undefined as appropriate).

Source: Coding guidelines

src/test/unit/compute.test.ts-1385-1415 (1)

1385-1415: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep the escrow refundable until container.start() succeeds. processServiceStart claims the lock before container creation/start, so a failure after that point records claimTx and bypasses the refund path. The consumer is charged even though the service never reaches Running, which conflicts with the documented claim-on-success/refund-on-failure flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/compute.test.ts` around lines 1385 - 1415, Update
processServiceStart so the escrow lock remains refundable until
container.start() completes successfully; move claimTx creation/claimLock
handling after the successful start, while preserving cleanup and Error status
on failures. Update the related documentation in docs/API.md (1927-1930) if
needed to match this claim-on-success/refund-on-failure behavior; the test
scenarios in src/test/unit/compute.test.ts (1385-1415) require no direct change
beyond validating the corrected flow.
src/components/core/service/utils.ts-84-100 (1)

84-100: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Scan the full range before reporting port exhaustion.

Trying only 50 random candidates can reject service startup even when many ports remain free. Pick a random starting offset, then inspect each candidate once.

Proposed fix
   const size = rangeEnd - rangeStart + 1
-  for (let i = 0; i < Math.min(size, 50); i++) {
-    const candidate = rangeStart + Math.floor(Math.random() * size)
+  const offset = Math.floor(Math.random() * size)
+  for (let i = 0; i < size; i++) {
+    const candidate = rangeStart + ((offset + i) % size)
     if (allocatedPorts.has(candidate)) continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/utils.ts` around lines 84 - 100, Update
allocateHostPort to scan the entire range exactly once instead of limiting
attempts to 50 random candidates. Choose a random starting offset, iterate
through all range offsets with wraparound, and retain the existing
allocatedPorts reservation and release behavior while checking each candidate.
src/components/httpRoutes/compute.ts-480-485 (1)

480-485: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log the signature and nonce.

req.query includes authentication material from Lines 492-493. Serializing the complete query exposes it to application logs. Log only non-sensitive fields such as serviceId, node, and since.

Proposed fix
     HTTP_LOGGER.logMessage(
-      `ServiceGetStreamableLogsCommand request received with query: ${JSON.stringify(
-        req.query
-      )}`,
+      `ServiceGetStreamableLogsCommand request received: ${JSON.stringify({
+        serviceId: req.query.serviceId,
+        node: req.query.node,
+        since: req.query.since
+      })}`,
       true
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/httpRoutes/compute.ts` around lines 480 - 485, Update the
request logging around ServiceGetStreamableLogsCommand to avoid serializing the
full req.query, which contains the signature and nonce. Log only the
non-sensitive fields serviceId, node, and since while preserving the existing
HTTP_LOGGER.logMessage call behavior.
src/components/database/sqlite.ts-22-39 (1)

22-39: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Preserve the Promise API without suppressing require-await.

The synchronous migration leaves every method async without an await and adds repeated lint suppressions. Use a small helper that converts synchronous results or exceptions into resolved/rejected Promises, then remove async and the disables.

Proposed pattern
+  private execute<T>(operation: () => T): Promise<T> {
+    try {
+      return Promise.resolve(operation())
+    } catch (error) {
+      return Promise.reject(error)
+    }
+  }

-  // eslint-disable-next-line require-await
-  async createNonce(address: string, nonce: number) {
-    this.db.run(insertSQL, [address, nonce])
-    return { id: address, nonce }
+  createNonce(address: string, nonce: number) {
+    return this.execute(() => {
+      this.db.run(insertSQL, [address, nonce])
+      return { id: address, nonce }
+    })
   }

As per coding guidelines, “Treat require-await violations as errors; do not leave unnecessary async functions.”

Also applies to: 42-80, 83-102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqlite.ts` around lines 22 - 39, Update
createTableForNonce, createTableForConfig, and the other migration methods in
the same file to remove async and require-await suppressions while preserving
their Promise-based API. Add and reuse a small helper that executes synchronous
database operations and returns a resolved Promise with the result or a rejected
Promise when an exception occurs.

Source: Coding guidelines

src/components/core/service/getStreamableLogs.ts-84-87 (1)

84-87: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose raw engine errors to clients.

Docker/runtime errors can contain internal paths, container details, or command data. Keep the detailed message in server logs and return a stable generic 500 response.

       CORE_LOGGER.error(message)
-      return { stream: null, status: { httpStatus: 500, error: message } }
+      return {
+        stream: null,
+        status: { httpStatus: 500, error: 'Failed to stream service logs' }
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/getStreamableLogs.ts` around lines 84 - 87,
Update the catch block in getStreamableLogs to keep the detailed error message
in CORE_LOGGER.error, but replace the returned status error with a stable
generic 500-safe message. Preserve the null stream and HTTP 500 response while
ensuring raw engine details are never exposed to clients.
src/components/core/service/getStreamableLogs.ts-1-14 (1)

1-14: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use Readable for the shared stream response type. The cast here is masking a broader type mismatch: P2PCommandResponse.stream and the C2D log producer signatures still use Stream/NodeJS.ReadableStream. Switch the shared response type and engine methods to Readable, then return the stream directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/getStreamableLogs.ts` around lines 1 - 14, Update
the shared stream typing used by getStreamableLogs: replace
Stream/NodeJS.ReadableStream with Node’s Readable in P2PCommandResponse and the
related C2D engine log-producer methods, then remove the cast and return the
Readable stream directly from the handler. Preserve existing stream behavior and
imports while aligning all affected signatures.

Source: Coding guidelines

src/components/database/sqliteClient.ts-35-38 (1)

35-38: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Avoid a 5-second synchronous lock wait. DatabaseSync runs on the main thread, so PRAGMA busy_timeout = 5000 can block request handling for up to 5 seconds whenever SQLite is locked. Use a shorter timeout with retry/backoff, or move SQLite work off the main thread.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteClient.ts` around lines 35 - 38, Update the
SQLite initialization flow around DatabaseSync and the busy_timeout pragma to
avoid blocking the main thread for up to five seconds: use a substantially
shorter timeout and implement bounded retry/backoff for transient lock failures,
preserving normal database operations and propagating persistent errors.
src/components/database/sqliteClient.ts-27-59 (1)

27-59: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Add close() to SqliteClient and wire wrapper shutdown
SqliteClient owns a persistent DatabaseSync handle, but SQLiteProvider, SQLiteAuthToken, SQLiteCompute, and PersistentStorageFactory never release it. Expose a close method and call it from the owning teardown path so shutdown and tests don’t leak file handles or leave the DB locked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteClient.ts` around lines 27 - 59, Add a public
close() method to SqliteClient that closes its DatabaseSync handle, then update
the teardown or shutdown paths of SQLiteProvider, SQLiteAuthToken,
SQLiteCompute, and PersistentStorageFactory to invoke the client’s close method
when they release their resources. Ensure each owner closes the database exactly
once, including test cleanup paths.
src/test/unit/auth/token.test.ts-90-101 (1)

90-101: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the require-await suppression.

Return a resolved promise from the fake instead of declaring an unnecessary async function.

Proposed fix
-      // eslint-disable-next-line require-await
-      sendTo: async (_peer: string, _msg: string) => {
+      sendTo: (_peer: string, _msg: string) => {
         tracker.calls++
         const httpStatus = opts.httpStatus ?? 200
         if (httpStatus !== 200) {
-          return { status: { httpStatus } }
+          return Promise.resolve({ status: { httpStatus } })
         }
-        return {
+        return Promise.resolve({
           status: { httpStatus: 200 },
           stream: Readable.from(JSON.stringify(opts.verdict))
-        }
+        })
       }

As per coding guidelines, “Treat require-await violations as errors; do not leave unused variables or unnecessary async functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/auth/token.test.ts` around lines 90 - 101, Update the fake
sendTo implementation in the token test to remove the unnecessary async
declaration and its eslint-disable suppression, while preserving the existing
tracker, status, and stream response behavior by returning a resolved promise
explicitly.

Source: Coding guidelines

src/components/core/service/extendService.ts-20-31 (1)

20-31: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require a finite positive integer duration.

parseInt allows values such as NaN, Infinity, or partially numeric strings to bypass this check and later corrupt expiry, duration, or pricing calculations.

Proposed fix
-    if (commandValidation.valid) {
-      if (parseInt(String(command.additionalDuration)) <= 0)
+    if (commandValidation.valid) {
+      if (
+        !Number.isSafeInteger(command.additionalDuration) ||
+        command.additionalDuration <= 0
+      )
         return buildInvalidRequestMessage('Invalid additionalDuration')
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 20 - 31, Update
validate in the ServiceExtendCommand validation flow to require
additionalDuration to be a finite, strictly positive integer, rejecting NaN,
Infinity, fractional values, and partially numeric strings. Preserve the
existing required-parameter validation and InvalidRequest response for invalid
durations.
src/components/core/service/extendService.ts-54-72 (1)

54-72: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the authenticated address for service operations.

The auth result is ignored after validation, so a valid token can be presented for one address while lookup, ownership checks, escrow, and mutation still run against task.consumerAddress.

  • src/components/core/service/extendService.ts#L54-L72: use auth.consumerAddress for job lookup, ACL checks, escrow calls, and persistence.
  • src/components/core/service/stopService.ts#L41-L60: use auth.consumerAddress for lookup, ownership checks, and stopService.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 54 - 72, Use the
authenticated address from the auth result, auth.consumerAddress, throughout
service operations instead of task.consumerAddress. In
src/components/core/service/extendService.ts lines 54-72, update job lookup,
ownership/ACL checks, escrow calls, and persistence; in
src/components/core/service/stopService.ts lines 41-60, update lookup, ownership
checks, and stopService calls. Preserve token validation while ensuring every
operation targets the authenticated consumer.
src/components/database/sqliteAuthToken.ts-37-46 (1)

37-46: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not suppress every schema-migration failure.

The catch also hides permission, corruption, I/O, and malformed-schema errors. Startup then succeeds with no chainId column, and later inserts fail. Check the schema before altering it or rethrow anything other than “column already exists.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 37 - 46, Update the
migration try/catch around the synchronous ALTER TABLE exec to suppress only the
expected duplicate-column case. Check whether authTokens already contains
chainId before altering it, or inspect the caught error and rethrow any failure
that is not specifically “column already exists,” so permission, corruption,
I/O, and malformed-schema errors propagate during startup.
src/components/database/sqliteAuthToken.ts-24-25 (1)

24-25: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the require-await suppressions.

These provider operations are synchronous now. Make the internal methods return void; the public database wrapper can retain its asynchronous API.

As per coding guidelines, require-await violations are errors and unnecessary async functions must not be left in place.

Also applies to: 49-50, 104-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 24 - 25, Remove the
require-await suppression comments and async modifiers from the synchronous
internal provider methods, including createTable and the methods at the other
referenced locations, changing their return types to void. Keep the public
database wrapper’s asynchronous API unchanged and update any internal calls as
needed to work without awaiting these methods.

Source: Coding guidelines

src/components/database/sqliteAuthToken.ts-95-97 (1)

95-97: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not write authentication tokens to logs.

The token is a bearer credential; anyone with log access could replay it before invalidation completes or retrieve it from retained logs.

Proposed fix
-      DATABASE_LOGGER.info(`Auth token ${token} is invalid`)
+      DATABASE_LOGGER.info('Expired auth token is being invalidated')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteAuthToken.ts` around lines 95 - 97, Remove the
bearer token value from the log message in the invalid-token branch of the token
validation method, while preserving the invalidation call and informational
logging with a non-sensitive message.

Source: Linters/SAST tools

src/components/core/service/startService.ts-111-123 (1)

111-123: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject decrypted userData unless it is a plain object.

Valid JSON can still be null, an array, or a primitive. Those values pass this pre-escrow check but cannot safely become the container environment map, causing a delayed background failure.

Proposed fix
       if (task.userData) {
         try {
-          await decryptUserData(task.userData, node.getKeyManager())
+          const userData = await decryptUserData(task.userData, node.getKeyManager())
+          if (
+            userData === null ||
+            typeof userData !== 'object' ||
+            Array.isArray(userData)
+          ) {
+            throw new Error('userData must decrypt to an object')
+          }
         } catch {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/startService.ts` around lines 111 - 123, Update
the userData validation in the startService flow around decryptUserData so it
captures the decrypted value and accepts it only when it is a non-null plain
object, rejecting arrays and primitive values through
buildInvalidParametersResponse/buildInvalidRequestMessage. Preserve the existing
ECIES decryption error response for values that cannot be decrypted.
src/components/core/service/startService.ts-36-37 (1)

36-37: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require a finite positive integer duration.

parseInt() accepts values such as "3600junk" and lets NaN pass because NaN <= 0 is false. Invalid durations can consequently reach pricing and job persistence.

Proposed fix
-      if (parseInt(String(command.duration)) <= 0)
+      if (
+        typeof command.duration !== 'number' ||
+        !Number.isSafeInteger(command.duration) ||
+        command.duration <= 0
+      )
         return buildInvalidRequestMessage('Invalid duration')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/startService.ts` around lines 36 - 37, Update the
duration validation in startService to require a finite positive integer: reject
non-numeric values, NaN, Infinity, decimals, and strings with trailing
characters such as “3600junk,” while preserving valid positive integer durations
before pricing or persistence.
🟡 Minor comments (6)
docs/env.md-198-198 (1)

198-198: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use level-three headings below ## Compute.

These level-four headings skip a hierarchy level and fail the reported Markdown lint rule.

Also applies to: 216-216, 243-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/env.md` at line 198, Update the “Connection-level fields” heading and
the other affected headings to level-three headings under “## Compute,”
preserving their existing titles and content while maintaining the required
Markdown heading hierarchy.

Source: Linters/SAST tools

src/utils/config/schemas.ts-285-293 (1)

285-293: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Detect split shell invocations.

Because command is an argument array, the normal form ['sh', '-c', payload] never matches /sh\s+-c/ within one argument. Check adjacent arguments as well, otherwise the intended injection warning is bypassed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 285 - 293, Update the
shell-injection detection in the tmpl.command iteration to also inspect adjacent
command arguments, so split invocations such as “sh” followed by “-c” trigger
the existing warning. Preserve detection of inline “sh -c” patterns and
backticks, and keep the current warning context and index behavior.
src/@types/OceanNode.ts-109-109 (1)

109-109: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the documented default path.

config.json uses docs/serviceTemplates/, while this comment claims the default is databases/serviceTemplates/. Keep the configuration contract unambiguous.

-  serviceTemplatesPath?: string // folder of *.json service templates; defaults to 'databases/serviceTemplates/'
+  serviceTemplatesPath?: string // folder of *.json service templates; defaults to 'docs/serviceTemplates/'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/`@types/OceanNode.ts at line 109, Update the documentation comment for
serviceTemplatesPath in OceanNode to state the configured default path
docs/serviceTemplates/ instead of databases/serviceTemplates/, keeping the
configuration contract unambiguous.
src/test/integration/services.test.ts-223-224 (1)

223-224: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the unnecessary async modifier and lint suppression

readLogsWithTimeout returns a Promise directly, so async is redundant here. Dropping it also removes the require-await disable.

Proposed fix
-  // eslint-disable-next-line require-await
-  async function readLogsWithTimeout(
+  function readLogsWithTimeout(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/services.test.ts` around lines 223 - 224, Update
readLogsWithTimeout to remove the unnecessary async modifier and return the
existing Promise directly; also delete the accompanying require-await eslint
suppression.

Source: Coding guidelines

docs/Ocean Node.postman_collection.json-5-20 (1)

5-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the issuer peer ID required when creating auth tokens.

CREATE_AUTH_TOKEN signs consumerAddress + nonce + command + issuerPeerId, but the collection describes only the first three fields and provides no peer-ID variable. Following this example therefore produces an invalid signature. Add the node peer ID and the command-specific signing instructions.

Also applies to: 785-799

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/Ocean` Node.postman_collection.json around lines 5 - 20, Update the
collection description and variables around CREATE_AUTH_TOKEN to document that
its signature includes issuerPeerId in addition to consumerAddress, nonce, and
command. Add a dedicated issuer peer ID collection variable with an appropriate
placeholder, and include command-specific signing guidance so generated
signatures use the required four fields.
src/test/unit/service/serviceUtils.test.ts-99-121 (1)

99-121: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Release successful allocations when any concurrent allocation fails.

Promise.all() can reject after some ports were reserved, before ports is assigned or the finally block is entered. Those reservations then leak into subsequent tests.

Proposed fix
-      const ports = await Promise.all(
+      const results = await Promise.allSettled(
         Array.from({ length: count }, () => allocateHostPort(rangeStart, rangeEnd))
       )
+      const ports = results.flatMap((result) =>
+        result.status === 'fulfilled' ? [result.value] : []
+      )
       try {
+        const failure = results.find((result) => result.status === 'rejected')
+        if (failure?.status === 'rejected') throw failure.reason
+
         expect(new Set(ports).size).to.equal(count) // all unique
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceUtils.test.ts` around lines 99 - 121, Update the
allocateHostPort concurrency test to track successfully resolved ports
independently of the Promise.all result, and release those ports in cleanup even
when another allocation rejects before ports is assigned. Preserve the
uniqueness and range assertions for successful allocations while ensuring every
reserved port is passed to releaseHostPort.
🧹 Nitpick comments (6)
src/components/database/sqliteCompute.ts (1)

149-167: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Index the service lifecycle queries.

The two-second engine loop filters service_jobs by clusterHash, status, and expiresAt. Without a matching index, every pending/running/expiry check scans the growing history table.

Proposed fix
       CREATE TABLE IF NOT EXISTS service_jobs (
         serviceId TEXT PRIMARY KEY,
         owner TEXT,
         clusterHash TEXT,
         status INTEGER,
         expiresAt INTEGER,
         dateCreated TEXT,
         body BLOB
       );
+
+      CREATE INDEX IF NOT EXISTS service_jobs_lifecycle_idx
+      ON service_jobs (clusterHash, status, expiresAt);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteCompute.ts` around lines 149 - 167, Update
createServiceTable to add an index on service_jobs covering the lifecycle query
filters clusterHash, status, and expiresAt, preferably alongside the table
creation using CREATE INDEX IF NOT EXISTS. Keep the existing table schema and
error handling intact.
docs/compute-pricing.md (1)

44-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to the fenced code block.

As highlighted by static analysis, this fenced code block lacks a language specifier. Using text will resolve the linting warning and improve rendering.

📝 Proposed fix
-```
+```text
 DOCKER_COMPUTE_ENVIRONMENTS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/compute-pricing.md` at line 44, Update the fenced code block containing
DOCKER_COMPUTE_ENVIRONMENTS to specify the text language, using a ```text fence
while preserving the block’s contents.

Source: Linters/SAST tools

docs/serviceTemplates/vllm-dual-lite-gpu.json (1)

6-6: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the vLLM images used by these templates to tested tags or digests. latest is used across all vLLM service templates here, so the container content can drift independently of the memory and CLI assumptions in these commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/serviceTemplates/vllm-dual-lite-gpu.json` at line 6, Update the vLLM
image tag in the service templates, including the configuration containing the
"tag" field, to use a tested, immutable version tag or digest instead of
"latest"; apply the same pinned image version consistently across all vLLM
templates while preserving the existing memory and CLI settings.
src/test/unit/service/serviceJobsDatabase.test.ts (2)

289-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make each resource-accounting test self-contained.

Lines 304-310 depend on the CPU job created by the preceding test, and Lines 336-360 depend on the GPU job created earlier. Running either test alone or changing execution order produces different results. Seed and clean up each test’s required jobs in lifecycle hooks or within the test itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceJobsDatabase.test.ts` around lines 289 - 360,
Make the resource-accounting tests around getUsedResources and
checkIfResourcesAreAvailable self-contained by creating their required CPU or
GPU service jobs within each test or a scoped setup hook. Remove reliance on
jobs seeded by preceding tests, and clean up created jobs afterward so execution
order and isolated runs produce the same results.

27-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the require-await suppression here

These overrides don’t await anything. Return Promise.resolve(...) from each method instead of keeping async and disabling require-await for the whole helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceJobsDatabase.test.ts` around lines 27 - 67,
Remove the require-await suppression around SharedAccountingEngine and make each
override non-async by returning Promise.resolve(...) with the existing values.
Update getComputeEnvironments, checkDockerImage, startComputeJob,
stopComputeJob, getComputeJobStatus, getComputeJobResult, and
cleanupExpiredStorage while preserving their current return types and results.

Source: Coding guidelines

src/test/integration/escrow.test.ts (1)

227-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the ReLock test create its own lock.

This test relies on state created by the preceding test, so running it with --grep, retrying it, or continuing after an earlier failure cannot reliably exercise ReLock. Create the prerequisite lock in this test or a lifecycle hook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/escrow.test.ts` around lines 227 - 258, Update the
“indexes a ReLock event” test to create its prerequisite lock before calling
reLock, rather than relying on state from the preceding test. Reuse the existing
lock-creation setup and ensure the locally created lock uses the same jobId,
payer, token, and initial amount values needed by the assertions; keep the
ReLock indexing and event validation unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b7e68224-98a7-4c94-bf93-b1734519f79b

📥 Commits

Reviewing files that changed from the base of the PR and between 83b5cf9 and 703a642.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (83)
  • .dockerignore
  • .env.example
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
  • .nvmrc
  • CLAUDE.md
  • Dockerfile
  • README.md
  • config.json
  • docs/API.md
  • docs/GPU.md
  • docs/Ocean Node.postman_collection.json
  • docs/compute-pricing.md
  • docs/env.md
  • docs/serviceTemplates/README.md
  • docs/serviceTemplates/llamacpp-phi4-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-gpu.json
  • docs/serviceTemplates/vllm-hf-model.json
  • docs/serviceTemplates/vllm-nomic-embed.json
  • docs/serviceTemplates/vllm-qwen-0_5b.json
  • docs/services.md
  • package.json
  • scripts/ocean-node-quickstart.sh
  • src/@types/C2D/C2D.ts
  • src/@types/C2D/ServiceOnDemand.ts
  • src/@types/Escrow.ts
  • src/@types/OceanNode.ts
  • src/@types/commands.ts
  • src/components/Auth/index.ts
  • src/components/Indexer/processor.ts
  • src/components/Indexer/processors/EscrowEventProcessor.ts
  • src/components/c2d/compute_engine_base.ts
  • src/components/c2d/compute_engine_docker.ts
  • src/components/c2d/compute_engines.ts
  • src/components/c2d/serviceResourceMatching.ts
  • src/components/core/compute/startCompute.ts
  • src/components/core/handler/authHandler.ts
  • src/components/core/handler/coreHandlersRegistry.ts
  • src/components/core/handler/handler.ts
  • src/components/core/handler/persistentStorage.ts
  • src/components/core/service/extendService.ts
  • src/components/core/service/getStatus.ts
  • src/components/core/service/getStreamableLogs.ts
  • src/components/core/service/getTemplates.ts
  • src/components/core/service/index.ts
  • src/components/core/service/restartService.ts
  • src/components/core/service/startService.ts
  • src/components/core/service/stopService.ts
  • src/components/core/service/templateLoader.ts
  • src/components/core/service/utils.ts
  • src/components/core/utils/escrow.ts
  • src/components/core/utils/nonceHandler.ts
  • src/components/database/AuthTokenDatabase.ts
  • src/components/database/C2DDatabase.ts
  • src/components/database/ElasticSchemas.ts
  • src/components/database/SQLLiteConfigDatabase.ts
  • src/components/database/SQLLiteNonceDatabase.ts
  • src/components/database/TypesenseSchemas.ts
  • src/components/database/sqlite.ts
  • src/components/database/sqliteAuthToken.ts
  • src/components/database/sqliteClient.ts
  • src/components/database/sqliteCompute.ts
  • src/components/httpRoutes/compute.ts
  • src/components/persistentStorage/PersistentStorageFactory.ts
  • src/test/integration/algorithmsAccess.test.ts
  • src/test/integration/auth.test.ts
  • src/test/integration/escrow.test.ts
  • src/test/integration/imageCleanup.test.ts
  • src/test/integration/services.test.ts
  • src/test/unit/auth/token.test.ts
  • src/test/unit/c2d/serviceResourceMatching.test.ts
  • src/test/unit/compute.test.ts
  • src/test/unit/service/serviceHandlers.test.ts
  • src/test/unit/service/serviceJobsDatabase.test.ts
  • src/test/unit/service/serviceSchemas.test.ts
  • src/test/unit/service/serviceUtils.test.ts
  • src/test/unit/service/templateLoader.test.ts
  • src/test/unit/sqliteClient.test.ts
  • src/test/utils/signature.ts
  • src/utils/config/constants.ts
  • src/utils/config/schemas.ts
  • src/utils/constants.ts
🛑 Comments failed to post (4)
src/components/c2d/compute_engine_docker.ts (2)

3272-3279: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Serialize start, stop, and restart per service.

processServiceStart() can continue while stopService() marks the same record stopped, then claim payment, create a container, and overwrite it back to Running. restartService() has the same overlap risk.

Use a per-service mutex or cancellation controller, and re-check persisted status before claiming or creating the container.

Also applies to: 3574-3585, 3633-3646

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3272 - 3279,
Serialize processServiceStart, stopService, and restartService per service using
a shared per-service mutex or cancellation controller. In processServiceStart,
re-read and validate the persisted service status after acquiring serialization
and immediately before claiming payment or creating the container, aborting if
it is no longer Starting; apply equivalent coordination to restartService so
overlapping lifecycle operations cannot restore a stopped service to Running.

3314-3329: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Make Escrow transitions durable and claim only after startup succeeds.

A crash after createLock() or claimLock() but before the following database update loses the on-chain outcome. Additionally, the lock is claimed before port allocation and container startup, so a later Docker failure leaves the consumer charged for a service that never ran.

Persist/reconcile each transaction state idempotently and move final claiming after successful container startup, with compensating teardown if claiming fails.

Also applies to: 3362-3404

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3314 - 3329, Update
the service-job escrow flow around createLock, claimLock, and the startup
sequence to persist each transaction state before and after on-chain operations,
and reconcile existing transaction IDs idempotently after crashes. Move
claimLock until port allocation and container startup have completed
successfully; if claiming fails, perform compensating container teardown and
preserve the recoverable job state.
src/components/c2d/serviceResourceMatching.ts (1)

3-13: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Fix potential undefined dereference when resolving dockerfile.

When dockerfile is provided but serviceId is not, serviceId!.toLowerCase() will throw a runtime error. Ensure serviceId is validated or provide a safe fallback before calling .toLowerCase().

🐛 Proposed fix to guard against undefined `serviceId`
 export function resolveServiceImage(
   image: string,
   tag?: string,
   checksum?: string,
   dockerfile?: string,
   serviceId?: string
 ): string {
-  if (dockerfile) return `${serviceId!.toLowerCase()}-svc-image:latest`
+  if (dockerfile && serviceId) return `${serviceId.toLowerCase()}-svc-image:latest`
   if (checksum) return `${image}@${checksum}`
   return `${image}:${tag ?? 'latest'}`
 }
📝 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.

export function resolveServiceImage(
  image: string,
  tag?: string,
  checksum?: string,
  dockerfile?: string,
  serviceId?: string
): string {
  if (dockerfile && serviceId) return `${serviceId.toLowerCase()}-svc-image:latest`
  if (checksum) return `${image}@${checksum}`
  return `${image}:${tag ?? 'latest'}`
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/serviceResourceMatching.ts` around lines 3 - 13, Update
resolveServiceImage so the dockerfile branch does not dereference an absent
serviceId: validate serviceId before calling toLowerCase and provide the
established safe fallback or handling for missing values. Preserve the checksum
and tag-based resolution branches unchanged.
src/components/core/service/extendService.ts (1)

175-218: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Persist a recoverable extension state before irreversibly claiming payment.

If updateServiceJob fails after claimLock succeeds, the consumer is charged but the service expiry remains unchanged. Persist a pending extension before claiming, then finalize it afterward with idempotent recovery/reconciliation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 175 - 218, Update
the extension flow around claimLock and updateServiceJob to persist a
recoverable pending-extension state before claiming payment, including the
payment and expiry details needed for reconciliation. After a successful claim,
finalize that pending state and record the extension; ensure failures after
claimLock remain recoverable through idempotent recovery/reconciliation rather
than leaving payment claimed with unchanged expiry.

@giurgiur99 giurgiur99 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! One small note

Comment thread src/components/database/sqliteAuthToken.ts
Comment thread src/components/database/sqliteCompute.ts
@coderabbitai coderabbitai Bot mentioned this pull request Jul 16, 2026
@alexcos20
alexcos20 merged commit c765ec6 into next-4 Jul 17, 2026
9 of 10 checks passed
@alexcos20
alexcos20 deleted the feature/use_native_sqlite branch July 17, 2026 08:05
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.

Use native node sqlite

3 participants