Skip to content

feat: add ManagedDeploymentLeaseCreated event and handler#2728

Merged
baktun14 merged 8 commits intomainfrom
feat/auto-enable-alert
Feb 18, 2026
Merged

feat: add ManagedDeploymentLeaseCreated event and handler#2728
baktun14 merged 8 commits intomainfrom
feat/auto-enable-alert

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Feb 13, 2026

Summary by CodeRabbit

  • New Features

    • Auto-enable deployment alerts for managed leases, using deployment escrow to set a sensible threshold (30%).
  • Improvements

    • Notification service now ensures a default channel, fetches deployment details, and skips gracefully when prerequisites (email, channel, or deployment) are missing.
    • Managed-lease events are published during the signing flow and the corresponding handler is registered at startup to trigger alerting.
  • Tests

    • Added unit tests covering the alert flow and managed-lease event handler.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

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

Adds a ManagedDeploymentLeaseCreated domain event, publishes it from managed-signer, registers a new handler that calls NotificationService.autoEnableDeploymentAlert, and extends NotificationService with user, deployment, and logger dependencies plus the auto-enable alert flow.

Changes

Cohort / File(s) Summary
Event Definition
apps/api/src/billing/events/managed-deployment-lease-created.ts
New ManagedDeploymentLeaseCreated domain event class with payload { userId, walletAddress, dseq }.
Signer: Publish Event & Tests
apps/api/src/billing/services/managed-signer/managed-signer.service.ts, apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
Publishes ManagedDeploymentLeaseCreated when handling MsgCreateLease; spec updated to account for additional publish(s) and funding-wallet scenarios.
Handler Implementation & Tests
apps/api/src/app/services/managed-deployment-lease-created/managed-deployment-lease-created.handler.ts, apps/api/src/app/services/managed-deployment-lease-created/managed-deployment-lease-created.handler.spec.ts
Adds ManagedDeploymentLeaseCreatedHandler (concurrency 5) that logs and calls notificationService.autoEnableDeploymentAlert; unit test verifies call with { userId, walletAddress, dseq }.
Handler Registration
apps/api/src/app/providers/jobs.provider.ts
Registers the new handler in the APP_INITIALIZER startup sequence (added to registerHandlers array).
Notification Service & Tests
apps/api/src/notifications/services/notification/notification.service.ts, apps/api/src/notifications/services/notification/notification.service.spec.ts
Extends NotificationService to accept UserRepository, DeploymentHttpService, and LoggerService; adds autoEnableDeploymentAlert to ensure channel, fetch deployment escrow, compute threshold, and upsert deployment alert; tests expanded for success and multiple skip paths.
Manifest / Misc
package.json
Minor manifest/test lines updated.

Sequence Diagram

sequenceDiagram
    participant ManagedSigner as ManagedSigner Service
    participant EventBus as Event Bus
    participant Handler as ManagedDeploymentLeaseCreated Handler
    participant UserRepo as User Repository
    participant DeploymentHTTP as Deployment HTTP Service
    participant NotificationsAPI as Notifications API Client

    ManagedSigner->>EventBus: publish(ManagedDeploymentLeaseCreated)
    EventBus->>Handler: handle(event)
    Handler->>UserRepo: findById(userId)
    UserRepo-->>Handler: user (email?)
    Handler->>NotificationsAPI: ensureDefaultChannel(userId)
    NotificationsAPI-->>Handler: channelId
    Handler->>DeploymentHTTP: fetchDeployment(walletAddress, dseq)
    DeploymentHTTP-->>Handler: deployment (escrow balance)
    Handler->>NotificationsAPI: upsertDeploymentAlert(channelId, threshold)
    NotificationsAPI-->>Handler: upsert result
    Handler-->>EventBus: complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A lease hops in and I prick my ear,
I fetch the user, the channel, the cheer,
I count the escrow, set a threshold anew,
I nudge the notifier to watch over you,
Hooray — a rabbit guard for your deployment too! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request lacks a description entirely. The template requires sections for 'Why' and 'What', but no content was provided by the author. Add a pull request description explaining the motivation for this change and what was implemented, including any related issues or breaking changes if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a new ManagedDeploymentLeaseCreated event and handler, which aligns with the substantial additions to the codebase.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auto-enable-alert

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

@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.08%. Comparing base (7d2914f) to head (3d8697a).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2728      +/-   ##
==========================================
+ Coverage   51.96%   52.08%   +0.11%     
==========================================
  Files        1041     1043       +2     
  Lines       27397    27454      +57     
  Branches     6332     6341       +9     
==========================================
+ Hits        14238    14300      +62     
+ Misses      12695    12681      -14     
- Partials      464      473       +9     
Flag Coverage Δ
api 76.91% <100.00%> (+0.24%) ⬆️
deploy-web 37.39% <ø> (+<0.01%) ⬆️
log-collector 75.35% <ø> (ø)
notifications 85.56% <ø> (ø)
provider-console 81.48% <ø> (ø)
provider-proxy 82.41% <ø> (ø)
tx-signer 79.29% <ø> (ø)
Files with missing lines Coverage Δ
apps/api/src/app/providers/jobs.provider.ts 0.00% <ø> (ø)
...eployment-alert/enable-deployment-alert.handler.ts 100.00% <100.00%> (ø)
...illing/commands/enable-deployment-alert.command.ts 100.00% <100.00%> (ø)
.../services/managed-signer/managed-signer.service.ts 100.00% <100.00%> (+2.85%) ⬆️
...ions/services/notification/notification.service.ts 100.00% <100.00%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@baktun14 baktun14 marked this pull request as ready for review February 13, 2026 19:26
@baktun14 baktun14 requested a review from a team as a code owner February 13, 2026 19:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@apps/api/src/app/services/managed-deployment-lease-created/managed-deployment-lease-created.handler.spec.ts`:
- Around line 29-39: Update the setup function to accept a single typed
parameter (e.g., params?: { notificationService?: Partial<NotificationService>;
logger?: Partial<LoggerService> }) and use that parameter to allow overrides
while still creating default mocked services; keep creating the handler with new
ManagedDeploymentLeaseCreatedHandler(params.notificationService ||
defaultMockNotificationService, params.logger || defaultMockLogger) and ensure
the default mock for notificationService still stubs autoEnableDeploymentAlert
as jest.fn().mockResolvedValue(undefined). This preserves existing behavior but
satisfies the "single typed parameter" setup signature rule.

In
`@apps/api/src/notifications/services/notification/notification.service.spec.ts`:
- Around line 276-283: The setup function currently has no parameters; change it
to accept a single inline-typed parameter (e.g., overrides?: { api?:
NotificationsApiClient; userRepository?: UserRepository; deploymentHttpService?:
DeploymentHttpService; logger?: LoggerService }) and create defaults using
mockDeep/mock for api, mock for userRepository, deploymentHttpService, and
logger, then merge any provided overrides into those defaults before
instantiating NotificationService; keep returned object shape { service, api,
userRepository, deploymentHttpService, logger } so tests can override individual
dependencies when calling setup({ api, logger }) while preserving strong typing
in the single parameter.
- Around line 173-274: Tests for autoEnableDeploymentAlert use "as any" casts
(e.g. on userRepository.findById return, api.v1 responses, and
deploymentHttpService.findByOwnerAndDseq) which violate the no-any rule; replace
these casts with proper typed fixtures or mocked return types by using the
actual interfaces/models or Jest's helpers (e.g. Partial<YourType> or
jest.Mocked<...>) so each mockResolvedValue returns a correctly typed object for
userRepository.findById, api.v1.createDefaultChannel,
api.v1.getNotificationChannels, deploymentHttpService.findByOwnerAndDseq, and
api.v1.upsertDeploymentAlert, updating the test setup to import/declare the
appropriate types and constructing minimal valid objects rather than casting to
any.

In `@apps/api/src/notifications/services/notification/notification.service.ts`:
- Around line 77-118: The new method autoEnableDeploymentAlert uses `as any` in
two API calls; remove those casts and replace them with the correct OpenAPI/SDK
operation parameter types: type the getNotificationChannels call using the
generated operation type for that endpoint (e.g.
operations['getNotificationChannels']['parameters'] or the client-specific
Parameters type) and construct the header/query object to match it, and likewise
type the upsertDeploymentAlert parameters using the upsertDeploymentAlert
operation type (e.g. operations['upsertDeploymentAlert']['parameters'] and its
request body type) so headers ("x-user-id", "x-owner-address"), path (dseq) and
body match the proper interfaces; update imports to bring in the generated
operation types if needed and adjust the createDefaultChannel call site only if
its signature requires typed input (createDefaultChannel,
notificationsApi.v1.getNotificationChannels,
notificationsApi.v1.upsertDeploymentAlert, and autoEnableDeploymentAlert are the
symbols to modify).

stalniy
stalniy previously approved these changes Feb 17, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/api/src/notifications/services/notification/notification.service.ts (1)

104-118: Guard against floating-point precision for escrow balances.

parseFloat can lose precision on very large blockchain amounts (e.g., "500000.000000000000000000"). While the current pattern matches existing code in draining-deployment-rpc.service.ts, consider using BigInt integer math for the calculation before converting back to number for the API, which requires a numeric type. Include a Number.isSafeInteger() check to handle amounts that exceed JavaScript's safe range.

Possible approach with integer math
-    const escrowBalance = deployment.escrow_account.state.funds.reduce((sum, { amount }) => sum + parseFloat(amount), 0);
-    const threshold = Math.ceil(0.3 * escrowBalance);
-    if (threshold === 0) return;
+    const escrowBalance = deployment.escrow_account.state.funds.reduce((sum, { amount }) => sum + BigInt(Math.floor(parseFloat(amount))), 0n);
+    const threshold = (escrowBalance * 3n + 9n) / 10n; // ceil(0.3 * balance)
+    if (threshold === 0n) return;
+    const thresholdNumber = Number(threshold);
+    if (!Number.isSafeInteger(thresholdNumber)) {
+      this.logger.warn({ event: "SKIP_AUTO_ENABLE_ALERT", reason: "Threshold exceeds safe integer range", dseq: input.dseq });
+      return;
+    }
-            deploymentBalance: { notificationChannelId: channelId, enabled: true, threshold }
+            deploymentBalance: { notificationChannelId: channelId, enabled: true, threshold: thresholdNumber }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/notifications/services/notification/notification.service.ts`
around lines 104 - 118, The current sum uses parseFloat and can lose precision;
replace the reduce that computes escrowBalance with BigInt integer math: parse
each string amount into a fixed-decimal BigInt (strip the decimal point assuming
the chain’s known scale, e.g., 18 decimals, or determine scale consistently),
sum into totalBigInt, compute thresholdBigInt as the ceiling of 30% via integer
arithmetic (e.g., thresholdBigInt = (totalBigInt * 3n + 9n) / 10n), then before
calling notificationsApi.v1.upsertDeploymentAlert convert thresholdBigInt back
to a JavaScript number only after checking Number.isSafeInteger(thresholdNum)
(or handle overflow by using Number.MAX_SAFE_INTEGER or failing early with a
logged error); update references around escrowBalance/threshold computation in
the block that feeds upsertDeploymentAlert so the API receives a safe numeric
threshold.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/src/notifications/services/notification/notification.service.ts`:
- Around line 104-118: The current sum uses parseFloat and can lose precision;
replace the reduce that computes escrowBalance with BigInt integer math: parse
each string amount into a fixed-decimal BigInt (strip the decimal point assuming
the chain’s known scale, e.g., 18 decimals, or determine scale consistently),
sum into totalBigInt, compute thresholdBigInt as the ceiling of 30% via integer
arithmetic (e.g., thresholdBigInt = (totalBigInt * 3n + 9n) / 10n), then before
calling notificationsApi.v1.upsertDeploymentAlert convert thresholdBigInt back
to a JavaScript number only after checking Number.isSafeInteger(thresholdNum)
(or handle overflow by using Number.MAX_SAFE_INTEGER or failing early with a
logged error); update references around escrowBalance/threshold computation in
the block that feeds upsertDeploymentAlert so the API receives a safe numeric
threshold.

@baktun14 baktun14 force-pushed the feat/auto-enable-alert branch from a004c56 to a5a7607 Compare February 17, 2026 22:07
@baktun14 baktun14 enabled auto-merge February 17, 2026 22:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/api/src/notifications/services/notification/notification.service.ts (1)

86-98: Avoid unconditional default-channel creation to prevent failures.

createDefaultChannel runs before checking existing channels. If the API isn’t idempotent (or rejects duplicates), this will throw and abort alert enablement for existing users. Consider fetching channels first and only creating when none are found (or explicitly ignoring “already exists” errors).

Proposed refactor
-    await this.createDefaultChannel({ id: input.userId, email: user.email });
-
-    const channelsResult = await this.notificationsApi.v1.getNotificationChannels({
-      parameters: {
-        header: { "x-user-id": input.userId },
-        query: { page: 1, limit: 1 }
-      } as operations["getNotificationChannels"]["parameters"] & { header: { "x-user-id": string } }
-    });
-    const channelId = channelsResult?.data?.data?.[0]?.id;
+    const channelsParams =
+      {
+        header: { "x-user-id": input.userId },
+        query: { page: 1, limit: 1 }
+      } as operations["getNotificationChannels"]["parameters"] & { header: { "x-user-id": string } };
+
+    let channelsResult = await this.notificationsApi.v1.getNotificationChannels({ parameters: channelsParams });
+    let channelId = channelsResult?.data?.data?.[0]?.id;
+
+    if (!channelId) {
+      await this.createDefaultChannel({ id: input.userId, email: user.email });
+      channelsResult = await this.notificationsApi.v1.getNotificationChannels({ parameters: channelsParams });
+      channelId = channelsResult?.data?.data?.[0]?.id;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/notifications/services/notification/notification.service.ts`
around lines 86 - 98, The code currently calls createDefaultChannel(...) before
checking existing channels which can fail on duplicate creation; change the flow
in notification.service.ts to first call
this.notificationsApi.v1.getNotificationChannels(...) (as already used) and only
call this.createDefaultChannel({ id: input.userId, email: user.email }) when
channelsResult.data.data is empty (no channelId), or wrap createDefaultChannel
in a try/catch that swallows/ignores a deterministic "already exists" error but
rethrows other errors; update logic around channelId (derived from
channelsResult?.data?.data?.[0]?.id) to reflect this new order so you only
create when necessary.
🤖 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/notifications/services/notification/notification.service.ts`:
- Around line 106-108: The escrow balance calculation in notification.service.ts
uses parseFloat directly in the reducer and should mirror
deployment-reader.service.ts by applying fixed 18-decimal normalization after
the sum; change the computation of escrowBalance (the variable named
escrowBalance) to first sum amounts (e.g., total =
deployment.escrow_account.state.funds.reduce(..., 0)), then call
total.toFixed(18) and convert that to a Number/parseFloat for escrowBalance
before computing threshold (still using
DEPLOYMENT_BALANCE_ALERT_THRESHOLD_RATIO).

---

Nitpick comments:
In `@apps/api/src/notifications/services/notification/notification.service.ts`:
- Around line 86-98: The code currently calls createDefaultChannel(...) before
checking existing channels which can fail on duplicate creation; change the flow
in notification.service.ts to first call
this.notificationsApi.v1.getNotificationChannels(...) (as already used) and only
call this.createDefaultChannel({ id: input.userId, email: user.email }) when
channelsResult.data.data is empty (no channelId), or wrap createDefaultChannel
in a try/catch that swallows/ignores a deterministic "already exists" error but
rethrows other errors; update logic around channelId (derived from
channelsResult?.data?.data?.[0]?.id) to reflect this new order so you only
create when necessary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/notifications/services/notification/notification.service.ts`:
- Around line 106-108: The current computation of escrowBalance and threshold
can result in NaN or negative values if any parsed amount is non-finite; update
the logic around escrowBalance, threshold and the use of
DEPLOYMENT_BALANCE_ALERT_THRESHOLD_RATIO to validate inputs: when reducing
deployment.escrow_account.state.funds, coerce each amount safely (e.g.,
parseFloat with fallback to 0) and compute escrowBalance, then check
Number.isFinite(escrowBalance) and escrowBalance > 0 before computing threshold;
after computing threshold also guard with Number.isFinite(threshold) and
threshold > 0 and return early if those checks fail so no invalid threshold is
sent to the API (refer to escrowBalance, threshold,
DEPLOYMENT_BALANCE_ALERT_THRESHOLD_RATIO).
- Around line 88-124: The external calls getNotificationChannels,
deploymentHttpService.findByOwnerAndDseq, and
notificationsApi.v1.upsertDeploymentAlert need retry/error handling: wrap each
call in the same backOff retry helper used by
createNotification/createDefaultChannel (or add equivalent try/catch with
retries) so transient errors are retried and failures are logged via
this.logger.warn/error with context (event, dseq, userId, walletAddress) and
cause graceful degradation (skip auto-enable flow when still failing).
Specifically, replace direct calls to
this.notificationsApi.v1.getNotificationChannels,
this.deploymentHttpService.findByOwnerAndDseq, and
this.notificationsApi.v1.upsertDeploymentAlert with backOff(() => /* call */)
and add catch blocks that log the error and return early where appropriate to
preserve current control flow.
- Around line 86-93: The call to createDefaultChannel is unconditional and can
throw on conflict; change autoEnableDeploymentAlert (or the function containing
the shown snippet) to first call notificationsApi.v1.getNotificationChannels (as
already available) and check if any channel exists for the userId before calling
createDefaultChannel({ id: input.userId, email: user.email }); alternatively
wrap createDefaultChannel in a try/catch that specifically handles conflict
responses (HTTP 409) by swallowing or logging them and only rethrowing
unexpected errors; reference createDefaultChannel and
notificationsApi.v1.getNotificationChannels when making the change.

@ygrishajev
Copy link
Contributor

please make sure to follow pull request template and describe the changes

@baktun14 baktun14 force-pushed the feat/auto-enable-alert branch from ca20355 to 93eef13 Compare February 18, 2026 17:52
@baktun14
Copy link
Contributor Author

image Tested locally and both are enabled by default when creating a deployment

@baktun14 baktun14 added this pull request to the merge queue Feb 18, 2026
Merged via the queue into main with commit 9f4ae3d Feb 18, 2026
54 of 55 checks passed
@baktun14 baktun14 deleted the feat/auto-enable-alert branch February 18, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments