v0.7.5: deployments API and block, vanta integration, performance improvements, styling consolidation#5016
Conversation
… + tenant-scoped query performance (#4915) * feat(tables): paginated background row-delete jobs via table_jobs * fix(tables): address review on async row-delete (filtered count, scoped optimistic clear, Cmd+A select-all, hide delete from tray) * improvement(tables): filter-aware select-all runs, delete-job read mask, keyset index + autovacuum tuning * feat(tables): run import/delete/export/backfill jobs on trigger.dev with in-process fallback * improvement(tables): raise delete page to 10k and export batch to 5k * improvement(tables): raise CSV import batch to 5k rows (param-cap bounded) * feat(tables): surface export jobs in the header tray with progress, cancel, and download * improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray * Revert "improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray" This reverts commit 1ea5871. * fix(tables): preserve export storage key (NoSuchKey) and unify jobs in one spinner tray * improvement(tables): jobs tray icon reflects aggregate state (spinner/check/alert) * fix(tables): restore jobs tray on the tables list (dropped in staging merge) * improvement(tables): keyset-paginate export row reads (offset paging was O(n^2) over large tables) * perf(tables): keyset pagination for grid infinite scroll Default-order row pages now cursor on (order_key, id) instead of OFFSET — each page is an index seek on tableOrderKeyIdx, where OFFSET re-scans and discards every prior row (O(N²) across deep scrolls and full drains like select-all/export-to-clipboard). Sorted views keep offset paging; the contract refines after+sort as mutually exclusive. v1 public rows API is unchanged (extends the unrefined base, omits after). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): show export in job tray immediately on kickoff The export-jobs query's poll only self-sustains once a running job is already in the cache, so a freshly kicked export stayed invisible until an SSE event or page refresh. Invalidate the tray query on kickoff success so the icon appears right away. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): surface real row/column write errors in toasts Drizzle wraps DB errors in DrizzleQueryError whose message is the failed SQL — the real cause (e.g. the row-limit trigger's RAISE) sits on .cause, so the routes' substring classification never matched and everything fell through to generic 500s ("Failed to insert row"). Add rootErrorMessage (cause-chain unwrap) and a shared rowWriteErrorResponse classifier that consolidates the per-route pattern lists and rewrites the trigger message into a friendly "Row limit exceeded — capped at N rows". Applied across the app and v1 row-write routes and the columns route. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-bound filtered row counts (12.7s -> 0.6s) JSONB filter predicates (->> ILIKE / range casts) are opaque to the planner: it estimates a handful of matches and picks a parallel seq scan over the entire shared user_table_rows relation — every tenant's rows — for the page-0 COUNT(*), so any non-equality filter on a large table cost 10s+ regardless of how few rows matched. Run filtered counts in a transaction with SET LOCAL enable_seqscan = off, forcing the tenant-bounded bitmap plan. Unfiltered counts keep their index-only scan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-bound Cmd+F search and stream its window (75s -> 2s) Same planner trap as the filtered count, compounded: the lateral jsonb_each_text ILIKE is unestimatable, so findRowMatches on a 1M-row table seq-scanned the whole 12M-row shared relation and disk-sorted ~120MB of window input (75s measured). SET LOCAL enable_seqscan=off bounds the scan to the tenant; on the default order, additionally penalizing bitmap/sort/parallel steers the planner onto the already- sorted (table_id, order_key, id) index walk so row_number() streams with no sort at all (2s measured). Flags only penalize plan shapes — a custom sort still sorts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-bound sorted pages and filtered write selections Extends the seqscan fix to every remaining jsonb-predicate path, all measured on a 1M-row table in a 12M-row shared relation: - sorted page query (ORDER BY data->>'col'): 9.7s/page -> 0.76s, and deep pages stop spilling ~130MB sorts to disk - updateRowsByFilter / deleteRowsByFilter row selection: 14.4s -> bounded - delete-job worker selectRowIdPage with a filter: 12.6s/page -> bounded - dispatcher filtered-scope window walk: same shape, same fix Shared withSeqscanOff helper moves to lib/table/planner.ts (service + dispatcher both consume it; dispatcher can't import service). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-scoped containment index (migration 0232) The plain GIN on user_table_rows.data matched @> candidates across every tenant sharing the relation — a hot value in someone else's table inflated everyone's equality filters (1.07M candidates fetched for a 33k-row match, lossy bitmap, 1.1s). Replace it with btree_gin (table_id, data jsonb_path_ops): the tenant intersection happens inside the index and paths are single hashed entries. Rare-equality probe 326ms -> 17ms with zero wasted candidates; unique-constraint checks and upsert conflict lookups ride the same index. The new index is smaller than the one it replaces (529MB vs 694MB on the 12M-row dev relation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-bound unique-constraint checks (3.5s -> <1s per write) The unique check runs lower(data->>'col') = $1 LIMIT 1 on every insert and cell edit touching a unique column. The predicate is unestimatable and a unique (non-conflicting) value never exits early, so the planner seq-scanned all 12.3M shared-relation rows per check — 3.5s measured. Tenant-bound both the single and batch variants; the batch path sets the flag on the caller's transaction when one is supplied (SET LOCAL dies at its commit, and the statements that follow are tenant-scoped writes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(tables): tenant-bound upsert conflict lookup Same unestimatable data->>key predicate as the unique checks; an insert-path upsert has no existing match so the lookup can't exit early and seq-scans the whole shared relation. The upsert already runs in a transaction — set the planner flag on it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(tables): consolidate executor types onto planner exports service.ts kept a private DbTransaction alias and two inline typeof db | DbTransaction unions after planner.ts began exporting the canonical DbTransaction/DbExecutor — import those instead. From the /simplify review of the perf series; no behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tests): drop narrow schema mock override in process-contents test The local vi.mock('@sim/db/schema') stubbed only document/knowledgeBase, but the file's import graph reaches lib/table/service whose module scope now references tableJobs. The global schema mock already covers all of it — rely on it per the testing rules instead of re-mocking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): scope cancels and counts to the filtered selection (review) Addresses the open Bugbot/Greptile findings on filtered select-all: - Filtered runs no longer cancel the whole table: cancelWorkflowGroupRuns takes a filter — it stops only dispatches with that exact filter scope and only in-flight cells on matching rows (semi-join); whole-table and differently-scoped dispatches keep running, their cancelled cells skipped via cancelledAt > requestedAt. - Stop on a filtered select-all sends the filter through cancel-runs (contract + route + mutation) instead of a table-wide cancel. - runColumnBodySchema rejects rowIds + filter together (mirrors deleteTableRowsBodySchema). - Select-all delete clears the selection in onSuccess, not at click, so a failed kickoff restores both rows and selection. - Clipboard copy/cut estimates use the filter-aware total (rowTotal) instead of the whole-table rowCount. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: retrigger CI (Actions dropped the previous push events) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump api-validation route baseline to 807 (staging route + merge) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): release job claim when trigger.dev dispatch fails If tasks.trigger (or its dynamic imports) throws after markTableJobRunning, the ghost running row held the table's one-write-job slot until the stale-job janitor fired (~15-20 min of 409s). All four kickoff routes now release the claim and rethrow; the backfill runner releases and warns (a failed backfill never fails the schema change). Greptile P1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(db): squash 0232 into 0231 (one migration for the PR) Both are branch-only — no environment has applied them through the migration ledger yet — so the tenant-scoped GIN (btree_gin extension, index swap) folds into 0231_table_jobs_and_keyset. Snapshot chain re-pointed; drizzle-kit generate confirms zero drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): context-menu delete label shows the true select-all count Under select-all the context menu counted only the loaded page ("Delete 1000 rows" on a 999k-row table) while the action correctly deletes every matching row via the background job. Delete now gets its own count from the filter-aware total minus deselections; the run-action labels keep the loaded-row count since those actions act on loaded rows only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): context-menu bulk actions act on the full select-all scope Follow-up to the label fix: under select-all the context menu's Run / Re-run / Stop only acted on the loaded page of rows. They now route through the same scopes as the action bar — runs dispatch by filter (whole table when unfiltered), Stop uses the filter-scoped cancel — and all labels share one true count (filter-aware total minus deselections, locale-formatted). Like the action bar, filter-scoped runs ignore deselections (the run API has no exclusion set). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tables): exclusion set for select-all runs and stops Select-all minus deselected rows now means exactly that for every bulk action, not just delete. runColumnBodySchema and cancelTableRunsBodySchema accept excludeRowIds (bounded by MAX_EXCLUDE_ROW_IDS, select-all scope only); the dispatch scope persists it and the dispatcher window walk, eager bulk-clear, pre-run cancel, and filter/table-scoped cancel all skip excluded rows. Client threads exclusions from the selection through the action bar and the grid context menu, including the optimistic stamps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): spare excluded-row dispatches on Stop; no orphan placeholder table Two Bugbot findings on the exclusion work: - Select-all-minus-deselections Stop (no filter) cancelled every active dispatch table-wide, killing row-scoped runs on deselected rows. markActiveDispatchesCancelled now spares dispatches whose scope.rowIds are fully contained in the exclusion set (coalesce(false) keeps table-wide dispatches cancellable). - Create-mode import: a failed trigger.dev dispatch released the job claim but left the just-created placeholder table in the workspace. Archive it on the failure path (no hard-delete surface exists). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): row counts reflect a running delete everywhere A mid-delete refresh resurrected the old counts: the optimistic update stripped cached rows but left page-0 totalCount (footer / select-all label) at the old total, and list/detail counts reported raw row_count including doomed-but-not-yet-deleted rows. - onMutate now sets the active view's totalCount to the kept rows and decrements the cached detail rowCount by the doomed estimate - the kickoff persists that estimate on the job (payload.doomedCount, clamped server-side); getTableById/listTables subtract the not-yet-deleted remainder (doomedCount - rows_processed) while the delete runs, so refetched counts match the read path's delete mask Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * copy(tables): drop background mention from delete confirm Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): clear select-all immediately when a delete kicks off The header checkbox lingered as a minus over the optimistically-emptied grid: rowSelectionCoversAll treats zero rows as not-covered, and the selection clear waited for the kickoff's onSuccess. Clear at click (failed kickoffs visibly restore rows + toast; re-selecting is cheap) and render an empty grid's header checkbox unchecked regardless — a selection over zero rows is vacuous. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tables): export takes the async path while a delete job runs The sync/async export choice reads rowCount, which is a doomed-estimate- adjusted number during a running delete (and the estimate is client- supplied) — an overstated estimate could route a still-large masked set through the synchronous stream. Mid-delete exports now always run as a job: safe at any size, and exports bypass the one-job-per-table gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(build): stop uploads setup from sweeping the project into route graphs next build (Turbopack) failed with "Two or more assets with different content were emitted to the same output path" on the server-root chunk. Root cause: setup.server.ts's unscoped path.resolve(process.cwd()) made node-file-tracing sweep the entire project — next.config.ts included — into every route graph reaching lib/uploads (the files/upload route and, since the export job, the export-async path). Two producers emitted the swept config into same-named chunks; staging's latest commits made their contents diverge and the names collided. Annotate the path derivation with turbopackIgnore per the NFT warning's own remediation — the build passes and all ~390 "unexpected file in NFT list" warnings disappear. Also inline the releaseJobClaim dynamic imports in the kickoff routes to plain static imports — service is already statically imported there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…post-index ANALYZE guard (#4997) * fix(tables): per-batch delete-job commits, real trigger.dev retries, post-index ANALYZE guard * fix(tables): resume job progress across retries, rethrow root cause for clean failure messages
… up tables feature (#4995) * improvement(tables): migrate inputs to emcn chip components and clean up tables feature * fix(tables): address review feedback — stale filter column label, shared FieldError in enrichment config * improvement(tables): scope create-table callback to stable mutateAsync
…ile, people, vendor, vulnerability, and risk tools (#4993) * feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools * fix(integrations): use write-only scope for vanta document submit and stream-cap document downloads * fix(integrations): vanta review feedback - unique svg ids, mime type for base64 uploads, auth check consistency * fix(integrations): bound vanta base64 fileContent size at the contract level * improvement(integrations): cache vanta tokens and retry once on revocation to avoid concurrent token races * improvement(integrations): deduplicate in-flight vanta token exchanges * fix(integrations): hash vanta client secret in token cache keys * improvement(integrations): expose vanta upload mime type in block and align fileContent bound with the 100MB cap * chore: resolve api-validation baseline after rebase onto staging (816 + 3 vanta routes)
#4998) * improvement(emcn): show per-chip error tooltips on invalid email chips * improvement(emcn): fall back to generic reason for invalid email chips
…display name (#5001) * fix(integrations): resolve OAuth connect UI by service id instead of display name * test(integrations): pin OAuth service resolution for all catalog integrations; fix credential branding reverse lookup * fix(docs-gen): blank string literals and comments before brace scanning in extractOAuthServiceId
…eight with the editor and page header (#5003)
…y last-used writes (#5000) * improvement(billing): self-heal null usage limits and debounce api-key last-used writes * fix(billing): make usage-limit self-heal best-effort and respect concurrent writes * improvement(api-key): widen last-used staleness window to 10 minutes
#5002) * improvement(files): fit-width previews and chip-chrome viewer controls - PDF and DOCX previews now treat 100% zoom as fit-to-width instead of capping at the page's natural print size, removing the dead gutters in wide panels (pdf.js re-renders the canvas at the target width and DOCX uses CSS zoom, so both stay crisp) - PreviewToolbar page/zoom controls move from 24px ghost Buttons with off-token labels to canonical emcn chips (icon-only Chip pills, text-sm --text-body value labels) - XLSX sheet tabs move from underline-style ghost Buttons to a chip cluster using the active pill state - Audio preview swaps the music emoji for the lucide Music icon on design-system tokens * improvement(files): debounce PDF panel-resize re-rasterisation With fit-to-width every pageWidth change re-rasterises all page canvases, so per-tick updates during a panel-divider drag re-rendered the document continuously. First measurement still applies immediately. * fix(files): don't let a zero-width first measurement consume the immediate resize slot A hidden container reports zero width from the ResizeObserver; treating that as the initial measurement pushed the real first width onto the debounce path and delayed initial render. * improvement(files): module cleanup — dedupe media previews, debounce docx refits - Merge the near-identical AudioPreview/VideoPreview into one MediaPreview (shared fetch/blob-URL/error/loading path; only the player differs) - Debounce docx resize refits the same way the PDF preview debounces width measurements (the initial fit comes from the render path, not the observer) - Document the load-bearing buffer copy in pdf-viewer (pdf.js transfers and detaches the ArrayBuffer it receives)
…#4996) A filtered select-all Stop only cancels matching rows server-side, but the optimistic update flipped in-flight cells across every cached rows query — stale unfiltered views showed workflows as cancelled until the refetch. snapshotAndMutateRows gains an onlyKey option; the cancel mutation passes the active view's exact cache key (filter + sort) when a filter is present, and onSettled's invalidation reconciles other views. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… modals (#5004) * v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li <theo@sim.ai> * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * feat(credentials): add Atlassian service account credentials * improvement(credentials): tighten Atlassian service account plumbing - Collapse fetchOAuthTokenBundle into fetchOAuthToken (returns the bundle) - Reuse serviceAccountJsonSchema in the JSON form instead of hand-rolled checks - Use parseAtlassianErrorMessage for log details; drop one-line bearer helper - Extract ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID/_SECRET_TYPE constants - Use Drizzle .returning() instead of post-insert SELECT - Helper for the duplicated 401/403 + non-OK pattern in the validator * docs(credentials): add Atlassian service account setup guide - New /integrations/atlassian-service-account doc covers token creation, scope selection, and adding the credential to Sim - Form's "View setup guide" link now points at the doc - Fix the existing Google form link that pointed to the wrong path Screenshot TODOs left inline as MDX comments for the docs team. * docs(credentials): add Atlassian service account screenshots - Auth type picker, Sim add-credential modal, Jira block credential dropdown - Scope-picker screenshot still TODO * docs(credentials): add Atlassian scope picker screenshot * fix(credentials): address greptile feedback on Atlassian SA - Drop stale 'email and API token' copy from the service description (we only collect a token + domain, no email field) - Move duplicate display-name check inside the create transaction so concurrent POSTs can't both pass the check and insert duplicates * fix(docs): move Atlassian screenshots to docs/public Docs site serves /static/* from apps/docs/public, not apps/sim/public — matches the existing google-service-account screenshot convention. * fix(credentials): address review feedback on Atlassian SA - SSRF: only accept *.atlassian.net / *.jira-dev.com hosts before fetching tenant_info, blocking probes against localhost/internal IPs - Confluence spaces selector: pull cloudId from the SA secret instead of calling accessible-resources, which 401s for scoped service-account tokens - Case-insensitive https?:// strip so HTTPS://team.atlassian.net normalizes correctly * chore: merge staging and bump API validation route baseline to 727 * perf(credentials): single-resolve in confluence spaces selector Atlassian SAs were hitting resolveOAuthAccountId twice (once via refreshAccessTokenIfNeeded, once directly to read cloudId) and decrypting the secret twice (via getAtlassianServiceAccountToken inside refresh, then again via getAtlassianServiceAccountSecret). Resolve once up front and branch the whole flow on the result — SA path skips refresh entirely and pulls token+cloudId from a single secret read. * refactor(credentials): consolidate Atlassian SA creation into /api/credentials Atlassian service-account creation lived in its own route, contract, and mutation hook, copy-pasting ~140 lines of insert/membership/audit/posthog boilerplate from /api/credentials. Two endpoints means two authz paths, two audit shapes, two TOCTOU stories — they will drift. Fold Atlassian into the existing service_account branch of /api/credentials, dispatching by providerId. The Atlassian validator (tenant_info + Bearer /myself, SSRF host allowlist, typed error codes) lives in lib/credentials/atlassian-service-account.ts and is the only Atlassian- specific piece left. AtlassianValidationError maps to a {code, error} 400 in the existing catch block; the rest of the flow (transaction, members, audit, posthog, dup-check) is now shared with Google SA + env credentials. Delete: - /api/auth/atlassian-service-account route - contracts/atlassian-service-account.ts + barrel export - useCreateAtlassianServiceAccount hook - API audit baseline 727 → 726 Both forms (Google JSON-key, Atlassian token+domain) now call useCreateWorkspaceCredential with the appropriate body shape. * fix(credentials): close TOCTOU and restore typed errors after consolidation - Add inner duplicate-guard inside the create transaction (DuplicateCredentialError) to close the race that the outer findExistingCredentialBySource leaves open. service_account rows have no DB-level unique index on (workspaceId, providerId, displayName), so this is the actual safety net. Tx-internal check applies to Google + env_workspace too — race-safety win for all credential types. - Re-emit {code: 'duplicate_display_name', error: ...} on conflict so the form's ERROR_MESSAGES.duplicate_display_name mapping is reachable again. - Thread Atlassian-specific audit metadata (atlassianDomain, atlassianCloudId) back into recordAudit; consolidation had dropped them. - Use ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID constant in contract superRefine. - Drop `error: any` in catch in favor of `error: unknown` + getPostgresErrorCode. * chore(credentials): drop dead createWorkspaceCredentialBodySchema + updateWorkspaceCredentialBodySchema Both shadowed the actually-used schemas (createCredentialBodySchema / updateCredentialByIdBodySchema) and were missing the apiToken/domain Atlassian fields. A future change could pick the wrong one and silently drop those fields. Confirmed zero non-definition references in the repo (grep across apps/, packages/, scripts/ minus build artifacts). * fix(credentials): scope inner duplicate re-check to service_account OAuth dedupes by accountId, env_* by envKey — both have DB-level partial unique indexes that surface as 23505. The previous inner re-check fired for all types and always threw DuplicateCredentialError, which mapped to 'duplicate_display_name' in the UI even when the real conflict was a duplicate OAuth account or env key. Restrict the in-tx re-check to service_account (the only type without a DB-level index) and let the 23505 handler emit a generic message for everything else. * feat(integrations): add Documentation link to service-account connect modals * fix(integrations): point service-account modal docs links at Sim guides * fix(integrations): rename service-account modal docs link to Setup guide --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
…ed the Table icon in loading fallbacks (#5007)
* improvement(organization): invite validation experience * address comments
…alidation (#5010) * ci(migrations): fail dev schema push with an actionable error on rename/drop prompt `drizzle-kit push --force` only suppresses the data-loss confirm, not the rename-vs-drop disambiguation prompt. That prompt fires whenever a diff both adds and drops tables/columns at once (e.g. migration 0231 created sim_trigger_state while dropping the workspace_notification_* tables), and in CI it crashes with a bare "Interactive prompts require a TTY" stack trace. Catch that specific failure in the dev push step and emit a GitHub error annotation explaining the cause and the fix (drop the stale objects on the dev DB to match schema.ts — the same DROPs the versioned migration already applied to staging/prod), instead of leaving an opaque trace. Exit status is preserved either way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(auth): layer disposable-email-domains into signup email validation Compose the disposable-email-domains list (exact + wildcard) into better-auth-harmony's validator alongside its bundled Mailchecker list, so signup rejects an email if either flags it. Server-only module to keep the dataset out of the client bundle. * improvement(auth): defer disposable-domains dataset behind lazy dynamic import Address review: load the ~120K-entry dataset on first use instead of at module import, so deployments with SIGNUP_EMAIL_VALIDATION_ENABLED off never pay the cost. Add a bare wildcard-base test case. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…5009) * feat(deployments): add v1 deployment endpoints and Deployments block * fix(deployments): require deployed workflow for rollback, normalize warnings, guard orphaned workspaceId * fix(deployments): workspace-bound tool routes, optional-body parsing, version bounds, and 404 masking - Tool routes now require the executing workspace ID and reject cross-workspace targets - v1 deploy/rollback read optional bodies via parseOptionalJsonBody (size-capped, 400 on malformed JSON) - Version numbers bounded to the Postgres integer range - v1 mutation routes mask access failures as 404, matching the v1 detail route - listWorkflowVersions returns description and normalizes admin-api deployedByName (parity with mothership get_deployment_log) - Workflow selector no longer auto-selects the first workflow (new autoSelectFirstOption opt-out) - Shared deployment version metadata field schemas across UI/v1/tool contracts * chore(api-validation): bump route baseline for rebased staging (826) * fix(docs): use real ID formats in OpenAPI examples Workflow, workspace, folder, knowledge-base, document, and execution IDs are plain UUIDv4; workspace file IDs are wf_<shortId>; table and row IDs are tbl_/row_ + de-dashed UUID. Replaces all fake prefixed example IDs (wf_abc123, ws_xyz789, exec_..., kb_..., etc.) accordingly and marks the deploy body description as nullable to match the shared schema. * feat(deployments): resolve workflow names in block UI, add workflow_undeployed Sim trigger event - Deployments block now uses the workflow-selector subblock (same as the Workflow block), so the canvas tile shows the workflow name instead of the raw ID; reverts the now-unneeded dropdown autoSelectFirstOption prop - Adds workflow_undeployed to the Sim workspace-event trigger, emitted by performFullUndeploy through a shared lifecycle-event dispatch loop
… v1 auth prologue (#5013) * refactor(deployments): consolidate version-list reads onto listWorkflowVersions The UI deployments list route and the mothership get_deployment_log handler each had their own inline version query; both now consume the shared persistence helper, so every deployment surface (UI, v1, admin, tools, mothership) reads versions through one code path. * refactor(deployments): shared status mapper, single-version fetch, and v1 workflow resolver - statusForOrchestrationError maps orchestration error codes to HTTP statuses in one place (was an identical ternary in six routes: UI deploy/activate, v1 deploy/rollback, tool deploy/promote) - getWorkflowDeploymentVersion consolidates the single-version fetch used by the UI version GET and the deployments tool version route - resolveV1DeploymentWorkflow extracts the v1 mutation prologue (active-record load, admin permission check, 404 masking) shared by deploy, undeploy, and rollback
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Deployments: OpenAPI and docs now document POST/DELETE Vanta: New integration docs and VantaIcon / icon mapping; SimDeploymentsIcon added for the deployments block. Tables (app): Async delete and export routes (filter + exclusions, job claims, Trigger.dev or detached workers, presigned download), with tests. Column run / cancel-runs accept filter and excludeRowIds. Cron stale cleanup moves from import status on table definitions to unified Organization invites: Batch org invites can send workspace-only invitations to existing members for workspaces they lack (with audits and seat logic scoped to new org invites). Non-batch path still rejects existing members. Docs/misc: Atlassian service-account guide prefers classic Jira/JSM scopes with a granular-scope warning. OpenAPI/deployment examples use UUID-style workflow and execution IDs. Reviewed by Cursor Bugbot for commit c3c3416. Bugbot is set up for automated code reviews on this repo. Configure here. |
…can't kill a live finalize (#5017) * fix(tables): heartbeat export job before upload so the stale janitor can't kill a live finalize * chore(tables): rename stale-janitor counters to cover all table job types
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 98948c0. Configure here.
Greptile SummaryThis release bundles several independent features and fixes: a v1 deployments API (deploy/undeploy/rollback endpoints + a Deployments block), a Vanta compliance integration (29 tool operations), background job infrastructure for large table deletes and exports (trigger.dev tasks, a new
Confidence Score: 4/5Safe to merge with one fix pending: the export task's retry configuration claims retries happen but they never fire, meaning transient export failures will permanently fail the job. The export task declares apps/sim/background/table-export.ts and apps/sim/lib/table/export-runner.ts — the retry configuration and error-propagation contract need to be aligned. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant ExportRoute as POST /export-async
participant TriggerDev as Trigger.dev
participant ExportRunner as runTableExport
participant DB as table_jobs (DB)
participant Storage
Client->>ExportRoute: "POST {tableId, format}"
ExportRoute->>DB: markTableJobRunning(jobId, 'export')
ExportRoute->>TriggerDev: tasks.trigger('table-export', payload)
TriggerDev->>ExportRunner: run(payload)
loop Each page (ownership-gated)
ExportRunner->>DB: updateJobProgress(tableId, exported, jobId)
ExportRunner->>DB: selectExportRowPage(after, limit)
end
ExportRunner->>DB: updateJobProgress (pre-upload heartbeat)
ExportRunner->>Storage: uploadFile(chunks)
ExportRunner->>DB: setJobResultKey(jobId, key)
ExportRunner->>DB: markJobReady(jobId)
ExportRunner-->>TriggerDev: returns normally (no throw)
Note over TriggerDev,ExportRunner: retry maxAttempts:3 never fires because run() always returns normally
Client->>+ExportRoute: "GET /export/download?jobId="
ExportRoute->>DB: getTableJob(tableId, jobId)
ExportRoute->>Storage: generatePresignedDownloadUrl(resultKey)
ExportRoute-->>-Client: "{url, fileName}"
Reviews (1): Last reviewed commit: "fix(tables): heartbeat export job before..." | Re-trigger Greptile |
…gh replaceTableRows (#5011) * v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li <theo@sim.ai> * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * fix(mothership): tenant-check outputTable writes and route them through replaceTableRows maybeWriteOutputToTable / maybeWriteReadCsvToTable accepted any table id without verifying it belongs to the caller's workspace, so a foreign table's rows could be wiped and replaced cross-tenant. They also wrote rows with raw drizzle keyed by column *name*, bypassing the service layer's job-slot lock, validation, plan row limits, rowCount maintenance, and the stable column-id keying every other writer uses. Both handlers now reject tables outside the caller's workspace and delegate to replaceTableRows with name→id remapped rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mothership): fail outputTable writes per-row when any row matches no columns Review feedback: the all-rows-empty guard let mixed batches slip unmatched rows through as empty objects. Reject on the first row that maps to zero columns, naming the row. Adds the missing CSV-suite parity tests (no-matching-headers, service-error surfacing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c3c3416. Configure here.

Uh oh!
There was an error while loading. Please reload this page.