* feat(enterprise): add data drains for continuous export to S3 / webhook
* chore(data-drains): regenerate migration on top of staging + bump route baseline
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(data-drains): clarify retention pairing is user-coupled, not enforced
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(data-drains): preserve explicit forcePathStyle=false + reserve x-sim-signature
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(data-drains): drift guard ensures every webhook header is reserved
Asserts that any header buildHeaders writes is rejected when reused as a
custom signatureHeader. Adding a new metadata header without mirroring it
into RESERVED_SIGNATURE_HEADER_NAMES now fails CI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(uploads): direct-to-S3 workspace files + shared transport
* chore(testing): centralize posthog and storage-service mocks
* fix(uploads): address PR review — abort propagation, orphan cleanup, error handling
- Throw immediately on AbortError in KB retry loop (no useless 14s backoff)
- Cleanup S3/Blob object on quota or size-cap rejection in registerUploadedWorkspaceFile
- Enforce MAX_WORKSPACE_FILE_SIZE at registration (defense vs presigned PUT lying about size)
- Handle non-OK / non-JSON responses in workspace-files upload paths
* fix(uploads): add Zod contracts for workspace presigned/register routes
* fix(uploads): correct BlobServiceClient type name in headBlobObject
* fix(uploads): address PR review — typo, complete-failure cleanup, double-increment
* fix(uploads): preserve fallback size and reuse existing display name on re-register
* fix(uploads): surface server error message and bypass quota for local-storage fallback
* fix(uploads): align register response schema with UserFile; skip presigned for KB large files
- registerWorkspaceFileResponseSchema now matches the UserFile shape the route actually returns; previous schema required workspace DB-row fields that were never populated, causing requestJson validation to reject successful uploads.
- KB batch presigned fetch now skips files >= LARGE_FILE_THRESHOLD since multipart bypasses the per-file presigned URL anyway.
* fix(uploads): idempotent register skips duplicate audit/posthog; add edge-case tests
- registerUploadedWorkspaceFile now returns { file, created } so the route can skip captureServerEvent and recordAudit on idempotent re-register (existing metadata reused). Previously a re-register fired duplicate analytics + audit log entries.
- Add tests covering: idempotent re-register skips audit/analytics, isNetworkError matches econnreset/timeout/etc keywords, multipart complete failure fires action=abort cleanup.
* fix(uploads): include 50MiB boundary in batch presigned fetch
* fix(uploads): trust HEAD size to prevent quota inflation
The head.size > 0 fallback let a client PUT 0 bytes and register
with an inflated size, debiting quota without storing data. HEAD
on an existing object always returns the true byte count, so trust
it directly — a genuine 0-byte file correctly contributes 0.
* fix(uploads): audit verified file size, not client-supplied
* fix(uploads): handle register retries and name-collision races
Two bugs in registerUploadedWorkspaceFile:
1. Register retry could orphan storage. When a successful response
was lost on the wire and the client retried, the quota check saw
the bytes already counted, failed, and cleanupOrphan deleted the
already-registered storage object — leaving the DB row pointing
to nothing. Fix: check getFileMetadataByKey before quota guard
and short-circuit on existing record.
2. Concurrent same-named uploads could lose data. allocateUniqueWorkspaceFileName
is best-effort; two racing uploads can pass it and both attempt
the same display name. The loser's insert hits 23505, the catch
block called cleanupOrphan, and successfully-uploaded bytes
were deleted. Fix: retry on 23505 with a fresh allocateUniqueWorkspaceFileName,
matching the pattern in uploadWorkspaceFile. Throw FileConflictError
after exhaustion.
* fix(uploads): retry transient DirectUploadErrors at outer KB level
The KB outer retry only triggered on isNetworkError, missing
transient 5xx from S3/Azure (DirectUploadError code
DIRECT_UPLOAD_ERROR or MULTIPART_ERROR). Adds isTransientUploadError
and retries on it, restoring resilience for small-file presigned
PUTs against flaky cloud storage.
* fix(uploads): only retry transient 5xx, not deterministic 4xx
DirectUploadError now carries the HTTP status. isTransientUploadError
gates on 5xx so callers don't loop on 400/403/404 (e.g., malformed
request, expired signature). Multipart per-part retry also short-circuits
on 4xx — same reasoning.
* refactor(uploads): collapse getFileContentType into resolveFileType
The two helpers differed only in whether application/octet-stream
falls back to the extension map. Add an option flag to resolveFileType
and keep getFileContentType as a thin wrapper for direct-PUT callers
that need to preserve the exact browser-reported content-type.
* chore(uploads): trim verbose comments
Drop inline comments that restate code ("Use the full storageKey as fileName"),
collapse a multi-line block comment into a tighter TSDoc on the existence
check, and prune verbose vitest file headers — describe blocks already
document what's tested.
* fix(uploads): regenerate fileId per insert retry; require cloud storage for register
* fix(uploads): cap formdata fallback at 100MB; drop unused size param
* fix(uploads): abort multipart on get-part-urls failure; retry register on transient errors
* fix(uploads): drop vestigial size field from register contract
* fix(uploads): abort multipart on complete-fetch throw
* fix(uploads): set kb presignedEndpoint fallback; race-safe blob HEAD
* fix(uploads): include ?type=knowledge-base on kb presigned fallback
* fix(uploads): remove abort listener on xhr timeout
* fix(uploads): add timeout/abort to kb api fallback upload
* feat(table): make plan table limits configurable via env vars
* fix(table): coerce env table limits to number for skipValidation env
* improvement(env): extract envNumber helper for numeric env coercion
* improvement(knowledge): use envNumber helper for KB_CONFIG_* env reads
* fix(testing): add envNumber to env mock factory
* fix(env): allow zero in envNumber for max-throughput configs
* fix(env): add min option to envNumber for strict-positive configs
* fix(terminal): correct error/cancel block status in logs panel
Three bugs in the workflow editor's terminal/logs panel where block
status diverged from the engine's truth on error paths:
1. **Errored block shown as "canceled"** — when the SSE 'add' mode
produced a duplicate entry on block error and `cancelRunningEntries`
then swept the original placeholder.
2. **Upstream blocks stuck on "Running"** — terminal events arrived
before the engine's last block events under reconnect/timeout, so
the live panel never received the per-block terminal state.
3. **Phantom "Run Error" pseudo-row** — the failing block rendered as
"canceled" while a synthetic row carried the real error text.
Fixes:
- **Fix B** (`addConsoleErrorEntry`): when a running placeholder exists
for `(blockId, executionId)`, route through `updateConsoleErrorEntry`
instead of creating a second entry. Aligns 'add' mode with the
existing 'update' mode behavior.
- **Fix C** (`reconcileFinalBlockLogs`): terminal SSE events now carry
`finalBlockLogs` (server-authoritative snapshot). On
execution:error / execution:cancelled, reconcile any still-running
entries with their server-side terminal state. Recovers correctness
on network drop, server timeout/abort, and reconnect-resume paths
where individual block:* events may not have reached the client.
- **Fix D** (`addExecutionErrorConsoleEntry`): cross-check
`useTerminalConsoleStore` for entries with `error` set scoped to the
executionId before emitting the synthetic "Run Error" row. Suppresses
the phantom row when the failing block already carries the message.
- **Signature refactor**: `handleExecutionErrorConsole` /
`handleExecutionCancelledConsole` now take a typed
`ExecutionConsoleDeps` object instead of stacking positional deps —
matches the existing `createBlockEventHandlers(config, deps)`
precedent in the same file.
Tests:
- 12 tests in `workflow-execution-utils.test.ts` covering Fix B/C/D and
the deps-object signature refactor.
- Centralized terminal-console store mock in `@sim/testing` so future
tests can stub the store without per-file boilerplate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(terminal): wire copilot cancellation to finalBlockLogs reconciliation
Address Greptile review:
- `executeWorkflowWithFullLogging`'s `onExecutionCancelled` was `() => {}`
and silently dropped the `finalBlockLogs` payload, so Bug 2's "upstream
blocks stuck on Running" fix did not fire on copilot-initiated
cancellations. Wire it through `handleExecutionCancelledConsole` to
match the SSE-route `onExecutionCancelled` path.
- Test for the `blockType !== 'error'` filter used a different
`executionId` than the seeded entry, so the executionId scope rejected
the entry before the blockType predicate ran. Align executionIds so
the test actually exercises the filter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(terminal): pass durationMs in reconnect cancellation handler
Reconnect-resume `onExecutionCancelled` was forwarding `finalBlockLogs`
but not `data?.duration`, so the "Run Cancelled" entry rendered with a
0ms duration. Match the other two `handleExecutionCancelledConsole`
callsites.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(auth): resolve CORS errors for self-hosted deployments behind reverse proxies
- auth client now uses browser origin first, falling back to NEXT_PUBLIC_APP_URL
- socket client falls back to page origin when served from non-localhost (assumes /socket.io is proxied)
- add TRUSTED_ORIGINS env var to extend Better Auth trustedOrigins (apex+www, alias hostnames)
- warn at startup when NEXT_PUBLIC_APP_URL is localhost in production
- preprocess empty NEXT_PUBLIC_SOCKET_URL so docker-compose ${VAR:-} works
- migrate remaining uuid/nanoid/randomUUID usages to @sim/utils generateId/generateShortId
- extend generateShortId with optional alphabet param (rejection sampling)
- document TRUSTED_ORIGINS in .env.example, docker-compose.prod.yml, and helm values.yaml
Fixessimstudioai/sim#1243
* fix(auth): address PR review comments
* chore(env): drop unnecessary NEXT_PUBLIC_SOCKET_URL preprocess (skipValidation is true)
* fix(docker): include @sim/utils in migrations image
Migration scripts now import generateId from @sim/utils/id; without copying packages/utils into the image, bun install fails to resolve the workspace dep at build time and the import fails at runtime.
* fix(helm): remove unused NEXT_PUBLIC_SOCKET_URL from realtime sections
The realtime service never reads NEXT_PUBLIC_SOCKET_URL — its env schema
only includes BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL, ALLOWED_ORIGINS,
BETTER_AUTH_SECRET, INTERNAL_API_SECRET, DATABASE_URL, and REDIS_URL.
Remove the dead config from all helm values files and the values schema.
* fix(helm): allow empty NEXT_PUBLIC_SOCKET_URL in values schema
The default in values.yaml is now "" (empty string), which falls back to
the page origin at runtime. The schema previously required a valid URI,
which would reject the default. Mirror the INTERNAL_API_BASE_URL pattern
using anyOf with const "". Also add TRUSTED_ORIGINS to the schema.
* docs(self-hosting): mark NEXT_PUBLIC_SOCKET_URL as optional
The page-origin fallback in getSocketUrl() means self-hosters no longer
need to set NEXT_PUBLIC_SOCKET_URL when realtime is on the same origin
as the app. Update docs to reflect this:
- Remove NEXT_PUBLIC_SOCKET_URL from .env scaffolding examples in
docker.mdx, platforms.mdx, environment-variables.mdx
- Mark the variable as Optional in the env vars table with the new
default behavior described
- Update troubleshooting to point at reverse-proxy /socket.io routing
rather than the env var
- Flip dev docker-compose defaults (local, ollama, devcontainer) from
http://localhost:3002 to empty for consistency with prod.yml; the
in-code localhost fallback handles the dev case identically
Applied across all 6 documentation languages (en/fr/de/ja/es/zh).
* chore: untrack and ignore .claude/scheduled_tasks.lock
* feat(governance): external workspace users from outside org
* update docs
* address comments
* edge case improvements
* remove unused fallback
* address comments
* add outbox for seat reduction
* fix edge case with org join after invite
* add server side batch invites for workspace
* use zod schema for route
* improvement(tables): race-free row-count trigger + scoped tx timeouts
* fix(tables): close upsert race + serialize replaceTableRows
Two concurrency bugs flagged by review:
1. `upsertRow` insert path: removing FOR UPDATE broke serialization between
the initial existing-row SELECT and the INSERT. Two concurrent upserts
on the same conflict target could both find no match, then both insert,
producing a duplicate that bypasses the app-level unique check. Fixed
by re-checking for the matching row *after* acquiring the per-table
advisory lock; if a racing tx already inserted, switch to UPDATE.
2. `replaceTableRows`: under READ COMMITTED each tx's DELETE uses its own
MVCC snapshot, so two concurrent replaces could each DELETE only the
rows visible at their start, then both INSERT, leaving the table with
the union of both row sets. Fixed by acquiring the per-table advisory
lock at the start of the tx to serialize replaces against each other
and against auto-position inserts.
Also updated a stale docstring on `upsertRow` that still referenced the
removed FOR UPDATE.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(tables): serialize explicit-position inserts with advisory lock
The `(table_id, position)` index is non-unique. Concurrent explicit-
position inserts at the same slot can both observe the slot empty, both
skip the shift, then each INSERT — producing duplicate `(table_id,
position)` rows.
Take the per-table advisory lock in the explicit-position branches of
`insertRow` and `batchInsertRows` (the auto-position branches already do
this). Updated the test that asserted the explicit path skipped the lock,
and added coverage for `batchInsertRows` with explicit positions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(tables): dedupe upsert UPDATE path + extract nextAutoPosition
Two pure cleanups on the round-1 changes:
1. Extract `nextAutoPosition(trx, tableId)` — the `SELECT coalesce(max(
position), -1) + 1` pattern was repeated in three call sites
(`insertRow` auto branch, `batchInsertRows` auto branch, `upsertRow`
insert branch). One helper, same behavior.
2. Consolidate `upsertRow` update path. The initial-SELECT match and the
post-lock re-select match previously had two literal duplicates of the
same UPDATE + return block. Resolve `matchedRowId` first, then run one
UPDATE branch. Lock is still only acquired when we don't find a match
on the first pass.
No behavior change. 98/98 table unit tests unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(files): default sort by updated and add updated sort option
* feat(files): show Last Updated column
Matches the visible-column pattern already used on Knowledge and Tables
so users can see the value they're sorting by.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* improvement(repo): restructuring to make realtime image narrower scoped
* improvements
* chore(repo): rebase fixes and quality improvements for realtime split
Addresses merge-time issues and gaps from the realtime app split:
- Retarget stale vi.mock paths to @sim/workflow-persistence/subblocks
- Restore README branding, fix AGENTS.md script reference
- Restore TSDoc on workflow-persistence subblocks helpers
- Use toError() from @sim/utils/errors in save.ts
- Add vitest config + local mocks so @sim/audit tests run standalone
- Move socket.io-client to devDependencies in apps/realtime
- Add missing package COPY steps to docker/app.Dockerfile
- Add check:boundaries/check:realtime-prune scripts and wire into CI
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(security): consolidate crypto primitives into @sim/security
Move general-purpose crypto primitives out of apps/sim into the
@sim/security package so both apps/sim and apps/realtime can share them.
@sim/security exports (all pure, dependency-free):
./compare safeCompare (constant-time HMAC-wrapped equality)
./encryption encrypt/decrypt (AES-256-GCM, iv:cipher:tag format)
./hash sha256Hex
./tokens generateSecureToken (base64url)
Migrate apps/sim call sites to use these + @sim/utils helpers:
crypto.randomUUID() -> generateId() from @sim/utils/id
createHash('sha256').digest -> sha256Hex
timingSafeEqual on hashed hex -> safeCompare
new Promise(setTimeout) -> sleep from @sim/utils/helpers
No behavior change: encryption format, digest output, and token
length are preserved exactly.
* refactor(copilot): use toError in remaining otel/finalize sites
Replace the last two `error instanceof Error ? error : new Error(String(error))`
patterns with toError from @sim/utils/errors. Completes the sweep of clean
candidates — no behavior change.
* refactor(security): consolidate HMAC-SHA256 primitives into @sim/security
Adds hmacSha256Hex and hmacSha256Base64 to @sim/security/hmac and migrates
15 webhook providers plus 5 other hot paths (deployment token signing,
outbound webhook requests, workspace notification delivery, notification
test route, Shopify OAuth callback) off bare `createHmac` calls. Secret
parameter accepts `string | Buffer` to cover base64-decoded Svix-style
secrets (Resend) and MS Teams' HMAC scheme. AWS SigV4 signing in S3 and
Textract tools intentionally retains direct `createHmac` usage — its
multi-step key derivation chain doesn't fit a generic helper.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(packages): post-audit test + packaging polish
- Add safeCompare unit tests (identity, length mismatch, hex-nibble diff).
- Add Buffer-secret cases to hmac tests to lock in Svix/MS-Teams contract.
- Declare `reactflow` as a peerDependency on @sim/workflow-types — only used for type imports.
- Add a barrel export to @sim/workflow-persistence for consumers that prefer package-level imports; subpath exports retained.
- Document the data-field invariant in load.ts for loop/parallel subflow patching.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(realtime): address PR review feedback
- Remove redundant SOCKET_PORT=3002 env from Dockerfile runner stage
(env.PORT already defaults to 3002 via zod schema).
- Reorder PORT fallback so an explicitly-set SOCKET_PORT wins over
the schema default for PORT; keeps SOCKET_PORT functional as an
override instead of dead code.
- Add dedicated type-check CI step for @sim/realtime so TS errors
surface pre-deploy (the Dockerfile runs source TS via Bun and has
no implicit build-time type check).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(realtime): remove unused SOCKET_PORT env var
SOCKET_PORT has lived in the socket server since the June 2025 refactor
but was never actually set in any deploy config — docker-compose.prod,
helm values/templates, .env.example, and docs all use PORT or the 3002
default exclusively. No self-hoster was ever pointed at SOCKET_PORT, so
removing it is safe.
Simplifies realtime port resolution to `env.PORT` (zod-validated with a
3002 default) and drops the orphaned sim-side schema entry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(deps): bump drizzle-orm to 0.45.2 (GHSA-gpj5-g38j-94v9)
Resolves Dependabot alert #98. Drizzle ORM <0.45.2 improperly escaped
quoted SQL identifiers, allowing SQL injection via untrusted input
passed to APIs like sql.identifier() or .as().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(mcp): adopt native SDK types after @modelcontextprotocol/sdk 1.25.3 bump
Replace hand-written schema/annotation shapes with the SDK's exported
Tool, JSONRPCResultResponse, and Tool['annotations'] types so changes
upstream flow through automatically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(types): use drizzle $inferSelect for row types
Replace hand-written interfaces that duplicated schema shape with
typeof table.$inferSelect aliases for webhook, workflow, and
workspaceFiles rows. Also simplify metadata insert/update to use
.returning() instead of field-by-field copies.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(uploads): fall through to INSERT if restore-deleted row races a hard delete
If a hard delete races between the initial SELECT and the restore UPDATE,
.returning() yields no row. Previously the function would return undefined
and silently violate the Promise<FileMetadataRecord> contract. Now the
function falls through to the INSERT path, which already handles
uniqueness races via the 23505 catch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(uploads): align metadata.ts with global standards
Replace dynamic uuid import with generateId() per @sim/utils/id
convention, narrow the error catch off `any`, and convert the inline
comment to TSDoc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* improvement(enterprise): slack wizard UI, enterprise docs, data retention updates
* improvement(docs): add enterprise screenshots to sso, access-control, whitelabeling pages
* form
* fix(enterprise): address PR review — h-full for recently-deleted, shared SettingRow, toast UX, stale form fix, emcn tokens
* fix(whitelabeling): scope drop zone to thumbnail only, not full upload row
* fix(whitelabeling): remove drop image text from drag overlay
* fix(config): add DATA_RETENTION_ENABLED to env schema to fix build type error
* fix(testing): add isDataRetentionEnabled to feature flags mock
* improvement(docs): remove redundant requirements section from data-retention page
* improvement(docs): remove requirements sections from all enterprise doc pages
* improvement(docs): add screenshot to audit-logs page
* fix(data-retention): bypass enterprise gate when billing is disabled for self-hosted
* fix(billing): close TOCTOU race in subscription transfer, centralize stripe test mocks
* more mocks
* fix(testing): provide complete Stripe.Event defaults in createMockStripeEvent
* fix(testing): make dbChainMock .for('update') chainable with .limit()
* fix(billing): gate subscription transfer noop behind membership check
Previously the 'already belongs to this organization' early return fired
before the org/member lookups, letting any authenticated caller probe
sub-to-org pairings without being a member of the target org. Move the
noop check after the admin/owner verification so unauthorized callers
hit the 403 first.
* improvement(codebase): migrate tests to dbChainMock, extract react-query hooks
Migrate 97 test files to centralized dbChainMock/dbChainMockFns helpers from
@sim/testing — removes hoisted chain-wiring boilerplate.
Extend dbChainMock to cover insert/update/delete/transaction/execute patterns.
Extract useGitHubStars and useVoiceSettings react-query hooks from inline fetches.
Centralize additional mocks (authMockFns, hybridAuthMockFns) and update docs.
* fix(github-stars): centralize fallback via initialData, remove stale constants
Move the placeholder star count into useGitHubStars as initialData with
initialDataUpdatedAt: 0 so `data` is always a narrowed string while still
refetching on mount. Fixes two Bugbot issues: stale '25.8k' in chat.tsx
(vs '27.8k' in navbar) and empty-string return in fetchGitHubStars that
bypassed `??` fallbacks in consumers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(testing): wire dbChainMock.db to shared transaction and execute fns
dbChainMock.db.transaction was an inline vi.fn() separate from the exported
dbChainMockFns.transaction, so dbChainMockFns.transaction.mockResolvedValueOnce
and assertions silently targeted the wrong instance. dbChainMock.db also
omitted execute, so tests for any module that calls db.execute (logging-session,
table service, billing balance) would throw TypeError. Both mocks now reference
the module-level constants so overrides and resetDbChainMock affect the same fn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(chat,testing): memoize welcome message and add selectDistinct to dbChainMock.db
Why:
- Welcome ChatMessage was rebuilt inline each render, producing a fresh
timestamp and new array identity — cascading to ChatMessageContainer
and VoiceInterface props on every tick.
- dbChainMockFns exports selectDistinct/selectDistinctOn but the
dbChainMock.db object omitted them, so tests that stub those builders
hit undefined on the mocked module.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(chat): re-attach scroll listener once container mounts
The scroll effect's empty dep array meant it ran only on the first
render, when `chatConfig` is still loading and the component returns
`<ChatLoadingState />` — so `messagesContainerRef.current` was null and
the listener was never attached. Depend on the gating conditions that
control which tree renders, so the effect re-runs once the real
container is in the DOM (and re-attaches when toggling in/out of voice
mode).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(chat): reset chat state on identifier change via key prop
Keying `<ChatClient>` on `identifier` guarantees a full remount on
route transitions between chats, so `conversationId`, `messages`, and
every other piece of local state start fresh — no reset effect
required.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(billing): route scope by subscription referenceId, sync plan from Stripe, transfer storage on org join
Route every billing decision (usage limits, credits, storage, rate
limit, threshold billing, webhooks, UI permissions) through the
subscription's `referenceId` instead of plan-name heuristics. Fixes
the production state where a `pro_6000` subscription attached to an
organization was treated as personal Pro by display/edit code while
execution correctly enforced the org cap.
Scope
- Add `isOrgScopedSubscription(sub, userId)` (pure) and
`isSubscriptionOrgScoped(sub)` (async DB-backed) helpers. One is
used wherever a user perspective is available; the other in webhook
handlers that only have a subscription row.
- Replace plan-name scope checks in ~20 files: usage/limit readers,
credits balance + purchase, threshold billing, storage limits +
tracking, rate limiter, invoice + subscription webhooks, seat
management, membership join/leave, `switch-plan` admin gate,
admin credits/billing routes, copilot 402 handler, UI subscription
settings + permissions + sidebar indicator, React Query types.
Plan sync
- Add `syncSubscriptionPlan(subscriptionId, currentPlan, planFromStripe)`
called from `onSubscriptionComplete` and `onSubscriptionUpdate` so
the DB `plan` column heals on every Stripe event. Pro->Team upgrades
previously updated price, seats, and referenceId but left `plan`
stale — this is what produced the `pro_6000`-on-org row.
Priority + grace period
- `getHighestPrioritySubscription` now prefers org over personal
within each tier (Enterprise > Team > Pro, org > personal at each).
A user with a `cancelAtPeriodEnd` personal Pro who joins a paid org
routes pooled resources to the org through the grace window.
- `calculateSubscriptionOverage` personal-Pro branch reads user_stats
directly (bypassing priority) and bills only `proPeriodCostSnapshot`
when the user joined a paid org mid-cycle, so post-join org usage
isn't double-charged on the personal Pro's final invoice.
`resetUsageForSubscription` mirrors this: preserves
`currentPeriodCost` / `currentPeriodCopilotCost` when
`proPeriodCostSnapshot > 0` so the org's next cycle-close captures
post-join usage correctly.
Uniform base-price formula
- `basePrice × (seats ?? 1)` everywhere: `getOrgUsageLimit`,
`updateOrganizationUsageLimit`, `setUsageLimitForCredits`,
`calculateSubscriptionOverage`, threshold billing,
`syncSubscriptionUsageLimits`, `getOrganizationBillingData`.
Admin dashboard math now agrees with enforcement math.
Storage transfer on join
- Invitation-accept flow moves `user_stats.storageUsedBytes` into
`organization.storageUsedBytes` inside the same transaction when
the org is paid.
- `syncSubscriptionUsageLimits` runs a bulk-backfill version so
members who joined before this fix, or orgs that upgraded from
free to paid after members joined, get pulled into the org pool
on the next subscription event. Idempotent.
UX polish
- Copilot 402 handler differentiates personal-scoped ("increase your
usage limit") from org-scoped ("ask an owner or admin to raise the
limit") while keeping the `increase_limit` action code the parser
already understands.
- Duplicate-subscription error on team upgrade names the existing
plan via `getDisplayPlanName`.
- Invitation-accept invalidates subscription + organization React
Query caches before redirect so settings doesn't flash the user's
pre-join personal view.
Dead code removal
- Remove unused `calculateUserOverage`, and the following fields on
`SubscriptionBillingData` / `getSimplifiedBillingSummary` that no
consumer in the monorepo read: `basePrice`, `overageAmount`,
`totalProjected`, `tierCredits`, `basePriceCredits`,
`currentUsageCredits`, `overageAmountCredits`, `totalProjectedCredits`,
`usageLimitCredits`, `currentCredits`, `limitCredits`,
`lastPeriodCostCredits`, `lastPeriodCopilotCostCredits`,
`copilotCostCredits`, and the `organizationData` subobject. Add
`metadata: unknown` to match what the server returns.
Notes for the triggering customer
- The `pro_6000`-on-org row self-heals on the next Stripe event via
`syncSubscriptionPlan`. For the one known customer, a direct
UPDATE is sufficient:
`UPDATE subscription SET plan='team_6000' WHERE id='aq2...' AND plan='pro_6000'`.
Made-with: Cursor
* fix tests
* address more comments
* progress
* harden further
* outbox service
* address comments
* address comment on check
* simplify
* cleanup code
* minor improvement
* feat(workspaces): add workspace logo upload
* feat(workspaces): add workspace logo upload
* fix(workspaces): validate logoUrl accepts only paths or HTTPS URLs
* fix(workspaces): add admin authorization, audit log, and posthog event for workspace logo uploads
* lint
* fix: add WebP support and use refs pattern in useProfilePictureUpload
- Add image/webp to ACCEPTED_IMAGE_TYPES in useProfilePictureUpload
- Add image/webp to file input accept attributes in whitelabeling settings
- Refactor useProfilePictureUpload to use refs for onUpload, onError, and
currentImage callbacks, matching the established codebase pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore cloudwatch/cloudformation files from staging
These files were accidentally regressed during rebase conflict resolution,
reverting changes from #4027. Restoring to staging versions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add workspace_logo_uploaded to PostHogEventMap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: separate workspaceId ref sync to prevent overwrite on re-render
Split the ref sync useEffect so workspaceIdRef only updates when the
workspaceId prop changes, not when onUpload/onError callbacks get new
references. Prevents setTargetWorkspaceId from being overwritten by
a re-render before the file upload completes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use Pick type for workspace dropdown in knowledge header
The shared Workspace type requires ownerId and other fields that aren't
available from the workspaces API response mapping. Use a Pick type to
accurately represent the subset of fields actually constructed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace raw fetch with useWorkspacesQuery in knowledge header
Remove useState + useEffect + fetch anti-pattern for loading workspaces.
Use useWorkspacesQuery from React Query with inline filter for write/admin
permissions. Eliminates ~30 lines of manual state management, any casts,
and the Pick type workaround.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ee): add enterprise audit logs settings page with server-side search
Add a new audit logs page under enterprise settings that displays all
actions captured via recordAudit. Includes server-side search, resource
type filtering, date range selection, and cursor-based pagination.
- Add internal API route (app/api/audit-logs) with session auth
- Extract shared query logic (buildFilterConditions, buildOrgScopeCondition,
queryAuditLogs) into app/api/v1/audit-logs/query.ts
- Refactor v1 and admin audit log routes to use shared query module
- Add React Query hook with useInfiniteQuery and cursor pagination
- Add audit logs UI with debounced search, combobox filters, expandable rows
- Gate behind requiresHosted + requiresEnterprise navigation flags
- Place all enterprise audit log code in ee/audit-logs/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(ee): fix build error and address PR review comments
- Fix import path: @/lib/utils → @/lib/core/utils/cn
- Guard against empty orgMemberIds array in buildOrgScopeCondition
- Skip debounce effect on mount when search is already synced
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(ee): fix type error with unknown metadata in JSX expression
Use ternary instead of && chain to prevent unknown type from being
returned as ReactNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ee): align skeleton filter width with actual component layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* feat(audit): add audit logging for passwords, credentials, and schedules
- Add PASSWORD_RESET_REQUESTED audit on forget-password with user lookup
- Add CREDENTIAL_CREATED/UPDATED/DELETED audit on credential CRUD routes
with metadata (credentialType, providerId, updatedFields, envKey)
- Add SCHEDULE_CREATED audit on schedule creation with cron/timezone metadata
- Fix SCHEDULE_DELETED (was incorrectly using SCHEDULE_UPDATED for deletes)
- Enhance existing schedule update/disable/reactivate audit with structured
metadata (operation, updatedFields, sourceType, previousStatus)
- Add CREDENTIAL resource type and Credential filter option to audit logs UI
- Enhance password reset completed description with user email
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): align metadata with established recordAudit patterns
- Add actorName/actorEmail to all new credential and schedule audit calls
to match the established pattern (e.g., api-keys, byok-keys, knowledge)
- Add resourceId and resourceName to forget-password audit call
- Enhance forget-password description with user email
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): sync audit mock with new AuditAction and AuditResourceType entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(audit-logs): derive resource type filter from AuditResourceType
Instead of maintaining a separate hardcoded list, the filter dropdown
now derives its options directly from the AuditResourceType const object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(audit): enrich all recordAudit calls with structured metadata
- Move resource type filter options to ee/audit-logs/constants.ts
(derived from AuditResourceType, no separate list to maintain)
- Remove export from internal cursor helpers in query.ts
- Add 5 new AuditAction entries: BYOK_KEY_UPDATED, ENVIRONMENT_DELETED,
INVITATION_RESENT, WORKSPACE_UPDATED, ORG_INVITATION_RESENT
- Enrich ~80 recordAudit calls across the codebase with structured
metadata (knowledge bases, connectors, documents, workspaces, members,
invitations, workflows, deployments, templates, MCP servers, credential
sets, organizations, permission groups, files, tables, notifications,
copilot operations)
- Sync audit mock with all new entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): remove redundant metadata fields duplicating top-level audit fields
Remove metadata entries that duplicate resourceName, workspaceId, or
other top-level recordAudit fields. Also remove noisy fileNames arrays
from bulk document upload audits (kept fileCount).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): split audit types from server-only log module
Extract AuditAction, AuditResourceType, and their types into
lib/audit/types.ts (client-safe, no @sim/db dependency). The
server-only recordAudit stays in log.ts and re-exports the types
for backwards compatibility. constants.ts now imports from types.ts
directly, breaking the postgres -> tls client bundle chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): escape LIKE wildcards in audit log search query
Escape %, _, and \ characters in the search parameter before embedding
in the LIKE pattern to prevent unintended broad matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): use actual deletedCount in bulk API key revoke description
The description was using keys.length (requested count) instead of
deletedCount (actual count), which could differ if some keys didn't
exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit-logs): fix OAuth label displaying as "Oauth" in filter dropdown
ACRONYMS set stored 'OAuth' but lookup used toUpperCase() producing
'OAUTH' which never matched. Now store all acronyms uppercase and use
a display override map for special casing like OAuth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(enterprise): cloud whitelabeling for enterprise orgs
* fix(enterprise): scope enterprise plan check to target org in whitelabel PUT
* fix(enterprise): use isOrganizationOnEnterprisePlan for org-scoped enterprise check
* fix(enterprise): allow clearing whitelabel fields and guard against empty update result
* fix(enterprise): remove webp from logo accept attribute to match upload hook validation
* improvement(billing): use isBillingEnabled instead of isProd for plan gate bypasses
* fix(enterprise): show whitelabeling nav item when billing is enabled on non-hosted environments
* fix(enterprise): accept relative paths for logoUrl since upload API returns /api/files/serve/ paths
* fix(whitelabeling): prevent logo flash on refresh by hiding logo while branding loads
* fix(whitelabeling): wire hover color through CSS token on tertiary buttons
* fix(whitelabeling): show sim logo by default, only replace when org logo loads
* fix(whitelabeling): cache org logo url in localstorage to eliminate flash on repeat visits
* feat(whitelabeling): add wordmark support with drag/drop upload
* updated turbo
* fix(whitelabeling): defer localstorage read to effect to prevent hydration mismatch
* fix(whitelabeling): use layout effect for cache read to eliminate logo flash before paint
* fix(whitelabeling): cache theme css to eliminate color flash before org settings resolve
* fix(whitelabeling): deduplicate HEX_COLOR_REGEX into lib/branding and remove mutation from useCallback deps
* fix(whitelabeling): use cookie-based SSR cache to eliminate brand flash on all page loads
* fix(whitelabeling): use !orgSettings condition to fix SSR brand cache injection
React Query returns isLoading: false with data: undefined during SSR, so the
previous brandingLoading condition was always false on the server — initialCache
was never injected into brandConfig. Changing to !orgSettings correctly applies
the cookie cache both during SSR and while the client-side query loads, eliminating
the logo flash on hard refresh.
* fix(sso): default tokenEndpointAuthentication to client_secret_post
better-auth's SSO plugin does not URL-encode credentials before Base64
encoding in client_secret_basic mode (RFC 6749 §2.3.1). When the client
secret contains special characters (+, =, /), OIDC providers decode them
incorrectly, causing invalid_client errors.
Default to client_secret_post when tokenEndpointAuthentication is not
explicitly set to avoid this upstream encoding issue.
Fixes#3626
* fix(sso): use nullish coalescing and add env var for tokenEndpointAuthentication
- Use ?? instead of || for semantic correctness
- Add SSO_OIDC_TOKEN_ENDPOINT_AUTH env var so users can explicitly
set client_secret_basic when their provider requires it
* docs(sso): add SSO_OIDC_TOKEN_ENDPOINT_AUTH to script usage comment
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
* fix(sso): validate SSO_OIDC_TOKEN_ENDPOINT_AUTH env var value
Replace unsafe `as` type cast with runtime validation to ensure only
'client_secret_post' or 'client_secret_basic' are accepted. Invalid
values (typos, empty strings) now fall back to undefined, letting the
downstream ?? fallback apply correctly.
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
---------
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
* feat(folders): soft-delete folders and show in Recently Deleted
Folders are now soft-deleted (archived) instead of permanently removed,
matching the existing pattern for workflows, tables, and knowledge bases.
Users can restore folders from Settings > Recently Deleted.
- Add `archivedAt` column to `workflowFolder` schema with index
- Change folder deletion to set `archivedAt` instead of hard-delete
- Add folder restore endpoint (POST /api/folders/[id]/restore)
- Batch-restore all workflows inside restored folders in one transaction
- Add scope filter to GET /api/folders (active/archived)
- Add Folders tab to Recently Deleted settings page
- Update delete modal messaging for restorable items
- Change "This action cannot be undone" styling to muted text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): add FOLDER_RESTORED to audit mock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): atomic restore transaction and scope to folder-deleted workflows
Address two review findings:
- Wrap entire folder restore in a single DB transaction to prevent
partial state if any step fails
- Only restore workflows archived within 5s of the folder's archivedAt,
so individually-deleted workflows are not silently un-deleted
- Add folder_restored to PostHog event map
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(folders): simplify restore to remove hacky 5s time window
The 5-second time window for scoping which workflows to restore was
a fragile heuristic (magic number, race-prone, non-deterministic).
Restoring a folder now restores all archived workflows in it, matching
standard trash/recycle-bin behavior. Users can re-delete any workflow
they don't want after restore.
The single-transaction wrapping from the prior commit is kept — that
was a legitimate atomicity fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(db): regenerate folder soft-delete migration with drizzle-kit
Replace manually created migration with proper drizzle-kit generated
one that includes the snapshot file, fixing CI schema sync check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(db): fix migration metadata formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): scope restore to folder-deleted workflows via shared timestamp
Use a single timestamp across the entire folder deletion — folders,
workflows, schedules, webhooks, etc. all get the exact same archivedAt.
On restore, match workflows by exact archivedAt equality with the
folder's timestamp, so individually-deleted workflows are not
silently un-deleted.
- Add optional archivedAt to ArchiveWorkflowOptions (backwards-compatible)
- Pass shared timestamp through deleteFolderRecursively → archiveWorkflowsByIdsInWorkspace
- Filter restore with eq(workflow.archivedAt, folderArchivedAt) instead of isNotNull
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(workflows): clear folderId on restore when folder is archived or missing
When individually restoring a workflow from Recently Deleted, check if
its folder still exists and is active. If the folder is archived or
missing, clear folderId so the workflow appears at root instead of
being orphaned (invisible in sidebar).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): format restoreFolderRecursively call to satisfy biome
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): close remaining restore edge cases
Three issues caught by audit:
1. Child folder restore used isNotNull instead of timestamp matching,
so individually-deleted child folders would be incorrectly restored.
Now uses eq(archivedAt, folderArchivedAt) for both workflows AND
child folders — consistent and deterministic.
2. No workspace archived check — could restore a folder into an
archived workspace. Now checks getWorkspaceWithOwner, matching
the existing restoreWorkflow pattern.
3. Re-restoring an already-restored folder returned an error. Now
returns success with zero counts (idempotent).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): add archivedAt to optimistic folder creation objects
Ensures optimistic folder objects include archivedAt: null for
consistency with the database schema shape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): handle missing parent folder during restore reparenting
If the parent folder row no longer exists (not just archived), the
restored folder now correctly gets reparented to root instead of
retaining a dangling parentId reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(core): consolidate ID generation to prevent HTTP self-hosted crashes
crypto.randomUUID() requires a secure context (HTTPS) in browsers,
causing white-screen crashes on self-hosted HTTP deployments. This
replaces all direct usage of crypto.randomUUID(), nanoid, and the uuid
package with a central utility that falls back to crypto.getRandomValues()
which works in all contexts.
- Add generateId(), generateShortId(), isValidUuid() in @/lib/core/utils/uuid
- Replace crypto.randomUUID() imports across ~220 server + client files
- Replace nanoid imports with generateShortId()
- Replace uuid package validate with isValidUuid()
- Remove nanoid dependency from apps/sim and packages/testing
- Remove browser polyfill script from layout.tsx
- Update test mocks to target @/lib/core/utils/uuid
- Update CLAUDE.md, AGENTS.md, cursor rules, claude rules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update bunlock
* fix(core): remove UUID_REGEX shim, use isValidUuid directly
* fix(core): remove deprecated uuid mock helpers that use vi.doMock
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(knowledge): add Live sync option to KB connector modal for Max/Enterprise users
Adds a "Live" (every 5 min) sync frequency option gated to Max and Enterprise plan users.
Includes client-side badge + disabled state, shared sync intervals constant, and server-side
plan validation on both POST and PATCH connector routes.
* fix(knowledge): record embedding usage cost for KB document processing
Adds billing tracking to the KB embedding pipeline, which was previously
generating OpenAI API calls with no cost recorded. Token counts are now
captured from the actual API response and recorded via recordUsage after
successful embedding insertion. BYOK workspaces are excluded from billing.
Applies to all execution paths: direct, BullMQ, and Trigger.dev.
* fix(knowledge): simplify embedding billing — use calculateCost, return modelName
- Use calculateCost() from @/providers/utils instead of inline formula, consistent
with how LLM billing works throughout the platform
- Return modelName from GenerateEmbeddingsResult so billing uses the actual model
(handles custom Azure deployments) instead of a hardcoded fallback string
- Fix docs-chunker.ts empty-path fallback to satisfy full GenerateEmbeddingsResult type
* fix(knowledge): remove dev bypass from hasLiveSyncAccess
* chore(knowledge): rename sync-intervals to consts, fix stale TSDoc comment
* improvement(knowledge): extract MaxBadge component, capture billing config once per document
* fix(knowledge): add knowledge-base to usage_log_source enum, fix docs-chunker type
* fix(knowledge): generate migration for knowledge-base usage_log_source enum value
* fix(knowledge): add knowledge-base to usage_log_source enum via drizzle-kit
* fix(knowledge): fix search embedding test mocks, parallelize billing lookups
* fix(knowledge): warn when embedding model has no pricing entry
* fix(knowledge): call checkAndBillOverageThreshold after embedding usage