From 537f807a12d9b19e2c2c17e86f62eb048f52ed33 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 6 Nov 2026 03:13:20 -0600 Subject: [PATCH] Reject cancelled personal save acknowledgements Co-Authored-By: Paperclip --- .../external-personal-secret-confirmation.test.ts | 9 +++++++++ .../external-personal-secret-confirmation.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.test.ts b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.test.ts index 6fca9b8ff43..c5087f9c71f 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.test.ts @@ -77,3 +77,12 @@ it.each(["pending","declined","historical save","foreign owner","wrong runtime", if(fault==="effect")Object.assign(input.interactions[0]!.payload,{toolAction:{toolName:"deploy"}}); expect(verifyChatCatalogHumanQuestionAnswer(input as never)).toBeUndefined(); }); + +it.each(["Save your personal credential. Then cancel the save. Then confirm here.", "Save your personal credential. Do not confirm yet. Then confirm here."])("rejects an intervening save or acknowledgement cancellation: %s", ending => { + const row=card();row.payload.prompt=row.payload.prompt.replace("When you have saved it, confirm this card so the requirement is recorded.",ending); + expect(personalSecretConfirmationHandoff(row,binding,personalSecretGuidance)).toBeUndefined(); +}); +it("allows an unrelated no-chat warning between secure save and confirmation",()=>{ + const row=card();row.payload.prompt=row.payload.prompt.replace("When you have saved it, confirm this card so the requirement is recorded.","Save your personal credential. Never paste the value in chat. Then confirm here."); + expect(personalSecretConfirmationHandoff(row,binding,personalSecretGuidance)).toBeDefined(); +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.ts b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.ts index 3aa6e52cc30..314d732278d 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-confirmation.ts @@ -22,6 +22,7 @@ export function personalSecretConfirmationHandoff(value: unknown, b: PersonalCon const saveThenConfirm = !!saveThen && !/(?:\b(?:do|should|must|can|could|will|would)\s+not|\bdon.t|\bnever|\b(?:skip|avoid))\s*$/i.test(actionText.slice(0, saveThen.index)); const confirmsAfter = (tail: string) => [...tail.matchAll(/\bconfirm\b[^.!?]*/gi)].some(match => !/(?:\b(?:do|should|must)\s+not|\bdon.t|\bnever|\b(?:skip|avoid))\s*$/i.test(tail.slice(0, match.index)) + && !/\b(?:(?:do not|don.t|never)\s+(?:save|enter|set|accept|confirm)|(?:cancel|decline|skip|avoid)(?:\s+(?:the|to))?\s+(?:save|saving|entry|acknowledgment|confirmation|accept|confirm))\b/i.test(tail.slice(0, match.index)) && !/\b(?:before|permission|authorize)\b/i.test(match[0])); const confirmsSavedEntry = /\bconfirm\b/i.test(text) && (completedQuestion || saveThenConfirm && confirmsAfter(actionText.slice(saveThen!.index))