@@ -150,6 +150,7 @@ async function spawnParkedAsk(
150150 askReply = params . askDirectorPort ?. register ( {
151151 question : "which file should I edit?" ,
152152 questionId : "ask-1" ,
153+ assessment : assessedAsk ,
153154 } ) ;
154155 void askReply ?. catch ( ( ) => undefined ) ;
155156 return gate . promise ;
@@ -255,6 +256,31 @@ async function expectListShows(
255256 expect ( defined ( parsed . agents [ 0 ] ) . status ) . not . toBe ( "awaiting_director" ) ;
256257}
257258
259+ /** Valid assessed ask payload for the escalation projection tests. */
260+ const assessedAsk = {
261+ policyVersion : "1" ,
262+ classification : "operator_decision_required" ,
263+ blockedOutcome : "cannot produce the authenticated verification result" ,
264+ unavailableDirectorPath : "the director has no credential authority" ,
265+ permittedAlternatives : [
266+ {
267+ attempted : "focused local test" ,
268+ result : "requires the missing credential" ,
269+ comparableConfidence : false ,
270+ } ,
271+ ] ,
272+ minimumAddition : "allow an authenticated read-only snapshot" ,
273+ declineConsequence : "implementation is complete but verification is blocked" ,
274+ requestedMechanism : "broad web access" ,
275+ minimumAuthority : "authenticated read-only snapshot" ,
276+ verification : {
277+ verificationOutcome : "integration verification" ,
278+ rootCause : "missing test credential" ,
279+ attemptedNarrowChecks : [ "focused local test" ] ,
280+ reducedConfidence : "integration behavior remains unverified" ,
281+ } ,
282+ } as const ;
283+
258284function parkAsk (
259285 sessions : ReturnType < typeof createSubAgentSessionStore > ,
260286 mailbox : ReturnType < typeof createFleetMailbox > ,
@@ -267,6 +293,7 @@ function parkAsk(
267293 sessions . registerAsk ( id , {
268294 question : "which file?" ,
269295 questionId : "ask-1" ,
296+ assessment : assessedAsk ,
270297 resolve : ( ) => undefined ,
271298 reject : ( ) => undefined ,
272299 } ) ,
@@ -1752,6 +1779,62 @@ describe("list_agents", () => {
17521779 gate . resolve ( { report : "done" } ) ;
17531780 } ) ;
17541781
1782+ test ( "wait and list JSON emit assessment and terminal_outcome with stable field names (SF1)" , async ( ) => {
1783+ const { gate, deps, list, id } = await spawnParkedAsk ( ) ;
1784+ // list first: surfacing via wait would refuse a duplicate list (anti-poll).
1785+ const listed = await callFleetToolRaw ( list , { } ) ;
1786+ expect ( listed . isError ) . not . toBe ( true ) ;
1787+ const parsed = parseFleetJson ( listed . content ) as {
1788+ agents : {
1789+ agent_id : string ;
1790+ status : string ;
1791+ assessment ?: Record < string , unknown > ;
1792+ terminal_outcome ?: Record < string , unknown > ;
1793+ } [ ] ;
1794+ } ;
1795+ expect ( defined ( parsed . agents [ 0 ] ) . assessment ) . toMatchObject ( {
1796+ policyVersion : "1" ,
1797+ classification : "operator_decision_required" ,
1798+ minimumAuthority : "authenticated read-only snapshot" ,
1799+ } ) ;
1800+ const wait = waitTool ( deps ) ;
1801+ const waited = await callFleetTool ( wait , {
1802+ targets : [ id ] ,
1803+ timeout_ms : 2000 ,
1804+ } ) ;
1805+ expect ( waited . timed_out ) . toBe ( false ) ;
1806+ const waitRow = defined ( ( waited . results as Record < string , unknown > [ ] ) [ 0 ] ) ;
1807+ expect ( waitRow . assessment ) . toMatchObject ( {
1808+ policyVersion : "1" ,
1809+ classification : "operator_decision_required" ,
1810+ minimumAuthority : "authenticated read-only snapshot" ,
1811+ } ) ;
1812+ // Resolve through the public store API; the terminal outcome must then
1813+ // surface in list JSON.
1814+ expect (
1815+ deps . sessions . resolveEscalationAsk ( id , {
1816+ kind : "declined" ,
1817+ answer : "No credential grant." ,
1818+ questionId : "ask-1" ,
1819+ } ) ,
1820+ ) . toBe ( true ) ;
1821+ const after = await callFleetToolRaw ( list , { } ) ;
1822+ expect ( after . isError ) . not . toBe ( true ) ;
1823+ const parsedAfter = parseFleetJson ( after . content ) as {
1824+ agents : {
1825+ agent_id : string ;
1826+ status : string ;
1827+ assessment ?: Record < string , unknown > ;
1828+ terminal_outcome ?: Record < string , unknown > ;
1829+ } [ ] ;
1830+ } ;
1831+ expect ( defined ( parsedAfter . agents [ 0 ] ) . terminal_outcome ) . toMatchObject ( {
1832+ kind : "verification_blocked" ,
1833+ resolution : "declined" ,
1834+ } ) ;
1835+ gate . resolve ( { report : "done" } ) ;
1836+ } ) ;
1837+
17551838 test ( "errors after wait_agents surfaces awaiting_director with a question" , async ( ) => {
17561839 const { gate, deps, list, id } = await spawnParkedAsk ( ) ;
17571840 const wait = waitTool ( deps ) ;
@@ -1839,6 +1922,7 @@ describe("list_agents", () => {
18391922 . register ( {
18401923 question : "which test should I add?" ,
18411924 questionId : "ask-2" ,
1925+ assessment : assessedAsk ,
18421926 } )
18431927 . catch ( ( ) => undefined ) ;
18441928 await waitUntilAwaitingDirector ( deps . fleetRecords , deps . sessions , id ) ;
@@ -2162,6 +2246,7 @@ describe("ask_director wait handshake", () => {
21622246 ?. register ( {
21632247 question : "which file?" ,
21642248 questionId : "ask-1" ,
2249+ assessment : assessedAsk ,
21652250 } )
21662251 . catch ( ( ) => {
21672252 // Session settlement rejects an unanswered ask; this test does not await it.
@@ -2241,7 +2326,11 @@ describe("ask_director wait handshake", () => {
22412326 process . on ( "unhandledRejection" , onUnhandled ) ;
22422327 try {
22432328 expect ( ( ) =>
2244- defined ( port ) . register ( { question : "late?" , questionId : "ask-1" } ) ,
2329+ defined ( port ) . register ( {
2330+ question : "late?" ,
2331+ questionId : "ask-1" ,
2332+ assessment : assessedAsk ,
2333+ } ) ,
22452334 ) . toThrow ( "could not register" ) ;
22462335 await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
22472336 expect ( unhandled ) . toEqual ( [ ] ) ;
0 commit comments