Skip to content

v0.7.5: deployments API and block, vanta integration, performance improvements, styling consolidation#5016

Merged
waleedlatif1 merged 22 commits into
mainfrom
staging
Jun 13, 2026
Merged

v0.7.5: deployments API and block, vanta integration, performance improvements, styling consolidation#5016
waleedlatif1 merged 22 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

TheodoreSpeaks and others added 17 commits June 12, 2026 03:03
… + 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
…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>
)

* fix(jira): add classic JSM scopes to close granular scope-set gap

* fix(jira): note read:user:jira requirement for granular-only tokens in docs
* 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
@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 13, 2026 2:02am

Request Review

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Deploy/rollback and async table delete/export touch production data and workflow availability; org invite logic changes could affect who receives workspace access in batch flows.

Overview
v0.7.5 expands programmatic workflow lifecycle and integration/docs surface area, plus several app changes around tables and organization invites.

Deployments: OpenAPI and docs now document POST/DELETE …/deploy and POST …/rollback, with a new Deployments integration page (deploy, undeploy, promote, list/get versions). Deployment guides add CI/CD curl examples; API reference nav lists the new workflow endpoints. Workflow Undeployed is documented for Sim triggers.

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 table_jobs (stale running jobs, terminal prune, export file cleanup). Column routes use rootErrorMessage for clearer 400s.

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.

Comment thread apps/sim/app/api/cron/cleanup-stale-executions/route.ts
Comment thread apps/sim/app/api/v1/workflows/[id]/rollback/route.ts
Comment thread apps/sim/app/api/organizations/[id]/invitations/route.ts
…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
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ 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-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This 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 table_jobs table replacing inline import_* columns, tenant-scoped GIN + keyset indexes), and several stability improvements (billing self-heal for null usage limits, API key lastUsed debounce, OAuth connect-UI fix for service-id resolution, batch workspace invitations for existing org members).

  • Deployments API (v1/workflows/[id]/deploy|rollback): new public endpoints gated behind workspace-admin permission, matching the existing UI deploy surface; auth failures are masked as 404s to prevent existence probing.
  • Table background jobs: table_jobs replaces the import_status columns on user_table_definitions; a partial-unique index enforces one running write-job per table; keyset pagination + a tenant-scoped GIN index replace a cross-tenant GIN that caused O(N²) scans.
  • Export task retry config is non-functional: background/table-export.ts declares retry: { maxAttempts: 3 } but runTableExport catches all errors internally and returns normally — trigger.dev never retries, and any transient failure permanently fails the export job.

Confidence Score: 4/5

Safe 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 retry: { maxAttempts: 3 } and its docstring says "Retry-safe", but runTableExport handles all errors internally (marks the job failed, fires the SSE event, returns normally). Trigger.dev sees a successful run on every attempt and never retries. A transient DB or upload error during a large export permanently strands the job as failed rather than retrying. The rest of the PR — new v1 deployment endpoints, Vanta integration, table background job infrastructure, billing self-heal, API key debounce — looks correct and well-tested.

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

Filename Overview
apps/sim/background/table-export.ts New trigger.dev wrapper for table export; declares retry: { maxAttempts: 3 } but runTableExport swallows all errors internally, so retries never fire
apps/sim/lib/table/export-runner.ts New background export worker; catches all errors and marks job failed without rethrowing, making it fail-fast (intentional or not) but inconsistent with the task's retry config
apps/sim/background/table-delete.ts Trigger.dev delete task correctly rethrows from runner + uses onFailure for terminal failure marking, enabling real retries
apps/sim/lib/table/delete-runner.ts New background delete worker; correctly rethrows non-superseded errors enabling trigger.dev retry, with per-batch commits and ownership gate
apps/sim/app/api/v1/workflows/[id]/deploy/route.ts New v1 deploy/undeploy endpoints with proper rate limiting, workspace admin authorization, and workflow lock checks
apps/sim/app/api/v1/workflows/[id]/rollback/route.ts New v1 rollback endpoint; correctly resolves previous version when none specified and activates the target version
apps/sim/app/api/v1/middleware.ts Adds admin level to validateWorkspaceAccess via PERMISSION_RANK ordering, used by new deployment endpoints
apps/sim/lib/api-key/service.ts Adds 10-minute staleness window to lastUsed updates to avoid row-lock serialization under high API key traffic
apps/sim/lib/billing/core/usage.ts Self-heals null usage limits to plan defaults with conditional UPDATE + concurrent-win fallback read, preventing execution failures for users with missing stats
apps/sim/app/api/organizations/[id]/invitations/route.ts Extends batch invitations to send workspace-only grants to existing org members who lack access, with seat count correctly excluding non-org invites
packages/db/migrations/0233_table_jobs_and_keyset.sql Adds table_jobs table, migrates legacy import_* columns, adds CONCURRENTLY-built tenant-scoped GIN + keyset indexes with autovacuum tuning
packages/db/schema.ts Replaces import_* columns on userTableDefinitions with the new tableJobs table; updates GIN index to tenant-scoped btree_gin variant
apps/sim/lib/integrations/oauth-service.ts Fixes OAuth connect UI by resolving services via oauthServiceId key rather than display-name matching, eliminating false-negative lookups
apps/sim/blocks/blocks/vanta.ts New Vanta block with 29 operations covering frameworks, controls, tests, documents, people, vendors, and vulnerabilities; credentials use correct user-only visibility in tool params
apps/sim/blocks/blocks/deployments.ts New Deployments block exposing deploy/undeploy/promote/list/get operations on workspace workflows

Sequence Diagram

sequenceDiagram
    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}"
Loading

Reviews (1): Last reviewed commit: "fix(tables): heartbeat export job before..." | Re-trigger Greptile

Comment thread apps/sim/background/table-export.ts
#5018)

* Revert "improvement(auth): layer disposable-email-domains into signup email validation (#5010)"

This reverts commit 2c0a10a.

* feat(mailer): gate outbound email on AppConfig access-control ban list

* ci(migrations): restore dev schema-push TTY rename/drop guard dropped by #5010 revert
…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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/sim/app/api/cron/cleanup-stale-executions/route.ts
@waleedlatif1 waleedlatif1 merged commit 79d98b3 into main Jun 13, 2026
32 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.

3 participants