@@ -270,6 +270,135 @@ describe('mothership private trace provenance transport', () => {
270270 expect ( contextRegistry ) . toBe ( lifecycleOptions . environmentContext ?. resolvedSecretTraceRegistry )
271271 } )
272272
273+ it ( 'forwards closed model controls and an ordered deduplicated safe timeline' , async ( ) => {
274+ mockRunHeadlessCopilotLifecycle . mockImplementation (
275+ async ( payload : Record < string , unknown > , options : CopilotLifecycleOptions ) => {
276+ expect ( payload ) . toMatchObject ( {
277+ modelSelection : { model : 'gpt-6-astra' , fastMode : true } ,
278+ effort : 'max' ,
279+ } )
280+ const base = { v : 1 as const , ts : new Date ( ) . toISOString ( ) , stream : { id : 'message-1' } }
281+ const thinking = {
282+ ...base ,
283+ seq : 1 ,
284+ type : 'text' as const ,
285+ payload : { channel : 'thinking' as const , text : 'Considering' } ,
286+ }
287+ await options . onEvent ?.( {
288+ ...base ,
289+ seq : 0 ,
290+ type : 'text' ,
291+ payload : { channel : 'assistant' , text : 'I will check.' } ,
292+ } )
293+ await options . onEvent ?.( thinking )
294+ await options . onEvent ?.( thinking )
295+ await options . onEvent ?.( {
296+ ...base ,
297+ seq : 2 ,
298+ type : 'tool' ,
299+ payload : {
300+ phase : 'call' ,
301+ toolCallId : 'tool-1' ,
302+ toolName : 'Lookup' ,
303+ executor : 'go' ,
304+ mode : 'sync' ,
305+ partial : true ,
306+ arguments : { secret : 'private-args' } ,
307+ } ,
308+ } )
309+ await options . onEvent ?.( {
310+ ...base ,
311+ seq : 3 ,
312+ type : 'tool' ,
313+ payload : {
314+ phase : 'call' ,
315+ toolCallId : 'tool-1' ,
316+ toolName : 'Lookup' ,
317+ executor : 'go' ,
318+ mode : 'sync' ,
319+ } ,
320+ } )
321+ await options . onEvent ?.( {
322+ ...base ,
323+ seq : 4 ,
324+ type : 'tool' ,
325+ payload : {
326+ phase : 'result' ,
327+ toolCallId : 'tool-1' ,
328+ toolName : 'Lookup' ,
329+ executor : 'go' ,
330+ mode : 'sync' ,
331+ success : true ,
332+ output : 'private-result' ,
333+ } ,
334+ } )
335+ await options . onEvent ?.( {
336+ ...base ,
337+ seq : 5 ,
338+ type : 'text' ,
339+ payload : { channel : 'assistant' , text : 'Answer' } ,
340+ } )
341+ return { ...successResult ( ) , content : 'Answer' }
342+ }
343+ )
344+ const response = await POST (
345+ createMockRequest (
346+ 'POST' ,
347+ {
348+ ...requestBody ,
349+ modelSelection : { model : 'gpt-6-astra' , fastMode : true } ,
350+ effort : 'max' ,
351+ } ,
352+ {
353+ Authorization : 'Bearer internal' ,
354+ 'x-sim-billing-attribution' : 'billing' ,
355+ Accept : 'application/x-ndjson' ,
356+ } ,
357+ 'http://localhost:3000/api/mothership/execute'
358+ ) ,
359+ undefined
360+ )
361+ const text = await response . text ( )
362+ const events = text
363+ . trim ( )
364+ . split ( '\n' )
365+ . map ( ( line ) => JSON . parse ( line ) )
366+ expect ( events . filter ( ( event ) => event . type === 'agent_event' ) ) . toEqual ( [
367+ { type : 'agent_event' , event : { type : 'thinking_delta' , text : 'Considering' } } ,
368+ { type : 'agent_event' , event : { type : 'turn_end' , turn : 'intermediate' } } ,
369+ { type : 'agent_event' , event : { type : 'tool_call_start' , id : 'tool-1' , name : 'Lookup' } } ,
370+ {
371+ type : 'agent_event' ,
372+ event : { type : 'tool_call_end' , id : 'tool-1' , name : 'Lookup' , status : 'success' } ,
373+ } ,
374+ { type : 'agent_event' , event : { type : 'turn_end' , turn : 'final' } } ,
375+ ] )
376+ expect ( events . filter ( ( event ) => event . type === 'chunk' ) ) . toEqual ( [
377+ { type : 'chunk' , content : 'I will check.' , turn : 'pending' } ,
378+ { type : 'chunk' , content : 'Answer' , turn : 'pending' } ,
379+ ] )
380+ expect ( text ) . not . toContain ( 'private-args' )
381+ expect ( text ) . not . toContain ( 'private-result' )
382+ } )
383+
384+ it . each ( [
385+ { modelSelection : { model : 'arbitrary' } } ,
386+ { modelSelection : { model : 'claude-opus-5' , fastMode : true } } ,
387+ { effort : 'ultra' } ,
388+ ] ) ( 'rejects unsupported controls before lifecycle dispatch: %j' , async ( selection ) => {
389+ const response = await POST (
390+ createMockRequest (
391+ 'POST' ,
392+ { ...requestBody , ...selection } ,
393+ { Authorization : 'Bearer internal' } ,
394+ 'http://localhost:3000/api/mothership/execute'
395+ ) ,
396+ undefined
397+ )
398+ expect ( response . status ) . toBe ( 400 )
399+ expect ( mockRunHeadlessCopilotLifecycle ) . not . toHaveBeenCalled ( )
400+ } )
401+
273402 it ( 'keeps context routing and display inputs raw until the lifecycle boundary' , async ( ) => {
274403 mockGetPersonalAndWorkspaceEnv . mockResolvedValueOnce ( {
275404 personalEncrypted : { API_KEY : 'encrypted-secret' } ,
0 commit comments