Skip to content

fix(deployment): await trial-wallet activation before creating a deployment - #3542

Merged
ygrishajev merged 1 commit into
mainfrom
fix/deployment-await-trial-activation
Aug 3, 2026
Merged

fix(deployment): await trial-wallet activation before creating a deployment#3542
ygrishajev merged 1 commit into
mainfrom
fix/deployment-await-trial-activation

Conversation

@ygrishajev

@ygrishajev ygrishajev commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Why

A managed (trial) wallet gets its address at registration, but can only spend once its on-chain
grants are provisioned. That provisioning ("start trial") was driven from the client, kicked off
when the user landed on onboarding. Anything that interrupted the browser between landing and the
first spend — a reload, a navigation, a closed tab — abandoned the in-flight start-trial, so the
action raced ahead of an un-activated wallet and failed terminally. This wasn't only deploys:
top-ups and coupon redemptions hit the same un-provisioned wallet the same way. A manual retry
usually "fixed" it, because by then the grants had landed.

A spending action should never have to assume a client-side side-effect completed. Activation is now
the server's responsibility, and every managed-wallet endpoint waits for it.

What

Trial-wallet activation moves off the client and behind a server-side background job, and every
managed-wallet spend/credit awaits activation via a retriable 409 wallet_provisioning instead of
failing. No happy-path UX change; the action just occasionally runs a little longer while grants land.

flowchart TD
    subgraph server["Server-side — activation runs on its own, survives client reloads"]
        direction TB
        A[User registers or verifies email] --> B{Email verified?}
        B -- no --> Z[No activation yet]
        B -- yes --> C[Enqueue ActivateTrial job]
        C --> D[Grant on-chain trial limits, then set activatedAt]
        D -- transient failure: retry with backoff --> D
        D -- success --> F([Wallet activated])
    end

    subgraph client["Client — deploy / top-up / coupon are managed-wallet actions"]
        direction TB
        G[User acts] --> I{Wallet activated?}
        I -- yes --> J([Action proceeds])
        I -- no --> K[API returns 409 wallet_provisioning<br/>and re-enqueues activation]
        K --> L{Retries left?}
        L -- yes: wait backoff --> G
        L -- no --> M[Show 'try again later or contact support']
    end

    F -. activation unblocks the gate .-> I
Loading

Server-side activation (apps/api)

  • Activation is enqueued as an idempotent ActivateTrial job the moment a user's email is verified —
    on registration, on email-verification sync, and on the passwordless code-verify path. The job grants
    the on-chain limits then stamps activatedAt; it retries with backoff, so a transient chain/queue
    hiccup self-heals without any client involvement.
  • activatedAt is set only after the grants land (same write as the allowances), so it doubles as
    the spend gate's "ready" signal — an action arriving mid-provisioning can't slip past onto a bare
    chain/funding error. No activation claim is needed: the queue is policy: singleton (one job per user)
    and the grants are idempotent, so a retry just re-converges.

Shared spend/credit guard

  • A reusable TrialActivationJobService.assertActivated best-effort re-enqueues activation and throws the
    retriable 409 wallet_provisioning when the wallet isn't activated. It's applied to create-deployment
    (managed signer), top-up confirm and coupon apply (StripeController). A failed re-enqueue is
    logged but the 409 is still returned, so the next client retry drives it.

Client retry + gate removal (apps/deploy-web)

  • One shared walletProvisioningRetry retries the wallet_provisioning 409 with backoff; wired into the
    create-deployment, top-up (confirmPayment), and coupon (applyCoupon) mutations. The predicate matches
    both client error shapes (openapi-sdk ApiError and the http-sdk AxiosError).
  • The client-side wallet-ready gate is removed from the deploy flow, the top-up form, and the coupon
    form — they act immediately and let the server 409 + retry handle provisioning. useEnsureTrialStarted
    is reduced to a pure reader; useCreateDeployment is gone (inlined into the flow). The now-dead
    isWalletReady prop is removed all the way up the Add-Credits chain.
  • On an exhausted retry budget the flow surfaces a clear "your account is still being set up — try again
    shortly or contact support" message.

Stale-cache self-heal

  • The managed-wallet query no longer sits on a stale no-address snapshot: it polls until the address
    lands
    (server-side provisioning can lag the first fetch), so post-deploy pages, the onboarding gate,
    and the balance stop requiring a manual reload.
  • A successful deploy also invalidates the onboarding lease-existence cache, so a client-side nav home
    isn't bounced to /onboarding. The top-up/coupon flows keep the existing balance-poll so funds are
    confirmed before the user proceeds to deploy.

Backward compatibility

  • POST /v1/start-trial is kept but reduced to ensure the wallet + enqueue activation (returning the
    wallet), so an older deploy-web still works if the API ships first in a staged rollout. New clients
    don't call it.

Observability (OpenTelemetry, not analytics)

  • The activation job records its outcome — a completions counter tagged by status and, on failure, a
    classified reason (transient vs. terminal blocks like unverified email or a duplicate-fingerprint
    block), job duration, and registration→activation latency. Failures also log TRIAL_ACTIVATION_JOB_FAILED.
    Activation is expected to always succeed, so any failure is a real signal worth a dashboard alert.

Notes

  • The email-verified and duplicate-fingerprint checks are kept: password auth (?auth=password) still
    exists, so the server must not assume every caller arrives already verified.
  • Follow-up (out of scope): retire the orphaned connectManagedWallet / ConnectManagedWalletButton
    cluster that pointed at the old client-create endpoint.

Summary by CodeRabbit

  • New Features

    • Trial activation is now scheduled automatically after email verification, registration, and wallet creation.
    • Payments, coupons, and deployments retry automatically while wallet provisioning completes.
    • Wallet status updates automatically during provisioning.
    • Deployment and funding flows no longer require manual trial activation or retry actions.
  • Bug Fixes

    • Improved payment polling and handling of wallet-provisioning delays.
    • Added clearer error reporting and monitoring for trial activation failures.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.48387% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.93%. Comparing base (1f263ad) to head (1b213b3).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../PaymentPollingProvider/PaymentPollingProvider.tsx 75.00% 2 Missing ⚠️
apps/deploy-web/src/utils/walletProvisioning.ts 75.00% 1 Missing and 1 partial ⚠️
...rification-code/email-verification-code.service.ts 66.66% 1 Missing ⚠️
apps/api/src/user/services/user/user.service.ts 87.50% 1 Missing ⚠️
...ackathonCouponNavEntry/HackathonCouponNavEntry.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3542      +/-   ##
==========================================
- Coverage   74.79%   73.93%   -0.86%     
==========================================
  Files        1155     1069      -86     
  Lines       30092    27784    -2308     
  Branches     7509     7054     -455     
==========================================
- Hits        22508    20543    -1965     
+ Misses       6701     6390     -311     
+ Partials      883      851      -32     
Flag Coverage Δ *Carryforward flag
api 88.14% <97.97%> (+0.19%) ⬆️
deploy-web 64.46% <91.07%> (-0.11%) ⬇️
log-collector ?
notifications 93.84% <ø> (ø) Carriedforward from 1f263ad
provider-console 81.38% <ø> (ø) Carriedforward from 1f263ad
provider-inventory ?
provider-proxy 88.17% <ø> (ø) Carriedforward from 1f263ad
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
apps/api/src/app/providers/jobs.provider.ts 0.00% <ø> (ø)
...rc/billing/controllers/stripe/stripe.controller.ts 81.48% <100.00%> (+0.71%) ⬆️
...rc/billing/controllers/wallet/wallet.controller.ts 100.00% <100.00%> (ø)
apps/api/src/billing/events/activate-trial.ts 100.00% <100.00%> (ø)
...repositories/user-wallet/user-wallet.repository.ts 81.35% <ø> (ø)
.../services/activate-trial/activate-trial.handler.ts 100.00% <100.00%> (ø)
...-trial/trial-activation-instrumentation.service.ts 100.00% <100.00%> (ø)
.../services/managed-signer/managed-signer.service.ts 100.00% <100.00%> (ø)
...ial-activation-job/trial-activation-job.service.ts 100.00% <100.00%> (ø)
...s/wallet-initializer/wallet-initializer.service.ts 100.00% <100.00%> (ø)
... and 21 more

... and 94 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.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Trial activation now runs through background jobs and server-side wallet provisioning. Spending endpoints wait for activation. The deploy-web app removes client-side trial gating, polls for wallet readiness, and retries wallet-provisioning errors.

Changes

Trial activation flow

Layer / File(s) Summary
Activation job and instrumentation
apps/api/src/billing/events/activate-trial.ts, apps/api/src/billing/services/activate-trial/*, apps/api/src/app/providers/jobs.provider.ts
Adds the ActivateTrial job, handler registration, activation processing, metrics, logs, and tests.
Activation scheduling and wallet provisioning
apps/api/src/auth/..., apps/api/src/user/..., apps/api/src/billing/services/trial-activation-job/*, apps/api/src/billing/services/wallet-initializer/*, apps/api/test/functional/*
Schedules activation after verification and user lifecycle updates. Wallet initialization validates users, grants allowances, stamps activatedAt, and records activation timing.
Activation checks before spending
apps/api/src/billing/services/managed-signer/*, apps/api/src/billing/controllers/stripe/*
Checks wallet activation before signing transactions, confirming payments, and applying coupons.
Wallet endpoint and route contract
apps/api/src/billing/controllers/wallet/*, apps/api/src/billing/routes/*, apps/api/src/billing/http-schemas/*, apps/api/src/billing/repositories/*
The wallet endpoint ensures a wallet and queues activation. Route documentation reflects the asynchronous behavior.

Deployment wallet readiness

Layer / File(s) Summary
Readiness polling and provisioning retry
apps/deploy-web/src/hooks/useEnsureTrialStarted.*, apps/deploy-web/src/queries/*, apps/deploy-web/src/utils/walletProvisioning.*, apps/deploy-web/src/context/PaymentPollingProvider/*
Wallet readiness is read from managed-wallet state. Address-less wallets poll every five seconds. Payment mutations retry wallet provisioning errors with exponential backoff.
Deployment flow and server-side provisioning
apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/*, apps/deploy-web/src/hooks/useAutoDeploymentFlow/*, apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/*, apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/*, apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/*, apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/*, apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/*
Deployment creation uses direct API mutations with provisioning retries. Trial error and retry props are removed from the deployment flow tree.
Billing and onboarding readiness contracts
apps/deploy-web/src/components/auth/AddCreditsSheet/*, apps/deploy-web/src/components/billing-usage/*, apps/deploy-web/src/components/layout/*, apps/deploy-web/src/components/onboarding-picker/*
Billing, onboarding, funding, and hardware components no longer pass or display client-side wallet readiness and trial retry state.

Estimated code review effort: 4 (Complex) | ~65 minutes

Possibly related PRs

Suggested reviewers: baktun14

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deployment-await-trial-activation

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

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)

52-74: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Separate “activation in progress” from completed activation.

claimActivation marks activatedAt before grants and event delivery finish. A worker crash after the claim, or a TrialStarted enqueue failure, leaves the wallet appearing activated; later jobs exit at Line 52 and never complete the missing grant/event. Persist a separate claim/lease state and set activatedAt only after the full activation workflow completes, with retryable event delivery.

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

In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts`
around lines 52 - 74, Update the wallet activation flow around claimActivation
and the activatedAt guard to track an independent activation-in-progress claim
or lease, rather than marking activatedAt before completion. Set activatedAt
only after grant creation, repository updates, and TrialStarted delivery
succeed; ensure crashes or enqueue failures leave the claim retryable and
prevent duplicate concurrent activation.
🧹 Nitpick comments (1)
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts (1)

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

Use the repository alias for this API import.

Replace the relative import with the equivalent @src/billing/services/activate-trial/trial-activation-instrumentation.service alias.

As per coding guidelines, files under apps/api should use the repository TypeScript path aliases @src/* where applicable.

Proposed fix
-import { TrialActivationInstrumentationService } from "../activate-trial/trial-activation-instrumentation.service";
+import { TrialActivationInstrumentationService } from "`@src/billing/services/activate-trial/trial-activation-instrumentation.service`";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts`
at line 17, Update the TrialActivationInstrumentationService import in
wallet-initializer.service.spec.ts to use the repository alias
`@src/billing/services/activate-trial/trial-activation-instrumentation.service`
instead of the relative path.

Source: Coding guidelines

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

Inline comments:
In
`@apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts`:
- Around line 80-82: Update the trial activation scheduling flow around
TrialActivationJobService.schedule so enqueue failures are not swallowed:
preserve the error after logging and fail or retry the verification flow before
it completes, ensuring users are not left verified without a queued activation
job.

In `@apps/api/src/billing/services/managed-signer/managed-signer.service.ts`:
- Around line 195-200: Update `#assertActivatedForSpending` so failures from
trialActivationJobService.schedule are caught and logged, then always throw the
existing 409 wallet_provisioning error. Preserve the current behavior for
non-spending transactions and already activated wallets.

---

Outside diff comments:
In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts`:
- Around line 52-74: Update the wallet activation flow around claimActivation
and the activatedAt guard to track an independent activation-in-progress claim
or lease, rather than marking activatedAt before completion. Set activatedAt
only after grant creation, repository updates, and TrialStarted delivery
succeed; ensure crashes or enqueue failures leave the claim retryable and
prevent duplicate concurrent activation.

---

Nitpick comments:
In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts`:
- Line 17: Update the TrialActivationInstrumentationService import in
wallet-initializer.service.spec.ts to use the repository alias
`@src/billing/services/activate-trial/trial-activation-instrumentation.service`
instead of the relative path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 518e099b-f97e-48c6-9d73-323fc53d5e0c

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd8a54 and 55e9bdb.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (36)
  • apps/api/src/app/providers/jobs.provider.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.spec.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
  • apps/api/src/billing/events/activate-trial.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.ts
  • apps/api/src/billing/services/trial-activation-job/trial-activation-job.service.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/routers/open-api-handlers.ts
  • apps/api/src/user/services/user/user.service.integration.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/ConfigureDeployment.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.ts
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.spec.tsx
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.tsx
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.spec.ts
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
💤 Files with no reviewable changes (7)
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.spec.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/api/src/routers/open-api-handlers.ts

Comment thread apps/api/src/billing/services/managed-signer/managed-signer.service.ts Outdated
Comment thread apps/api/src/billing/services/managed-signer/managed-signer.service.ts Outdated
@ygrishajev
ygrishajev force-pushed the fix/deployment-await-trial-activation branch from 55e9bdb to aa77a8d Compare July 30, 2026 15:33

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

♻️ Duplicate comments (1)
apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts (1)

80-82: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not swallow activation-enqueue failures. These paths report success after JobQueueService.enqueue() fails, leaving a verified user without an activation job; job retries cannot recover work that was never queued. This repeats the prior email-verification finding and also affects the new registration and sync triggers.

  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts#L80-L82: log then rethrow, or persist a retryable handoff.
  • apps/api/src/user/services/user/user.service.ts#L67-L71: preserve the scheduling failure rather than returning successful registration silently.
  • apps/api/src/user/services/user/user.service.ts#L140-L144: preserve the scheduling failure rather than returning a successful sync silently.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts`
around lines 80 - 82, Do not swallow trial-activation scheduling failures: in
EmailVerificationCodeService, log the failure and rethrow it or persist a
retryable handoff; in UserService, preserve and propagate scheduling failures
from both the registration and sync activation triggers so they cannot return
success when enqueueing fails. Apply the required changes at
apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts:80-82,
apps/api/src/user/services/user/user.service.ts:67-71, and
apps/api/src/user/services/user/user.service.ts:140-144.
🧹 Nitpick comments (2)
apps/api/src/billing/services/managed-signer/managed-signer.service.ts (1)

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

Duplicated hasSpendingTx derivation.

Same messages.some(... SPENDING_TXS...) expression appears in both #ensureAutoReloadSchedule and #assertActivatedForSpending. Consider extracting a small #hasSpendingTx(messages) helper.

Also applies to: 195-197

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

In `@apps/api/src/billing/services/managed-signer/managed-signer.service.ts`
around lines 180 - 186, Extract the duplicated spending-transaction detection
expression into a private `#hasSpendingTx`(messages) helper, then reuse it in both
`#ensureAutoReloadSchedule` and `#assertActivatedForSpending`. Preserve the existing
SPENDING_TXS matching behavior and each method’s current control flow.
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts (1)

76-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add assertions for the event publish + instrumentation side effects.

This test verifies the allowance persistence but not that domainEvents.publish(new TrialStarted(...)) or trialActivationInstrumentation.recordActivated(...) were actually invoked — both are core outputs of this PR (event-driven consumers, activation latency metric). Worth locking these in given they're easy to silently regress.

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

In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts`
around lines 76 - 99, Extend the test for
WalletInitializerService.initializeAndGrantTrialLimits to assert that the
trial-start event is published and activation instrumentation is recorded.
Capture the relevant domain event publisher and trialActivationInstrumentation
mocks from setup, then verify publish receives a TrialStarted event for the user
and recordActivated is invoked with the activation result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts`:
- Around line 80-82: Do not swallow trial-activation scheduling failures: in
EmailVerificationCodeService, log the failure and rethrow it or persist a
retryable handoff; in UserService, preserve and propagate scheduling failures
from both the registration and sync activation triggers so they cannot return
success when enqueueing fails. Apply the required changes at
apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts:80-82,
apps/api/src/user/services/user/user.service.ts:67-71, and
apps/api/src/user/services/user/user.service.ts:140-144.

---

Nitpick comments:
In `@apps/api/src/billing/services/managed-signer/managed-signer.service.ts`:
- Around line 180-186: Extract the duplicated spending-transaction detection
expression into a private `#hasSpendingTx`(messages) helper, then reuse it in both
`#ensureAutoReloadSchedule` and `#assertActivatedForSpending`. Preserve the existing
SPENDING_TXS matching behavior and each method’s current control flow.

In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts`:
- Around line 76-99: Extend the test for
WalletInitializerService.initializeAndGrantTrialLimits to assert that the
trial-start event is published and activation instrumentation is recorded.
Capture the relevant domain event publisher and trialActivationInstrumentation
mocks from setup, then verify publish receives a TrialStarted event for the user
and recordActivated is invoked with the activation result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9e3e05b6-ecef-41ed-9f2f-68c97de1992f

📥 Commits

Reviewing files that changed from the base of the PR and between 55e9bdb and aa77a8d.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (39)
  • apps/api/src/app/providers/jobs.provider.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.spec.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
  • apps/api/src/billing/events/activate-trial.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.spec.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.spec.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.ts
  • apps/api/src/billing/services/trial-activation-job/trial-activation-job.service.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/routers/open-api-handlers.ts
  • apps/api/src/user/services/user/user.service.integration.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/ConfigureDeployment.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.ts
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.spec.tsx
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.tsx
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.spec.ts
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
💤 Files with no reviewable changes (7)
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/api/src/routers/open-api-handlers.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/billing/events/activate-trial.ts

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the inline findings, I also checked whether removing the client-side create() fallback in useEnsureTrialStarted drops the only retry path for a failed wallet/address derivation — it doesn't: WalletInitializerService.ensureWallet (address derivation) is now invoked server-side on registration and on syncEmailVerified, so the client no longer needs to drive it.

Extended reasoning...

Verified the candidate concern about useEnsureTrialStarted losing its wallet-creation retry path. Confirmed the hook now only reads wallet state (apps/deploy-web/src/hooks/useEnsureTrialStarted.ts) and that address derivation moved server-side into WalletInitializerService.ensureWallet, called from UserService.registerUser and UserService.syncEmailVerified in this diff — so a failed derivation is retried on the next verification/registration event rather than relying on a client-side mutation retry. Not a bug.

Comment thread apps/api/src/billing/services/managed-signer/managed-signer.service.ts Outdated
Comment thread apps/api/src/billing/controllers/wallet/wallet.controller.ts
Comment thread apps/api/src/billing/services/managed-signer/managed-signer.service.ts Outdated
@ygrishajev
ygrishajev force-pushed the fix/deployment-await-trial-activation branch from aa77a8d to 31856b2 Compare July 31, 2026 12:26

@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

🧹 Nitpick comments (3)
apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx (1)

852-853: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use mock() for the service mock.

Line 852 uses mockDeep(). Use mock<ReturnType<typeof DEPENDENCIES.useServices>>() to follow the required spec mock pattern.

Proposed fix
-    const services = mockDeep<ReturnType<typeof DEPENDENCIES.useServices>>();
+    const services = mock<ReturnType<typeof DEPENDENCIES.useServices>>();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx`
around lines 852 - 853, Replace the mockDeep call used to initialize services in
the deployment flow spec with mock<ReturnType<typeof
DEPENDENCIES.useServices>>(), while preserving the existing createDeployment
mutation setup.

Source: Coding guidelines

apps/deploy-web/src/components/billing-usage/RedeemCouponForm/RedeemCouponForm.spec.tsx (1)

76-76: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Keep coverage for the readiness-independent submission path.

The deleted test covered the old disabled state. Add a test that enters a non-empty coupon and asserts that RedeemCouponForm enables the button and invokes applyCoupon without an isWalletReady input. Keep polling-based disabling covered.

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

In
`@apps/deploy-web/src/components/billing-usage/RedeemCouponForm/RedeemCouponForm.spec.tsx`
at line 76, Restore coverage in the RedeemCouponForm tests for submission
without an isWalletReady prop: enter a non-empty coupon, assert the submit
button becomes enabled, and verify applyCoupon is invoked. Preserve the existing
polling-based disabled-state coverage.
apps/api/src/billing/services/managed-signer/managed-signer.service.ts (1)

180-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated spending-message check.

#assertActivatedForSpending (Line 196) and #ensureAutoReloadSchedule (Line 181) compute hasSpendingTx with the identical expression. Extract one private helper and call it from both sites. This keeps the activation gate and the auto-reload trigger in sync if SPENDING_TXS changes later.

♻️ Proposed refactor
+  `#hasSpendingTx`(messages: EncodeObject[]): boolean {
+    return messages.some(message => SPENDING_TXS.some(msg => message.typeUrl.endsWith(msg.$type)));
+  }
+
   async `#ensureAutoReloadSchedule`(userId: UserWalletOutput["userId"], messages: EncodeObject[]) {
-    const hasSpendingTx = messages.some(message => SPENDING_TXS.some(msg => message.typeUrl.endsWith(msg.$type)));
-
-    if (hasSpendingTx) {
+    if (this.#hasSpendingTx(messages)) {
       await this.walletReloadJobService.scheduleImmediate({ userId });
     }
   }
   ...
   async `#assertActivatedForSpending`(userWallet: UserWalletOutput, messages: EncodeObject[]) {
-    const hasSpendingTx = messages.some(message => SPENDING_TXS.some(msg => message.typeUrl.endsWith(msg.$type)));
-    if (!hasSpendingTx) return;
+    if (!this.#hasSpendingTx(messages)) return;

     await this.trialActivationJobService.assertActivated(userWallet);
   }

Also applies to: 195-200

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

In `@apps/api/src/billing/services/managed-signer/managed-signer.service.ts`
around lines 180 - 186, Extract the duplicated spending-message detection
expression into a private helper in the managed signer service, then use that
helper from both `#ensureAutoReloadSchedule` and `#assertActivatedForSpending`.
Preserve the existing boolean behavior and ensure both activation validation and
auto-reload scheduling rely on the same helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.tsx`:
- Line 56: Update OnboardingPickerPage around useEnsureTrialStarted so
trial-start failures remain visible to the user instead of only gating the LLM
card through isWalletReady. Preserve the returned error state and render the
shared retry action or route failures to the existing provisioning error UI,
while keeping the successful loading and readiness behavior unchanged.

In
`@apps/deploy-web/src/context/PaymentPollingProvider/PaymentPollingProvider.tsx`:
- Around line 215-224: Update the polling logic around initialBalanceRef and
initialTrialingRef so initializing the balance baseline no longer returns early.
Continue evaluating wasTrialing in the same effect run, allowing
initialTrialingRef to be set when both balance data first arrives and the trial
has already completed.

In `@apps/deploy-web/src/queries/useManagedWalletQuery.ts`:
- Around line 21-22: Update the refetchInterval callback in the managed wallet
query so polling occurs only when query.state.data exists and its wallet address
is missing; return false for undefined data, including API or authorization
failures, while preserving the existing polling interval and stop condition when
an address is present.

---

Nitpick comments:
In `@apps/api/src/billing/services/managed-signer/managed-signer.service.ts`:
- Around line 180-186: Extract the duplicated spending-message detection
expression into a private helper in the managed signer service, then use that
helper from both `#ensureAutoReloadSchedule` and `#assertActivatedForSpending`.
Preserve the existing boolean behavior and ensure both activation validation and
auto-reload scheduling rely on the same helper.

In
`@apps/deploy-web/src/components/billing-usage/RedeemCouponForm/RedeemCouponForm.spec.tsx`:
- Line 76: Restore coverage in the RedeemCouponForm tests for submission without
an isWalletReady prop: enter a non-empty coupon, assert the submit button
becomes enabled, and verify applyCoupon is invoked. Preserve the existing
polling-based disabled-state coverage.

In
`@apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx`:
- Around line 852-853: Replace the mockDeep call used to initialize services in
the deployment flow spec with mock<ReturnType<typeof
DEPENDENCIES.useServices>>(), while preserving the existing createDeployment
mutation setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f479cda6-3393-4972-aa50-177a22e5a729

📥 Commits

Reviewing files that changed from the base of the PR and between aa77a8d and 31856b2.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (63)
  • apps/api/src/app/providers/jobs.provider.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.spec.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.spec.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
  • apps/api/src/billing/events/activate-trial.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.spec.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.spec.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.ts
  • apps/api/src/billing/services/trial-activation-job/trial-activation-job.service.spec.ts
  • apps/api/src/billing/services/trial-activation-job/trial-activation-job.service.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/user/services/user/user.service.integration.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/test/functional/stripe-transactions-confirm.spec.ts
  • apps/deploy-web/src/components/auth/AddCreditsSheet/AddCreditsSheet.spec.tsx
  • apps/deploy-web/src/components/auth/AddCreditsSheet/AddCreditsSheet.tsx
  • apps/deploy-web/src/components/billing-usage/AddCreditsForm/AddCreditsForm.spec.tsx
  • apps/deploy-web/src/components/billing-usage/AddCreditsForm/AddCreditsForm.tsx
  • apps/deploy-web/src/components/billing-usage/AddCreditsTabs/AddCreditsTabs.spec.tsx
  • apps/deploy-web/src/components/billing-usage/AddCreditsTabs/AddCreditsTabs.tsx
  • apps/deploy-web/src/components/billing-usage/RedeemCouponForm/RedeemCouponForm.spec.tsx
  • apps/deploy-web/src/components/billing-usage/RedeemCouponForm/RedeemCouponForm.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/AutoDeployFlow/AutoDeployFlow.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/ConfigureDeployment.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/ConfigureDeployment.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.ts
  • apps/deploy-web/src/components/layout/FundingBanner/FundingBanner.spec.tsx
  • apps/deploy-web/src/components/layout/FundingBanner/FundingBanner.tsx
  • apps/deploy-web/src/components/layout/HackathonCouponNavEntry/HackathonCouponNavEntry.spec.tsx
  • apps/deploy-web/src/components/layout/HackathonCouponNavEntry/HackathonCouponNavEntry.tsx
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.spec.tsx
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.tsx
  • apps/deploy-web/src/context/PaymentPollingProvider/PaymentPollingProvider.tsx
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.spec.ts
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
  • apps/deploy-web/src/queries/useManagedWalletQuery.ts
  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/utils/walletProvisioning.spec.ts
  • apps/deploy-web/src/utils/walletProvisioning.ts
💤 Files with no reviewable changes (6)
  • apps/deploy-web/src/components/billing-usage/AddCreditsForm/AddCreditsForm.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.spec.ts
  • apps/deploy-web/src/components/billing-usage/AddCreditsTabs/AddCreditsTabs.spec.tsx
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/useCreateDeployment/useCreateDeployment.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.spec.tsx
  • apps/deploy-web/src/components/auth/AddCreditsSheet/AddCreditsSheet.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (21)
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeployment/ConfigureDeployment.tsx
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.spec.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/billing/services/activate-trial/activate-trial.handler.ts
  • apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
  • apps/api/src/billing/events/activate-trial.ts
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.spec.ts
  • apps/api/src/user/services/user/user.service.integration.ts
  • apps/api/src/app/providers/jobs.provider.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/DeploymentFlowProvider/DeploymentFlowProvider.spec.tsx
  • apps/api/src/auth/services/email-verification-code/email-verification-code.service.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.spec.tsx
  • apps/deploy-web/src/hooks/useAutoDeploymentFlow/useAutoDeploymentFlow.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.spec.ts
  • apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentForm/ConfigureDeploymentForm.tsx
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
  • apps/api/src/billing/services/activate-trial/trial-activation-instrumentation.service.ts

Comment thread apps/deploy-web/src/components/onboarding-picker/OnboardingPickerPage.tsx Outdated
Comment thread apps/deploy-web/src/context/PaymentPollingProvider/PaymentPollingProvider.tsx Outdated
Comment thread apps/deploy-web/src/queries/useManagedWalletQuery.ts Outdated
@ygrishajev
ygrishajev force-pushed the fix/deployment-await-trial-activation branch from 31856b2 to e8b8c10 Compare July 31, 2026 12:41

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the two nits already posted inline, this run also checked and ruled out three other candidates: a claimed permanent corruption of the top-up balance baseline in PaymentPollingProvider (it resets each poll session, not permanently); the CodeRabbit-flagged early return skipping trial-completion detection in that same effect (a later poll tick still catches the flip before the poll times out); and a missing error/retry affordance in OnboardingPickerPage if wallet provisioning stalls (the wallet-ready poll self-heals without one).

Extended reasoning...

This run's bug hunt found two nit-level issues (already posted as inline comments: the unbounded managed-wallet poll on persistent fetch errors, and a stale AddCreditsForm docstring) plus three candidate issues that finder agents raised and verifier agents examined and refuted: a permanent-corruption claim on PaymentPollingProvider's balance baseline, a claim aligned with CodeRabbit's 'Major' finding about the early return skipping trial-completion detection, and a claim that OnboardingPickerPage lacks an error/retry affordance for stalled provisioning. Given the PR is XL, touches security-sensitive billing/wallet-activation code, and inline comments already carry the actionable findings, this note is informational context for the author and any future review pass so these three items are not re-explored from scratch, not a restatement of the inline findings or a guarantee of correctness.

Comment thread apps/deploy-web/src/queries/useManagedWalletQuery.ts Outdated
@ygrishajev
ygrishajev force-pushed the fix/deployment-await-trial-activation branch from e8b8c10 to 7a40053 Compare July 31, 2026 13:06
…spends

A managed trial wallet gets its address at registration, but its on-chain deployment/fee grants
were provisioned separately by a client-driven start-trial call. A spend firing before that
finished failed terminally, so onboarding auto-deploy, the manual quote request, top-ups, and
coupon redemptions all raced a wallet that was never ready.

Trial activation moves to a background job triggered when the user registers or verifies their
email, so it can't be aborted by a client reload and pg-boss retries it. Activation stamps
activatedAt only after the on-chain grants land, so it doubles as the spend gate's ready signal.

A shared assertActivated guard rejects an unactivated wallet with a retriable 409 (re-enqueuing
activation as a self-heal) on every managed-wallet endpoint - create-deployment, top-up confirm,
and coupon apply - and the client retries each with backoff until activation lands. The
client-side wallet-ready gate is removed from the deploy, top-up, and coupon flows; readiness is
enforced server-side. The client-driven POST /v1/start-trial is kept but reduced to ensuring the
wallet and enqueuing activation, so an older client still works during a staged rollout.

The managed-wallet query self-heals a stale no-address snapshot (polls until the address lands),
so post-deploy pages, the onboarding gate, and the balance no longer need a reload; a successful
deploy also refreshes the onboarding lease-existence cache. Activation job outcomes are recorded
via OpenTelemetry so a failure is visible.
@ygrishajev
ygrishajev force-pushed the fix/deployment-await-trial-activation branch from 7a40053 to 1b213b3 Compare July 31, 2026 13:37

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No new bugs found in this pass. This is a large (XL), security-sensitive change touching wallet provisioning, auth-adjacent access control, and payment flows across 64 files, so I'd still like a human to review before merge.

What was reviewed:

  • Confirmed the previously-flagged IDOR on POST /v1/start-trial is fixed — WalletController.create now asserts the request userId matches the authenticated caller before calling the unscoped ensureWallet.
  • Checked the new server-side activation job (ActivateTrialHandler, TrialActivationJobService) and its wiring into registration, email verification, and the spend/top-up/coupon guards.
  • Checked wallet-provisioning retry plumbing on the client (walletProvisioningRetry, useManagedWalletQuery polling) for both the openapi-sdk and http-sdk error shapes.
Extended reasoning...

Overview

This PR moves trial-wallet activation from a client-driven call to a server-side background job (ActivateTrial / ActivateTrialHandler), gates managed-wallet spend/top-up/coupon actions behind a retriable 409 wallet_provisioning via TrialActivationJobService.assertActivated, and removes the corresponding client-side wallet-readiness gates across the deploy flow, top-up form, and coupon form. It also adds OpenTelemetry instrumentation for the activation job and changes managed-wallet query polling behavior. 64 files touched across apps/api (billing/auth/user services, routes, DI wiring) and apps/deploy-web (deployment flow, payment forms, onboarding).

Security risks

The main risk surface is access control around POST /v1/start-trial: an earlier version of this PR dropped the CASL per-user scoping when WalletController.create switched to the unscoped WalletInitializerService.ensureWallet, which would have let an authenticated caller read another user's wallet data and trigger their trial provisioning (IDOR). That regression has since been fixed with an explicit assert(userId === this.authService.currentUser.id, 403, ...) check in the controller before ensureWallet is called. I re-verified this fix is present and correctly scoped. No other injection/auth-bypass/data-exposure concerns stood out in this pass.

Level of scrutiny

This warrants a higher-than-default level of scrutiny: it changes production billing/payment code paths (Stripe top-up, coupon redemption, deployment creation) and touches wallet provisioning logic that previously had a real access-control bug during development. The change is also large (XL) and cuts across DI wiring, job queue handlers, and several React hooks/components, increasing the chance of a subtle interaction the automated pass didn't catch even though none was found this run.

Other factors

The PR has solid test coverage for the new pieces (handler specs, instrumentation specs, controller specs including a dedicated test for the fixed IDOR-adjacent ownership check), and CodeRabbit's own findings on this PR have been addressed in a follow-up commit. Given the size, security-sensitivity, and history of a real access-control bug on this PR, I'm deferring to a human reviewer rather than approving.

@ygrishajev
ygrishajev added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit dd1eebb Aug 3, 2026
58 checks passed
@ygrishajev
ygrishajev deleted the fix/deployment-await-trial-activation branch August 3, 2026 08:24
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.

2 participants