mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
policy: reject out-of-range seed boundaries; carry baselines through session-query
Review fixes (ds-review-bot on #623): - overrideOf (both knobs) rejects a seedLength past the log end before slicing: a malformed durable boundary would otherwise empty the own-switch slice until the log outgrew it, letting a wide baseline shadow a REAL later tightening. Malformed durable metadata fails loud, never open. - The session-query derived index carries the two baseline fields end to end: schema columns on both session tables (SESSION_QUERY_SQLITE_SCHEMA _VERSION 6 — derived, rebuilds in place), inserts, header selects, the candidates CTE, rowHeader, sameHeader, and the cross-source assertSessionHeadersCompatible — so a search hit's header keeps the child's inherited confinement and conflicting live/persisted baselines reject. Red-first: out-of-range seedLength tests in both policy suites; baseline round-trip and live/persisted baseline-conflict tests in the session-query sqlite suite.
This commit is contained in:
@@ -146,6 +146,8 @@ interface SessionHeaderRow {
|
||||
parent_session: string | null
|
||||
seed_length: number | null
|
||||
delegation_depth: number | null
|
||||
sandbox_mode: string | null
|
||||
approval_policy: string | null
|
||||
}
|
||||
|
||||
interface SearchRow extends SessionHeaderRow {
|
||||
@@ -533,8 +535,8 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
const db = this._requireDb()
|
||||
db.prepare(`
|
||||
INSERT INTO persisted_sessions
|
||||
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, revision, generation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, sandbox_mode, approval_policy, revision, generation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
entry.header.id,
|
||||
entry.header.version,
|
||||
@@ -543,6 +545,8 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
entry.header.parentSession ?? null,
|
||||
entry.header.seedLength ?? null,
|
||||
entry.header.delegationDepth ?? null,
|
||||
entry.header.sandboxMode ?? null,
|
||||
entry.header.approvalPolicy ?? null,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
@@ -569,8 +573,8 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
const db = this._requireDb()
|
||||
db.prepare(`
|
||||
INSERT INTO temp.live_sessions
|
||||
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, fingerprint, persisted, generation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, sandbox_mode, approval_policy, fingerprint, persisted, generation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
entry.header.id,
|
||||
entry.header.version,
|
||||
@@ -579,6 +583,8 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
entry.header.parentSession ?? null,
|
||||
entry.header.seedLength ?? null,
|
||||
entry.header.delegationDepth ?? null,
|
||||
entry.header.sandboxMode ?? null,
|
||||
entry.header.approvalPolicy ?? null,
|
||||
entry.fingerprint,
|
||||
persisted ? 1 : 0,
|
||||
generation,
|
||||
@@ -671,7 +677,7 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
const db = this._requireDb()
|
||||
const live = db.prepare(
|
||||
`SELECT
|
||||
id AS session_id, version, created_at, cwd, parent_session, seed_length, delegation_depth, generation
|
||||
id AS session_id, version, created_at, cwd, parent_session, seed_length, delegation_depth, sandbox_mode, approval_policy, generation
|
||||
FROM temp.live_sessions
|
||||
WHERE id = ?`,
|
||||
).get(sessionId) as (SessionHeaderRow & { generation: number }) | undefined
|
||||
@@ -681,7 +687,7 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
if (persistenceBinding.service !== undefined) {
|
||||
const persisted = db.prepare(
|
||||
`SELECT
|
||||
id AS session_id, version, created_at, cwd, parent_session, seed_length, delegation_depth, generation
|
||||
id AS session_id, version, created_at, cwd, parent_session, seed_length, delegation_depth, sandbox_mode, approval_policy, generation
|
||||
FROM persisted_sessions
|
||||
WHERE id = ?`,
|
||||
).get(sessionId) as (SessionHeaderRow & { generation: number }) | undefined
|
||||
@@ -740,6 +746,8 @@ function selectedDocumentsSql(): { sql: string } {
|
||||
ps.parent_session AS parent_session,
|
||||
ps.seed_length AS seed_length,
|
||||
ps.delegation_depth AS delegation_depth,
|
||||
ps.sandbox_mode AS sandbox_mode,
|
||||
ps.approval_policy AS approval_policy,
|
||||
0 AS live,
|
||||
1 AS persisted,
|
||||
CAST(pd.seq AS INTEGER) AS seq,
|
||||
@@ -762,6 +770,8 @@ function selectedDocumentsSql(): { sql: string } {
|
||||
ls.parent_session AS parent_session,
|
||||
ls.seed_length AS seed_length,
|
||||
ls.delegation_depth AS delegation_depth,
|
||||
ls.sandbox_mode AS sandbox_mode,
|
||||
ls.approval_policy AS approval_policy,
|
||||
1 AS live,
|
||||
CASE WHEN ? = 1 THEN ls.persisted ELSE 0 END AS persisted,
|
||||
CAST(ld.seq AS INTEGER) AS seq,
|
||||
@@ -870,6 +880,8 @@ function sameHeader(a: SessionHeader, b: SessionHeader): boolean {
|
||||
&& a.parentSession === b.parentSession
|
||||
&& a.seedLength === b.seedLength
|
||||
&& (a.delegationDepth ?? 0) === (b.delegationDepth ?? 0)
|
||||
&& a.sandboxMode === b.sandboxMode
|
||||
&& a.approvalPolicy === b.approvalPolicy
|
||||
}
|
||||
|
||||
function rowHeader(row: SessionHeaderRow): SessionHeader {
|
||||
@@ -881,6 +893,8 @@ function rowHeader(row: SessionHeaderRow): SessionHeader {
|
||||
...row.parent_session === null ? {} : { parentSession: row.parent_session as SessionId },
|
||||
...row.seed_length === null ? {} : { seedLength: row.seed_length },
|
||||
...row.delegation_depth === null ? {} : { delegationDepth: row.delegation_depth },
|
||||
...row.sandbox_mode === null ? {} : { sandboxMode: row.sandbox_mode },
|
||||
...row.approval_policy === null ? {} : { approvalPolicy: row.approval_policy },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mkdir, open } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
/** Current derived-index schema version. Incompatible versions reset in place. */
|
||||
export const SESSION_QUERY_SQLITE_SCHEMA_VERSION = 5
|
||||
export const SESSION_QUERY_SQLITE_SCHEMA_VERSION = 6
|
||||
|
||||
/** SQLite application id protecting unrelated databases from derived resets. */
|
||||
export const SESSION_QUERY_SQLITE_APPLICATION_ID = 0x44534851
|
||||
@@ -117,6 +117,8 @@ function ensurePersistentSchema(db: DatabaseSync): void {
|
||||
parent_session TEXT,
|
||||
seed_length INTEGER,
|
||||
delegation_depth INTEGER,
|
||||
sandbox_mode TEXT,
|
||||
approval_policy TEXT,
|
||||
revision TEXT NOT NULL,
|
||||
generation INTEGER NOT NULL
|
||||
) STRICT
|
||||
@@ -146,6 +148,8 @@ function ensureTemporarySchema(db: DatabaseSync): void {
|
||||
parent_session TEXT,
|
||||
seed_length INTEGER,
|
||||
delegation_depth INTEGER,
|
||||
sandbox_mode TEXT,
|
||||
approval_policy TEXT,
|
||||
fingerprint TEXT NOT NULL,
|
||||
persisted INTEGER NOT NULL CHECK (persisted IN (0, 1)),
|
||||
generation INTEGER NOT NULL
|
||||
|
||||
@@ -220,6 +220,40 @@ describe('SQLite session search', () => {
|
||||
.resolves.toMatchObject({ items: [{ header: { ...session.header, seedLength: 1 }, live: true, persisted: false }] })
|
||||
})
|
||||
|
||||
it('round-trips the inherited policy baselines through search headers', async () => {
|
||||
// A delegated child's header carries the sandbox/approval baselines; the
|
||||
// derived index must return them — a consumer resuming from a search hit
|
||||
// would otherwise rebuild a child without its inherited confinement.
|
||||
const ctx = await liveContext({ path: ':memory:' })
|
||||
const session = ctx.sessions.create(SessionId('live-baseline'), {
|
||||
meta: { cwd: '/work', createdAt: 10, sandboxMode: 'read-only', approvalPolicy: 'never' },
|
||||
})
|
||||
session.append(
|
||||
'user/message',
|
||||
{ content: [{ type: 'text', text: 'baseline needle' }], source: { kind: 'user' } },
|
||||
{ surfaceOp: 'append' },
|
||||
)
|
||||
|
||||
const result = await ctx.sessionQuery.searchSessions({ query: 'needle' })
|
||||
expect(result.items[0]?.header).toMatchObject({ sandboxMode: 'read-only', approvalPolicy: 'never' })
|
||||
const events = await ctx.sessionQuery.searchEvents({ sessionId: session.id, query: 'needle' })
|
||||
expect(events.session).toMatchObject({ sandboxMode: 'read-only', approvalPolicy: 'never' })
|
||||
})
|
||||
|
||||
it('rejects live/persisted sources whose policy baselines conflict', async () => {
|
||||
const shared = header('baseline-conflict', 10, { sandboxMode: 'read-only' })
|
||||
TestPersistence.reset([{ meta: shared, events: messageEvents('persisted needle') }])
|
||||
const ctx = await liveContext()
|
||||
await ctx.plugin(TestPersistence)
|
||||
ctx.sessions.create(shared.id, {
|
||||
seed: messageEvents('live needle'),
|
||||
meta: { createdAt: 10, sandboxMode: 'danger-full-access' },
|
||||
})
|
||||
|
||||
await expect(ctx.sessionQuery.searchSessions({ query: 'needle' }))
|
||||
.rejects.toThrow(expectCode('SESSION_QUERY_SOURCE_CONFLICT'))
|
||||
})
|
||||
|
||||
it('searches all surfaces by default and applies metadata before ranking', async () => {
|
||||
const ctx = await liveContext({ path: ':memory:', defaultLimit: 10, maxLimit: 20 })
|
||||
const parent = SessionId('parent')
|
||||
|
||||
Reference in New Issue
Block a user