Skip to content

feat: decouple platform and application logic via GitOps shards - #136

Merged
pramodnarayana merged 9 commits into
developmentfrom
feature/platform-app-decoupling
May 4, 2026
Merged

pramodnarayana merged 9 commits into
developmentfrom
feature/platform-app-decoupling

Conversation

@pramodnarayana

@pramodnarayana pramodnarayana commented Apr 30, 2026 •

Copy link
Copy Markdown
Owner
  • Replaced legacy ApplicationExecutorService with dynamic ApplicationLoaderService
  • Introduced PipelineHookBrokerService as the sole interface for platform-to-shard communication
  • Replaced static registries in piece-framework with dynamic shard loads
  • Implemented webhook-triggered GitOps synchronization (GitopsSyncController, Worker)
  • Updated TargetBuilder, Replica, and Normalization services to use the new broker pattern
  • Migrated TenantDatabaseManager CLI to load domain provisioners dynamically
  • Added tests for GitopsWebhookGuard and updated existing pipeline specs

Summary by CodeRabbit

  • New Features

    • GitOps webhook endpoint with queue-backed sync; tenant-provisioner service and automatic warm DB provisioning.
    • Dynamic shard loader and pipeline hook broker for app-specific pipeline hooks.
  • Improvements

    • Stronger webhook authentication and safer shard/branch validation.
    • Immediate queue-driven worker syncs, cache invalidation on updates, and multi-tenant DB support.
    • Node runtime pinned to 20.18.1; added JS engine runtime package.
  • Deprecations

    • Legacy in-memory hook registries marked deprecated.
  • Tests

    • Added and expanded tests across webhook, worker, pipeline, provisioning, and DB flows.

- Replaced legacy ApplicationExecutorService with dynamic ApplicationLoaderService
- Introduced PipelineHookBrokerService as the sole interface for platform-to-shard communication
- Replaced static registries in piece-framework with dynamic shard loads
- Implemented webhook-triggered GitOps synchronization (GitopsSyncController, Worker)
- Updated TargetBuilder, Replica, and Normalization services to use the new broker pattern
- Migrated TenantDatabaseManager CLI to load domain provisioners dynamically
- Added tests for GitopsWebhookGuard and updated existing pipeline specs
@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces static in-memory app hooks with dynamic shard loading (ApplicationLoaderService + PipelineHookBrokerService), adds GitOps webhook + guarded endpoint and queue-driven syncs, migrates pipeline workers to broker hooks, implements tenant-per-database provisioning and tenant provisioner service, updates migrations/snapshots, and bumps Node to 20.18.1.

Changes

Platform sharding, GitOps, and multi-tenant database DAG

Layer / File(s) Summary
Data shape / types
engine/sync/platform/core/src/sharding/application-shard.types.ts, apps/api/src/modules/workspaces/capacity-manager.types.ts
Adds ApplicationShardModule/WebhookResponseShape contract and ProvisionDatabaseEvent interface defining shard and provisioning payload shapes.
Core implementation
engine/sync/platform/core/src/sharding/application-loader.service.ts, .../pipeline-hook-broker.service.ts, .../application-loader.module.ts, packages/pieces/platform/framework/src/app-hooks.ts
Introduces ApplicationLoaderService (dynamic shard import + cache/invalidation) and PipelineHookBrokerService (delegates extract/normalize/write/buildTarget/provisionDomain/getWebhookResponse). Marks old in-memory app-hook registry APIs deprecated.
Authentication / GitOps guard
apps/api/src/modules/gitops/gitops-webhook.guard.ts
Adds GitopsWebhookGuard implementing HMAC (X-Hub-Signature-256), X-Gitlab-Token, and Authorization: Bearer validation with constant-time digest comparisons and specific failure modes.
API controller & wiring
apps/api/src/modules/gitops/gitops-sync.controller.ts, apps/api/src/modules/gitops/gitops.module.ts, apps/api/src/app/app.module.ts, packages/queue/src/constants.ts
Adds /internal/gitops/sync POST endpoint protected by the guard that enqueues { source: 'webhook', triggeredAt } to QueueName.GitopsQueue; registers GitopsModule and new queue enum values.
Worker integration & GitOps worker
apps/worker/src/modules/pipeline/gitops-sync.worker.ts, .../gitops-sync.worker.spec.ts, apps/worker/src/db/database-manager.ts
Worker consumes GitopsQueue, adds onModuleInit, introduces syncShard(shardName) with path canonicalization, .git checks, branch whitelist, safe git pull --ff-only, and cache invalidation via ApplicationLoaderService.invalidateCache. provisionLocal in worker DB manager now resolves domain provisioner via engine loader/broker.
Pipeline services -> broker rewiring
apps/worker/src/modules/pipeline/normalization.service.ts, .../replica.service.ts, .../target-builder.service.ts, apps/worker/src/modules/pipeline/pipeline.module.ts
Rewires normalization, replica extraction, and target-building to call PipelineHookBrokerService (injecting hookBroker/ApplicationLoaderModule) replacing previous piece-framework hooks. Adjusts error handling and logging around hook failures.
Tenant provisioning service & worker
apps/tenant-provisioner/src/**, apps/tenant-provisioner/package.json, apps/tenant-provisioner/tsconfig.json, apps/tenant-provisioner/vitest.config.ts, apps/tenant-provisioner/nest-cli.json
Adds new tenant-provisioner app with TenantProvisionWorker consuming TenantProvisionQueue, creating tenant DBs, running tenant migrations, and updating tenant_storage_registry; includes module, bootstrap, tests, build/test configs.
Database manager multi-tenant and migrations
apps/api/src/db/database-manager.ts, packages/database/drizzle.config.ts, packages/database/drizzle.config.tenant.ts
DatabaseManager now supports global migrations (migrateGlobal), tenant migrations (migrateTenant), tenant DB creation (createTenantDatabase), and provisionLocal() provisions tenant DBs and upserts tenant fixtures. Adds tenant-specific Drizzle config.
Workspaces capacity & provisioning integration
apps/api/src/modules/workspaces/capacity-manager.service.ts, .../workspaces.module.ts, .../workspaces.service.ts, .../workspaces.service.spec.ts
Adds CapacityManagerService scheduled task to maintain a warm DB pool and enqueue ProvisionDatabaseEvent; WorkspacesService.create now claims a warm slot atomically or returns 503 when none available.
Queue / local testing scripts
scripts/init-localstack.sh, packages/queue/src/constants.ts
LocalStack init includes gitops-queue/DLQ creation and queue redrive policy; QueueName enum extended with GitopsQueue and GitopsQueueDLQ.
Module DI token consolidation
apps/*/src/modules/dbmanager/dbmanager.module.ts, multiple @nexiom/* imports across apps/api and apps/worker
Replaces local DB_MANAGER token declarations with import from @nexiom/dbmanager and updates numerous modules/tests to inject DB_MANAGER (multi-tenant DatabaseManager).
Migrations and snapshots
packages/database/drizzle/global/**, packages/database/drizzle/*, packages/database/drizzle/meta/**
Adds new global migration set (global/0000/0001/0003), moves migration outputs to drizzle/global, introduces tenant migration config, removes several older migration files and many prior snapshot JSON files (meta snapshots removed/updated).
Tests & tooling
many *.spec.ts, engine/sync/platform/core/vitest.config.ts, package.json, .nvmrc
Adds/updates many unit tests for guard, controller, workers, normalization/replica/target-builder, capacity manager, and tenant-provisioner. Changes Vitest to singleFork, pins Node to >=20.18.1, and updates pnpm config and root scripts.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant API as API Server
    participant Guard as GitopsWebhookGuard
    participant Ctrl as GitopsSyncController
    participant Queue as QueueService

    Client->>API: POST /internal/gitops/sync (webhook)
    API->>Guard: canActivate(req)
    Guard->>Guard: validate HMAC / X-Gitlab-Token / Authorization Bearer
    alt auth fails
        Guard-->>API: throw UnauthorizedException
        API-->>Client: 401
    else auth succeeds
        Guard-->>API: allow
        API->>Ctrl: triggerSync()
        Ctrl->>Queue: send(GitopsQueue, { source: "webhook", triggeredAt })
        Queue-->>Ctrl: enqueued
        Ctrl-->>API: { accepted: true } (202)
        API-->>Client: 202
    end
Loading
sequenceDiagram
    participant Queue as QueueService
    participant Worker as GitopsSyncWorker
    participant AppLoader as ApplicationLoaderService
    participant Shard as Shard Module

    Queue->>Worker: message { source, triggeredAt }
    Worker->>Worker: syncShard(shardName)
    Worker->>Worker: canonicalize path, check .git, validate branch
    Worker->>Shard: git pull origin <branch> --ff-only
    alt new commits pulled
        Shard-->>Worker: pull indicates changes
        Worker->>AppLoader: invalidateCache(shardName)
        AppLoader-->>AppLoader: remove cached module
    end
    alt error
        Worker-->>Queue: log & rethrow (retry)
    else success
        Worker-->>Queue: ack processed
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I nibble code beneath the moonlit stack,
Shards awaken where the old hooks packed.
Webhooks knock, the queue replies with cheer,
Cache cleared, dynamic shards appear.
Hop, git, and broker — syncs now run fast!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/platform-app-decoupling

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (1)
scripts/init-localstack.sh (1)

13-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the missing ai-copilot-queue to keep queue definitions in sync.

At Line 18, gitops-queue was added, but the list still omits ai-copilot-queue even though QueueName.AiCopilotQueue exists in packages/queue/src/constants.ts. This breaks the documented “must match” contract and can cause missing-queue failures in local/dev workers.

Suggested patch
 QUEUES=(
   "inbound-queue"
   "replica-queue"
   "normalized-queue"
   "delivery-queue"
+  "ai-copilot-queue"
   "gitops-queue"
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/init-localstack.sh` around lines 13 - 19, The QUEUES array is missing
the ai-copilot-queue entry and must match the QueueName.AiCopilotQueue constant;
add the string "ai-copilot-queue" to the QUEUES list (alongside "gitops-queue",
"inbound-queue", etc.) so the QUEUES array and QueueName.AiCopilotQueue remain
in sync and local/dev workers don’t fail due to a missing queue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/gitops/gitops-sync.controller.ts`:
- Around line 20-28: The webhook auth mismatch: update GitopsWebhookGuard so it
accepts native provider headers instead of only "Authorization: Bearer <secret>"
— implement checking for X-Hub-Signature-256 by computing the HMAC SHA256 of the
raw request body using the same GITOPS_WEBHOOK_SECRET and comparing to the
header for GitHub, and accept X-Gitlab-Token exact-match to
GITOPS_WEBHOOK_SECRET for GitLab; ensure the guard reads the raw request body
(used for HMAC) and preserves existing Authorization: Bearer behavior as a
fallback so canActivate (or the guard's validate/authorize method) supports all
three validation paths.

In `@apps/api/src/modules/gitops/gitops-webhook.guard.ts`:
- Around line 29-32: The constructor in GitopsWebhookGuard uses
config.getOrThrow('GITOPS_WEBHOOK_SECRET') but doesn't reject empty or
whitespace-only secrets; after retrieving the secret in the constructor, trim
and validate it (e.g., ensure secret.trim().length > 0 and optionally a minimum
byte/character length) and throw a clear error if invalid before computing
expectedDigest; likewise, apply the same non-empty/whitespace validation to any
bearer token retrieval/validation code referenced around lines 44-49 (e.g.,
where bearer tokens are read or compared), failing fast with a thrown error so
blank credentials cannot degrade authentication.

In `@apps/worker/src/db/database-manager.ts`:
- Around line 701-713: The domainProvisionerResolver currently swallows errors
from loaderInstance.load and from shard.provisionDomain which can hide failed
provisioning; update domainProvisionerResolver to surface failures by capturing
errors from loaderInstance.load and from shard.provisionDomain (or at minimum
log them with context including appName, schemaName) and then rethrow (or return
a rejected promise) so plan execution fails visibly; specifically update the
async inner function that calls loaderInstance.load(`${appName}-${appName}`) and
the conditional shard.provisionDomain(tenantDb, schemaName) to handle exceptions
(log via your process/logger and throw) rather than silently returning
null/no-op.

In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts`:
- Around line 93-98: syncShard builds repoPath from shardName without
path-traversal guards; normalize and validate shardName before filesystem ops:
resolve repoPath with path.resolve(this.SHARD_BASE_PATH, shardName), ensure the
resolved repoPath is contained within this.SHARD_BASE_PATH (e.g., check
path.relative(this.SHARD_BASE_PATH, repoPath) does not start with '..' or that
repoPath.startsWith(path.resolve(this.SHARD_BASE_PATH) + path.sep)), and reject
or throw for absolute/escaped inputs; update the syncShard implementation
(references: syncShard, repoPath, SHARD_BASE_PATH) to perform this validation
before calling fs.stat.

In `@apps/worker/src/modules/pipeline/normalization.service.spec.ts`:
- Around line 91-99: Update the tests in normalization.service.spec.ts to
actually assert that NormalizationService calls the broker methods: keep the
provided PipelineHookBrokerService mock but add expectations that the
mock.normalize(...) was called in the existing happy-path test (when
mockResolvedValue(null) is used ensure
vi.mocked(normalize).toHaveBeenCalledWith(...) or equivalent), and add a new
test case where the broker mock.normalize is set to resolve a non-null
normalized payload and then assert that broker.writeNormalized(...) was called
(and that the piece.normalize path was not used if applicable). Reference the
PipelineHookBrokerService mock, the NormalizationService method under test, and
the mock methods normalize and writeNormalized when adding these assertions.

In `@apps/worker/src/modules/pipeline/normalization.service.ts`:
- Around line 162-167: The catch that swallows errors when calling
this.hookBroker.normalize (assigning normalizedFromShard) should log the error
instead of being silent; update the promise catch to capture the error and call
a warning logger (e.g., this.logger.warn or processLogger.warn) with context
including connectionAppName, replica.entityType and the error message/stack,
then return null to preserve the fallback to piece.normalize, ensuring
operational visibility while keeping existing behavior.

In `@engine/sync/platform/core/src/sharding/application-executor.service.ts`:
- Around line 144-149: The code currently returns JSON.parse(resultStr) directly
(after reading resultStr via vm.getProp/vm.getString and disposing resultHandle)
which can yield null, arrays, or primitives; add a runtime guard after parsing
resultStr to ensure the parsed value is a non-null object and not an array
(e.g., const parsed = JSON.parse(resultStr); if (typeof parsed !== 'object' ||
parsed === null || Array.isArray(parsed)) throw new Error('Invalid shard mapping
return: expected Record<string, unknown>'); return parsed as Record<string,
unknown>), so that the method in application-executor.service.ts enforces the
expected return shape and fails fast on invalid shard outputs.
- Around line 92-133: The guest execution currently sets only a memory limit but
no interrupt/deadline, so before calling vm.evalCode(scriptCode) set an
interrupt handler via
vm.runtime.setInterruptHandler(shouldInterruptAfterDeadline(deadline)) with a
computed deadline, and change the async wrapper invoked in scriptCode to resolve
a guest Promise that the host can race against a timeout (use
Promise.race([guestPromise, timeoutPromise])) so the host can detect async
timeouts; after vm.evalCode and before vm.runtime.executePendingJobs() check
whether the deadline has elapsed (or the interrupt fired) and only call
executePendingJobs() when within the deadline, throwing/cleaning up otherwise.
Ensure you reference and update vm.runtime.setInterruptHandler,
shouldInterruptAfterDeadline(deadline), vm.evalCode(scriptCode), the async IIFE
in scriptCode (make it produce a guestPromise), Promise.race, and
vm.runtime.executePendingJobs() in your changes.

In `@engine/sync/platform/core/src/sharding/application-loader.service.ts`:
- Around line 49-57: The current string-based boundary check using resolvedBase
and shardPath can be bypassed via symlinks; replace it with real filesystem
canonicalization: call fs.realpathSync or await fs.promises.realpath on both
this.SHARD_BASE_PATH (or resolvedBase) and the computed shard path (shardPath)
to get baseReal and shardReal, then verify shardReal === baseReal or
shardReal.startsWith(baseReal + path.sep) (and throw the same Security violation
error if it fails). Update the checks around resolvedBase/shardPath in
application-loader.service.ts (the path validation block) and do the same for
the similar guard later (lines ~60-67).
- Around line 74-80: The code imports a shard module and caches it without
validating its exported shape, which can cause downstream failures; update the
load logic that imports `${shardPath}?v=${Date.now()}` into the variable `mod`
(typed as `ApplicationShardModule`) to perform explicit runtime checks on
required exports (e.g., ensure expected functions/classes/properties exist and
have correct types) before calling `this.cache.set(shardName, mod)` and logging;
if validation fails, throw or log a descriptive error including `shardName` and
`shardPath` so the module is not cached and failures surface immediately.

In `@engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts`:
- Around line 138-143: The current try/catch around loader.load and
shard.getWebhookResponse in getWebhookResponse swallows all errors; change it so
only the "shard missing" case returns null. Call
this.loader.load(this.shardName(appName, appProfile)) and if it throws, inspect
the error to determine "not found" (return null); for any other loader errors
rethrow or log and rethrow. After successfully getting shard, invoke
shard.getWebhookResponse?.(body, headers) without catching its exceptions here
(or catch them to log via process logger but then rethrow) so runtime hook
failures are surfaced; reference methods loader.load, shardName, and
getWebhookResponse to locate and update the logic accordingly.

In `@packages/pieces/platform/framework/src/app-hooks.ts`:
- Around line 4-13: Add JSDoc deprecation tags to each exported API in this
module (e.g., registerNormalizedWriter, getTargetBuilder, registerTargetBuilder,
registerReplicaService, getReplicaService, registerNormalizationService,
getNormalizationService and any other exported register*/get* functions) by
placing a /** `@deprecated` {message} */ comment immediately above each exported
function or constant; use a concise message such as "Deprecated — superseded by
the dynamic shard loading architecture (ApplicationLoaderService +
PipelineHookBrokerService); will be removed in the next release" so editors/TS
tooling surface the deprecation during the one-release migration window.

---

Outside diff comments:
In `@scripts/init-localstack.sh`:
- Around line 13-19: The QUEUES array is missing the ai-copilot-queue entry and
must match the QueueName.AiCopilotQueue constant; add the string
"ai-copilot-queue" to the QUEUES list (alongside "gitops-queue",
"inbound-queue", etc.) so the QUEUES array and QueueName.AiCopilotQueue remain
in sync and local/dev workers don’t fail due to a missing queue.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c437a907-7fbb-4204-90e3-e0f2a34a08cf

📥 Commits

Reviewing files that changed from the base of the PR and between e76603b and 8ae3966.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (34)
  • .nvmrc
  • apps/api/src/app/app.module.ts
  • apps/api/src/modules/gitops/gitops-sync.controller.spec.ts
  • apps/api/src/modules/gitops/gitops-sync.controller.ts
  • apps/api/src/modules/gitops/gitops-webhook.guard.spec.ts
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/modules/gitops/gitops.module.ts
  • apps/worker/src/db/database-manager.ts
  • apps/worker/src/main.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.spec.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts
  • apps/worker/src/modules/pipeline/normalization.service.ts
  • apps/worker/src/modules/pipeline/pipeline.module.ts
  • apps/worker/src/modules/pipeline/replica.service.spec.ts
  • apps/worker/src/modules/pipeline/replica.service.ts
  • apps/worker/src/modules/pipeline/target-builder.service.spec.ts
  • apps/worker/src/modules/pipeline/target-builder.service.ts
  • engine/sync/platform/core/package.json
  • engine/sync/platform/core/src/index.ts
  • engine/sync/platform/core/src/sharding/application-executor.module.ts
  • engine/sync/platform/core/src/sharding/application-executor.service.spec.ts
  • engine/sync/platform/core/src/sharding/application-executor.service.ts
  • engine/sync/platform/core/src/sharding/application-loader.module.ts
  • engine/sync/platform/core/src/sharding/application-loader.service.ts
  • engine/sync/platform/core/src/sharding/application-shard.types.ts
  • engine/sync/platform/core/src/sharding/logic-resolver.module.ts
  • engine/sync/platform/core/src/sharding/logic-resolver.service.ts
  • engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts
  • engine/sync/platform/core/vitest.config.ts
  • package.json
  • packages/pieces/platform/framework/src/app-hooks.ts
  • packages/queue/src/constants.ts
  • scripts/init-localstack.sh
💤 Files with no reviewable changes (3)
  • engine/sync/platform/core/src/sharding/logic-resolver.module.ts
  • apps/worker/src/main.ts
  • engine/sync/platform/core/src/sharding/logic-resolver.service.ts

Comment thread apps/api/src/modules/gitops/gitops-sync.controller.ts
Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts
Comment thread apps/worker/src/db/database-manager.ts
Comment thread apps/worker/src/modules/pipeline/gitops-sync.worker.ts
Comment thread apps/worker/src/modules/pipeline/normalization.service.spec.ts
Comment thread engine/sync/platform/core/src/sharding/application-executor.service.ts Outdated
Comment thread packages/pieces/platform/framework/src/app-hooks.ts
@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 9 file(s) based on 12 unresolved review comments.

Files modified:

  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/worker/src/db/database-manager.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts
  • apps/worker/src/modules/pipeline/normalization.service.ts
  • engine/sync/platform/core/src/sharding/application-executor.service.ts
  • engine/sync/platform/core/src/sharding/application-loader.service.ts
  • engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts
  • packages/pieces/platform/framework/src/app-hooks.ts

Commit: 7f257a42c389817302bf8b11618d0b391fb24e1b

The changes have been pushed to the feature/platform-app-decoupling branch.

Time taken: 7m 57s

Fixed 9 file(s) based on 12 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

♻️ Duplicate comments (3)
apps/worker/src/modules/pipeline/gitops-sync.worker.ts (1)

93-115: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Realpath the repo before touching .git or running git pull.

This guard is still string-based. A symlinked shard directory under SHARD_BASE_PATH will satisfy the current containment check, and both fs.stat() and execFile(..., { cwd: repoPath }) will then follow it outside the trusted tree.

Suggested hardening
   async syncShard(shardName: string): Promise<void> {
-    const resolvedBasePath = path.resolve(this.SHARD_BASE_PATH);
-    const repoPath = path.resolve(this.SHARD_BASE_PATH, shardName);
+    const resolvedBasePath = await fs.realpath(path.resolve(this.SHARD_BASE_PATH));
+    const requestedRepoPath = path.resolve(this.SHARD_BASE_PATH, shardName);
+    const repoPath = await fs.realpath(requestedRepoPath);

-    const relativePath = path.relative(resolvedBasePath, repoPath);
+    const relativePath = path.relative(resolvedBasePath, repoPath);
     if (
-      relativePath.startsWith('..') ||
-      path.isAbsolute(relativePath) ||
-      !repoPath.startsWith(resolvedBasePath + path.sep)
+      relativePath.startsWith('..') ||
+      path.isAbsolute(relativePath)
     ) {
       this.logger.warn(
         `Path traversal attempt detected: shardName="${shardName}" escapes SHARD_BASE_PATH. Rejecting sync.`,
       );
       throw new Error(
         `Security violation: shardName escapes trusted boundary — shardName="${shardName}"`,
       );
     }

     try {
       await fs.stat(path.join(repoPath, ".git"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts` around lines 93 -
115, The current containment check in syncShard is string-based and can be
bypassed by symlinks; resolve this by calling fs.realpath on the repoPath (and
ideally on path.join(repoPath, ".git")) and use the resolved realRepoPath for
the containment check against the resolvedBasePath and as the cwd for any git
operations (e.g., execFile). Update syncShard to realpath repoPath, verify
path.relative(resolvedBasePath, realRepoPath) does not escape, and use
realRepoPath when touching .git or running git pull to prevent symlink escape
from SHARD_BASE_PATH.
engine/sync/platform/core/src/sharding/application-loader.service.ts (1)

73-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Import the canonical path you just validated.

load() verifies shardReal, but import() still executes shardPath. If the file or symlink target changes between realpath() and import(), this can validate one path and execute another. Import shardReal/its file URL instead so the boundary check and the loaded module refer to the same file.

Suggested hardening
+import { pathToFileURL } from 'node:url';
...
-    const mod = (await import(
-      `${shardPath}?v=${Date.now()}`
-    )) as ApplicationShardModule;
+    const mod = (await import(
+      `${pathToFileURL(shardReal).href}?v=${Date.now()}`
+    )) as ApplicationShardModule;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/sync/platform/core/src/sharding/application-loader.service.ts` around
lines 73 - 95, The code validates the canonical path in load() (shardReal) but
then imports the original shardPath, allowing a TOCTOU where the target can
change between realpath() and import(); update the import to use the canonical
path (shardReal) converted to a file URL (e.g., via pathToFileURL) and preserve
the timestamp cache-buster query so the same validated file is what gets
executed; change the import expression that currently uses
`${shardPath}?v=${Date.now()}` to use the file-URL built from shardReal with the
same ?v= timestamp, and keep the existing boundary check that compares shardReal
to baseReal.
engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts (1)

139-147: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t classify loader failures by substring matching.

errMsg.includes('not found') can also match broken imports like missing transitive modules, and those should surface as shard regressions, not silently return null. Have ApplicationLoaderService.load() throw a dedicated not-found error/code and only suppress that exact case here.

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

In `@engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts`
around lines 139 - 147, The catch block in PipelineHookBrokerService where you
call this.loader.load(this.shardName(...)) must not rely on substring matching
of loadErr.message; instead have ApplicationLoaderService.load() throw a
specific NotFound error/class or set a distinct error.code (e.g.,
'SHARD_NOT_FOUND'), then change the catch to check for that exact signal (error
instanceof ShardNotFoundError or error.code === 'SHARD_NOT_FOUND') and return
null only in that case, otherwise rethrow the error so genuine loader failures
surface; update/reuse the loader's public API (ApplicationLoaderService.load)
and the callsite in the loader.load(...) catch accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/gitops/gitops-webhook.guard.ts`:
- Around line 33-44: The code trims the env value into trimmedSecret but then
uses the original secret for this.secret and expectedDigest, causing mismatch;
change assignments to use the normalized value by setting this.secret =
trimmedSecret and computing this.expectedDigest =
createHash('sha256').update(trimmedSecret).digest() (the lookup via
config.getOrThrow can remain as-is) so the guard and digest both use the trimmed
secret consistently.

In `@apps/worker/src/db/database-manager.ts`:
- Around line 693-729: The current domainProvisionerResolver manually loads
shards via ApplicationLoaderService and calls shard.provisionDomain; instead
instantiate PipelineHookBrokerService with the ApplicationLoaderService (or pass
loaderInstance into its constructor) and have domainProvisionerResolver delegate
to broker.provisionDomain(appName, tenantDb, schemaName), catching and
rethrowing errors and logging the same contextual messages; replace direct calls
to loaderInstance.load(...) and shard.provisionDomain(...) with a single call to
broker.provisionDomain(...) and keep the existing error handling around that
call.

In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts`:
- Around line 32-38: The queue consumer callback in onModuleInit (the callback
passed to queueService.consume for QueueName.GitopsQueue) currently always
resolves even when syncShardRepositories is skipped or fails, which ACKs
messages prematurely; change the handler so it throws (or returns a rejected
Promise) when sync is skipped or when an error occurs instead of swallowing it.
Concretely: update the consume callback to inspect the result of
syncShardRepositories (and any early-return “already running” path) and throw a
specific Error when nothing was performed, and in your top-level try/catch
around the call log the error but rethrow it (do not swallow) so the queue can
NACK/retry; apply the same pattern for the other handlers referenced around
queue consumer code (the other consume callbacks at the same module).

In `@apps/worker/src/modules/pipeline/normalization.service.spec.ts`:
- Around line 114-145: The test is asserting against a PipelineHookBrokerService
instance that wasn’t the one injected into the NormalizationService handler;
fetch the broker from the same TestingModule used to build the service/handler
created in beforeEach (the module that produced handler from
NormalizationService.onModuleInit) instead of a second module, or register a spy
object for PipelineHookBrokerService in that original module’s providers so the
injected broker is the spy; update the expect to call mockBroker from that same
module (reference NormalizationService, handler, PipelineHookBrokerService, and
module.get) so the assertion validates the actual broker used by the service.
- Around line 304-355: The test's shared transaction stub is missing a
transaction method so NormalizationService's call to tx.transaction(...) falls
back to the warning path and never reaches broker.writeNormalized; update the
test's stub for the DB/tx object used by the module (the same object referenced
by the handler invocation) to include a transaction method that accepts a
callback, executes that callback with a tx-like object (or the stub itself) and
returns the callback's result (or a resolved promise), ensuring
NormalizationService.transaction(...) runs and the broker.writeNormalized path
is exercised when using mockBroker in the spec.

In `@apps/worker/src/modules/pipeline/normalization.service.ts`:
- Around line 253-266: The call to this.hookBroker.writeNormalized inside
tx.transaction passes the savepoint handle sp but still passes this.db (the root
connection), allowing shard hooks to perform writes outside the current
transaction; update the call so the transactional handle sp is passed in place
of this.db (i.e., pass sp for both connection arguments) or alternatively change
the hook contract to accept only a single transactional handle; locate the
invocation of this.hookBroker.writeNormalized in the tx.transaction callback and
replace the this.db argument with sp (or refactor writeNormalized to only accept
the transactional handle).

In `@engine/sync/platform/core/src/sharding/application-executor.service.ts`:
- Around line 65-66: Replace the error messages that expose internal paths and
raw execution details by throwing a sanitized InternalServerErrorException
(e.g., "Failed to load shard module" or "Internal server error") while logging
the detailed diagnostic (including modulePath and the original error/stack) to
the service logger; update the throw at the location using modulePath and the
similar throw around lines 181-184 to use the generic client-facing message and
ensure the real error and modulePath are recorded via this.logger.error (or
processLogger.error) before throwing.
- Around line 53-59: Resolve real filesystem paths before performing the
trusted-boundary check to prevent symlink traversal: call fs.realpathSync (or
async fs.promises.realpath) on this.SHARD_BASE_PATH to get resolvedBasePath and
on the candidate module path (built from this.SHARD_BASE_PATH and shardName into
modulePath) to get realModulePath, then enforce that realModulePath startsWith
resolvedBasePath + path.sep or equals resolvedBasePath; throw the same
InternalServerErrorException if it escapes. Apply the same realpath-based check
to the other validation that uses resolved paths (the second occurrence
involving module resolution) so both checks use real paths rather than
path.resolve.

---

Duplicate comments:
In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts`:
- Around line 93-115: The current containment check in syncShard is string-based
and can be bypassed by symlinks; resolve this by calling fs.realpath on the
repoPath (and ideally on path.join(repoPath, ".git")) and use the resolved
realRepoPath for the containment check against the resolvedBasePath and as the
cwd for any git operations (e.g., execFile). Update syncShard to realpath
repoPath, verify path.relative(resolvedBasePath, realRepoPath) does not escape,
and use realRepoPath when touching .git or running git pull to prevent symlink
escape from SHARD_BASE_PATH.

In `@engine/sync/platform/core/src/sharding/application-loader.service.ts`:
- Around line 73-95: The code validates the canonical path in load() (shardReal)
but then imports the original shardPath, allowing a TOCTOU where the target can
change between realpath() and import(); update the import to use the canonical
path (shardReal) converted to a file URL (e.g., via pathToFileURL) and preserve
the timestamp cache-buster query so the same validated file is what gets
executed; change the import expression that currently uses
`${shardPath}?v=${Date.now()}` to use the file-URL built from shardReal with the
same ?v= timestamp, and keep the existing boundary check that compares shardReal
to baseReal.

In `@engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts`:
- Around line 139-147: The catch block in PipelineHookBrokerService where you
call this.loader.load(this.shardName(...)) must not rely on substring matching
of loadErr.message; instead have ApplicationLoaderService.load() throw a
specific NotFound error/class or set a distinct error.code (e.g.,
'SHARD_NOT_FOUND'), then change the catch to check for that exact signal (error
instanceof ShardNotFoundError or error.code === 'SHARD_NOT_FOUND') and return
null only in that case, otherwise rethrow the error so genuine loader failures
surface; update/reuse the loader's public API (ApplicationLoaderService.load)
and the callsite in the loader.load(...) catch accordingly.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d16755aa-e409-41d6-85d5-97f012970d13

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae3966 and 7f257a4.

📒 Files selected for processing (9)
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/worker/src/db/database-manager.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts
  • apps/worker/src/modules/pipeline/normalization.service.ts
  • engine/sync/platform/core/src/sharding/application-executor.service.ts
  • engine/sync/platform/core/src/sharding/application-loader.service.ts
  • engine/sync/platform/core/src/sharding/pipeline-hook-broker.service.ts
  • packages/pieces/platform/framework/src/app-hooks.ts

Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts Outdated
Comment thread apps/worker/src/db/database-manager.ts Outdated
Comment thread apps/worker/src/modules/pipeline/gitops-sync.worker.ts
Comment thread apps/worker/src/modules/pipeline/normalization.service.spec.ts
Comment thread apps/worker/src/modules/pipeline/normalization.service.spec.ts
Comment thread apps/worker/src/modules/pipeline/normalization.service.ts
Comment thread engine/sync/platform/core/src/sharding/application-executor.service.ts Outdated
Comment thread engine/sync/platform/core/src/sharding/application-executor.service.ts Outdated
@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 6 file(s) based on 8 unresolved review comments.

Files modified:

  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/worker/src/db/database-manager.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts
  • apps/worker/src/modules/pipeline/normalization.service.ts
  • engine/sync/platform/core/src/sharding/application-executor.service.ts

Commit: 03b9bf003f7508d0c553084e26f2a54212e2211a

The changes have been pushed to the feature/platform-app-decoupling branch.

Time taken: 5m 11s

Fixed 6 file(s) based on 8 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@pramodnarayana
pramodnarayana marked this pull request as draft April 30, 2026 15:46
@pramodnarayana
pramodnarayana marked this pull request as ready for review April 30, 2026 15:46

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

♻️ Duplicate comments (3)
apps/worker/src/modules/pipeline/gitops-sync.worker.ts (2)

78-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't swallow sync failures before they reach the queue consumer.

onModuleInit() now rethrows queue-handler errors, but these two catches still absorb them. A failed readdir, timeout, or git pull will therefore ACK the webhook message and skip retry, which pushes recovery back to the 5-minute cron path. If non-repo directories should be ignored, keep that as an explicit early return and rethrow actual sync failures.

Suggested split between “skip” and “fail” paths
   try {
     // Ensure the base sync directory exists
     await fs.mkdir(this.SHARD_BASE_PATH, { recursive: true });
@@
     for (const shard of shardDirs) {
       await this.syncShard(shard.name);
     }
   } catch (error) {
     this.logger.error("GitOps shard synchronization failed", error);
+    throw error;
   } finally {
     this.isSyncRunning = false;
   }
 }
@@
   async syncShard(shardName: string): Promise<void> {
@@
-    try {
-      await fs.stat(path.join(repoPath, ".git"));
+    try {
+      await fs.stat(path.join(repoPath, ".git"));
+    } catch {
+      this.logger.debug(`[${shardName}] Skipping non-git directory.`);
+      return;
+    }
 
+    try {
       // Validate branch name to prevent command injection
       const branchName = this.DEFAULT_BRANCH;
@@
       } else {
         this.logger.log(
           `[${shardName}] New commits pulled — invalidating module cache:\n${stdout}`,
         );
         this.applicationLoaderService.invalidateCache(shardName);
       }
     } catch (err) {
-      this.logger.warn(
-        `[${shardName}] Not a git repository or git pull failed — skipping.`,
+      this.logger.warn(
+        `[${shardName}] GitOps sync failed.`,
         err instanceof Error ? err.message : String(err),
       );
+      throw err;
     }
   }

Also applies to: 164-168

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

In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts` around lines 78 -
101, The catch around reading/iterating shards is currently swallowing all
errors (fs.readdir, syncShard, git pull, timeouts) which causes queue messages
to be ACKed; change the handler in gitops-sync.worker.ts so that only the
expected "no shard dirs" case returns early (keep the existing if
(shardDirs.length === 0) return) but in the catch block log the error with
this.logger.error and then rethrow it so queue consumers can retry; apply the
same change to the other analogous catch (the one around lines referenced
164-168) so both the method containing SHARD_BASE_PATH/shardDirs and the other
failing block forward real failures instead of absorbing them, while still
ignoring non-repo directories explicitly before the try/catch if needed.

112-128: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Canonicalize shard paths before enforcing the boundary.

This check is only lexical. A symlink inside SHARD_BASE_PATH can still pass resolve/relative while fs.stat() and git pull operate on a repository outside the trusted tree. engine/sync/platform/core/src/sharding/application-loader.service.ts:45-86 already uses fs.realpath() to close this gap; the worker should do the same.

Suggested hardening
   async syncShard(shardName: string): Promise<void> {
-    const resolvedBasePath = path.resolve(this.SHARD_BASE_PATH);
-    const repoPath = path.resolve(this.SHARD_BASE_PATH, shardName);
+    const resolvedBasePath = path.resolve(this.SHARD_BASE_PATH);
+    const repoPath = path.resolve(resolvedBasePath, shardName);
+    const [baseReal, repoReal] = await Promise.all([
+      fs.realpath(resolvedBasePath),
+      fs.realpath(repoPath),
+    ]);
 
-    const relativePath = path.relative(resolvedBasePath, repoPath);
+    const relativePath = path.relative(baseReal, repoReal);
     if (
       relativePath.startsWith("..") ||
       path.isAbsolute(relativePath) ||
-      !repoPath.startsWith(resolvedBasePath + path.sep)
+      (repoReal !== baseReal && !repoReal.startsWith(baseReal + path.sep))
     ) {
       this.logger.warn(
         `Path traversal attempt detected: shardName="${shardName}" escapes SHARD_BASE_PATH. Rejecting sync.`,
       );
       throw new Error(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts` around lines 112 -
128, The path containment check is only lexical and can be bypassed via
symlinks; canonicalize both the SHARD_BASE_PATH and the resolved repo path using
fs.realpath (or fs.promises.realpath) before performing the containment check.
In the gitops-sync.worker.ts block that computes resolvedBasePath, repoPath and
relativePath for shardName, replace the current path.resolve usage with realpath
results for the base and the candidate repo (handle and propagate realpath
errors), then enforce that the canonical repo realpath startsWith the canonical
base realpath + path.sep (and reject/throw with the same warning message if
not).
apps/worker/src/modules/pipeline/normalization.service.ts (1)

170-178: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize shard hook errors before logging.

Line 176–177 logs raw message/stack. That bypasses the existing sanitizeError pattern used in this service and can leak sensitive context from shard exceptions.

Suggested fix
           this.logger.warn(
             {
               event: "l3.shard_normalize_failed",
               connectionAppName,
               appProfile,
               entityType: replica.entityType,
-              err: err instanceof Error ? err.message : String(err),
-              stack: err instanceof Error ? err.stack : undefined,
+              err: sanitizeError(err),
             },
             "Shard normalize failed — falling back to piece.normalize",
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/worker/src/modules/pipeline/normalization.service.ts` around lines 170 -
178, The log in the normalization service is currently emitting raw err.message
and err.stack in the this.logger.warn call (event "l3.shard_normalize_failed");
instead call the existing sanitizeError utility used elsewhere in this class to
produce a sanitized error object and log its safe fields instead of raw
message/stack—i.e., obtain const safe = sanitizeError(err) and replace err
instanceof Error ? err.message : String(err) and err instanceof Error ?
err.stack : undefined with safe.message and safe.stack (or the sanitized fields
your sanitizeError returns) while keeping connectionAppName, appProfile, and
replica.entityType intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/gitops/gitops-webhook.guard.ts`:
- Around line 131-133: The code recomputes secretDigest for every GitLab token
check; instead reuse the precomputed digest stored on the instance
(this.expectedDigest) like validateBearerToken does: compute tokenDigest with
createHash('sha256').update(token).digest() and compare it against
this.expectedDigest rather than recomputing secretDigest from
this.secret/trimmedSecret; update the logic in the GitLab token validation path
(the tokenDigest/secretDigest block) to reference this.expectedDigest.

In `@apps/api/src/scripts/admin-bootstrap.ts`:
- Around line 57-58: The call to seedSystemRbac currently casts db to any which
hides a real type mismatch; locate the seedSystemRbac declaration and its
expected DB type, then remove the "as any" cast on the db argument by either (A)
updating seedSystemRbac's signature to accept NodePgDatabase<typeof schema> (or
the concrete DB type used here) or (B) creating a narrow typed adapter/bridge
variable that converts/annotates the local db (the db identifier in this file)
to the exact type seedSystemRbac expects; update imports/usages accordingly and
remove the eslint-disable comment so the compiler enforces the correct contract.

In `@apps/worker/src/modules/pipeline/normalization.service.spec.ts`:
- Around line 148-149: Remove the leftover console.log debug statements from the
test file: delete the console.log("SPY CALLS:",
JSON.stringify(mockBroker.normalize.mock.calls)); lines (both occurrences around
the mockBroker.normalize assertion) so the test only contains the expectation
calls (e.g., expect(mockBroker.normalize).toHaveBeenCalledWith(...)); keep the
mockBroker.normalize spy and assertions intact and do not add any other logging.

---

Duplicate comments:
In `@apps/worker/src/modules/pipeline/gitops-sync.worker.ts`:
- Around line 78-101: The catch around reading/iterating shards is currently
swallowing all errors (fs.readdir, syncShard, git pull, timeouts) which causes
queue messages to be ACKed; change the handler in gitops-sync.worker.ts so that
only the expected "no shard dirs" case returns early (keep the existing if
(shardDirs.length === 0) return) but in the catch block log the error with
this.logger.error and then rethrow it so queue consumers can retry; apply the
same change to the other analogous catch (the one around lines referenced
164-168) so both the method containing SHARD_BASE_PATH/shardDirs and the other
failing block forward real failures instead of absorbing them, while still
ignoring non-repo directories explicitly before the try/catch if needed.
- Around line 112-128: The path containment check is only lexical and can be
bypassed via symlinks; canonicalize both the SHARD_BASE_PATH and the resolved
repo path using fs.realpath (or fs.promises.realpath) before performing the
containment check. In the gitops-sync.worker.ts block that computes
resolvedBasePath, repoPath and relativePath for shardName, replace the current
path.resolve usage with realpath results for the base and the candidate repo
(handle and propagate realpath errors), then enforce that the canonical repo
realpath startsWith the canonical base realpath + path.sep (and reject/throw
with the same warning message if not).

In `@apps/worker/src/modules/pipeline/normalization.service.ts`:
- Around line 170-178: The log in the normalization service is currently
emitting raw err.message and err.stack in the this.logger.warn call (event
"l3.shard_normalize_failed"); instead call the existing sanitizeError utility
used elsewhere in this class to produce a sanitized error object and log its
safe fields instead of raw message/stack—i.e., obtain const safe =
sanitizeError(err) and replace err instanceof Error ? err.message : String(err)
and err instanceof Error ? err.stack : undefined with safe.message and
safe.stack (or the sanitized fields your sanitizeError returns) while keeping
connectionAppName, appProfile, and replica.entityType intact.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84244287-f696-47d1-bf90-32a55a319446

📥 Commits

Reviewing files that changed from the base of the PR and between 7f257a4 and bf39bcd.

📒 Files selected for processing (7)
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/modules/webhooks/webhooks.controller.spec.ts
  • apps/api/src/scripts/admin-bootstrap.ts
  • apps/worker/src/db/database-manager.ts
  • apps/worker/src/modules/pipeline/gitops-sync.worker.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts
  • apps/worker/src/modules/pipeline/normalization.service.ts

Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts Outdated
Comment thread apps/api/src/scripts/admin-bootstrap.ts Outdated
Comment thread apps/worker/src/modules/pipeline/normalization.service.spec.ts Outdated
@coderabbitai

coderabbitai Bot commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 3 file(s) based on 3 unresolved review comments.

Files modified:

  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/scripts/admin-bootstrap.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts

Commit: c807162c0a2beae65bae4537562920349f6452f9

The changes have been pushed to the feature/platform-app-decoupling branch.

Time taken: 5m 1s

Fixed 3 file(s) based on 3 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/gitops/gitops-webhook.guard.ts`:
- Around line 124-132: The code trims the incoming token into trimmedToken but
still hashes the original token variable; update the digest calculation to use
trimmedToken (e.g., replace createHash('sha256').update(token).digest() with
createHash('sha256').update(trimmedToken).digest()) and ensure any other similar
blocks (the repeated logic that creates tokenDigest) also use trimmedToken for
normalization before comparison so whitespace is consistently removed prior to
hashing; keep the UnauthorizedException check for empty/whitespace as-is.
- Around line 52-63: The guard currently casts request header values to string,
which breaks if headers are arrays; add a helper (e.g., getSingleHeader) that
accepts string | string[] | undefined and returns a single string or throws
UnauthorizedException when Array.isArray(value) to reject duplicate headers, and
use it when reading 'x-hub-signature-256' before calling validateGitHubSignature
and when reading 'x-gitlab-token' before validateGitLabToken; also normalize the
GitLab token by trimming it before any validation and before computing its
SHA-256 digest in validateGitLabToken so the digest is derived from the trimmed
token and matches the expectedDigest created in the constructor.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2adc7786-de9a-4d3d-959d-e2c26803ebd9

📥 Commits

Reviewing files that changed from the base of the PR and between bf39bcd and c807162.

📒 Files selected for processing (3)
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/scripts/admin-bootstrap.ts
  • apps/worker/src/modules/pipeline/normalization.service.spec.ts

Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts Outdated
Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts Outdated
@coderabbitai

coderabbitai Bot commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 2 unresolved review comments.

Files modified:

  • apps/api/src/modules/gitops/gitops-webhook.guard.ts

Commit: b907194e3f079ffd1655a33f80d15e3f8c0d71f9

The changes have been pushed to the feature/platform-app-decoupling branch.

Time taken: 4m 28s

coderabbitai Bot and others added 2 commits May 1, 2026 05:58
Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (3)
apps/api/src/modules/dbmanager/dbmanager.module.ts (1)

19-31: ⚠️ Potential issue | 🟠 Major

DbManagerModule must implement OnModuleDestroy to close cached tenant connection pools on graceful shutdown.

The review comment incorrectly states pools are created per request. However, TenantDatabaseManager caches pools by tenantId (line 21 in packages/dbmanager/src/impl/tenant-database-manager.ts), so only one pool per tenant is created.

The actual issue: DbManagerModule lacks an OnModuleDestroy hook to invoke TenantDatabaseManager.closeAll() during shutdown. The closeAll() method exists (lines 151–170) and properly closes all cached connections, but it's never called. When the app terminates, for N active tenants, N × 20 connections remain open, risking connection exhaustion on subsequent deployments or restart cycles. Implement OnModuleDestroy on the provider or module to ensure graceful cleanup.

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

In `@apps/api/src/modules/dbmanager/dbmanager.module.ts` around lines 19 - 31,
DbManagerModule currently never calls TenantDatabaseManager.closeAll(), so
cached per-tenant Pool connections leak on shutdown; implement Nest's
OnModuleDestroy on the provider or DbManagerModule and in the onModuleDestroy
lifecycle method call the created TenantDatabaseManager.closeAll() (ensure you
reference the instance returned by the factory that constructs
TenantDatabaseManager) so all pooled connections are closed during graceful
shutdown.
apps/api/src/modules/workspaces/workspaces.service.ts (1)

34-83: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Potential WARM slot leak if workspace insert fails after claiming.

If the WARM slot claim succeeds (lines 47-57) but the subsequent workspace insert (lines 71-78) fails with a non-unique-violation error (e.g., connection error, timeout), the org will have claimed an ACTIVE database slot but no workspace record exists. The CapacityManager won't replenish this slot since it's marked ACTIVE.

Consider wrapping the claim and insert in a transaction so the claim is rolled back if the insert fails.

💡 Suggested approach
   async create(orgId: string, body: CreateWorkspace) {
-    try {
+    return this.db.transaction(async (tx) => {
+      try {
       // ── Deferred Provisioning: Claim a WARM database slot ──────────────
-      const hasDb = await this.db.execute<{ tenant_id: string }>(
+      const hasDb = await tx.execute<{ tenant_id: string }>(
         sql`SELECT tenant_id
             FROM tenant_storage_registry
             ...
       );

       if ((hasDb.rowCount ?? 0) === 0) {
-        const claimed = await this.db.execute<{ tenant_id: string }>(
+        const claimed = await tx.execute<{ tenant_id: string }>(
           sql`UPDATE tenant_storage_registry
               ...
         );
         ...
       }

-      const [workspace] = await this.db
+      const [workspace] = await tx
         .insert(uiWorkspaces)
         ...
+      } catch (err) {
+        if (isUniqueViolation(err)) {
+          throw new ConflictException(...);
+        }
+        throw err;
+      }
+    });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/modules/workspaces/workspaces.service.ts` around lines 34 - 83,
The WARM slot can leak if the UPDATE that claims tenant_storage_registry
succeeds but the subsequent insert into uiWorkspaces fails; wrap the
claim+insert in a single DB transaction so the UPDATE (claimed) is rolled back
on any insert error: begin a transaction via this.db.transaction (or your DB
client's transaction API), perform the SELECT/UPDATE claim logic (the same
statements referencing tenant_storage_registry, hasDb, claimed) and then perform
the insert into uiWorkspaces and .returning(); only commit if the insert
succeeds, otherwise roll back so the tenant_storage_registry UPDATE is undone
and the slot remains WARM. Ensure the FOR UPDATE SKIP LOCKED behavior remains
inside the same transaction scope.
apps/api/src/modules/mappings/mappings.service.ts (1)

45-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve tenant-resolution HTTP errors in create().

getTenantDb(tenantId) now runs inside this try, so any HttpException it throws gets rewritten as BadRequestException('Failed to create Mapping'). That changes the API contract for missing or unavailable tenants. update() already preserves HttpException; create() should do the same here.

Suggested fix
   } catch (error: unknown) {
+    if (error instanceof HttpException) {
+      throw error;
+    }
     const safePayload = {
       appName: payload.appName,
       category: payload.category,
       entity: payload.entity,
       viewMode: payload.viewMode,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/modules/mappings/mappings.service.ts` around lines 45 - 73, The
create() method currently wraps getTenantDb(tenantId) in a try/catch and
unconditionally throws a BadRequestException, which masks HttpExceptions from
tenant resolution; change the catch in create() to detect and rethrow existing
HttpException instances (same behavior as update()), e.g., if error is an
instance of HttpException rethrow it, otherwise log the safePayload and throw
the BadRequestException as before; refer to the create() method and
getTenantDb() call to locate where to add the HttpException-preserving logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/db/database-manager.ts`:
- Around line 884-886: The catch block that sets hostUrl =
'postgresql://user:password@localhost:5432' silently hides parse failures for
DATABASE_URL; update the error handling in the catch inside the database-manager
logic to log a warning (using the same logger used elsewhere in this file) that
includes the original DATABASE_URL value (or a redacted version) and the caught
error, then keep the fallback assignment if you want to preserve behavior;
reference the catch surrounding the DATABASE_URL parsing and the hostUrl
variable so you can add a logger.warn/processLogger.warn call that includes the
error and the env value before assigning the fallback.
- Around line 1052-1055: The finally currently calls await tenantPool.end() and
await globalClient.end() but if tenantPool creation throws earlier, globalClient
may never be closed; modify the flow so globalClient is guaranteed closed:
either move globalClient setup and its own try/finally to an outer scope (create
globalClient, then try { create tenantPool ... } finally { await
globalClient.end() }) or ensure you check for existence before closing (e.g., if
(tenantPool) await tenantPool.end()) and wrap tenantPool lifecycle in its own
try/finally so both tenantPool and globalClient are closed on all error paths;
update the code referencing tenantPool and globalClient in database-manager.ts
accordingly.

In `@apps/api/src/modules/gitops/gitops-webhook.guard.ts`:
- Around line 94-123: The HMAC verification currently decodes providedDigest
without validating it, which allows inputs like "sha256=<valid>zz" to be
truncated and pass timingSafeEqual; before Buffer.from(providedDigest, 'hex') in
the Gitops webhook guard (the code using providedDigest, timingSafeEqual,
createHmac and throwing UnauthorizedException), validate that providedDigest
matches /^[a-fA-F0-9]{64}$/ (exactly 64 hex characters) and if not, throw an
UnauthorizedException with an appropriate message; only then proceed to
Buffer.from(...) and the constant-time comparison.

In `@apps/api/src/modules/workspaces/capacity-manager.service.ts`:
- Around line 57-83: The loop in CapacityManager (capacity-manager.service.ts)
performs this.db.execute(INSERT ...) then this.queueService.send(...), causing
orphaned INITIALIZING rows if queue send fails; change the order to call
this.queueService.send(QueueName.TenantProvisionQueue, event) first and only
INSERT the INITIALIZING row after the send succeeds so a failed send never
leaves a committed INITIALIZING entry, and ensure the worker (provision handler)
tolerates a missing registry row (or creates it) on start; additionally add a
periodic cleanup routine (e.g., cleanupStaleInitializing or within existing
maintenance cron) to remove/reset INITIALIZING rows older than a configurable
TTL to guard against any remaining edge cases.
- Around line 101-112: deriveHostUrl() currently reconstructs and returns
DATABASE_URL including credentials and is used to populate hostUrl in
ProvisionDatabaseEvent; instead, strip any username/password and return a
credentials-free base URL (protocol, hostname, port) so credentials are not sent
in the queue. Update deriveHostUrl() to parse process.env.DATABASE_URL but omit
parsed.username and parsed.password from the returned string, and adjust the
consumer code (e.g., runMigrations or the handler that receives
ProvisionDatabaseEvent) to recompose the full connection string by reading
credentials from its own process.env.DATABASE_URL at runtime before connecting.
Ensure identifiers: deriveHostUrl(), ProvisionDatabaseEvent, and runMigrations
are updated accordingly.
- Around line 109-111: The catch block that currently returns the literal
placeholder 'postgresql://user:password@localhost:5432' must be removed so
misconfigured/invalid DATABASE_URL errors are not silently masked; instead,
rethrow the original error (or throw a new Error that includes the original
error message) from that try/catch in the host URL parsing function so the
failure surfaces to the caller (e.g., replenishPool) and is logged/handled
there. Ensure you reference and update the try/catch around the DATABASE_URL
parsing in capacity-manager.service.ts to throw the error rather than returning
the placeholder.

In `@apps/api/src/modules/workspaces/capacity-manager.types.ts`:
- Around line 1-13: ProvisionDatabaseEvent is duplicated across services causing
contract drift; extract the interface into a single shared workspace package
(e.g., create `@nexiom/contracts` or `@nexiom/queue`) and export
ProvisionDatabaseEvent from there, then update capacity-manager.service.ts and
tenant-provision.worker.ts to import the type from the new shared package
instead of redefining it so both sides use the exact same contract.

In `@apps/api/src/scripts/admin-bootstrap.ts`:
- Around line 58-62: Replace the unsafe cast by creating a new Drizzle instance
scoped to the identity schema and pass that to seedSystemRbac: instead of
casting db as NodePgDatabase<typeof identitySchema>, call drizzle(client, {
schema: identitySchema }) (matching the pattern in database-manager.ts) to
produce an identityDb and invoke seedSystemRbac(identityDb, config, console);
update references to use identityDb and remove the cast so seedSystemRbac
receives a correctly typed NodePgDatabase<typeof identitySchema>.

In `@apps/api/src/shared/db.utils.spec.ts`:
- Around line 39-42: Add a symmetric unit test for extractPgError that ensures a
non-string direct code returns null: create a new spec mirroring the existing
"returns null if cause is present but has no code string" test but pass { code:
123 } (no cause) and assert extractPgError(error) is null; place it next to the
current test in apps/api/src/shared/db.utils.spec.ts so both branches (direct
code and cause.code) are covered and the function's type-guard on code being a
string is exercised.
- Around line 9-13: Update the test case in apps/api/src/shared/db.utils.spec.ts
for the function extractPgError to include undefined among the non-object
inputs; specifically, modify the 'returns null for non-objects' test (the it
block that calls extractPgError) to also assert
expect(extractPgError(undefined)).toBeNull(), ensuring the utility safely
returns null for undefined values as well as null, strings, and numbers.

In `@apps/tenant-provisioner/src/modules/provisioner/tenant-provision.worker.ts`:
- Around line 30-40: The consumer callback registered in onModuleInit via
this.queueService.consume(QueueName.TenantProvisionQueue, ...) lacks error
handling: wrap the call to await this.provision(event) inside a try/catch, log
the error with this.logger.error including event.poolSlotId and error details,
and rethrow the error from the catch so the queue consumer
(QueueService.consume) can nack/trigger retries instead of letting an unhandled
rejection crash or silently drop the message.
- Around line 121-134: In registerWarmSlot, the UPDATE call to
tenant_storage_registry that sets status='WARM' for tenant_id
`WARM-${poolSlotId}` ignores the query result; change the code to capture the
returned result from adminClient.query and check result.rowCount — if rowCount
=== 0, emit a warning via this.logger.warn (include the poolSlotId and the
attempted tenant id) or throw an error depending on desired behavior so missing
rows are surfaced; keep the existing finally block that calls adminClient.end().
- Around line 96-103: The hard-coded rootDir calculation using path.resolve(...,
'../../../../') is fragile; change tenant-provision.worker.ts to derive the
migrationsFolder from a configurable source (e.g., process.env.MIGRATIONS_DIR or
an injected ConfigService value) and only fall back to the current
fileURLToPath-based resolution if that config is absent. Update the code that
sets migrationsFolder (and any use of rootDir) to first read the env/config
value, validate it exists, and use path.join(configValue,
'packages/database/drizzle/tenant') or the provided absolute path directly;
ensure the migrate() caller uses this migrationsFolder variable so deployments
can override the location without relying on the '../../../../' relative path.
- Around line 78-87: The SELECT-existence check uses existing.rowCount which the
pg docs discourage; change the check to inspect existing.rows.length (e.g., if
(existing.rows.length === 0) ...) when determining whether to CREATE DATABASE
for dbName in the tenant provisioning logic that uses adminClient and existing;
keep the CREATE DATABASE and logging behavior (this.logger.log and
this.logger.warn) unchanged but replace the rowCount check with rows.length for
reliability.

In `@apps/tenant-provisioner/tsconfig.json`:
- Line 3: Remove "vitest/globals" from the root tsconfig.json to stop test
globals leaking into production and create a new test-specific config named
tsconfig.spec.json that extends the base and adds "types": ["vitest/globals"]
and includes only spec files (e.g., "src/**/*.spec.ts"); finally update your
Vitest configuration (vitest.config.ts) to point to the new tsconfig.spec.json
so tests compile with Vitest globals while the main build no longer exposes
describe/it/vi/expect.

In `@apps/worker/src/modules/pipeline/normalized-outbox.worker.spec.ts`:
- Around line 51-58: The "schema query errors" test is no longer exercising
production code because connection discovery is mocked via mockTenants.where;
update the failing path to reject from the actual call the worker now uses:
change the test's failure mock to make globalDb.where(...) (or alternatively
tenantDb.transaction(...) for the other case at lines 191-194) return a rejected
promise (e.g., mockRejectedValue) instead of failing tenantDb.from(), so the
worker's error handling for connection discovery/transaction is exercised;
locate the mocks referencing mockTenants.where, globalDb, and
tenantDb.transaction and swap the resolved/error behavior accordingly.

In `@apps/worker/src/modules/pipeline/normalized-outbox.worker.ts`:
- Around line 50-59: Query only "drainable" app connections from this.globalDb
before calling getTenantDb(): change the select over appConnections to include
the schema/plan predicate that proves normalized_outbox is provisioned (so only
namespace connections with normalized_outbox are returned), e.g., add the
condition checking the schema-plan field on appConnections that guarantees
normalized_outbox exists; then short-circuit (return/continue) when the
resulting connections array is empty to avoid calling getTenantDb() and running
drainWorkspaceOutbox() unnecessarily for tenants with no eligible connections.

In `@engine/ai/core/src/categories/mapping.service.ts`:
- Around line 29-43: The code currently only queries the tenant-local DB (using
this.dbManager.getTenantDb and canonicalMappings) and returns null if nothing is
found; restore the documented "tenant override, then global fallback" behavior
by, after the tenantDb select (mappingRecord), checking if no row was returned
and then performing the identical select against the shared/global canonical
mappings DB (e.g., obtain the global DB via this.dbManager.getGlobalDb() or the
known canonical tenant DB and re-run the same
.select(...).from(canonicalMappings).where(...).limit(1)); return the global
result when present so callers get the shared default when the tenant has no
override.

In `@package.json`:
- Around line 62-67: Move the pnpm only-built-dependencies setting out of the
package.json "pnpm.onlyBuiltDependencies" block and into the workspace config by
adding an onlyBuiltDependencies entry in pnpm-workspace.yaml (the canonical
location since pnpm 10.7+); keep the same values ("@nestjs/core", "@swc/core",
"esbuild", "sharp"), remove the onlyBuiltDependencies key from package.json, and
ensure the workspace YAML uses the same allowlist so lifecycle scripts for
native-binary deps continue to run correctly.

In `@packages/database/drizzle.config.ts`:
- Around line 45-49: The global Drizzle config (export default object)
incorrectly includes tenant schema globs in the schema array; remove
'./dist/schema/tenant/**/*.js' from the schema array in
packages/database/drizzle.config.ts so only ['./dist/schema/global/**/*.js'] is
present and tenant DDL is no longer emitted into the './drizzle/global' output
(tenant tables should be managed by drizzle.config.tenant.ts). Ensure the out
setting remains './drizzle/global' and run the global migrator to confirm only
global migrations are generated.

---

Outside diff comments:
In `@apps/api/src/modules/dbmanager/dbmanager.module.ts`:
- Around line 19-31: DbManagerModule currently never calls
TenantDatabaseManager.closeAll(), so cached per-tenant Pool connections leak on
shutdown; implement Nest's OnModuleDestroy on the provider or DbManagerModule
and in the onModuleDestroy lifecycle method call the created
TenantDatabaseManager.closeAll() (ensure you reference the instance returned by
the factory that constructs TenantDatabaseManager) so all pooled connections are
closed during graceful shutdown.

In `@apps/api/src/modules/mappings/mappings.service.ts`:
- Around line 45-73: The create() method currently wraps getTenantDb(tenantId)
in a try/catch and unconditionally throws a BadRequestException, which masks
HttpExceptions from tenant resolution; change the catch in create() to detect
and rethrow existing HttpException instances (same behavior as update()), e.g.,
if error is an instance of HttpException rethrow it, otherwise log the
safePayload and throw the BadRequestException as before; refer to the create()
method and getTenantDb() call to locate where to add the
HttpException-preserving logic.

In `@apps/api/src/modules/workspaces/workspaces.service.ts`:
- Around line 34-83: The WARM slot can leak if the UPDATE that claims
tenant_storage_registry succeeds but the subsequent insert into uiWorkspaces
fails; wrap the claim+insert in a single DB transaction so the UPDATE (claimed)
is rolled back on any insert error: begin a transaction via this.db.transaction
(or your DB client's transaction API), perform the SELECT/UPDATE claim logic
(the same statements referencing tenant_storage_registry, hasDb, claimed) and
then perform the insert into uiWorkspaces and .returning(); only commit if the
insert succeeds, otherwise roll back so the tenant_storage_registry UPDATE is
undone and the slot remains WARM. Ensure the FOR UPDATE SKIP LOCKED behavior
remains inside the same transaction scope.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e277c165-9ace-45b2-884e-c0d567864731

📥 Commits

Reviewing files that changed from the base of the PR and between c807162 and a15916b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (78)
  • apps/api/src/db/database-manager.ts
  • apps/api/src/modules/connections/connectors.service.spec.ts
  • apps/api/src/modules/connections/connectors.service.ts
  • apps/api/src/modules/dbmanager/dbmanager.module.ts
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/modules/mappings/mappings.controller.spec.ts
  • apps/api/src/modules/mappings/mappings.controller.ts
  • apps/api/src/modules/mappings/mappings.service.spec.ts
  • apps/api/src/modules/mappings/mappings.service.ts
  • apps/api/src/modules/mappings/mappings.validation.ts
  • apps/api/src/modules/pipeline/inbound-outbox.service.spec.ts
  • apps/api/src/modules/pipeline/inbound-outbox.service.ts
  • apps/api/src/modules/pipeline/replica-outbox.service.spec.ts
  • apps/api/src/modules/pipeline/replica-outbox.service.ts
  • apps/api/src/modules/trigger/trigger-executor.service.ts
  • apps/api/src/modules/trigger/trigger.module.ts
  • apps/api/src/modules/workspaces/capacity-manager.service.spec.ts
  • apps/api/src/modules/workspaces/capacity-manager.service.ts
  • apps/api/src/modules/workspaces/capacity-manager.types.ts
  • apps/api/src/modules/workspaces/workspaces.module.ts
  • apps/api/src/modules/workspaces/workspaces.service.spec.ts
  • apps/api/src/modules/workspaces/workspaces.service.ts
  • apps/api/src/scripts/admin-bootstrap.ts
  • apps/api/src/shared/db.utils.spec.ts
  • apps/tenant-provisioner/eslint.config.mjs
  • apps/tenant-provisioner/nest-cli.json
  • apps/tenant-provisioner/package.json
  • apps/tenant-provisioner/src/app.module.ts
  • apps/tenant-provisioner/src/main.ts
  • apps/tenant-provisioner/src/modules/provisioner/provision-database.event.ts
  • apps/tenant-provisioner/src/modules/provisioner/provisioner.module.ts
  • apps/tenant-provisioner/src/modules/provisioner/tenant-provision.worker.spec.ts
  • apps/tenant-provisioner/src/modules/provisioner/tenant-provision.worker.ts
  • apps/tenant-provisioner/tsconfig.json
  • apps/tenant-provisioner/vitest.config.ts
  • apps/worker/src/modules/dbmanager/dbmanager.module.ts
  • apps/worker/src/modules/pipeline/normalized-outbox.worker.spec.ts
  • apps/worker/src/modules/pipeline/normalized-outbox.worker.ts
  • engine/ai/core/package.json
  • engine/ai/core/src/categories/mapping.service.ts
  • engine/ai/core/src/services/chat-persistence.service.ts
  • package.json
  • packages/database/drizzle.config.tenant.ts
  • packages/database/drizzle.config.ts
  • packages/database/drizzle/0002_add_status_enum_values.sql
  • packages/database/drizzle/0003_add_gem_indexes.sql
  • packages/database/drizzle/0004_verify_gem_indexes.sql
  • packages/database/drizzle/0005_greedy_eddie_brock.sql
  • packages/database/drizzle/0006_quick_puck.sql
  • packages/database/drizzle/0007_clammy_aaron_stack.sql
  • packages/database/drizzle/MIGRATION_NOTES.md
  • packages/database/drizzle/global/0000_mixed_sleepwalker.sql
  • packages/database/drizzle/global/0001_shocking_microbe.sql
  • packages/database/drizzle/global/0003_peaceful_beast.sql
  • packages/database/drizzle/global/meta/0000_snapshot.json
  • packages/database/drizzle/global/meta/0001_snapshot.json
  • packages/database/drizzle/global/meta/0003_snapshot.json
  • packages/database/drizzle/global/meta/_journal.json
  • packages/database/drizzle/meta/0001_snapshot.json
  • packages/database/drizzle/meta/0002_snapshot.json
  • packages/database/drizzle/meta/0005_snapshot.json
  • packages/database/drizzle/meta/0007_snapshot.json
  • packages/database/drizzle/meta/_journal.json
  • packages/database/drizzle/tenant/0000_oval_calypso.sql
  • packages/database/drizzle/tenant/meta/0000_snapshot.json
  • packages/database/drizzle/tenant/meta/_journal.json
  • packages/database/package.json
  • packages/database/src/index.ts
  • packages/database/src/schema/global/canonical_mappings.ts
  • packages/database/src/schema/global/storage_registry.ts
  • packages/database/src/schema/tenant/canonical_mappings.ts
  • packages/database/src/schema/tenant/chat.ts
  • packages/database/src/schema/tenant/tenant.ts
  • packages/database/src/schema/tenant/workspace.ts
  • packages/dbmanager/src/impl/sql-database-manager.ts
  • packages/dbmanager/src/interfaces.ts
  • packages/queue/src/constants.ts
  • scripts/init-localstack.sh
💤 Files with no reviewable changes (11)
  • packages/database/drizzle/0007_clammy_aaron_stack.sql
  • packages/database/drizzle/0002_add_status_enum_values.sql
  • packages/database/drizzle/meta/0001_snapshot.json
  • packages/database/drizzle/0003_add_gem_indexes.sql
  • packages/database/drizzle/meta/0002_snapshot.json
  • packages/database/drizzle/MIGRATION_NOTES.md
  • packages/database/drizzle/0006_quick_puck.sql
  • packages/database/drizzle/meta/0005_snapshot.json
  • packages/database/drizzle/0005_greedy_eddie_brock.sql
  • packages/database/drizzle/0004_verify_gem_indexes.sql
  • apps/api/src/modules/mappings/mappings.validation.ts

Comment thread apps/api/src/db/database-manager.ts Outdated
Comment thread apps/api/src/db/database-manager.ts
Comment thread apps/api/src/modules/gitops/gitops-webhook.guard.ts
Comment thread apps/api/src/modules/workspaces/capacity-manager.service.ts
Comment thread apps/api/src/modules/workspaces/capacity-manager.service.ts
Comment thread apps/worker/src/modules/pipeline/normalized-outbox.worker.spec.ts
Comment thread apps/worker/src/modules/pipeline/normalized-outbox.worker.ts Outdated
Comment thread engine/ai/core/src/categories/mapping.service.ts
Comment thread package.json Outdated
Comment thread packages/database/drizzle.config.ts
@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 15 file(s) based on 20 unresolved review comments.

Files modified:

  • apps/api/src/db/database-manager.ts
  • apps/api/src/modules/gitops/gitops-webhook.guard.ts
  • apps/api/src/modules/workspaces/capacity-manager.service.ts
  • apps/api/src/scripts/admin-bootstrap.ts
  • apps/api/src/shared/db.utils.spec.ts
  • apps/tenant-provisioner/src/modules/provisioner/tenant-provision.worker.ts
  • apps/tenant-provisioner/tsconfig.json
  • apps/tenant-provisioner/vitest.config.ts
  • apps/worker/src/modules/pipeline/normalized-outbox.worker.spec.ts
  • apps/worker/src/modules/pipeline/normalized-outbox.worker.ts
  • engine/ai/core/src/categories/mapping.service.ts
  • package.json
  • packages/database/drizzle.config.ts
  • packages/queue/src/index.ts
  • pnpm-workspace.yaml

Commit: 8490b5f0ea97b9bf1ba25f5dc22a9767f08a9262

The changes have been pushed to the feature/platform-app-decoupling branch.

Time taken: 10m 12s

Fixed 15 file(s) based on 20 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@pramodnarayana
pramodnarayana marked this pull request as draft May 4, 2026 08:59
@pramodnarayana
pramodnarayana marked this pull request as ready for review May 4, 2026 08:59
@pramodnarayana pramodnarayana self-assigned this May 4, 2026
@pramodnarayana
pramodnarayana marked this pull request as draft May 4, 2026 09:05
@pramodnarayana
pramodnarayana marked this pull request as ready for review May 4, 2026 09:05
@pramodnarayana

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pramodnarayana
pramodnarayana merged commit 346815c into development May 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant