Skip to content

✨ server: integrate sardine#594

Merged
cruzdanilo merged 1 commit intomainfrom
sardine
Dec 23, 2025
Merged

✨ server: integrate sardine#594
cruzdanilo merged 1 commit intomainfrom
sardine

Conversation

@cruzdanilo
Copy link
Copy Markdown
Member

@cruzdanilo cruzdanilo commented Dec 23, 2025

closes #480


Note

Integrates Sardine risk evaluation and event tracking into key user flows.

  • New utils/sardine.ts client (customer, feedback, default risk) with strict schemas and request handling
  • Persona webhook (hooks/persona.ts): enriches payload, calls customer for risk; annotates spans; blocks on very_high risk; minor Sentry context refactor
  • Card issuance (api/card.ts): after creating a card, sends Sardine customer event with card payment method metadata
  • Credential creation (utils/createCredential.ts): sends Sardine customer event on signup
  • Config: adds SARDINE_API_KEY and SARDINE_API_URL to runtime env, OpenAPI script, and Vitest config
  • Tests: mock Sardine module and update Persona tests to expect Sardine calls

Written by Cursor Bugbot for commit 378de8d. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Integrated Sardine risk/customer/feedback checks into signup, card issuance, and credential flows
    • Risk evaluation runs before account creation and can short-circuit very-high-risk requests
  • Enhancements

    • Expanded verification payloads with richer contact, address, and demographic fields
    • External calls are non-blocking and errors are reported without interrupting main flows
  • Tests

    • Added Sardine mocks and updated tests to simulate customer/risk responses
  • Chores

    • Added runtime and test environment entries for Sardine API key and URL

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 23, 2025

🦋 Changeset detected

Latest commit: ddc66a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @cruzdanilo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the integration of the Sardine fraud detection platform into the server-side application. The primary goal is to enhance security and mitigate fraud by performing real-time risk assessments at critical user touchpoints, such as account creation, card provisioning, and identity verification via Persona. This involves setting up necessary API credentials, expanding data collection from Persona, and implementing calls to Sardine's API to evaluate customer risk levels, with the capability to prevent high-risk actions.

Highlights

  • Sardine Integration: Integrated the Sardine fraud detection platform into the server, enabling risk assessment during key user actions.
  • Event-Driven Risk Assessment: Sardine's customer endpoint is now called during user signup, card issuance, and Persona inquiry approval to capture relevant event data.
  • Environment Configuration: Added SARDINE_API_KEY and SARDINE_API_URL environment variables for secure API access and configuration.
  • Persona Data Enrichment: The Persona webhook now collects more comprehensive user attributes, including detailed address, contact, and personal information, to feed into Sardine's risk models.
  • Risk-Based Blocking: Implemented logic to block users with a 'very_high' risk level as determined by Sardine during the Persona inquiry approval process.
  • Sardine Utility Module: Introduced a new utility file (server/utils/sardine.ts) to encapsulate Sardine API interactions, including request handling, authentication, and schema validation using Valibot.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@cruzdanilo
Copy link
Copy Markdown
Member Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request integrates the Sardine fraud detection service into the application. Key changes include adding Sardine API key and URL environment variables, creating a new sardine.ts utility for API interactions with valibot schemas, and incorporating Sardine customer calls at critical points: user signup, Persona inquiry approval, and card issuance. The Persona integration was expanded to collect more detailed customer data for Sardine. Testing mocks were also updated to accommodate the new service. Review comments address an incorrect Basic authentication header format for Sardine, the need for the request utility to handle empty API responses gracefully by returning undefined, the brittleness of hardcoded string mappings for annualSalary and expectedMonthlyVolume in the Persona hook, and an incomplete mock for the Sardine feedback function in tests.

Comment thread server/utils/sardine.ts
Comment thread server/utils/sardine.ts
Comment thread server/hooks/persona.ts
Comment thread server/test/mocks/sardine.ts Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread server/utils/sardine.ts Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request integrates Sardine for risk assessment across several key user flows. A new Sardine client is introduced with strict schemas, and it's wired into credential creation, Persona KYC hooks, and card issuance. The changes are well-structured, with corresponding updates to configuration and tests. My review includes a few suggestions to improve robustness, maintainability, and test correctness. I've identified a critical issue in the response handling of the new Sardine client, a potential availability risk due to a very short timeout, and some opportunities for code refinement and fixing incorrect test mocks.

Comment thread server/utils/sardine.ts Outdated
Comment thread server/test/mocks/sardine.ts
Comment thread server/hooks/persona.ts
Comment thread server/hooks/persona.ts Outdated
Comment thread server/utils/sardine.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 23, 2025

Codecov Report

❌ Patch coverage is 89.95098% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.51%. Comparing base (566a1f0) to head (ddc66a8).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
server/utils/sardine.ts 92.08% 25 Missing ⚠️
server/hooks/persona.ts 85.50% 10 Missing ⚠️
server/utils/createCredential.ts 0.00% 4 Missing ⚠️
server/script/openapi.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #594      +/-   ##
==========================================
+ Coverage   46.02%   48.51%   +2.49%     
==========================================
  Files          40       41       +1     
  Lines        6584     6985     +401     
  Branches      478      501      +23     
==========================================
+ Hits         3030     3389     +359     
- Misses       3537     3579      +42     
  Partials       17       17              
Flag Coverage Δ
github 48.41% <89.95%> (+2.51%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cruzdanilo
Copy link
Copy Markdown
Member Author

@sentry review

@cruzdanilo
Copy link
Copy Markdown
Member Author

@sentry review

@cruzdanilo
Copy link
Copy Markdown
Member Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request integrates Sardine for risk evaluation and event tracking. I've identified a critical authentication issue in the new Sardine client, a brittle data mapping in the Persona webhook handler, and an issue with handling empty API responses from Sardine. The latter is particularly important as unexpected empty responses from external systems should be treated as errors, aligning with our guideline for handling missing expected data. Addressing these issues will significantly improve the correctness and robustness of the integration.

Comment thread server/utils/sardine.ts
Comment thread server/hooks/persona.ts
Comment thread server/utils/sardine.ts Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a Sardine HTTP client and env vars, wires Sardine calls (customer / feedback / risk) into persona verification, card issuance, and credential creation, updates persona payload and span attributes, and provides Vitest mocks and test updates.

Changes

Cohort / File(s) Summary
Release & Deployment
**.changeset**
\.changeset/forty-ads-lie.md`, `.do/app.yaml``
Adds a patch changeset and adds SARDINE_API_KEY (secret) and SARDINE_API_URL env entries to the deployment manifest.
Tooling / Env
\server/script/openapi.ts`, `server/vitest.config.mts``
Registers SARDINE_API_KEY and SARDINE_API_URL in OpenAPI script and Vitest test env.
Sardine client
\server/utils/sardine.ts``
New Sardine client module exposing customer(data, timeout), feedback(data), and default risk(data) with request helper, BasicAuth, timeouts, request-id, response validation, and Valibot schemas.
Business logic integrations
\server/hooks/persona.ts`, `server/api/card.ts`, `server/utils/createCredential.ts``
Calls sardine.customer(...) during persona verification, card issuance, and credential creation; expands persona payload (contact/address/demographics), sets span attributes (exa.*) instead of public Sentry context, handles risk-level branching (early exit on "very_high"), and captures Sardine errors without breaking primary flow.
Tests / Mocks
\server/test/mocks/sardine.ts`, `server/test/hooks/persona.test.ts``
Adds Vitest mock for Sardine (customer, feedback, default) and updates persona tests to stub Sardine responses alongside existing mocks.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Sardine
    participant Database

    Client->>Server: Submit persona / create card / create credential
    activate Server
    Note over Server: Extract/enrich payload (contact, address, demographics)\nset span attributes (exa.*)
    Server->>Sardine: customer(flow, customer, transaction) [async / fire-and-forget in some paths]
    activate Sardine
    Sardine-->>Server: {status, level, sessionKey, score}
    deactivate Sardine
    alt level == "very_high"
        Server-->>Client: 200 { code: "very high risk" }
    else
        Server->>Database: persist user / card / credential
        activate Database
        Database-->>Server: success
        deactivate Database
        Server-->>Client: 200 { success payload }
    end
    deactivate Server
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: integrating Sardine risk evaluation and event tracking into the server. It is specific, concise, and reflects the primary objective of the PR.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from #480: Sardine client with schemas, integration into Persona/card/credential flows, risk evaluation with very_high blocking, span annotations, and runtime configuration.
Out of Scope Changes check ✅ Passed All changes are directly related to Sardine integration. Minor Sentry context refactoring in persona.ts is a necessary adjustment to accommodate Sardine risk evaluation and is within scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sardine

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 566a1f0 and 3319f1a.

📒 Files selected for processing (10)
  • .changeset/forty-ads-lie.md
  • .do/app.yaml
  • server/api/card.ts
  • server/hooks/persona.ts
  • server/script/openapi.ts
  • server/test/hooks/persona.test.ts
  • server/test/mocks/sardine.ts
  • server/utils/createCredential.ts
  • server/utils/sardine.ts
  • server/vitest.config.mts
🧰 Additional context used
🧬 Code graph analysis (4)
server/api/card.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/utils/createCredential.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/hooks/persona.ts (1)
server/utils/sardine.ts (2)
  • risk (36-38)
  • customer (30-32)
server/utils/sardine.ts (1)
server/instrument.cjs (1)
  • domain (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (20)
server/script/openapi.ts (1)

26-27: LGTM!

The Sardine environment variables follow the established pattern for OpenAPI spec generation.

server/vitest.config.mts (1)

30-31: LGTM!

The Sardine environment variables are correctly configured for the test environment.

server/test/hooks/persona.test.ts (2)

16-16: LGTM!

The import of the Sardine utility module is correctly placed for test mocking.


37-41: LGTM!

The Sardine mock is properly configured with the required response fields (sessionKey, status, level) to exercise the test path.

.do/app.yaml (1)

137-143: LGTM!

The Sardine environment variables follow the established pattern: API key is correctly marked as SECRET with encrypted fallback, and the API URL is provided as a plain value.

server/utils/createCredential.ts (2)

16-16: LGTM!

The import of the Sardine customer function is correctly placed.


61-63: credentialId is an Ethereum address, not PII, and is appropriate for Sardine tracking.

Ethereum addresses are public blockchain identifiers validated by isAddress(), not personally identifiable information. They're standard identifiers used by fraud detection services for blockchain-based applications. The fire-and-forget pattern with error capture is correctly implemented—timeouts won't impact credential creation completion since errors are caught and logged without blocking the user flow.

Likely an incorrect or invalid review comment.

server/api/card.ts (2)

34-34: LGTM!

The import of the Sardine customer function is correctly placed.


369-384: Verify card data sharing with Sardine complies with security and compliance requirements.

This code sends card details (last4, expiry dates, card ID hash) to the external Sardine service. The codebase schema defines customer.type: "customer" as a valid value among 17 documented types, so that field is correctly populated. However, Sardine's public API documentation does not mention the customer.type field, suggesting either a private API variation or an undocumented feature—confirm this matches the actual Sardine endpoint being called.

Ensure this data sharing:

  1. Complies with PCI-DSS and your security policies
  2. Is covered by your data processing agreement with Sardine
  3. Is disclosed in your privacy policy

The fire-and-forget pattern with error capture is appropriate for this telemetry use case.

server/utils/sardine.ts (6)

24-28: LGTM - Environment setup is correctly validated at module load.

The early validation ensures the module fails fast if required configuration is missing, preventing runtime surprises.


30-38: LGTM - Well-structured public API with appropriate timeouts.

The API surface is clean with properly typed inputs via InferInput<typeof Schema>.


40-65: LGTM - Robust request handling with proper timeout and validation.

The implementation correctly:

  • Uses AbortSignal.timeout() for cancellation
  • Throws on empty responses (as discussed in past reviews)
  • Validates responses against schemas

265-296: LGTM - Feedback schema with proper dynamic defaults.

All timestamp and UUID defaults correctly use arrow functions to ensure fresh values per request.


298-337: LGTM - Clean terminal type transformation with safe fallback.

The transform elegantly maps terminal type codes to semantic values, with an appropriate "unspecified" fallback for unknown codes.


339-346: LGTM - Response schema correctly defined.

server/hooks/persona.ts (5)

59-79: LGTM - Payload schema expanded to support Sardine customer data.

The additional fields (name parts, address, contact info) are properly typed with appropriate nullable constraints matching the Persona API structure.


205-212: Verify: Fail-open risk assessment allows user creation when Sardine is unavailable.

If the Sardine customer() call throws (network failure, timeout, etc.), the error is logged but user creation proceeds. This is resilient but means users can be onboarded without risk evaluation when Sardine is down.

Confirm this fail-open behavior is intentional for your risk tolerance. If blocking is required when risk cannot be assessed, consider returning early with an appropriate response code.


155-155: LGTM - Span attributes correctly replace setContext for better Sentry integration.


209-212: Confirm: Only very_high risk level blocks user creation.

Risk levels high, medium, low, and unknown will all proceed to user creation. Verify this threshold aligns with your risk policy. If status: "Timeout" responses should also be blocked or flagged separately, additional handling may be needed.


225-229: LGTM - User creation and credential update flow is correct.

The pandaId is properly persisted and recorded in the span for observability.

Comment thread server/test/mocks/sardine.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3319f1a and bc89580.

📒 Files selected for processing (10)
  • .changeset/forty-ads-lie.md
  • .do/app.yaml
  • server/api/card.ts
  • server/hooks/persona.ts
  • server/script/openapi.ts
  • server/test/hooks/persona.test.ts
  • server/test/mocks/sardine.ts
  • server/utils/createCredential.ts
  • server/utils/sardine.ts
  • server/vitest.config.mts
🧰 Additional context used
🧬 Code graph analysis (4)
server/utils/createCredential.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/api/card.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/hooks/persona.ts (1)
server/utils/sardine.ts (2)
  • risk (36-38)
  • customer (30-32)
server/utils/sardine.ts (3)
src/utils/accountClient.ts (1)
  • request (146-185)
server/utils/alchemy.ts (1)
  • headers (10-10)
server/instrument.cjs (1)
  • domain (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (16)
server/script/openapi.ts (1)

26-27: LGTM: Environment variables follow established patterns.

The addition of SARDINE_API_KEY and SARDINE_API_URL for OpenAPI spec generation is consistent with how other external services are configured in this script.

server/utils/createCredential.ts (2)

61-63: Verify credentialId is appropriate to send to external service.

The customer call sends credentialId as the customer identifier to Sardine (an external risk evaluation service). Ensure this doesn't violate privacy policies or expose sensitive user data to third parties.

Consider whether you should use a different identifier (like a hashed version or account address) or verify that credentialId is already anonymized/appropriate for external sharing.


61-63: Fire-and-forget pattern is appropriate here.

The Sardine customer call is correctly implemented as fire-and-forget within Promise.all, ensuring signup tracking doesn't block credential creation. Error handling via captureException with error level is appropriate.

server/vitest.config.mts (1)

30-31: LGTM: Test environment variables configured correctly.

The Sardine environment variables are properly configured for the test environment, following the same pattern as other external service configurations.

server/test/hooks/persona.test.ts (1)

37-41: LGTM: Sardine mock properly integrated into test.

The test correctly mocks the sardine.customer call with appropriate return values (sessionKey, status, level) that align with the CustomerResponse schema. This ensures the test can verify the integration without making actual external API calls.

.do/app.yaml (1)

137-143: LGTM: Sardine environment variables properly configured with security best practices.

The configuration correctly:

  • Marks SARDINE_API_KEY as SECRET type
  • Uses encrypted secret fallback pattern (ENCRYPTED_SARDINE_API_KEY || SARDINE_API_KEY)
  • Configures SARDINE_API_URL as RUN_TIME scope (appropriate for non-sensitive data)
  • Follows established patterns for other external service integrations
server/api/card.ts (1)

369-384: Fire-and-forget pattern appropriately used.

The Sardine customer call is correctly implemented as fire-and-forget, ensuring risk tracking doesn't block the card creation response. Error handling via captureException with error level is appropriate.

server/test/mocks/sardine.ts (1)

3-8: LGTM: Mock implementations align with Sardine schemas.

The mock correctly:

  • Preserves original module exports while overriding test-specific functions
  • Returns schema-compliant values with proper capitalization ("Success")
  • Provides all required fields (sessionKey, status, level for customer; status for feedback; amlLevel, level, sessionKey, status for risk)

Based on past review comments, the status capitalization issue was already addressed.

server/utils/sardine.ts (4)

1-28: LGTM - Environment checks and configuration.

The module-level guards ensure required environment variables are present at startup, preventing runtime failures. The Base64 encoding of the API key for Basic auth is appropriate given the clarified format.


40-65: LGTM - Request helper with proper error handling.

The generic request function correctly:

  • Builds headers with auth and request ID
  • Handles timeouts via AbortSignal.timeout()
  • Throws on non-2xx responses with useful context
  • Throws on empty response bodies (addressing prior feedback)
  • Parses and validates responses against the provided schema

265-294: Schema defaults correctly use factory functions.

The FeedbackRequest schema properly uses arrow functions for id and timeMillis defaults (lines 273, 275, 288, 290), ensuring fresh UUIDs and timestamps per request rather than stale values from module load time. This addresses the prior feedback.


298-337: Well-structured RiskRequest with terminal type enrichment.

The RiskRequest schema includes a transform step that maps raw terminal type codes to human-readable type/operator pairs. The fallback to "unspecified" handles unknown codes gracefully.

server/hooks/persona.ts (4)

59-79: LGTM - Expanded payload schema captures necessary customer data.

The additional fields (contact info, address details, demographics) enable rich customer profiles for Sardine risk evaluation. The nullable() wrappers appropriately handle optional middle name and street2.


154-159: LGTM - Consistent span attribute usage.

The refactor from setContext to getActiveSpan()?.setAttribute aligns with the Sentry best practice of using span attributes for request-scoped data.


225-229: LGTM - Proper completion of user creation flow.

The database update and span attribute assignment correctly finalize the persona inquiry handling.


162-212: Sardine integration with resilient error handling.

The implementation correctly:

  • Builds a comprehensive customer payload with income, address, and tags
  • Uses .catch() to capture errors without blocking the flow
  • Sets span attributes for observability (risk level and score)
  • Blocks only on very_high risk with a retriable 200 response

One consideration: if the Sardine call fails (network issues, timeouts), risk will be undefined and the user creation proceeds. This is a deliberate resilience choice, but consider logging at warning level instead of error to better reflect that this is graceful degradation rather than a critical failure.

Comment thread server/api/card.ts
Comment thread server/utils/sardine.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc89580 and 7dbb904.

📒 Files selected for processing (10)
  • .changeset/forty-ads-lie.md
  • .do/app.yaml
  • server/api/card.ts
  • server/hooks/persona.ts
  • server/script/openapi.ts
  • server/test/hooks/persona.test.ts
  • server/test/mocks/sardine.ts
  • server/utils/createCredential.ts
  • server/utils/sardine.ts
  • server/vitest.config.mts
🧰 Additional context used
🧬 Code graph analysis (4)
server/utils/createCredential.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/api/card.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/utils/sardine.ts (1)
server/instrument.cjs (1)
  • domain (1-1)
server/hooks/persona.ts (1)
server/utils/sardine.ts (2)
  • risk (36-38)
  • customer (30-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (21)
server/script/openapi.ts (1)

26-27: LGTM!

The Sardine environment variables follow the established pattern for OpenAPI generation.

.changeset/forty-ads-lie.md (1)

1-5: LGTM!

Standard changeset format for the Sardine integration.

.do/app.yaml (1)

137-143: LGTM!

The Sardine environment variables are correctly configured with appropriate secret handling, following the same pattern as other API credentials.

server/vitest.config.mts (1)

30-31: LGTM!

Test environment variables for Sardine are properly configured and consistent with other test setup.

server/utils/sardine.ts (6)

24-28: LGTM!

Environment variable validation ensures required Sardine configuration is present at startup.


30-38: LGTM!

The three exported functions provide a clean API for Sardine integration with appropriate timeout defaults (note: the 500ms timeout for risk() is intentional for time-constrained authorization flows).


40-65: LGTM!

The request helper properly handles timeouts, error responses, and empty response bodies (throwing an error since 204 is not expected from these Sardine endpoints).


246-263: LGTM!

CustomerResponse schema correctly models the risk assessment response with level and optional risk details.


265-294: LGTM!

FeedbackRequest schema properly models authorization and settlement feedback with appropriate status enums.


298-337: LGTM!

RiskRequest schema includes a clever transform that maps terminal type codes to standardized type/operator pairs for Sardine's API.

server/test/hooks/persona.test.ts (2)

16-16: LGTM!

Importing the Sardine module enables proper mocking in tests.


37-41: LGTM!

The mock properly simulates a successful Sardine customer response with all required fields.

server/utils/createCredential.ts (2)

16-16: LGTM!

Import enables Sardine customer tracking during credential creation.


61-63: LGTM!

The Sardine customer call runs in parallel with other credential setup tasks and errors are captured without blocking the main flow. The minimal payload (flow and customer ID) is appropriate for signup tracking.

server/api/card.ts (2)

34-34: LGTM!

Import enables Sardine customer tracking for card issuance events.


369-384: LGTM!

The Sardine customer call appropriately tracks card issuance with minimal card metadata (last4, expiry, hash). The fire-and-forget pattern with error logging ensures the main card creation flow isn't blocked. Compliance aspects (DPA, privacy policy, user consent) were confirmed in previous review.

server/test/mocks/sardine.ts (1)

3-8: LGTM! Mock implementation correctly matches Sardine response schemas.

The mock properly returns all required fields with valid enum values. Previous schema mismatch issues have been resolved.

server/hooks/persona.ts (4)

2-2: LGTM! Import changes support Sentry span refactor and Sardine integration.

The shift from setContext to getActiveSpan with attributes is appropriate, and the customer import enables risk evaluation.

Also applies to: 26-26


59-80: LGTM! Schema expansion provides complete identity data for Sardine risk evaluation.

The new required fields (emailAddress, phoneNumber, birthdate, name components, address details) enable comprehensive customer profiling. Validation will appropriately reject incomplete Persona payloads.


162-212: Verify fail-open behavior is intentional for risk evaluation.

The Sardine customer() call uses .catch() that captures exceptions without rethrowing (lines 205-207). This means if Sardine fails (network error, timeout, API error), risk becomes undefined, the if (risk) check on line 209 is skipped, and the flow proceeds to user creation on line 215 without risk evaluation.

This fail-open behavior allows legitimate users through if Sardine is unavailable, but also permits potentially risky users to bypass risk checks during Sardine outages.

Confirm this is the intended security/reliability trade-off. If risk evaluation is critical, consider fail-closed behavior (return early on Sardine errors) or additional safeguards.


155-155: LGTM! Span attribute updates align with Sentry observability pattern.

The refactor from setContext to span attributes (lines 155, 227) properly associates inquiry and panda IDs with the active span. Minor note: line 227 uses setAttributes (plural) while line 155 uses setAttribute (singular), but both are valid.

Also applies to: 227-227

Comment thread server/utils/sardine.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dbb904 and ddc66a8.

📒 Files selected for processing (10)
  • .changeset/forty-ads-lie.md
  • .do/app.yaml
  • server/api/card.ts
  • server/hooks/persona.ts
  • server/script/openapi.ts
  • server/test/hooks/persona.test.ts
  • server/test/mocks/sardine.ts
  • server/utils/createCredential.ts
  • server/utils/sardine.ts
  • server/vitest.config.mts
🧰 Additional context used
🧬 Code graph analysis (2)
server/utils/createCredential.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
server/api/card.ts (1)
server/utils/sardine.ts (1)
  • customer (30-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (14)
.do/app.yaml (1)

137-143: LGTM! Environment variable configuration follows established patterns.

The Sardine API key and URL are properly configured with appropriate scopes and security settings, consistent with other service integrations in this file.

server/script/openapi.ts (1)

26-27: LGTM! Environment initialization follows the existing pattern.

The Sardine API key and URL are initialized consistently with other service configurations in this file.

server/test/hooks/persona.test.ts (2)

16-16: LGTM! Sardine utility import enables proper test mocking.

The import is necessary to support mocking of Sardine calls in the test scenarios.


37-41: LGTM! Sardine mock provides correct response shape.

The mock return value includes all required fields (sessionKey, status, level) and matches the CustomerResponse schema expectations.

server/utils/createCredential.ts (2)

16-16: LGTM! Import enables Sardine integration for signup flow.

The customer function import is correctly placed and necessary for the signup event tracking.


61-63: No changes needed. The schema defines the customer.type field as optional, not required. The implementation in createCredential.ts correctly passes only the required id field, which is valid. The different customer object shapes across calls reflect intentional flow-specific requirements—the signup flow doesn't need the type field, while other flows like card.issued and account_update optionally include it. Both patterns are correct per the schema.

server/api/card.ts (2)

34-34: LGTM! Import enables Sardine integration for card issuance flow.

The customer function import is correctly placed and necessary for the payment method link event tracking.


369-384: LGTM! Sardine integration for card issuance is properly implemented.

The fire-and-forget pattern with error handling is appropriate for this non-critical tracking call. The transaction payload includes only necessary card metadata (no full PAN), and past review comments confirm that DPA, privacy policy, and user consent coverage are in place.

server/vitest.config.mts (1)

30-31: LGTM! Test environment configuration is correct.

The Sardine environment variables are properly configured for test execution, consistent with the OpenAPI script configuration.

server/test/mocks/sardine.ts (1)

1-8: LGTM! Sardine mock properly implements all required response fields.

The mock correctly returns response shapes that match the Sardine API schemas, including proper enum capitalization (e.g., "Success" not "success"). Past review feedback has been addressed.

server/utils/sardine.ts (1)

1-346: LGTM! Past issues have been addressed.

All previously flagged concerns have been resolved:

  • Schema defaults now properly use factory functions (() => Date.now(), () => crypto.randomUUID())
  • billingAddress.regionCode and billingAddress.company are now optional, consistent with customer.address
  • Empty response handling is appropriate for these endpoints
  • Request timeout and error handling are well-structured

The Sardine client implementation follows best practices with strict schema validation, proper timeout handling, and clean separation of concerns.

server/hooks/persona.ts (3)

155-156: Good use of span attributes for tracing.

The migration from setContext to getActiveSpan()?.setAttribute() for tracking inquiry and panda IDs aligns with modern OpenTelemetry best practices and provides better distributed tracing support.


209-211: Risk-based blocking correctly implemented.

The early return on very_high risk level with a 200 status prevents user creation while signaling to the webhook sender not to retry. This correctly implements the risk-based blocking requirement.


162-163: Clarify the flow type for Persona identity verification.

The flow type is set to "account_update" for a Persona inquiry approval event. While both "account_update" and "identity_verification" are valid schema options (server/utils/sardine.ts lines 71-87), "identity_verification" appears more semantically appropriate since this webhook represents a completed identity verification/KYC approval from Persona. Consider whether the current choice is intentional or if "identity_verification" better represents this flow.

Comment thread server/hooks/persona.ts
@cruzdanilo cruzdanilo merged commit ddc66a8 into main Dec 23, 2025
4 checks passed
@cruzdanilo cruzdanilo deleted the sardine branch December 23, 2025 19:56
@cruzdanilo cruzdanilo mentioned this pull request Dec 23, 2025
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.

server: sardine AI integration

2 participants