OUT-4067: migrate to @assembly-js/node-sdk with workspace-scoped auth - #281
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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>
Greptile SummaryThe PR migrates Assembly integration calls to the v4 workspace-scoped SDK and separates one-shot token decoding from long-running API clients.
Confidence Score: 5/5The 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
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
Reviews (2): Last reviewed commit: "fix(OUT-4067): drop copilot-node-sdk bui..." | Re-trigger Greptile |
|
@greptileai Already fixed in 79517b3. |
|
Confirmed in 79517b3 — both patch scripts deleted, |
|
@greptileai review PR again |
| import { CopilotAPI } from '@/utils/copilotAPI' | ||
| import { AssemblyTokenPayload } from '@/utils/assemblyTokenPayload' | ||
|
|
||
| export async function getTokenPayload(token: string): Promise<Token | null> { |
There was a problem hiding this comment.
Why not do TokenSchema.safeParse in this function or even withing getTOkenPayload?
And reuse this function.
| const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token) | ||
| const payload = TokenSchema.safeParse(tokenPayload) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
again might be heloful if reuse the function you created for this.
|
|
||
| const copilot = new CopilotAPI(token) | ||
| const tokenPayload = await copilot.getTokenPayload() | ||
| const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token) |
There was a problem hiding this comment.
Again replace with the function you created.
|
|
||
| const copilot = new CopilotAPI(token) | ||
| const tokenPayload = await copilot.getTokenPayload() | ||
| const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token) |
There was a problem hiding this comment.
replace with the function you created.
|
|
||
| const copilot = new CopilotAPI(token) | ||
| const tokenPayload = await copilot.getTokenPayload() | ||
| const tokenPayload = await new AssemblyTokenPayload().getTokenPayload(token) |
There was a problem hiding this comment.
Replace with the function you created.
…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>
Summary
Migrates the Copilot integration from the outdated
copilot-node-sdkto@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
tokenIdembedded in the request token (enforced server-side viaX-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
AssemblyAPI). All backend/sync/webhook work now builds the SDK fromworkspaceId/apiKeywith no token (ASSEMBLY_ENV/COPILOT_ENV=local). NotokenId⇒ 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.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 fromAssemblyAPIdeliberately: 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)
chore— swapcopilot-node-sdk→@assembly-js/node-sdkrefactor— renameCopilotAPI→AssemblyAPI, scope the client byworkspaceIdrefactor— extract token decode intoAssemblyTokenPayloadrefactor— requiredeliveryTargetsfields inNotificationRequestBodySchematest— update mocks and fixtures for the migrationdocs— addCLAUDE.mdNotes for reviewers
assemblyApi()returns a per-request client scoped viaAsyncLocalStorage; it never mutates globalOpenAPI. 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_ENVmust belocalin every deployment for the workspace-scoped credential to resolve — this is the one operational prerequisite.tsconfig.jsonexcludestest/, sotscdoes not type-check test files. Recommend addingtsc -p test/tsconfig.jsonto 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-oauthEnvironmentType, a drizzle overload, anUnitPriceshape) worth a follow-up.Testing
yarn test— 77 files / 329 tests pass (unit + integration).tsc --noEmit(app) andtsc -p test/tsconfig.json(this branch's files) clean.yarn lint:checkclean.🤖 Generated with Claude Code