Skip to content

chore(search): make indexed org search dormant behind a single gate and add DB recovery tooling - #8243

Open
waleedlatif1 wants to merge 6 commits into
stagingfrom
chore/dormant-indexed-org-search
Open

waleedlatif1 wants to merge 6 commits into
stagingfrom
chore/dormant-indexed-org-search

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Org Sim Search is live-only; the indexed implementation stays in the repo but dormant, moved to lib/sim-search/indexed/ (history kept) with a README covering what it is, the gate, re-enable steps, and the DB objects it depends on
  • One gate: isIndexedOrgSearchEnabled() (indexed/gate.ts, inverse of SIM_SEARCH_LIVE); new check:indexed-org-search-boundary audit fails on any import into the dormant module outside a small allowlist of gated entry points
  • Closes the gaps where indexed work still ran while dormant:
    • searching a search-index knowledge base by id returns 409 "This search index is inactive; use Sim Search." (internal, Knowledge block, v1, v2) before any billing or embedding
    • projector fill skips search-index rows and writes no Tin keyword rows
    • indexed org document page 404s
  • Hardens the projection-filled probe: capped at 250 ms of the leg budget and a failed answer is remembered briefly, so a slow probe can no longer consume a whole search's deadline
  • Adds apps/sim/scripts/dormant-org-search/ runbook + scripts (dry-run by default) to recover DB health: drop Tin triggers and truncate the Tin projection (with restore), guarded resumable batched deletion of a search-index knowledge base's connector documents (storage cleanup via the app's outbox), then reindex/vacuum
  • Workspace knowledge bases and live search behave exactly as before; shared projection, members-mode connector, and access code stay in place

Type of Change

  • Improvement

Testing

New unit tests for the gate, 409 paths, projector skips, probe cap, audit script, and ops script guards (each fails on the old code); knowledge/sim-search/mothership/v1/v2/org suites, packages/db, type-check, lint, check:audits (50) pass. Ops scripts exercised end-to-end against a throwaway local Postgres only.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Live Search is the only active Sim Search backend. The indexed backend
(retrieval over is_search_index knowledge bases) stays in the tree for a
possible re-enable, so it now lives in one dormant module behind one switch.

- Move the exclusively-indexed code into apps/sim/lib/sim-search/indexed/
  with git mv: scoped owner search, indexed document reads, Tin keyword
  ranking, and the projection-fill probe. Shared code (projections,
  projector, member/ACL connector machinery, source overview/setup, prewarm)
  stays where it is. README documents the dormant state, the gate,
  re-enable steps, and the DB objects it depends on.
- Add isIndexedOrgSearchEnabled() in indexed/gate.ts (the inverse of
  SIM_SEARCH_LIVE) and use it at every indexed branch: the internal search
  route, MCP tools, Sim's search/read tools, the indexed document page,
  shared retrieval, and connector indexing policy.
- Add check:indexed-org-search-boundary (runs in check:audits): outside the
  directory only allowlisted entry files may import the use-case or
  retrieval barrel, each must import the gate, and no deep imports.
- Refuse a search that names a search-index knowledge base while dormant
  (Knowledge block, internal, v1, v2) with SearchIndexDormantError -> 409
  "This search index is inactive; use Sim Search." Workspace knowledge
  bases are unaffected.
- While dormant, the projector writes no embedding_keyword_tin rows and the
  source/ACL fill passes over search-index rows; the decision is passed
  into packages/db from the app caller. Workspace projection is unchanged.
- Cap the projection-fill probe at 250 ms of the leg budget and remember an
  unknown answer as unfilled for 5 s instead of re-probing every search.
Organization indexed search is dormant now that live search serves those
queries, but an organization search index can still hold most of the
knowledge search rows and all of the Tin keyword projection, inflating the
shared vector index for every workspace search. This adds an ordered,
resumable runbook and operator scripts to reclaim that space. Every script
only reads unless --execute is passed.

- disable-tin-projection: drops the three Tin sync triggers and truncates
  embedding_keyword_tin in one transaction, with short lock timeouts retried
  within a budget. The shared document ACL fan-out is left intact and becomes
  a probe of an empty table.
- restore-tin-projection: reinstalls those triggers from the 0019 and 0024
  migration functions themselves, with an optional 0019 backfill.
- delete-search-index-documents: deletes one search index's connector
  documents and chunks in cursor pages, mirroring the app's connector cleanup
  worker. It refuses unless the base is a search index and every live
  connector is paused or disabled with no sync lease, re-checked before every
  page. Storage objects are queued through the app's own storage cleanup
  outbox in the deleting transaction, with a backlog ceiling. The knowledge
  base and connectors are kept; their listing cursors are reset so a resumed
  connector lists everything again. Deletes fire no row triggers and write no
  projector marks, since projection rows go by foreign-key cascade.
- maintenance: health report, REINDEX INDEX CONCURRENTLY (HNSW first, before
  vacuum) and one-table-at-a-time VACUUM (VERBOSE, ANALYZE).

Unit tests cover the guards, paging and resume, retries, backpressure and
dry runs. A local-only PostgreSQL integration test exercises the real
scripts end to end and is not registered in CI. No migration or schema
changes.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner September 24, 2026 18:52
@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
docs Ready Ready Preview Sep 24, 2026 7:53pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the remaining timer overhead is non-blocking.

Findings

  1. P2 Probe timers remain active ▶

Summary

The PR makes indexed organization search dormant behind a shared gate, moves its implementation into an indexed module, and adds guarded database recovery scripts. The changes since the previous review replace the probe’s cancellable timer with a shared sleep helper and extend the import-boundary audit to catch literal paths held in variables.

  • The three previous findings are resolved in the current code.
  • The probe timer change leaves a small, avoidable performance cost when indexed search is enabled.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  G{Indexed search enabled?}
  G -- No --> L[Live Search]
  G -- Yes --> I[Indexed organization search]
  I --> P[Projection-fill probe]
  I --> T[Tin keyword retrieval]
  R[Recovery scripts] --> D[Disable Tin projection]
  R --> X[Delete connector documents]
  X --> M[Reindex and vacuum]
Loading

Reviews (4) · Last reviewed commit: "fix(search): catch variable-bound dynami..."

Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion-store.ts Outdated
Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion-store.ts Outdated

@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 70 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion.ts
Comment thread scripts/check-indexed-org-search-boundary.ts Outdated
Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion-store.ts
Comment thread apps/sim/app/api/v1/knowledge/search/route.ts
Comment thread apps/sim/lib/sim-search/indexed/retrieval/projection-filled.ts Outdated
Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion-store.ts Outdated
Comment thread apps/sim/scripts/dormant-org-search/search-index-deletion-store.ts Outdated
…n and reset member relist state

Each deleting transaction share-locks the search index and its connectors and re-decides the guard, so a connector resumed between pages waits for the page in flight and the next page refuses. Every document-deleting transaction also resets the stopped connectors' listing state, now including the directory checkpoint and each member's retry time, so a run stopped partway never leaves a connector that would skip deleted documents on resume.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

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

No issues found across 70 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

The v1 route refuses a dormant search index before billing admission, so the caller sees why the request cannot run rather than its usage. Each search waits on a shared projection probe no longer than its own share or deadline. The boundary audit requires allowlisted entries to call the gate, not only import it. The deletion script accepts only organization-owned search indexes and deletes chunks only of documents the connectors still own, so a document detached mid-run keeps its chunks.
…nk name collision

Turbopack truncates chunk content hashes to seven characters (vercel/next.js#97765), and this branch's module graph produced two different server chunks with the same name, failing the build. Renaming the module re-rolls the hash, as #7593 did; the collision can recur on unrelated changes until Next ships untruncated hashes.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment thread apps/sim/lib/sim-search/indexed/retrieval/projection-fill.ts Outdated

@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 70 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread scripts/check-indexed-org-search-boundary.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment on lines +92 to +93
const unanswered = sleep(waitMs).then(() => undefined)
return (await Promise.race([answer.catch(() => undefined), unanswered])) ?? false

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.

P2 Probe timers remain active When the projection answer arrives before the 250 ms limit, sleep(waitMs) cannot be cancelled. Each indexed-search leg then leaves a timer active after returning, adding avoidable timer handles and event-loop wakeups under search load.

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

No issues found across 70 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

This branch was successfully deployed

1 active deployment
Preview — a9209733 Deployed Sep 24, 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.

1 participant