feat(session-query): ship full-text session search opt-in via openAt never

The shipped bundles keep ctx.sessionQuery mounted but set the new
session-query-sqlite `openAt: never` phase: searchSessions/searchEvents
fail with the typed SESSION_QUERY_SEARCH_DISABLED code before any request
normalization, node:sqlite is never imported or opened, and no source
observation or reconciliation runs. Every inherited exact read, filter,
and trace — session export descendants, subagent-fork Workspace
inheritance, title reads — keeps working, and the Web sidebar search
degrades to its designed local title/workspace matching. Enabling content
search is a one-line openAt override in a later patch layer; the web e2e
scaffold keeps it enabled as the assembled opt-in coverage.
This commit is contained in:
Hypatia May
2026-08-13 11:38:38 +08:00
parent 137c3c9254
commit b6b6a72df7
23 changed files with 207 additions and 41 deletions
@@ -21,15 +21,22 @@ import { describe, expect, it } from 'vitest'
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
const builtBin = join(repoRoot, 'apps/cli/lib/bin.js')
const webDist = join(repoRoot, 'apps/web/dist/index.html')
// The web bundle's patch owns the session-query-sqlite lazy-open row.
const configPath = join(repoRoot, 'packages/bundle/web-app/cordis.patch.yml')
// Full-text session search ships off (`openAt: never` on both layers): the
// base patch carries the default, and the web restatement must not re-enable it.
const baseConfigPath = join(repoRoot, 'packages/bundle/base/cordis.patch.yml')
const webConfigPath = join(repoRoot, 'packages/bundle/web-app/cordis.patch.yml')
const requireBuiltArtifacts = process.env.DSH_REQUIRE_BUILT_CLI_SMOKE === '1'
interface ConfigRow {
id?: string
disabled?: unknown
config?: { openAt?: unknown }
}
interface PatchEntry extends ConfigRow {
insert?: ConfigRow[]
}
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
kind: 'scalar',
construct: value => String(value),
@@ -92,12 +99,20 @@ function runBuiltWeb(cwd: string): Promise<{ stdout: string; stderr: string; cod
}
describe.skipIf(!requireBuiltArtifacts)('built CLI lazy-search startup', () => {
it('boots and disposes the shipped composition without a SQLite startup warning', async () => {
it('boots and disposes the shipped composition with full-text search off by default', async () => {
expect(existsSync(builtBin), `missing built CLI ${resolve(builtBin)}; run pnpm build`).toBe(true)
expect(existsSync(webDist), `missing Web dist ${resolve(webDist)}; run pnpm run build:web`).toBe(true)
const rows = yaml.load(await readFile(configPath, 'utf8'), { schema: configSchema }) as ConfigRow[]
const searchRow = rows.find(row => row.id === 'session-query-sqlite')
expect(searchRow?.config?.openAt).toBe('first-search')
const baseRows = (yaml.load(await readFile(baseConfigPath, 'utf8'), { schema: configSchema }) as PatchEntry[])
.flatMap(entry => entry.insert ?? [entry])
const webRows = (yaml.load(await readFile(webConfigPath, 'utf8'), { schema: configSchema }) as PatchEntry[])
.flatMap(entry => entry.insert ?? [entry])
const baseRow = baseRows.find(row => row.id === 'session-query-sqlite')
const webRow = webRows.find(row => row.id === 'session-query-sqlite')
expect(baseRow?.config?.openAt).toBe('never')
expect(baseRow?.disabled).toBeUndefined()
// The web restatement keeps the shipped default; opting in is a later layer's override.
expect(webRow?.config?.openAt).toBe('never')
expect(webRow?.disabled).toBeUndefined()
const cwd = await mkdtemp(join(tmpdir(), 'dsh-cli-lazy-search-'))
try {
+4
View File
@@ -391,6 +391,10 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
},
},
{ id: 'session-persistence-jsonl', config: { root: persistenceRoot } },
// Content search is enabled here although the shipped bundles default it
// off (`openAt: never`, pinned by apps/cli/tests/lazy-search-startup):
// the seeded-session scenarios navigate by content search, and these e2e
// runs are the assembled coverage for the opt-in search path.
{ id: 'session-query-sqlite', config: { path: ':memory:', openAt: 'first-search' } },
// storage-json's yml root is anchored to the real $DSH_HOME; pin the row
// to an absolute temp root (removed with the workspace at close) so tests