Conversation
🦋 Changeset detectedLatest commit: 31c6b9e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds encrypted chat identity utilities and authenticated chat association endpoints. Credentials store unique WhatsApp IDs, Redis holds verification state and cooldowns, and integration tests cover token validation, code verification, conflicts, and authentication. ChangesChat association
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ChatAPI
participant Redis
participant Database
Client->>ChatAPI: Submit encrypted chat token
ChatAPI->>Redis: Store verification code and cooldown
ChatAPI->>Database: Associate credential waId
ChatAPI-->>Client: Return status or waId
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to link a WhatsApp ID (waId) to a user's credential during registration. It adds a unique waId column to the credentials database table, validates the encrypted Wa-Id header in the registration endpoints, and implements JWT token utilities for encoding and decoding the WhatsApp ID. The feedback suggests adding a unit test to verify that attempting to register a duplicate waId is properly rejected.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 79eeb584-b2b5-4ed4-8af5-0d523fd12c66
📒 Files selected for processing (10)
.changeset/quick-otters-link.mdserver/api/auth/registration.tsserver/database/schema.tsserver/script/openapi.tsserver/test/api/auth.test.tsserver/test/utils/token.test.tsserver/utils/chatSecret.tsserver/utils/createCredential.tsserver/utils/token.tsserver/vitest.config.mts
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
Actionable comments posted: 13
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1fe655b3-83e2-4674-b97b-c0e26e240c08
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (100)
.changeset/afraid-mangos-follow.md.changeset/blue-bottles-wave.md.changeset/bumpy-regions-read.md.changeset/bumpy-toes-jump.md.changeset/chubby-papayas-visit.md.changeset/free-lamps-pump.md.changeset/khaki-pugs-heal.md.changeset/large-candies-brake.md.changeset/plain-pumas-relax.md.changeset/proud-tools-sneeze.md.changeset/short-radios-stand.md.changeset/spicy-ideas-mix.md.changeset/ten-planets-prove.md.changeset/tender-foxes-feel.md.changeset/version.sh.changeset/warm-otters-associate.md.github/workflows/server-base-sepolia.yaml.github/workflows/server-base.yaml.github/workflows/server-build.yaml.github/workflows/server-deploy.yaml.github/workflows/server-production.yaml.github/workflows/server-sandbox.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yamlAGENTS.mdcommon/stack.jscspell.jsoninfra/Pulumi.base-sepolia.yamlinfra/Pulumi.meta.yamlinfra/Pulumi.yamlinfra/eslint.config.mjsinfra/index.tsinfra/meta.tsinfra/package.jsoninfra/tsconfig.jsoninfra/utils/rejectSecrets.tspnpm-workspace.yamlserver/Dockerfileserver/api/card.tsserver/api/chat.tsserver/api/index.tsserver/database/schema.tsserver/hooks/activity.tsserver/hooks/block.tsserver/hooks/panda.tsserver/hooks/persona.tsserver/i18n/es.jsonserver/i18n/pt.jsonserver/index.tsserver/instrument.cjsserver/package.jsonserver/script/openapi.tsserver/test/api/auth.test.tsserver/test/api/card.test.tsserver/test/api/chat.test.tsserver/test/e2e.tsserver/test/hooks/activity.test.tsserver/test/hooks/block.test.tsserver/test/hooks/panda.test.tsserver/test/hooks/persona.test.tsserver/test/mocks/alchemy.tsserver/test/mocks/keeper.tsserver/test/mocks/sentry.tsserver/test/mocks/wallet.tsserver/test/utils/alchemy.test.tsserver/test/utils/chat.test.tsserver/test/utils/createCredential.test.tsserver/test/utils/secret.test.tsserver/test/utils/wallet.test.tsserver/test/workers/allow.test.tsserver/test/workers/credit.test.tsserver/test/workers/poke.test.tsserver/test/workers/refund.test.tsserver/test/workers/subscribe.test.tsserver/tsconfig.jsonserver/utils/activityWebhook.tsserver/utils/alchemy.tsserver/utils/chat.tsserver/utils/createCredential.tsserver/utils/onesignal.tsserver/utils/panda.tsserver/utils/secret.tsserver/utils/wallet.tsserver/vitest.config.mtsserver/workers/allow/job.tsserver/workers/allow/queue.tsserver/workers/allow/worker.tsserver/workers/credit/job.tsserver/workers/credit/queue.tsserver/workers/credit/worker.tsserver/workers/poke/job.tsserver/workers/poke/queue.tsserver/workers/poke/worker.tsserver/workers/refund/job.tsserver/workers/refund/queue.tsserver/workers/refund/worker.tsserver/workers/subscribe/job.tsserver/workers/subscribe/queue.tsserver/workers/subscribe/worker.ts
💤 Files with no reviewable changes (3)
- .github/workflows/test.yaml
- server/test/mocks/keeper.ts
- server/utils/panda.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 13
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1fe655b3-83e2-4674-b97b-c0e26e240c08
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (100)
.changeset/afraid-mangos-follow.md.changeset/blue-bottles-wave.md.changeset/bumpy-regions-read.md.changeset/bumpy-toes-jump.md.changeset/chubby-papayas-visit.md.changeset/free-lamps-pump.md.changeset/khaki-pugs-heal.md.changeset/large-candies-brake.md.changeset/plain-pumas-relax.md.changeset/proud-tools-sneeze.md.changeset/short-radios-stand.md.changeset/spicy-ideas-mix.md.changeset/ten-planets-prove.md.changeset/tender-foxes-feel.md.changeset/version.sh.changeset/warm-otters-associate.md.github/workflows/server-base-sepolia.yaml.github/workflows/server-base.yaml.github/workflows/server-build.yaml.github/workflows/server-deploy.yaml.github/workflows/server-production.yaml.github/workflows/server-sandbox.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yamlAGENTS.mdcommon/stack.jscspell.jsoninfra/Pulumi.base-sepolia.yamlinfra/Pulumi.meta.yamlinfra/Pulumi.yamlinfra/eslint.config.mjsinfra/index.tsinfra/meta.tsinfra/package.jsoninfra/tsconfig.jsoninfra/utils/rejectSecrets.tspnpm-workspace.yamlserver/Dockerfileserver/api/card.tsserver/api/chat.tsserver/api/index.tsserver/database/schema.tsserver/hooks/activity.tsserver/hooks/block.tsserver/hooks/panda.tsserver/hooks/persona.tsserver/i18n/es.jsonserver/i18n/pt.jsonserver/index.tsserver/instrument.cjsserver/package.jsonserver/script/openapi.tsserver/test/api/auth.test.tsserver/test/api/card.test.tsserver/test/api/chat.test.tsserver/test/e2e.tsserver/test/hooks/activity.test.tsserver/test/hooks/block.test.tsserver/test/hooks/panda.test.tsserver/test/hooks/persona.test.tsserver/test/mocks/alchemy.tsserver/test/mocks/keeper.tsserver/test/mocks/sentry.tsserver/test/mocks/wallet.tsserver/test/utils/alchemy.test.tsserver/test/utils/chat.test.tsserver/test/utils/createCredential.test.tsserver/test/utils/secret.test.tsserver/test/utils/wallet.test.tsserver/test/workers/allow.test.tsserver/test/workers/credit.test.tsserver/test/workers/poke.test.tsserver/test/workers/refund.test.tsserver/test/workers/subscribe.test.tsserver/tsconfig.jsonserver/utils/activityWebhook.tsserver/utils/alchemy.tsserver/utils/chat.tsserver/utils/createCredential.tsserver/utils/onesignal.tsserver/utils/panda.tsserver/utils/secret.tsserver/utils/wallet.tsserver/vitest.config.mtsserver/workers/allow/job.tsserver/workers/allow/queue.tsserver/workers/allow/worker.tsserver/workers/credit/job.tsserver/workers/credit/queue.tsserver/workers/credit/worker.tsserver/workers/poke/job.tsserver/workers/poke/queue.tsserver/workers/poke/worker.tsserver/workers/refund/job.tsserver/workers/refund/queue.tsserver/workers/refund/worker.tsserver/workers/subscribe/job.tsserver/workers/subscribe/queue.tsserver/workers/subscribe/worker.ts
💤 Files with no reviewable changes (3)
- .github/workflows/test.yaml
- server/test/mocks/keeper.ts
- server/utils/panda.ts
🛑 Comments failed to post (13)
.github/workflows/server-base-sepolia.yaml (1)
5-7: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== workflow metadata and permissions ==" if [ -f .github/workflows/server-base-sepolia.yaml ]; then nl -ba .github/workflows/server-base-sepolia.yaml | sed -n '1,120p' else echo "missing .github/workflows/server-base-sepolia.yaml" fi echo echo "== reusable workflow permissions ==" if [ -f .github/workflows/server-build.yaml ]; then nl -ba .github/workflows/server-build.yaml | sed -n '1,160p' else echo "missing .github/workflows/server-build.yaml" fi echo echo "== all server workflow files mentioning server-build or permissions ==" rg -n "uses: \./\.github/workflows/server-build\.yaml|permissions:|id-token|packages|contents|attest" .github/workflows/server-*.yaml .github/workflows 2>/dev/null || true echo echo "== parse YAML workflow structure (minimal, read-only) ==" python3 - <<'PY' from pathlib import Path import re paths = [Path(".github/workflows/server-base-sepolia.yaml"), Path(".github/workflows/server-build.yaml")] for path in paths: print(f"--- {path} ---") if not path.exists(): print("MISSING") continue text = path.read_text() lines = text.splitlines() root_job = None current_job = None is_in_permissions = False for i,l in enumerate(lines, 1): # crude but sufficient to collect first top-level run/workflow-level uses? no. if re.match(r'\b(\w+(?:\.\w+)?)\s*=|^\s*permissions\s*:', l): is_in_permissions = bool(re.match(r'\s*permissions\s*:', l)) # collect jobs and callable workflow uses if re.match(r'^\s{2,4}(\w+)\s*:', l): pass if re.match(r'\s{2}-?\s*uses:\s*\./\.github/workflows/server-build\.yaml', l): print("call at line", i, l.strip()) # show permissions block in same job preceding lines for j in range(max(1,i-12), i): if re.match(r'^\s{2}permissions\s*:', lines[j-1]): print("precede permissions snippet:") for k in range(j, min(i, j+8)+1): print(f"{k}: {lines[k-1]}") break # collect explicit permission lines for i,l in enumerate(lines, 1): if re.match(r'\s*permissions\s*:', l): print("permission line at", i, l.strip()) PYRepository: exactly/exa
Length of output: 229
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== workflow metadata and permissions ==" if [ -f .github/workflows/server-base-sepolia.yaml ]; then awk '{printf "%6d %s\n", NR, $0}' .github/workflows/server-base-sepolia.yaml | sed -n '1,140p' else echo "missing .github/workflows/server-base-sepolia.yaml" fi echo echo "== reusable workflow permissions ==" if [ -f .github/workflows/server-build.yaml ]; then awk '{printf "%6d %s\n", NR, $0}' .github/workflows/server-build.yaml | sed -n '1,200p' else echo "missing .github/workflows/server-build.yaml" fi echo echo "== all server workflow files mentioning server-build or permissions ==" rg -n "uses: \./\.github/workflows/server-build\.yaml|permissions:|id-token|packages|contents|attest" .github/workflows/server-*.yaml .github/workflows 2>/dev/null || true echo echo "== parser/check permissions around reusable calls (read-only) ==" python3 - <<'PY' from pathlib import Path import re paths = [Path(".github/workflows/server-base-sepolia.yaml"), Path(".github/workflows/server-build.yaml")] for path in paths: print(f"--- {path} ---") if not path.exists(): print("MISSING") continue text = path.read_text() lines = text.splitlines() in_permissions = None for i,l in enumerate(lines, 1): if re.match(r'\s*permissions\s*:', l): in_permissions = i elif re.search(r'^(\s{2,6}\w|\s+permissions)\s*:\s*\S', l): in_permissions = None if re.search(r'\s{6,}-?\s*uses:\s*\./\.github/workflows/server-build\.yaml', l): print("callable call at line", i, l.strip()) if in_permissions: print("preceding permissions at", in_permissions, lines[in_permissions-1].strip()) for j in range(max(1, in_permissions), min(i, in_permissions+12)+1): print(f"{j}: {lines[j-1]}") else: print("no preceding permission line in parsed scope before call") print("permission line occurrences:") for i,l in enumerate(lines, 1): if re.search(r'^\s*permissions\s*:', l): print(i, l.strip()) PYRepository: exactly/exa
Length of output: 5397
🌐 Web query:
GitHub Actions reusable worklows called workflow call default permissions reusable workflow permissions💡 Result:
When using GitHub Actions reusable workflows, the permissions for the GITHUB_TOKEN are governed by the caller's job configuration [1]. If you do not explicitly define a permissions block for the job that calls a reusable workflow (using jobs.<job_id>.permissions), the called reusable workflow will inherit the default GITHUB_TOKEN permissions [1]. These default permissions are typically set at the repository or organization level and grant a broad set of permissions [2][3]. Key rules regarding reusable workflow permissions include: 1. Inheritance and Constraints: A called workflow receives permissions based on the context of the caller job [1][4]. These permissions can only be maintained or downgraded; they cannot be elevated in the called workflow [1][5]. For example, if a caller workflow only grants read access to contents, a nested or called workflow cannot be granted write access to contents [1]. 2. Validation: GitHub validates the caller job's declared permissions against the requirements of the called reusable workflow [6]. If the called workflow (or its nested jobs) requires specific permissions and the caller job does not explicitly grant at least those permissions—or if the caller job defaults are insufficient—the workflow may fail validation before it begins [6]. 3. Best Practice: To ensure security and predictable behavior, it is highly recommended to explicitly define the permissions at the job level within the caller workflow [4]. This allows you to follow the principle of least privilege by granting only the minimum access required for the reusable workflow to function [2][3][4]. For further details on configuring these settings, refer to the official GitHub documentation on reusing workflow configurations [1] and workflow syntax [3].
Citations:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 3: https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions
- 4: https://www.kenmuse.com/blog/github-actions-workflow-permissions/
- 5: https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows
- 6: github/gh-aw#21071
Set explicit job-level permissions for the reusable-workflow calls.
server-base-sepoliainherits repository-default token scopes for bothbuildanddeploy, whileserver-buildexpects write scopes for packages/attestations/ID token andserver-deployneeds ID-token write. Declare the minimal permissions required by each called job (for example,contents: read,packages: write,attestations: write,id-token: writefor the build job;contents: read,id-token: writefor deploy) instead of relying on repository defaults.🧰 Tools
🪛 zizmor (1.26.1)
[warning] 5-7: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
Source: Linters/SAST tools
.github/workflows/server-base.yaml (1)
10-12: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Replace
secrets: inheritwith the declared secrets only. The reusable deploy workflow only needsDIGITALOCEAN_TOKENand, for production Sentry publishing,SENTRY_AUTH_TOKEN; inheriting every caller secret violates least privilege.
.github/workflows/server-base.yaml#L10-L12: pass onlyDIGITALOCEAN_TOKEN..github/workflows/server-production.yaml#L10-L12: passDIGITALOCEAN_TOKENandSENTRY_AUTH_TOKEN..github/workflows/server-sandbox.yaml#L10-L12: pass onlyDIGITALOCEAN_TOKEN.🧰 Tools
🪛 zizmor (1.26.1)
[warning] 10-10: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow
(secrets-inherit)
📍 Affects 3 files
.github/workflows/server-base.yaml#L10-L12(this comment).github/workflows/server-production.yaml#L10-L12.github/workflows/server-sandbox.yaml#L10-L12Source: Linters/SAST tools
.github/workflows/server-deploy.yaml (2)
22-22: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "Workflow files:" git ls-files .github/workflows || true echo echo "Relevant action uses in workflows:" if [ -d .github/workflows ]; then for f in .github/workflows/*.yaml .github/workflows/*.yml; do [ -f "$f" ] || continue echo "-- $f" rg -n "uses:\s*[^#]+" "$f" || true done fi echo echo "server-deploy relevant lines:" if [ -f .github/workflows/server-deploy.yaml ]; then nl -ba .github/workflows/server-deploy.yaml | sed -n '1,110p' fi echo echo "server-build relevant lines:" if [ -f .github/workflows/server-build.yaml ]; then nl -ba .github/workflows/server-build.yaml | sed -n '1,80p' fiRepository: exactly/exa
Length of output: 4178
🌐 Web query:
GitHub Actions security hardening pin action to commit SHA mutable tag documentation💡 Result:
Pinning GitHub Actions to a full-length commit SHA is a recommended security best practice to prevent supply chain attacks, such as those involving malicious updates to mutable version tags [1][2][3]. Key points regarding SHA pinning and mutable tags: Immutable Reference: Pinning an action to a full-length (40-character) commit SHA provides an immutable reference [1][4]. Unlike tags (e.g.,
@v1) or branch names (e.g.,@main), which are mutable pointers that can be updated or deleted by maintainers or unauthorized actors, a commit SHA points to a specific, unchangeable state of the repository [1][5][6]. Security Risk of Tags: Using mutable tags leaves workflows vulnerable to "retargeting" attacks, where a compromised account or repository can redirect a tag to execute malicious code [5][6][3]. Shortened commit SHAs should never be used, as they are susceptible to collision attacks [7]. Policy Enforcement: GitHub allows administrators to enforce SHA pinning at the repository, organization, and enterprise levels [8][9][10]. When this policy is active, workflows that use mutable references like tags or branches will fail [11]. Note that this policy applies to the entire dependency tree, meaning if a primary action internally references other actions by tag, those sub-actions must also be pinned to SHAs to pass the policy check [11]. Maintenance Strategies: - Tracking Versions: To maintain the convenience of semantic versioning while utilizing the security of SHA pinning, it is common practice to append the version tag as a comment in the workflow file (e.g., uses: actions/checkout@a824008 # v4.2.1) [5][6]. - Automated Updates: Tools like Dependabot and Renovate support this "SHA pin with version comment" convention and can automatically create pull requests to update the SHA while maintaining the version comment [5][6][3]. Official documentation emphasizes that while pinning to a tag is more convenient, it should only be done if you trust the action's creator, whereas pinning to a full-length SHA is the only way to ensure an action is used as an immutable release [1][7][9].Citations:
- 1: https://docs.github.com/en/actions/reference/security/secure-use
- 2: https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide
- 3: https://emmer.dev/blog/pin-your-github-actions-to-protect-against-mutability/
- 4: https://docs.github.com/en/actions/how-tos/create-and-publish-actions/manage-custom-actions
- 5: https://starsling.dev/best-practices/github-actions/pin-action-shas
- 6: https://safeguard.sh/resources/blog/github-actions-pinning-sha-vs-tag
- 7: https://docs.github.com/en/enterprise-server@2.22/actions/security-guides/security-hardening-for-github-actions
- 8: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
- 9: https://github.com/github/docs/blob/main/content/actions/reference/security/secure-use.md
- 10: https://github.com/github/docs/blob/main/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-github-actions-in-your-enterprise.md
- 11: https://www.romainlespinasse.dev/posts/github-actions-commit-sha-pinning/
Pin all GitHub Actions to full-length commit SHAs. Mutable tags can be retargeted, causing workflows to run a changed action graph with access to package, OIDC, attestation, and deployment credentials.
Apply this across all action pins, including the server deploy/build workflows and the broader set of current version-tagged actions such as
actions/checkout,actions/setup-node,actions/cache, Docker actions, Sentry, Codecov, and changesets.🧰 Tools
🪛 zizmor (1.26.1)
[error] 22-22: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
📍 Affects 2 files
.github/workflows/server-deploy.yaml#L22-L22(this comment).github/workflows/server-deploy.yaml#L26-L26.github/workflows/server-deploy.yaml#L30-L30.github/workflows/server-deploy.yaml#L32-L32.github/workflows/server-deploy.yaml#L36-L36.github/workflows/server-deploy.yaml#L49-L49.github/workflows/server-deploy.yaml#L56-L56.github/workflows/server-deploy.yaml#L59-L59.github/workflows/server-deploy.yaml#L86-L86.github/workflows/server-build.yaml#L16-L16Source: Linters/SAST tools
40-40: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Make deployment wait for infrastructure provisioning.
infraanddigitaloceancurrently run concurrently. The server can deploy beforepulumi upcreates or updates its required infrastructure. Addneeds: infratodigitalocean.Proposed fix
digitalocean: + needs: infra runs-on: ubuntu-latest📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.digitalocean: needs: infra🧰 Tools
🪛 zizmor (1.26.1)
[info] 40-40: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
.github/workflows/version.yaml (1)
28-36: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== workflow file ==" cat -n .github/workflows/version.yaml | sed -n '1,80p' echo echo "== all workflow action refs in repository ==" rg -n "uses:\s*[^#]+@" .github/workflows -g '*.yml' -g '*.yaml' | sed -n '1,200p' echo echo "== search for pinning/security policy docs ==" rg -n -i "pin.*sha|sha|immutable|updatecli|untrusted|workflow|action|version\(y\)aml|changesets/create-github-app-token|security" README.md README .github docs SECURITY* 2>/dev/null | sed -n '1,240p'Repository: exactly/exa
Length of output: 23862
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== files named updatecli/metadata / policy-related ==" find .github . -maxdepth 4 \( -name '*update*' -o -name 'SECURITY*' -o -name '*secret*' -o -name 'README.md' \) -type f -print | sort | sed -n '1,200p' echo echo "== targeted security/pinning text ==" rg -n -i "pin.*sha|immutable|commit sha|actionlint|untrusted|unverified|supply chain|scorecard|release.*policy|github app|create-github-app-token|changesets/action|third (party|party)|external.*action" README.md README .github docs SECURITY* Makefile .github/workflows/version.yaml 2>/dev/null | sed -n '1,240p' echo echo "== repo top-level files ==" git ls-files | awk -F/ '{print $1}' | sort -u | sed -n '1,120p'Repository: exactly/exa
Length of output: 617
Pin release workflow actions to immutable commit SHAs.
This workflow reads
EXA_VERSION_KEYand creates tokens withcontentsandpull-requestswrite permissions, so the mutableactions/create-github-app-token@v3andchangesets/action@v1refs should use full 40-character commit SHAs.🧰 Tools
🪛 actionlint (1.7.12)
[error] 28-28: missing input "app-id" which is required by action "actions/create-github-app-token@v3". all required inputs are "app-id", "private-key"
(action)
[error] 31-31: input "client-id" is not defined in action "actions/create-github-app-token@v3". available inputs are "app-id", "github-api-url", "owner", "permission-actions", "permission-administration", "permission-checks", "permission-codespaces", "permission-contents", "permission-custom-properties-for-organizations", "permission-dependabot-secrets", "permission-deployments", "permission-email-addresses", "permission-enterprise-custom-properties-for-organizations", "permission-environments", "permission-followers", "permission-git-ssh-keys", "permission-gpg-keys", "permission-interaction-limits", "permission-issues", "permission-members", "permission-metadata", "permission-organization-administration", "permission-organization-announcement-banners", "permission-organization-copilot-seat-management", "permission-organization-custom-org-roles", "permission-organization-custom-properties", "permission-organization-custom-roles", "permission-organization-events", "permission-organization-hooks", "permission-organization-packages", "permission-organization-personal-access-token-requests", "permission-organization-personal-access-tokens", "permission-organization-plan", "permission-organization-projects", "permission-organization-secrets", "permission-organization-self-hosted-runners", "permission-organization-user-blocking", "permission-packages", "permission-pages", "permission-profile", "permission-pull-requests", "permission-repository-custom-properties", "permission-repository-hooks", "permission-repository-projects", "permission-secret-scanning-alerts", "permission-secrets", "permission-security-events", "permission-single-file", "permission-starring", "permission-statuses", "permission-team-discussions", "permission-vulnerability-alerts", "permission-workflows", "private-key", "repositories", "skip-token-revoke"
(action)
🪛 zizmor (1.26.1)
[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
Source: Linters/SAST tools
infra/index.ts (1)
58-111: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
gcp.cloudrunv2.WorkerPool launchStage required GA@pulumi/gcp9💡 Result:
In Pulumi's
@pulumi/gcpprovider (including version 9.x), the launchStage property for gcp.cloudrunv2.WorkerPool is not required [1][2]. The launchStage property is an optional field that accepts one of the following values: UNIMPLEMENTED, PRELAUNCH, EARLY_ACCESS, ALPHA, BETA, GA, or DEPRECATED [1]. If no value is specified, GA (General Availability) is assumed by default [1]. You only need to explicitly set launchStage to a preview stage (such as BETA or ALPHA) if you intend to use preview features that are not yet generally available [1]. Otherwise, omitting the field is standard practice for production-ready, GA-level resources [1].Citations:
- 1: https://cdn.jsdelivr.net/npm/@pulumi/gcp@9.3.0/cloudrunv2/workerPool.d.ts
- 2: https://app.unpkg.com/@pulumi/gcp@9.28.0/files/cloudrunv2/workerPool.d.ts
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== package manifests ==" for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do if [ -f "$f" ]; then echo "--- $f ---" if [ "$f" = "package.json" ]; then cat "$f"; else rg -n '"`@pulumi/gcp`"|`@pulumi/gcp`|gcp:' "$f" | head -40 || true; fi fi done echo "== infra WorkerPool occurrences ==" if [ -f infra/index.ts ]; then ast-grep outline infra/index.ts || true sed -n '1,180p' infra/index.ts | cat -n else fd -a 'index\.ts$' . | rg '/infra/index\.ts$|infra/' || true fi echo "== available npm package docs snippets ==" npm view `@pulumi/gcp`@9.28.0 version dist.tarball dist.shasum --json 2>/dev/null || true npm view `@pulumi/gcp`@9.3.0 version dist.tarball dist.shasum --json 2>/dev/null || true python3 - <<'PY' import urllib.request, urllib.error, re, json, tarfile, io for version in ["9.3.0", "9.28.0"]: try: info = json.loads(urllib.request.urlopen(f"https://registry.npmjs.org/@pulumi%2fgcp", timeout=20).read().decode()) tarball = info["versions"][version]["dist"]["tarball"] print(f"\n== {version} tarball {tarball} ==") with urllib.request.urlopen(tarball, timeout=30) as resp: data = io.BytesIO(resp.read()) with tarfile.open(fileobj=data, mode="r:gz") as tf: names = [n for n in tf.getnames() if n.startswith("cloudrunv2/") and (n.endswith(".d.ts") or n.endswith("workerPool.ts"))] print("files:", sorted(names)[:50]) for name in names: content = tf.extractfile(name) if not isinstance(content, io._io.BufferedReader): content = io.TextIOWrapper(content) text = content.read() if "deletionProtection" in text or "launchStage" in text or "preview" in text.lower(): print(f"\n--- {name} ---") for i, line in enumerate(text.splitlines(), 1): if "deletionProtection" in line or "launchStage" in line or "preview" in line.lower(): print(f"{i}: {line}") except Exception as e: print(f"ERROR {version}: {type(e).__name__}: {e}") PYRepository: exactly/exa
Length of output: 25949
🌐 Web query:
@pulumi/gcp 9.31.0 cloudrunv2 WorkerPool deletionProtection launchStage TypeScript types💡 Result:
In the Pulumi GCP provider version 9.31.0, the gcp.cloudrunv2.WorkerPool resource includes explicit TypeScript types for both deletionProtection and launchStage. 1. deletionProtection: This property is a boolean (or pulumi.Input) that, when set to true, prevents the resource from being destroyed [1][2][3]. 2. launchStage: This property defines the lifecycle stage of the resource (e.g., ALPHA, BETA, GA) as per Google Cloud Platform Launch Stages [2]. It accepts a string value and defaults to GA if not specified [2]. The supported values are: UNIMPLEMENTED, PRELAUNCH, EARLY_ACCESS, ALPHA, BETA, GA, and DEPRECATED [2]. The Pulumi GCP provider is built upon the underlying Google Cloud Terraform provider [4]. In version 9.31.0, the provider has been upgraded to utilize terraform-provider-google-beta v7.40.0 [5][6]. You can access these properties directly within the WorkerPoolArgs interface when defining a new WorkerPool resource in your TypeScript program [1][2][7].
Citations:
- 1: https://www.pulumi.com/registry/packages/gcp/api-docs/cloudrunv2/workerpool/
- 2: https://cdn.jsdelivr.net/npm/@pulumi/gcp@9.3.0/cloudrunv2/workerPool.d.ts
- 3: https://www.pulumi.com/registry/packages/gcp@8.x/api-docs/cloudrunv2/workerpool/
- 4: https://www.pulumi.com/registry/packages/gcp/api-docs/cloudrunv2/
- 5: https://github.com/pulumi/pulumi-gcp/releases/tag/v9.31.0
- 6: pulumi/pulumi-gcp@v9.30.0...v9.31.0
- 7: pulumi/pulumi-gcp#3421
🌐 Web query:
terraform-provider-google-beta 7.40.0 cloudrun_v2_worker_pool deletionProtection launchStage💡 Result:
In the terraform-provider-google-beta provider, including version 7.40.0, the google_cloud_run_v2_worker_pool resource includes both deletion_protection and launch_stage as optional arguments [1][2]. deletion_protection: This argument determines whether Terraform is prevented from destroying the resource [1][3]. It defaults to true [1][3]. When set to true (or left unset), any terraform apply or terraform destroy command that would delete the WorkerPool will fail [1][3]. When set to false, deletion of the WorkerPool is permitted [1][3]. launch_stage: This argument defines the launch stage of the resource according to Google Cloud Platform Launch Stages [1][3]. It supports the values: UNIMPLEMENTED, PRELAUNCH, EARLY_ACCESS, ALPHA, BETA, GA, and DEPRECATED [1][3]. If no value is specified, GA is assumed [1][3]. Setting this to a preview stage (e.g., ALPHA or BETA) on input allows the use of preview features associated with that stage [1][3]. On output, this field reflects whether the resource is currently utilizing preview features; for instance, if ALPHA is provided as input but only BETA-level features are utilized, the field will report BETA upon reading the state [1][3].
Citations:
- 1: https://registry.terraform.io/providers/hashicorp/google-beta/7.28.0/docs/resources/cloud_run_v2_worker_pool
- 2: https://github.com/hashicorp/terraform-provider-google-beta/releases/tag/v7.40.0
- 3: https://registry.terraform.io/providers/hashicorp/google-beta/latest/docs/resources/cloud_run_v2_worker_pool
Be explicit about WorkerPool lifecycle behavior.
The five
cloudrunv2.WorkerPoolresources omitlaunchStage, which is optional and treats GA as the default, so this is not a required preview-stability fix. However, WorkerPools use the beta provider path wheredeletionProtectiondefaults totrue; adddeletionProtection: falsefor any ephemeral/dev stacks or intentional teardown scenarios, otherwise destroy/replace can be blocked by default.server/api/chat.ts (1)
14-16: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Extract shared
audience/issuerconstants instead of duplicating"chat-wa"/"chat-webhook".These literals appear twice in this file (lines 34, 96) and are re-declared separately in the test suite. Since this is a second use and enforces a cross-boundary invariant (encode/decode/webhook must agree on aud/iss), it should be extracted into a shared constant (e.g., exported from
../utils/chat) rather than duplicated inline.As per coding guidelines: "Keep single-use values, functions, and destructuring inline; extract only after a second use or when enforcing an invariant across the project."
♻️ Proposed extraction
+export const chatAudience = "chat-wa"; +export const chatIssuer = "chat-webhook";- const waId = await decode(c.req.valid("query").token, "chat-wa", "chat-webhook").catch(() => undefined); + const waId = await decode(c.req.valid("query").token, chatAudience, chatIssuer).catch(() => undefined);- waId = await decode(c.req.valid("json").token, "chat-wa", "chat-webhook"); + waId = await decode(c.req.valid("json").token, chatAudience, chatIssuer);Also applies to: 34-34, 96-96
Source: Coding guidelines
server/hooks/block.ts (1)
60-60: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
Cache the in-flight keeper initialization.
Awaiting
getWallet("keeper")on the right-hand side leaveskeeperunset during initialization, so concurrent queue callbacks can create multiple wallet clients.
server/hooks/block.ts#L60-L60: add a shared promise-based initializer that clears the promise on rejection.server/hooks/block.ts#L260-L260: use the helper for nonce skipping.server/hooks/block.ts#L296-L296: use the helper for proposal execution.server/hooks/block.ts#L512-L512: use the helper for withdrawals.Proposed fix
let keeper: Awaited<ReturnType<typeof getWallet>> | undefined; +let keeperPromise: ReturnType<typeof getWallet> | undefined; +const getKeeper = async () => { + if (keeper) return keeper; + const promise = (keeperPromise ??= getWallet("keeper")); + try { + return (keeper ??= await promise); + } finally { + if (keeperPromise === promise) keeperPromise = undefined; + } +}; - (keeper ??= await getWallet("keeper")).exaSend( + (await getKeeper()).exaSend(Apply the same call-site replacement to the proposal execution and withdrawal paths.
📍 Affects 1 file
server/hooks/block.ts#L60-L60(this comment)server/hooks/block.ts#L260-L260server/hooks/block.ts#L296-L296server/hooks/block.ts#L512-L512server/hooks/persona.ts (1)
337-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Extract the duplicated allow-enqueue block.
The enqueue payload at Lines 340-346 is byte-identical to Lines 404-410. Extract a small helper (e.g.
enqueueAllow) takingaccount.outputandcredential, so the two branches can't diverge.As per coding guidelines: "Duplicate code (copy/paste, similar logic, abstractions)" and extract "after a second use".
♻️ Sketch
const enqueueAllow = (output: Address) => enqueue({ account: output, chainId: chain.id, factory: parse(Address, credential.factory), publicKey: bytesToHex(credential.publicKey), source: credential.source }); // ... if (account.success && firewallAddress) await enqueueAllow(account.output);server/test/mocks/alchemy.ts (1)
4-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Type the hoisted mocks against their real signatures.
findWebhook,createWebhook, andaddWebhookAddressesare declared with untypedvi.fn(), unlike sibling mock files in this PR that parametrizevi.fn<typeof X>()to keep mocks checked against the real export signature.As per coding guidelines,
**/*.{ts,tsx}: Use strict TypeScript; do not use any; prefer TypeScript and Valibot types for compile-time safety.♻️ Proposed typed mocks
+import type { addWebhookAddresses as AddWebhookAddresses, createWebhook as CreateWebhook, findWebhook as FindWebhook } from "../../utils/alchemy"; + const { addWebhookAddresses, createWebhook, findWebhook } = vi.hoisted(() => ({ - findWebhook: vi.fn().mockResolvedValue({ id: "activity", signing_key: "mock-signing-key" }), - createWebhook: vi.fn().mockResolvedValue({ id: "mock-webhook-id", signing_key: "mock-signing-key" }), - addWebhookAddresses: vi.fn().mockResolvedValue(undefined), // eslint-disable-line unicorn/no-useless-undefined + findWebhook: vi.fn<typeof FindWebhook>().mockResolvedValue({ id: "activity", signing_key: "mock-signing-key" }), + createWebhook: vi.fn<typeof CreateWebhook>().mockResolvedValue({ id: "mock-webhook-id", signing_key: "mock-signing-key" }), + addWebhookAddresses: vi.fn<typeof AddWebhookAddresses>().mockResolvedValue(undefined), // eslint-disable-line unicorn/no-useless-undefined }));Source: Coding guidelines
server/test/workers/refund.test.ts (1)
247-265: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Consider moving the hoisted
mocksandvi.mockblocks to the top for consistency.
mocksis used inbeforeEach(Lines 122-123) but declared here at the bottom; it works only because Vitest hoistsvi.hoisted/vi.mock. Sibling suites (server/test/workers/allow.test.ts,server/test/workers/poke.test.ts) place these near the top, which reads more clearly.server/utils/chat.ts (1)
5-6: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
jose library recommended key derivation for dir A256GCM JWE encryption key from arbitrary secret💡 Result:
When using the "dir" (direct) encryption algorithm in JWE with A256GCM, there is no key derivation step [1]. The "dir" algorithm is a pass-through mechanism where the shared symmetric key you provide is used directly as the Content Encryption Key (CEK) for the chosen encryption algorithm [2][3][1]. Because "dir" uses the provided secret directly, it is critical that the secret you provide meets the requirements of the encryption algorithm being used [1]. For A256GCM, your secret must be a 256-bit (32-byte) key [2][4][5]. Implementation Guidance: 1. Requirement for Secret Size: Since "dir" does not perform key derivation (such as KDF or KEK wrapping), you must provide a secret that is exactly the size required by the "enc" algorithm (e.g., 256 bits for A256GCM) [2][3][1]. 2. Avoid Arbitrary Strings: Do not pass a raw, arbitrary string of arbitrary length. If you have an arbitrary password or secret, you should first pass it through a proper Key Derivation Function (KDF) like PBKDF2, Argon2, or HKDF to derive a cryptographically strong 256-bit key before using it in the "dir" JWE operation [1]. 3. Library-Specific Key Generation: Most JOSE libraries provide utility functions to generate secrets of the correct size if you need a new key [6][7]. If you are migrating a legacy secret, ensure it is properly hashed or derived to the exact bit length required by the A256GCM algorithm before passing it to the library's encryption method [1]. In short, "dir" assumes you have already performed any necessary key derivation; it does not perform one for you [1]. If you provide a secret that is not 256 bits, the underlying cryptographic implementation will likely fail or produce insecure results [1]. [2][3][1]
Citations:
- 1: https://datatracker.ietf.org/doc/html/rfc7518
- 2: https://hexdocs.pm/jose/1.11.5/JOSE.JWE.html
- 3: https://www.rubydoc.info/gems/jose/JOSE/JWE
- 4: https://hexdocs.pm/jose/JOSE.JWE.html
- 5: https://jose.hexdocs.pm/JOSE.JWE.html
- 6: panva/jose#450
- 7: https://raw.githubusercontent.com/cisco/node-jose/master/README.md
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '--- files ---\n' git ls-files | rg '(^|/)chat\.ts$|node\.json|pnpm-lock\.yaml|package\.json|pulumi|infra|README|\.env.example|env|deploy|Docker|compose|terraform' || true printf '\n--- server/utils/chat.ts ---\n' if [ -f server/utils/chat.ts ]; then cat -n server/utils/chat.ts fi printf '\n--- search CHAT_IDENTITY_KEY ---\n' rg -n -C 3 'CHAT_IDENTITY_KEY|chat key|chat identity|createHash|EncryptJWT|DecryptJWT|AUTH_SECRET|WALLET_EXTENSION_SECRET' . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' --glob '!server/generated/**' || true printf '\n--- package jose usage ---\n' rg -n 'jose' package.json pnpm-lock.yaml node_modules 2>/dev/null || trueRepository: exactly/exa
Length of output: 240
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '%s\n' '--- files ---' git ls-files | rg '(^|/)chat\.ts$|node\.json|pnpm-lock\.yaml|package\.json|pulumi|infra|README|\.env\.example|env|deploy|Docker|compose|terraform' || true printf '\n%s\n' '--- server/utils/chat.ts ---' if [ -f server/utils/chat.ts ]; then cat -n server/utils/chat.ts fi printf '\n%s\n' '--- search CHAT_IDENTITY_KEY and related env ---' rg -n -C 3 'CHAT_IDENTITY_KEY|chat key|chat identity|createHash|EncryptJWT|DecryptJWT|AUTH_SECRET|WALLET_EXTENSION_SECRET' . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' --glob '!server/generated/**' || true printf '\n%s\n' '--- jose package references ---' rg -n 'jose' package.json pnpm-lock.yaml 2>/dev/null || trueRepository: exactly/exa
Length of output: 20921
Derive
CHAT_IDENTITY_KEYbefore passing it to JWE “dir” encryption.
jose“dir” passes the key directly to A256GCM, so a raw/low-entropyCHAT_IDENTITY_KEYgets normalized but not hardened; using an unsalted fast hash does not add brute-force resistance. Use a provided 32-byte high-entropy key or derive it through a proper KDF/secret-provisioning path before using it here.server/workers/poke/queue.ts (1)
43-43: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
BullMQ queue.add ignores duplicate jobId if completed job retained by removeOnComplete💡 Result:
In BullMQ, the queue.add method ignores a job with a duplicate jobId if that ID already exists in the queue [1][2]. This behavior persists even if the existing job is in a completed or failed state, provided it has not yet been removed from the queue's storage [3][2]. When you configure removeOnComplete or removeOnFail, BullMQ removes these jobs lazily, typically only when a new job completes or fails [4][5]. Consequently, if a job is in the "completed" or "failed" set, it is still technically present in the queue, and any subsequent attempt to add a job with the same jobId will be treated as a duplicate, ignored, and trigger a "duplicated" event [4][1][6]. To ensure a new job can be added with an existing jobId, you must ensure the previous job with that ID is removed [1][6]. Strategies to manage this include: 1. Configuring stricter retention: Use removeOnComplete or removeOnFail settings (such as setting them to true or using a small count/age) to ensure completed or failed jobs are removed promptly [4][7][8]. 2. Adjusting your jobId logic: If your application requires different jobs to have different IDs even for the same "type" of task, include unique data (like a timestamp or a UUID) in the jobId generation [3]. 3. Manual removal: In specific workflows, you may need to explicitly clean up or remove the old job before adding a new one with the same ID, though this is generally less common than adjusting retention policies [1][6]. Note that the auto-removal process is lazy, meaning it does not run continuously in the background; it relies on events triggered by other job activity [4][5]. If your queue becomes "stalled" because all jobs are blocked by existing IDs in the completed/failed sets, you may need to manually trigger cleanup using the queue.clean method [9].
Citations:
- 1: https://taskforcesh-bullmq.mintlify.app/jobs/job-ids
- 2: https://docs.bullmq.net/http-api/queues/adding-jobs/custom-job-ids
- 3: taskforcesh/bullmq#1337
- 4: https://docs.bullmq.io/guide/queues/auto-removal-of-jobs
- 5: https://github.com/taskforcesh/bullmq/blob/master/docs/gitbook/guide/queues/auto-removal-of-jobs.md
- 6: https://mintlify.wiki/taskforcesh/bullmq/jobs/job-ids
- 7: https://github.com/taskforcesh/bullmq/blob/master/docs/gitbook/guide/workers/auto-removal-of-jobs.md
- 8: https://mintlify.wiki/taskforcesh/bullmq/jobs/removing
- 9: taskforcesh/bullmq#1799
🏁 Script executed:
#!/bin/bash set -euo pipefail printf 'Repository files matching queue.ts:\n' fd 'queue\.ts$' . | sed 's#^\./##' | sort printf '\nTarget file outline and relevant lines:\n' if [ -f server/workers/poke/queue.ts ]; then wc -l server/workers/poke/queue.ts cat -n server/workers/poke/queue.ts | sed -n '1,120p' else echo 'missing server/workers/poke/queue.ts' fi printf '\nSearch for poke job id usages and deposits:\n' rg -n "jobId|process deposit|deposit|assets|removeOnComplete|poke|pokeQueue|Poke" -S server . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: exactly/exa
Length of output: 23842
🏁 Script executed:
#!/bin/bash set -euo pipefail printf 'server/hooks/activity.ts relevant section:\n' cat -n server/hooks/activity.ts | sed -n '90,170p' printf '\nBullMQ package declaration / lock references:\n' if [ -f package.json ]; then cat package.json | grep -E '"(devDependencies|dependencies)"|bullmq' || true; fi for f in pnpm-lock.yaml package-lock.json yarn.lock bun.lockb; do [ -f "$f" ] && echo "--- $f" && rg -n "bullmq|node_modules/.pnpm/bullmq|versions.*bullmq|bullmq@" "$f" | head -40 done printf '\nPackage metadata via npm registry if resolvable (no install):\n' npm view bullmq peerDependencies dependencies version version --json 2>/dev/null | head -80 || trueRepository: exactly/exa
Length of output: 4409
Include a time component in the poke job id.
jobIdis deduped by account + assets, and BullMQ keeps completed jobs with that id until retention eviction. A later deposit of the same asset set for an account whose previous poke is still in the retained completed set will be ignored, leaving funds unpoked. Add a per-deposit key such as timestamp/id, or remove completed jobs immediately.
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2dae0789-7049-4c72-9bcc-452d5976f750
📒 Files selected for processing (9)
.changeset/warm-otters-associate.mdserver/api/chat.tsserver/api/index.tsserver/database/schema.tsserver/script/openapi.tsserver/test/api/chat.test.tsserver/test/utils/chat.test.tsserver/utils/chat.tsserver/vitest.config.mts
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
server/utils/chat.ts (1)
34-38: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winStill logging the OTP code alongside the WA id.
debug("send validation code %s to %s", code, waId)logs a live one-time code with a phone-number identifier. This was already flagged in a previous review and remains unresolved.🔒 Suggested fix
export function sendCode(waId: string, code: string) { // TODO integrate message senders - debug("send validation code %s to %s", code, waId); + debug("send validation code to %s", waId); return Promise.resolve(); }server/api/chat.ts (1)
100-103: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
cspell:ignorestill misplaced.The flagged word "cooldown" is on line 100; the ignore annotation must be on the same line, not on a separate line below it. As per coding guidelines: "place
cspell:ignoreon the same line as the unknown word."✏️ Suggested fix
- if (!(await redis.set(`chat:cooldown:${waId}`, "1", "PX", 60_000, "NX"))) { - // cspell:ignore cooldown + if (!(await redis.set(`chat:cooldown:${waId}`, "1", "PX", 60_000, "NX"))) { // cspell:ignore cooldown return c.json({ code: "too soon" }, 429); }Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5ee140b1-7a3b-4326-b2cd-6b3f8b336ae1
📒 Files selected for processing (8)
.changeset/warm-otters-associate.mdserver/api/chat.tsserver/api/index.tsserver/script/openapi.tsserver/test/api/chat.test.tsserver/test/utils/chat.test.tsserver/utils/chat.tsserver/vitest.config.mts
d330c43 to
d67b7f6
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7570d401-5dc8-45e9-8410-0fa8133f4d16
📒 Files selected for processing (9)
.changeset/warm-otters-associate.mdserver/api/chat.tsserver/api/index.tsserver/database/schema.tsserver/script/openapi.tsserver/test/api/chat.test.tsserver/test/utils/chat.test.tsserver/utils/chat.tsserver/vitest.config.mts
| const Token = object({ | ||
| token: pipe(string(), title("Chat token"), description("Encrypted token encoding the chat id to associate.")), | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Rename Token to token.
This is a schema value, not a type, component, or event. As per coding guidelines, “use camelCase for variables, functions, and modules.”
Source: Coding guidelines
| export function sendCode(waId: string, code: string) { | ||
| // TODO integrate message senders |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Implement delivery before reporting success.
This resolves without sending anything, so POST /chat/code returns 200 and persists a verification that the user cannot receive. Integrate a sender and clear pending/cooldown state when delivery fails. I can help add the provider integration and failure-path test.
Also applies to: 37-37
co-authored-by: danilo neves cruz <cruzdanilo@gmail.com>
9b7a4f6 to
999ec58
Compare
6d1b378 to
72bc020
Compare
Summary by CodeRabbit