feat(sandbox-policy): describe enforced file families
This commit is contained in:
@@ -59,6 +59,8 @@ interface SdkScenario {
|
||||
expectedFiles?: Readonly<Record<string, string>>
|
||||
/** Assembled model-facing tool names and required argument keys. */
|
||||
expectedTools?: Readonly<Record<string, readonly string[]>>
|
||||
/** Stable policy-context clauses the real assembled request must include or omit. */
|
||||
policyContext?: { includes: readonly string[]; excludes: readonly string[] }
|
||||
}
|
||||
|
||||
const SCENARIOS: SdkScenario[] = [
|
||||
@@ -88,6 +90,10 @@ const SCENARIOS: SdkScenario[] = [
|
||||
configs: { live: persistentToolsLiveConfig, replay: persistentToolsReplayConfig },
|
||||
expectedFiles: { 'note.txt': 'target:\n\tnew\n' },
|
||||
expectedTools: { bash: ['command'], str_replace_editor: ['command', 'path'] },
|
||||
policyContext: {
|
||||
includes: ['the write and edit tools', 'terminal sessions'],
|
||||
excludes: ['one-shot bash commands'],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -117,7 +123,7 @@ async function persistedLogs(sessionsRoot: string): Promise<PersistedLog[]> {
|
||||
|
||||
interface LoggedRequestHeader {
|
||||
type?: string
|
||||
data?: { header?: { tools?: Array<{ name: string; parameters: { required?: string[] } }> } }
|
||||
data?: { header?: { system?: unknown; tools?: Array<{ name: string; parameters: { required?: string[] } }> } }
|
||||
}
|
||||
|
||||
function assembledToolRequirements(log: PersistedLog): Record<string, string[]> {
|
||||
@@ -129,6 +135,15 @@ function assembledToolRequirements(log: PersistedLog): Record<string, string[]>
|
||||
return Object.fromEntries(tools.map(tool => [tool.name, tool.parameters.required ?? []]))
|
||||
}
|
||||
|
||||
function assembledSystem(log: PersistedLog): string {
|
||||
const event = log.content.trimEnd().split('\n')
|
||||
.map(line => JSON.parse(line) as LoggedRequestHeader)
|
||||
.find(candidate => candidate.type === 'request/header')
|
||||
const system = event?.data?.header?.system
|
||||
if (typeof system !== 'string') throw new Error('session log has no request/header system')
|
||||
return system
|
||||
}
|
||||
|
||||
function contextOf(logs: readonly { content: string; header: Record<string, unknown> }[], cwd: string): NormalizeContext {
|
||||
return {
|
||||
sessionIds: logs.flatMap(log => typeof log.header.id === 'string' ? [log.header.id] : []),
|
||||
@@ -359,6 +374,13 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
expect(assembledToolRequirements(parent)).toEqual(scenario.expectedTools)
|
||||
}
|
||||
if (scenario.policyContext !== undefined) {
|
||||
const parent = ordered[0]
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
const system = assembledSystem(parent)
|
||||
for (const clause of scenario.policyContext.includes) expect(system).toContain(clause)
|
||||
for (const clause of scenario.policyContext.excludes) expect(system).not.toContain(clause)
|
||||
}
|
||||
if (scenario.children > 0) {
|
||||
expect(notifications.some(n => n.method === 'subagent.started')).toBe(true)
|
||||
expect(notifications.some(n => n.method === 'subagent.finished')).toBe(true)
|
||||
|
||||
Reference in New Issue
Block a user