Skip to content

OUT-4067: migrate to @assembly-js/node-sdk with workspace-scoped auth - #281

Merged
SandipBajracharya merged 11 commits into
masterfrom
OUT-4067
Aug 20, 2026
Merged

OUT-4067: migrate to @assembly-js/node-sdk with workspace-scoped auth#281
SandipBajracharya merged 11 commits into
masterfrom
OUT-4067

Conversation

@SandipBajracharya

Copy link
Copy Markdown
Collaborator

Summary

Migrates the Copilot integration from the outdated copilot-node-sdk to @assembly-js/node-sdk (v4), and restructures how the app authenticates so a token expiring mid-sync can no longer break QuickBooks syncing.

The core problem

The new SDK ties request expiry to the tokenId embedded in the request token (enforced server-side via X-API-Key: workspaceId/apiKey/tokenId). A long-running webhook sync that kept using the token-derived credential could hit a 401 partway through.

The fix

  • Workspace-scoped client (AssemblyAPI). All backend/sync/webhook work now builds the SDK from workspaceId/apiKey with no token (ASSEMBLY_ENV/COPILOT_ENV=local). No tokenId ⇒ no server-side session to expire ⇒ mid-sync death is structurally impossible. This also fixes a latent multi-tenancy bug where a workspaceId passed into the old constructor fell back to a bare, unscoped API key.
  • Token decode isolated (AssemblyTokenPayload). The one place that still needs the token — the auth boundary — lives in its own module. Decode is a fast, one-shot operation and never runs the long-lived path. Kept separate from AssemblyAPI deliberately: it keeps the token-scoped SDK out of the wholesale-mocked client, which is what lets the test suite mock it cleanly.

Commits (grouped for review)

  1. chore — swap copilot-node-sdk@assembly-js/node-sdk
  2. refactor — rename CopilotAPIAssemblyAPI, scope the client by workspaceId
  3. refactor — extract token decode into AssemblyTokenPayload
  4. refactor — require deliveryTargets fields in NotificationRequestBodySchema
  5. test — update mocks and fixtures for the migration
  6. docs — add CLAUDE.md

Notes for reviewers

  • v4 is concurrency-safe. assemblyApi() returns a per-request client scoped via AsyncLocalStorage; it never mutates global OpenAPI. Added a unit test (test/unit/utils/assemblyTokenPayload.test.ts) that fires 20 interleaved decodes for different workspaces and asserts no cross-request bleed.
  • ASSEMBLY_ENV/COPILOT_ENV must be local in every deployment for the workspace-scoped credential to resolve — this is the one operational prerequisite.
  • CI gap: the root tsconfig.json excludes test/, so tsc does not type-check test files. Recommend adding tsc -p test/tsconfig.json to CI — it caught a fixture drift (shouldRetry) this branch fixes, and there are a few pre-existing test-only type errors it would surface (intuit-oauth EnvironmentType, a drizzle overload, an UnitPrice shape) worth a follow-up.

Testing

  • yarn test77 files / 329 tests pass (unit + integration).
  • tsc --noEmit (app) and tsc -p test/tsconfig.json (this branch's files) clean.
  • yarn lint:check clean.

🤖 Generated with Claude Code

SandipBajracharya and others added 6 commits August 18, 2026 21:38
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ient by workspaceId

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uestBodySchema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 18, 2026

Copy link
Copy Markdown

OUT-4067

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quickbooks-sync Error Error Aug 19, 2026 10:40am
quickbooks-sync (dev) Ready Ready Preview Aug 19, 2026 10:40am

Request Review

The v4 @assembly-js/node-sdk needs no patch; the old cp step failed the Vercel build because node_modules/copilot-node-sdk no longer exists. Removes the build step, both dead patch scripts, and lib-patches/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SandipBajracharya SandipBajracharya changed the title refactor(OUT-4067): migrate to @assembly-js/node-sdk with workspace-scoped auth OUT-4067: migrate to @assembly-js/node-sdk with workspace-scoped auth Aug 18, 2026
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

The PR migrates Assembly integration calls to the v4 workspace-scoped SDK and separates one-shot token decoding from long-running API clients.

  • Replaces copilot-node-sdk with @assembly-js/node-sdk.
  • Scopes backend SDK clients by workspace while retaining token-based decoding at authentication boundaries.
  • Removes obsolete SDK patch files, package scripts, and the preview-build patch invocation.
  • Updates affected services, schemas, mocks, fixtures, and tests.

Confidence Score: 5/5

The PR appears safe to merge because the previously reported preview-build failure has been removed and no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
scripts/build.sh Removes the obsolete non-production SDK patch step, fully addressing the previously reported preview-build failure.
package.json Replaces the legacy SDK dependency and removes both obsolete patch commands.
src/utils/assemblyAPI.ts Introduces the workspace-scoped Assembly SDK wrapper used by backend and synchronization operations.
src/utils/assemblyTokenPayload.ts Isolates token-scoped SDK initialization and token payload decoding at authentication boundaries.
src/app/api/core/utils/authenticate.ts Migrates request authentication to the dedicated token payload decoder.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    R[Incoming request token] --> D[AssemblyTokenPayload]
    D --> U[Authenticated User with workspaceId]
    U --> C[AssemblyAPI workspace-scoped client]
    C --> A[Assembly backend calls]
    B[Webhook, cron, or sync work] --> C
Loading

Reviews (2): Last reviewed commit: "fix(OUT-4067): drop copilot-node-sdk bui..." | Re-trigger Greptile

Comment thread package.json
@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai Already fixed in 79517b3. scripts/build.sh no longer runs patch-copilot-node-sdk, and both patch scripts were removed from package.json. The Vercel build passes on the latest commit.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Confirmed in 79517b3 — both patch scripts deleted, lib-patches/ removed, and scripts/build.sh no longer references patch-copilot-node-sdk. Good cleanup.

@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai review PR again

Comment thread src/action/copilot.action.ts Outdated
import { CopilotAPI } from '@/utils/copilotAPI'
import { AssemblyTokenPayload } from '@/utils/assemblyTokenPayload'

export async function getTokenPayload(token: string): Promise<Token | null> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not do TokenSchema.safeParse in this function or even withing getTOkenPayload?

And reuse this function.

Comment thread src/app/api/core/utils/authenticate.ts Outdated
Comment on lines +11 to +12
const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token)
const payload = TokenSchema.safeParse(tokenPayload)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we not reuse getTokenPayload in here?

// check if token is valid or not
const copilot = new CopilotAPI(token)
const tokenPayload = await copilot.getTokenPayload()
const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

again might be heloful if reuse the function you created for this.

Comment thread src/cmd/backfillProductInfo/index.ts Outdated

const copilot = new CopilotAPI(token)
const tokenPayload = await copilot.getTokenPayload()
const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Again replace with the function you created.

Comment thread src/cmd/syncMissedInvoices/index.ts Outdated

const copilot = new CopilotAPI(token)
const tokenPayload = await copilot.getTokenPayload()
const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

replace with the function you created.

Comment thread src/cmd/syncMissedProducts/index.ts Outdated

const copilot = new CopilotAPI(token)
const tokenPayload = await copilot.getTokenPayload()
const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Replace with the function you created.

Comment thread src/utils/assemblyAPI.ts Outdated
Comment thread src/utils/assemblyAPI.ts
Comment thread src/utils/assemblyTokenPayload.ts Outdated
SandipBajracharya and others added 4 commits August 19, 2026 15:56
…lyTokenPayload function

Stateless single-method class that was always instantiated just to call it once; a plain function is simpler and drops the pointless `new`. Stays in its own module so the auth-boundary decode remains separately mockable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getTokenPayload was a one-line pass-through to getAssemblyTokenPayload with no 'use server' boundary; Home (a server component) now calls the helper directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getAssemblyTokenPayload already validates against TokenSchema; use the Token|null result directly instead of safeParse-ing it again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…wait

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

@SandipBajracharya
SandipBajracharya merged commit ee74a72 into master Aug 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants