Skip to content

v0.9.20: project membership enforcement, db and perf improvements, realtime and auth hardening, CI and test fixes - #8963

Open
TheodoreSpeaks wants to merge 35 commits into
mainfrom
staging
Open

TheodoreSpeaks wants to merge 35 commits into
mainfrom
staging

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

icecrasher321 and others added 30 commits October 12, 2026 12:26
… 2026 (#8931)

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…s finalized (#8935)

The v2 sync execute path (executeWorkflowService) returned a completed
run while executeWorkflowCore was still writing its log and execution
state in the background, so a run-from-block naming that run as its
source could read no state and 404. The legacy execute route already
awaits waitForPostExecution before responding; do the same here.
* chore(db): drop retired storage whose readers have shipped

Drops tables and an index whose last readers and writers were removed in
already-released versions, makes chat.include_tool_calls NOT NULL, and
un-declares six dead columns ahead of a next-release DROP COLUMN.
embedding_keyword_tin is dropped by script migration 0031 so it runs after
0030 retires the triggers that still write it on skipped-release upgrades.

* fix(db): bound the chat lock in 0409 with a statement timeout
* fix(enrichments): verified-first waterfalls, billing fixes, and an error state for outages

- Work email: hold unverified hits (Hunter accept_all, Wiza risky,
  Dropcontact non-nominative) as a fallback while later providers look for a
  verified one; the held hit still fills the cell if none verifies. Ask
  Prospeo for verified emails only, keep personal addresses out of Wiza
  results, and move People Data Labs (unverified, $0.28 per hit) to the end,
  skipping it once a fallback is held. Order is otherwise unchanged.
- Phone: skip PDL on a bare name (not a valid match), pass the LinkedIn
  profile to PDL, prefer its mobile, and ask Prospeo for verified mobiles
  only (no billed person match without one).
- Email verification: unverified/error verdicts fall through; bill nothing
  for ZeroBounce unknown or MillionVerifier catch-all/unknown, which those
  vendors do not charge for.
- Table cells: when every provider errored, mark the cell errored instead of
  completing it blank, so an outage no longer silently finalizes empty rows.

* fix(enrichments): address review — blank outputs on an all-error run, LeadMagic verified check, toArray

* fix(enrichments): accept both Dropcontact verified-address labels
…16 service (#8938)

- readTestRedisUrl throws when CI is set and TEST_REDIS_URL is missing, so
  Redis-gated integration suites can no longer pass by skipping
- start the postgres:16 service only on integration (push, 1/4), the one job
  whose step uses it
- move the two env-gated smoke files that no workflow enables to *.live.test.ts
  and shrink the test-patterns baseline accordingly
… of 120k (#8939)

The durable-manifest test built 120,000 small rows (~16 MB). Compaction
walks every element (a promise, Object.entries and JSON.stringify per row
and per field), so the test ran 575-1522 ms on healthy CI runners and
10.6 s on a contended one, timing out. 64 rows whose payloads sum to
LARGE_VALUE_THRESHOLD_BYTES cross the same threshold with the same
assertions; the 9 MB string tests beside it run in ~50 ms.
…und trips the server budget (#8937)

* fix(e2e): connect the mobile suite to realtime and give dev-server round trips the server budget

- Start the realtime server for the mobile group. Without it every workspace page raised a
  persistent "Reconnecting..." toast ~2s after load; on the locked-workflow reload it raced the
  lock toast and, when it arrived second, sat in front of the Unlock Workflow action, so the tap
  timed out on "subtree intercepts pointer events"
- Wait 120s, the suite's budget for a next dev round trip, for the URL after a sidebar link tap,
  the Files list after it, and the table cell after a reload. next dev spent 16.4s (application
  code 70ms) serving the /home RSC payload and left the rows request unanswered past 20s
- Install the browsers while the app boots

* fix(e2e): stop the background browser install on exit and surface install and realtime logs on failure
…kes (#8940)

* fix(desktop-e2e): remove root causes of four retry-masked desktop flakes

- packaged-smoke: remove the profile only after every process that can write
  it (the app's process group, Crashpad's handler) is gone; SIGKILLing main
  left helpers creating files during rmSync (ENOTEMPTY)
- background-executor: answer shell dialogs by waiting for their window to
  close, since the dialog destroys itself before the click is acknowledged
- background-executor A: replace the 1.5 s p95 wall-clock threshold with a
  gated check that every call is claimed while chat B's first command still
  holds its queue, within Sim's 15 s pickup window, and that held leases renew
- driver: a click whose tab closes itself mid-dispatch (popup Allow ->
  window.close()) is a dispatched click, not a failed one, whichever of the
  close and the input acknowledgement lands first
- page-dialogs / browser-focus / browser-tools: print the tool error and the
  shown dialog id when they fail, to root-cause the remaining flakes

* fix(desktop-e2e): rescue only a click whose final release hit a tab that then closed

- cdp.clickAt raises TabClosedOnReleaseError only when the final mouseReleased
  was sent to a live tab and the tab is destroyed when it rejects; the driver
  rescues only that error (no string match, no pre-input/press/hold cases),
  with a note that does not claim the page closed itself and doNotRetry
- background-executor A: assert each call is claimed within Sim's 15 s pickup
  window measured from issue, and always release the gated commands

* fix(desktop): report a click whose tab closed mid-release as unconfirmed

The tab being destroyed proves teardown, not that Chromium processed the
release, so the rescued result no longer claims effectObserved and its note
says the outcome could not be confirmed. Adds clickAt tests for each
TabClosedOnReleaseError guard and a driver test for the rescued shape.
…conciliation in 2026 (#8932)

* feat(library): 6 Best AI Agents for Invoice Processing and Finance Reconciliation in 2026

* fix(library): clarify finance recommendations and enterprise features

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
Co-authored-by: Waleed Latif <walif6@gmail.com>
* improvement(changelog): expand release stories with practical detail

* improvement(changelog): cover release changes without padding
* chore(changelog): document twice-weekly editorial reviews

* docs(changelog): distinguish separate manual editions
* ci: scope pull request checks to what the diff can affect

A `changes` job classifies the pull request's files against explicit
allowlists (.github/scripts/ci-changes.sh, replacing
desktop-live-changes.sh):

- content posts, author profiles and their public assets: lint, test, build
- apps/docs: lint, test
- apps/desktop: lint, test, desktop-live
- agent guidance (.agents, .claude, .cursor, AGENTS.md, CLAUDE.md, root README): lint

Any other file, any .github change, an empty diff, or a diff that cannot
be worked out runs everything; pushes and dispatches always run
everything. The live desktop suite keeps its own skip list, now including
the content asset folders it previously missed.

`ci` accepts a skip only when `changes` set that job's flag to exactly
`false`, so a failed or skipped `changes`, or a lost output, fails it.

* ci: move the ci gate into a tested script and reject non-boolean change flags

The ci aggregator's skip rules now live in .github/scripts/ci-gate.sh, read from
NEEDS, with table-driven tests covering every guard. The change-classifier test
helper fails on any flag value other than exactly true or false.
…tion foreign keys (#8943)

Inserts into embedding could stall for seconds merging the shared GIN pending
list in the foreground, while holding the document row lock. Turn fastupdate
off (the 0364 pattern) and drain the existing list once.

User and workspace deletion ran sequential scans for four referential actions
inside the deleting transaction, holding row locks other writers queued behind.
Add concurrent indexes backing document.uploaded_by,
user_table_rows.created_by, user_table_definitions.created_by and
execution_large_values.workspace_id.
* improvement(perf): bound trash, file-path and attachment reads

* fix(recently-deleted): cast chat ids in the trash union, keep loaded pages on errors
…ts (#8944)

* improvement(rate-limit): admit provider requests in one statement on the database bucket

Lock, refill, decide and spend every dimension in a single CTE so a hot
bucket row stays locked for one statement plus the commit instead of a
round trip per dimension. The clock is read before the statement, matching
the Redis backend, so a contended admission never sees more tokens.

* improvement(connectors): skip the live document count on unfinished sync slices

An unfinished content pass keeps the count its last finished pass recorded,
the pass lastSyncAt still names. Connectors with no recorded count are still
counted so a first pass shows progress.

* fix(rate-limit): judge atomic admission at the moment the bucket locks are held

Advance the caller's clock by the lock wait measured inside the statement, so
a contended admission refills the time it spent waiting as it did before the
single-statement change. Cover the spend rollback when the deadline passes or
the caller aborts while the statement runs.

* fix(rate-limit): count only the lock wait in the atomic admission clock

Sample clock_timestamp() before the first row can reach the lock and again
after the last is held, so an uncontended admission decides at exactly the
caller's clock and planning or execution time never shifts a refill.

* fix(connectors): keep counting every slice until a connector's first pass finishes

A first pass's own unfinished slices wrote a count, so later slices of that
pass saw it and stopped counting. Skip only once lastSyncAt shows a finished
pass, which unfinished slices never advance.
…er (#8948)

* ci: run the mobile e2e suite on the shared production build, one job per browser

The build job now packages .next (zstd, cache excluded) and uploads it for a
day. A new e2e-mobile job, one per browser (chromium, webkit), extracts it and
serves it with `next start`, so the suite no longer waits on dev compiles or
runs both browsers back to back. The mobile group leaves the e2e matrix.

On runs that execute e2e, the build bakes NEXT_PUBLIC_APP_URL=http://localhost:3000,
which is what production images bake, so the served build is the one that ships.
Content-only pull requests keep the hosted value, under which next build
prerenders every post.

http-e2e.sh gains a `next start` mode used only by mobile: it installs only the
selected browser, drops the dev heap override, and rebuilds the HTTP status log
from RouteHandler's JSON lines at LOG_LEVEL=INFO. The ci gate treats e2e-mobile
like e2e: a skip passes only when `full` is exactly false.

* ci: build mobile's artifact in its own job and keep the hosted build untouched

The `build` job is back to staging byte for byte: its www.sim.ai build is CI's
only render of every post, and it must stay on every run. A new `e2e-build`
job, gated on `full` with its own Turbo cache key, builds with
NEXT_PUBLIC_APP_URL=http://localhost:3000 (the Dockerfile default, so the same
public-env mode production images use; images run the standalone server) and
packages and uploads it for e2e-mobile, now kept 3 days and without
.next/trace.

e2e-mobile fails fast when the downloaded build prerendered posts, which only a
hosted build does; the URL is no marker, since next.config's redirects name
www.sim.ai in every build. The ci gate treats e2e-build like e2e.

* ci: key the non-hosted guard on prerendered post pages, not every /blog/ route

* ci: expect the socket-token rate limit in the mobile e2e suite

Each document load mints one realtime token from a route capped at 30/min
per IP. On the production build the suite loads well over 30 documents a
minute from loopback, so the route's designed 429 is recorded apart from
httpErrors (only with Retry-After), and each browser must still mint at
least once.
…8590)

* feat(projects): add project identity and lifecycle foundation

* feat(projects): create projects with their initial environment

* docs(projects): record project files follow-up

* docs(projects): explain project and workspace creation flows

* fix(projects): stage activation after compatible writers deploy

* feat(projects): enforce membership after the staged backfill

* fix(projects): retry writes after concurrent membership changes

* fix(projects): align integration fixtures with membership constraints

* refactor(projects): prepare compatible writers for the SQL backfill

* refactor(projects): backfill through bounded SQL migration batches

* fix(projects): clean up automatically created fixture Projects

* fix(projects): backfill only families missing membership

* fix(projects): bound the trigger installation lock window

* fix(workflows): guard restore against concurrent workspace archive

* fix(projects): keep workflow lifecycle guards at workspace scope

* fix(workflows): guard restore against concurrent workspace archive

* fix(projects): close lifecycle races and surface rollout conflicts

* fix(workflows): return not found when import loses archive race

* fix(projects): tighten rollout checks and deduplicate lifecycle validation

* test(projects): detect missing lifecycle validation calls

* test(projects): verify concurrent lifecycle lock contention

* test(projects): update new workspace fixtures for enforcement

* fix(tests): create Project membership in desktop and CLI fixtures

* fix(tests): restore Project enforcement fixtures after migration renumbering

* fix(tests): scope migration probes and align remaining Project fixtures

* feat(projects): prepare workspace project column compatibility

* test(projects): clean up compatibility fixtures atomically

* feat(projects): move membership to workspace column with rollout bridge

* test(projects): align transfer mocks with workspace membership

* test(projects): provision required membership in transfer unit fixtures

* improvement(workspaces): present API responses explicitly

* feat(projects): prepare legacy membership with a resumable operator tool

* fix(projects): support PostgreSQL 16 column migration

* fix(projects): allow multi-batch copies to finish

* fix(projects): support PostgreSQL 16 preparation and current fixtures

* fix(projects): reconcile memberships during schema push

* fix(projects): enforce safe schema push provisioning

* test(projects): provision newly merged workspace fixtures

* fix(projects): use the direct connection for push reconciliation

* test(billing): wait for parked transactions before racing writers

* test(billing): freeze the clock for exact deadline assertions

* fix(projects): close operator verification and contention gaps

* test(billing): stabilize member ledger timeout checks

* fix(projects): await cleanup and bound backfill artifacts

* fix(projects): honor operator cancellation and status exits

* fix(projects): reject verification of conflicted plans

* fix(projects): defer archive repair on database contention

* fix(projects): coordinate read-only maintenance commands

* ci(projects): use the standard PostgreSQL test coverage

* fix(projects): support trigger-free column rollout

* fix(projects): run bounded membership backfill before enforcement

* test(projects): verify column-only archive repair assignment

* fix(projects): retain healthy indexes during schema push

* ci(projects): use standard integration coverage for enforcement

* fix(projects): correct rollout fixtures and schema-push index

* fix(projects): reconcile assigned environments before enforcement

* fix(projects): preserve backfill repair and resume guarantees

Lock and verify effective legacy memberships before materializing reviewed
assignments and applying the shared detach policy. Reject resume reports
whose code hash differs before changing their checkpoint. Isolate local
pubsub subscriber failures so healthy archive subscribers still receive
notifications.

Extend existing CLI and real-database integration coverage for mixed
memberships, concurrent and stale connector changes, unchanged mismatched
reports, replay, and local subscriber delivery.

* test(projects): align fixture lifecycle with enforced project ownership

* test(projects): skip archive repair suite when Redis is unset

* chore(projects): normalize enforcement SQL whitespace

* feat(projects): commit membership authority before reconciliation

* fix(projects): switch membership authority after compatible rollout

* test(projects): guard optional authority fixtures explicitly

* test(projects): seed authority in billing lock-order mock

* fix(projects): bound authority barriers and verify push replay

* fix(projects): pin operator schema and pace detach repairs

* docs(projects): record native constraint rollout decisions

* refactor(projects): replace integrity triggers with native foreign keys

* fix(projects): recognize completed rollout without its marker

* fix(projects): retire rollout marker with legacy membership

* test(projects): model expansion authority in workspace move fixtures

* fix(projects): keep repair reads compatible with expansion schema

* fix(projects): retain project name for workspace archival

* docs(projects): consolidate permanent storage rules in README

* fix(tests): seed Project membership in new staging fixtures

* Update packages/db/scripts/reconcile-project-membership.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Fails on webkit/tablet with a strict-mode violation: '.workspace-content-shell'
resolves to two elements (previous Logs page still mounted) after navigating
to the workflow. Phone keeps running it.


Claude-Session: https://claude.ai/code/session_01UEUUMctneYa5p25wTg5b57

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ves (#8951)

* Revert "ci(e2e): temporarily skip tablet workflow-controls mobile check (#8950)"

This reverts commit 23b0561.

* fix(e2e): read the visible workspace shell while a streamed page arrives

On the production build, a query made mid-stream can also match the next
segment's shell that React parks in a hidden <div id="S:n"> until it swaps it
in, so the class-only locator hit a strict-mode violation in WebKit. Scope the
shell to the visible element, and restore the tablet workflow-controls check
#8950 skipped for the same cause.
…etry-After (#8952)

* fix(realtime): end the socket attempt when the token mint fails

The Socket.IO auth callback never invoked cb when POST /api/auth/socket-token
failed (429, 5xx, network), so the open transport sat until the realtime
server's 45 s connectTimeout while the UI showed Reconnecting, and Retry-After
was ignored. The client `timeout` cannot help: the Manager clears it when the
transport opens, and resets its backoff on every open, so each retry ran at
the 1 s floor.

On a retryable mint failure, disconnect the socket and reconnect it on the
existing auth backoff (1 s to 30 s, matching the reconnection config), never
before the response's Retry-After. 401 keeps its cb({ token: null }) path.

* fix(realtime): clear connected state and jitter Retry-After on token retry

disconnect() on a socket that has not connected yet emits no 'disconnect'
event. After a native reconnect is followed by a failed token mint,
isConnected stayed true for the whole outage. Clear it, and the socket id,
exactly as the 'disconnect' handler does.

Spread a Retry-After-driven delay by up to +20% above the floor, so tabs
behind one IP do not retry in the same second.

* fix(realtime): tighten the token-retry comment

* fix(realtime): ignore token failures from a superseded handshake and report persistent token outages
The seven suites excluded from the integration run since 2026-09-25 failed against a
freshly provisioned database (fixtures predate connector and credential changes) and never
ran in CI. Delete them, the quarantine list in vitest.config.ts, their explicit-any baseline
entries, and getCredentialGroupOAuthContext (plus its @sim/testing mock), whose only caller
was the GitHub member suite. The testing rule now says to fix or delete a failing suite
rather than exclude it.
* refactor(knowledge): share the processing checkpoint store

OCR and embedding checkpoints each carried their own copy of the framed
header + body format, the outbox cleanup reservation and the cleanup
handler factory. Move them into processing-checkpoint-store so a third
checkpoint kind can reuse them.

* feat(knowledge): index Confluence image attachments with a vision model

Confluence images (PNG, JPEG, WebP, GIF) were dropped at listing, so
tables, scans and screenshots attached to pages never reached search.

- Connectors declare an image policy; Confluence reads images with a
  dedicated Gemini adapter that returns the image's text, every other
  source keeps OCR unchanged.
- A new Image Attachments setting on the Confluence connector, on by
  default, so existing knowledge bases pick images up on their next sync.
- Decorative images (logos, icons, images under 64 px) complete with no
  chunks; files under 2 KB and SVGs are never listed.
- Each sync admits at most KB_IMAGE_EXTRACTIONS_PER_SYNC new images
  (default 500); the rest wait for the next sync, which reports partial.
- A vision checkpoint lets a retried pass reuse the extraction instead
  of paying for a second call; usage is billed per image.
- Completing a pass now always replaces old embeddings, so a document
  that re-indexes to zero chunks drops its stale ones.
- sharp is added to the Trigger.dev bundle for image preparation.

* feat(knowledge): explain image documents with nothing to index

A completed image with no chunks shows an empty state instead of bare
table headers, and the connector and self-hosting docs cover image
attachments and the two new environment variables.

* fix(knowledge): bound image re-reads and handle image opt-out directly

Review follow-ups for Confluence image indexing.

- The per-sync cap now counts changed images as well as new ones, and a
  member sync shares one budget across its members. Held-back images
  that already exist are still marked seen, so they are never reconciled
  away.
- A full resync no longer re-reads unchanged images; their bytes are
  pinned to the attachment version.
- Without any Gemini key, images are left out of the sync instead of
  becoming failed documents.
- Turning Image Attachments off removes the indexed images on the next
  sync, outside the soft-delete and mass-deletion hold meant for source
  deletions.
- Gemini errors: a response cut off at the token limit, a rejected key
  or region, and an unknown model fail permanently with an accurate
  message instead of retrying or blaming the image; an image whose pixel
  data cannot be decoded fails as invalid.
- The admission wait keeps the post-call time reserve, Retry-After uses
  the shared parser, and a configured model must be a catalog Gemini
  model so its pricing is known.

* fix(knowledge): make image indexing opt-in for existing Confluence connectors

- New connectors are created with Image Attachments on, through a
  server-side defaultSourceConfig on the connector meta that every create
  surface applies; an unset value now means off, so existing connectors
  only read images once their owner turns the setting on.
- The Gemini call retries transient server and network errors within the
  extraction's time budget; throttling still defers the pass.
- Gemini error bodies are reduced to their status and reason codes.
- Images under the byte floor complete as decorative even when the source
  reported no size, and the extraction model must be a generateContent
  Gemini model.
- A member sync that held images back forces a full listing next time,
  the image count survives a listing restart and is saved per batch, and
  turning images off removes them for member-credential connectors too.
- The disabled-image removal skips rows the delete would refuse.
- An integration suite covers the cap, held-back images staying indexed,
  full resync, opt-out removal, opt-in for existing connectors, zero-chunk
  re-indexing and the no-key hold-back against real PostgreSQL; the OCR
  input-failure suite now uses a source that still sends images to OCR.

* fix(knowledge): retry malformed extractions in-call and clean up images on every member run

- A Gemini reply that is not valid JSON is retried within the extraction's
  budget and fails permanently once the attempts are spent, instead of
  costing a whole document retry each time.
- A member sync that skips its content pass (content not due yet) still
  removes images once Image Attachments is turned off.
- The reconciliation integration suite runs with images on, so it keeps
  exercising reconciliation alone.

* fix(knowledge): count image removals in member runs and bill every extraction attempt

- Member runs add the lifecycle's purges to the run's deletion counts
  instead of overwriting them, so image removals after Image Attachments
  is turned off are reported and logged as purges.
- A retried extraction reports the tokens of every attempt Gemini
  answered, since each one was billed.
- The checkpoint header check uses isRecordLike.
- The create-time default is tested through the real creation path in
  the integration suite, replacing a mock-only unit test.

* fix(knowledge): resume a member run whose image removal hit its deadline

An unfinished disabled-image removal now keeps the member run marked as
having work remaining, so the next run starts promptly instead of waiting
a full sync interval with images still searchable.

* refactor(knowledge): declare image extraction once and remove images only when queued

- Connectors declare image extraction once on their meta
  (`imageExtraction: { settingId }`), read by the shared
  `isImageExtractionEnabled`; drops `ConnectorConfig.imageExtractionEnabled`
  and the Confluence-only helper.
- Turning the image setting off sets `knowledge_connector.image_removal_pending`
  (migration 0412); the next sync removes the indexed images and clears it, so
  ordinary syncs no longer scan a connector's documents for images.
- Gemini specifics (key, Gemini 3 model resolution, error classification) live
  in the adapter. Requests send `thinkingLevel: LOW`, and any finish reason other
  than STOP fails without paid retries.
- Image reads get their own admission budget (`vision`,
  KB_CONFIG_VISION_REQUESTS_PER_MINUTE, default 300) instead of sharing OCR's.
- One zod schema validates the reply and generates the request's JSON schema.
- The prompt is source-neutral and treats text in an image as content, never
  instructions; sharp loads lazily so it can only fail image documents.
- Full relisting after held-back work is one named rule
  (`noticeRequiresFullListing`).

* fix(knowledge): keep op narrowing in processDocOps and clean up Project fixtures

* refactor(knowledge): tighten image extraction after a full review

- Every Gemini attempt takes its own admission, and 429/503 records a provider
  cooldown for the key; the retry delay is read from the body's RetryInfo when
  no Retry-After header is sent.
- The image is base64-encoded once per extraction, rotated upright even when it
  already fits, and flattened onto white before a JPEG fallback.
- Cost goes through calculateBillableModelCost; both indexing charges share one
  recordIndexingUsage helper.
- Reconciliation waits until queued image removal finishes, and the removal walks
  by id instead of rescanning from the start.
- The per-sync image cap is at least 1, so a sync can always finish.
- canExtractImages lives with the extractor; isSemanticImage treats an undeclared
  policy as OCR, so callers drop their fallbacks.
- Tests: unique embedding vectors, restored pool keys, waits for dispatched syncs,
  fake timers in finally, native noise fixtures; drops mock-only assertions.

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>
The collapsed toast stack paints newest-first and shows only the front
card whole; back cards are clamped to the front card's height and peek
13px above it. A persistent status toast raised after an actionable one
("Reconnecting..." / "Joining workflow..." after "This workflow is
locked") therefore covered the action, and on touch the first tap only
expanded the stack.

Order the stack so persistent toasts without an action sit behind every
other toast; everything else stays newest-first.
…ope (#8957)

Real-Postgres integration suite for the member sync's scope-unavailable branch
(member-sync-engine listForMember): a provider answer classified as a lost
scope is an authoritative, complete, empty listing that withdraws only that
member's observations on that connector, while a 503 or timeout keeps access.
Asserts on document ACL rows, observation rows, and search visibility. Each
document gets a unique one-hot embedding (index < 512) and is searched with its
own vector, so visibility never depends on HNSW ties or ranking.
)

* fix(auth): rate-limit socket-token mints per user, keep a generous IP backstop

The 30/min budget was keyed on client IP and checked before the session,
so an office behind one NAT address (or every tab reconnecting after a
deploy) shared a single budget and throttled each other.

The same 30 burst / 30 per minute now applies per session user, after a
cookie-cached session read; the mint still re-reads the session from the
database. A 300/min IP backstop stays in front of everything so
unauthenticated traffic is rejected before any session lookup. 401 and
429 + Retry-After responses are unchanged.

* docs(realtime): the Retry-After jitter spreads any tabs sharing a 429 window

* fix(auth): give the socket-token IP backstop its own bucket and pin both budgets in tests
* chore(tests): ratchet real wall-clock waits in unit tests (check:test-patterns real-wait)

New `real-wait` rule in scripts/check-test-patterns.ts flags a unit test that
waits on the wall clock with no fake timers in scope:

- `sleep`/`interruptibleSleep` resolved through their `@sim/utils/helpers`
  import binding (aliases included; skipped when the file mocks that module),
- `setTimeout` from `node:timers/promises`,
- `new Promise((resolve) => setTimeout(resolve, ms))` and variants whose timer
  callback resolves the promise (reject-only timeout guards are not waits).

Zero or omitted delays are macrotask flushes and are not flagged. Fake timers
cover a wait when an earlier `vi.useFakeTimers()` in an enclosing function or
the module, or a `beforeEach`/`beforeAll` of an enclosing describe or the
module, fakes `setTimeout` (`toFake: ['Date']` and `shouldAdvanceTime: true`
do not). Integration and *.live.test.ts files are exempt: their waits are on
real services. Each occurrence is a numbered entry, capping the count per file.

`--update --init-rule <rule>` admits the one-time baseline for a rule the
baseline does not track yet; `--update` stays shrink-only. Baseline: 121
real-wait entries across 46 files (72 -> 193 total).

* fix(tests): keep a real-wait flagged before the fake-timer install in its own hook

A beforeEach/beforeAll that installs fake timers covered its whole suite,
including waits earlier in the same hook body, which run on the real clock.
Only waits after the install in that hook are exempt now; tests and other
hooks in the suite keep the exemption.

* fix(tests): make real-wait's fake-timer exemption exact and drop --init-rule

- Remove `--update --init-rule`: it admitted entries for any rule with no
  remaining baseline entries. `--update` is strictly shrink-only again; the
  real-wait entries enter once, in this PR's baseline diff.
- `vi.useRealTimers()` ends the exemption from where it runs, including a
  nested suite restoring real timers after an outer hook faked them.
- A clock installed in `beforeEach` no longer covers `beforeAll`/`afterAll`
  or earlier sibling `beforeEach` hooks; `beforeAll` no longer covers earlier
  sibling `beforeAll` hooks.
- Only `vi.useFakeTimers()` with no config, or a literal config proven to fake
  setTimeout without shouldAdvanceTime, exempts a wait.
- Namespace imports (`helpers.sleep`, `timers.setTimeout`) are flagged.

* chore(tests): make the wait-export table read-only

* fix(tests): let a module-scope fake-timer install cover helper functions declared above it

Functions run only after the module loads, so a module-scope vi.useFakeTimers()
(or vi.useRealTimers()) applies to every function body regardless of source
order; module-scope waits still follow source order.

* fix(tests): exempt helpers from a module-scope fake-timer install only when nothing ran before it

A helper called at module scope before vi.useFakeTimers() waits on the real
clock. A module-scope install now reaches function bodies only when every
earlier module-scope statement is load-safe (imports, function/class
declarations, type-only statements, vi.mock/doMock/hoisted, and variables
whose initializers call nothing); otherwise source order applies. A
module-scope vi.useRealTimers() still reaches every function body.

* fix(tests): skip only helpers called before a module-scope fake-timer install

Any unrelated module-scope call (const run = vi.fn()) used to drop the
helper exemption. Now a module-scope vi.useFakeTimers() covers every function
body except a helper (function declaration or variable bound to a function)
that earlier module-scope code calls directly by name; calls through other
helpers are not traced. WAIT_EXPORTS and HOOK_KINDS are as const.

* fix(tests): recognize module-scope helpers whose initializer is wrapped in a type assertion

helperName now unwraps as/satisfies/non-null/parenthesized initializers with
the shared unwrap helper, so a wrapped arrow called before a module-scope
vi.useFakeTimers() stays flagged.
* ci: ratchet type errors in apps/sim unit tests per file

apps/sim's tsconfig excludes *.test.ts(x), so ~3.4k test files are never
type-checked and any new test can add errors. check:test-types runs the
native tsc on apps/sim/tsconfig.tests.json (the app config with tests left
in), counts errors per file, and compares with a shrink-only baseline,
matching check:explicit-any's semantics: a rise or a new file fails, a drop
fails until --update shrinks it, --update never raises, --update --init only
creates a missing baseline. Output it cannot place (global diagnostics, a
crash) fails outright instead of reading as fewer errors.

It type-checks the whole app, so it runs in its own test-types job gated
like test (wired into ci-gate.sh), not in check:audits. On failure the job
uploads counts.json and the compiler output; --counts <file> lets the
baseline be bootstrapped or shrunk from that artifact without a local
compile.

* ci: commit the test type-error baseline from CI and drop the one-time --init path

Baseline from run 38342772080's test-types counts: 3209 errors in 874 files.
--update stays shrink-only; a missing baseline is restored from git.

* ci: ratchet test type errors per file and TS code, with command-level tests

A per-file count let one new error replace a fixed one with the total flat.
The baseline is now { file: { TSnnnn: count } }: any (file, code) count
that rises fails, including a new code in a baselined file, and --update
stays shrink-only per pair. The key stops at the code: line and column
move with edits, and messages print types that change with unrelated
source or compiler versions.

--diagnostics <file> replaces --counts: it reads a saved compiler output
(the CI artifact's diagnostics.txt) through the same parser and undercount
checks, so a fix can shrink the baseline without a local compile. The
baseline was migrated from run 38342772080's diagnostics.txt with the
script's parser: 3209 errors in 874 files across 52 codes, every per-file
total equal to the previous baseline's.

Command-level tests run the real script on fixture diagnostics: unchanged,
a rising count, a new code replacing a fixed error, a new file, a dropped
count, --update refusing a raise, --update shrinking, and unparseable or
inconsistent output. The checks.yml comment now describes the artifact as
input for shrinking the existing baseline.

* ci: verify saved test-type diagnostics against the compile's exit status

--diagnostics read an empty or truncated diagnostics.txt as fewer errors,
so a failed compile's artifact plus --update could erase the baseline.
--report now writes status.json (exit code and printed error count) once
the compile's output and exit code agree, and --diagnostics requires it:
exit 0 must mean no errors, the parse must match the recorded count, and
a missing or malformed status fails. Command-level tests cover each case
and that --update never writes the baseline from such input.

Also passes a route context to the socket-token route test's POST call,
the one new test type error (TS2554) the ratchet caught after #8958.

* ci: read the saved compiler status through toRecord
…8960)

* fix(ui): simplify responsive layouts and compact workflow controls

* fix(ui): align mobile breakpoints and preserve compact widths

* fix(test): sample responsive resize geometry atomically
@TheodoreSpeaks
TheodoreSpeaks requested a review from a team as a code owner October 13, 2026 05:24
@vercel

vercel Bot commented Oct 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
docs Skipped Skipped Oct 13, 2026 6:43am UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 387 files

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/hooks/queries/recently-deleted.ts
Comment thread apps/sim/connectors/confluence/meta.ts
Comment thread apps/sim/lib/knowledge/documents/vision-checkpoints.ts
Comment thread apps/sim/app/api/workspaces/[id]/recently-deleted/route.ts
@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[High impact] Adds database migration validation and restructures CI job gating.

Fix the lost billing for failed image reads before merging.

Findings

  1. P1 Failed image reads lose charges ▶
  2. P2 Disabled images return to search ▶

Summary

This release enforces Project membership, retires old storage, adds Confluence image indexing, and bounds several large reads. It also changes realtime retries, responsive controls, and CI coverage.

  • Workspaces keep their Project membership directly on each workspace.
  • Recently Deleted searches and pages all supported resources together.
  • Confluence connectors can index image attachments.
  • Socket-token requests use user limits and prompt retry timing.
  • Rate-limit admission checks and spends buckets in one database statement.
  • CI selects checks by changed files and adds test-code guardrails.
  • Workflow controls and workspace panels adapt to smaller screens.

Intentional changes noted: TheodoreSpeaks explicitly lists removal of the Project image-digest migration gate and quarantined integration suites in the PR description.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Confluence image] --> B[Gemini returns reply]
  B --> C[Count paid tokens]
  C --> D{Reply passes checks?}
  D -->|Yes| E[Save extraction checkpoint]
  E --> F[Index document]
  F --> G[Record vision usage]
  D -->|No| H[Fail document without recording usage]
Loading

Reviews (1) · Last reviewed commit: "fix(ui): simplify responsive layouts and..." · Reviewed by Greptile

Comment thread apps/sim/lib/knowledge/documents/image-extraction.ts
Comment thread apps/sim/lib/knowledge/connectors/sync-content-pass.ts
mzxchandra and others added 2 commits October 12, 2026 23:00
* fix(db): reduce Project backfill round trips and scheduling limits

* fix(db): repair archived workspace workflows during Project backfill

* fix(db): retry busy Project archive repairs
* fix(lifecycle): retain shared resources when creators leave

* fix(lifecycle): preserve replacement workspace grants

* fix(lifecycle): preserve upload history during creator handoff

* fix(lifecycle): preserve empty upload attribution through handoff

* fix(lifecycle): order workspace locks before file mutations

* fix(admin): use renamed member not-found response

* fix(lifecycle): acquire organization fences before workspace handoff

* fix(lifecycle): lock resource workspaces before account erasure barrier

* fix(lifecycle): refuse erasure while creator references are in flight

* fix(lifecycle): lock Project workspaces before successor grants

* fix(lifecycle): make shared file contexts readonly
…ges off while disabled (#8965)

- Vision checkpoints include the filename the extraction prompt names, so a rename never reuses text extracted under the old title
- Restoring an excluded connector image is rejected while its image attachments are off; the setting check runs after the document update in the same transaction under a connector share lock, matching sync's lock order

This branch was previously deployed

1 inactive deployment
Preview — 831d11d2 Deployed Oct 13, 2026 by vercel[bot]
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.

4 participants