Recognize ordered secure-save acknowledgments across explanatory clauses

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-06 03:19:36 -06:00
1 parent 5f74dc6545
commit bf22f7ba69
2 files changed
+43 -2

No files matched your search

@@ -1,6 +1,6 @@
import {expect,it} from "vitest";
import {readFile} from "node:fs/promises";
import {canonicalCompanySecretKey,companySecretConfirmationHandoff,validCompanySecretMetadata} from "./external-company-secret-confirmation.js";
import {canonicalCompanySecretKey,companySecretConfirmationHandoff,companySecretSaveMatches,validCompanySecretMetadata} from "./external-company-secret-confirmation.js";
import {deploymentSecretGuidance,companyOperatorBindingRequested} from "./external-company-secret.js";
const prompt='I cannot store the deployment key value for you: only a human board operator can enter it. In Settings > Secrets (`/RUN/company/settings/secrets`), choose New secret, name it "Deployment API Key", set key `DEPLOYMENT_API_KEY`, save the value, then accept this card. Do not paste the key here. I will bind the saved secret to Morgan\'s environment.';
const binding={companyId:"company",issueId:"source",agentId:"ceo",userId:"person",runId:"first",canonicalCommentId:"human"};
@@ -42,3 +42,30 @@ it("admits a bound completed-entry question with native Confirm and rejects othe
expect(companySecretConfirmationHandoff({...card(),payload:{...card().payload,prompt}},binding,guidance),prompt).toBeUndefined();
}
});
it("admits explicit secure-save acknowledgments across explanatory sentences and paragraphs",()=>{
const actual='I can\'t store credential values from chat. Please open /RUN/company/settings/secrets, choose New secret, and save the deployment key there. Suggested name: "Deployment API key"; suggested key/env var: DEPLOYMENT_API_KEY. Then confirm here that secure entry is complete so I can bind it to Morgan\'s environment. Do not paste the key in chat or in this card.';
const paragraph="Open Settings > Secrets (`/RUN/company/settings/secrets`) and save the deployment key there.\n\nUse the company-owned secret entry form.\n\nThen accept this card to confirm the deployment key is saved so I can bind its reference to Morgan's environment. Never paste the value here.";
for(const text of [actual,paragraph])expect(companySecretConfirmationHandoff({...card(),payload:{...card().payload,prompt:text}},binding,guidance),text).toBeDefined();
const context="Use Settings > Secrets (`/RUN/company/settings/secrets`) for the deployment key; I will bind the saved reference to Morgan's environment. ";
for(const text of [
"May I save the deployment key? Then accept this card.",
"Confirm this card first. Save the deployment key there.",
"Do not save the deployment key. Then accept this card.",
"Save the deployment key, then cancel the save. Then accept this card.",
"Save your profile. Then confirm here that secure entry is complete.",
"Save the other key. Then accept this card.",
"Earlier, save the deployment key there. Then accept this card.",
"Save the deployment key there. Then confirm the other card.",
"Save the deployment key there. Then confirm permission to bind Morgan's secret reference.",
"Save the deployment key there. Then accept this card and paste the key value here.",
])expect(companySecretConfirmationHandoff({...card(),payload:{...card().payload,prompt:context+text}},binding,guidance),text).toBeUndefined();
});
it("keeps secure-save acknowledgment acceptance dependent on a later current-person save",()=>{
const handoff=companySecretConfirmationHandoff(card(),binding,guidance)!;
const saved={id:"secret",companyId:"company",scope:"company",status:"active",createdByUserId:"person",createdAt:"2026-11-04T13:30:00Z",name:"Deployment API Key",key:"deployment_api_key"};
expect(companySecretSaveMatches(saved,binding,handoff)).toBe(true);
for(const change of [{createdAt:"2026-11-04T13:28:00Z"},{createdByUserId:"other"},{companyId:"other"},{key:"other_key"},{name:"Other secret"}])expect(companySecretSaveMatches({...saved,...change},binding,handoff)).toBe(false);
expect(companySecretConfirmationHandoff({...card(),originCommentIds:["other"]},binding,guidance)).toBeUndefined();
});
@@ -12,6 +12,20 @@ export function validCompanySecretMetadata(value:unknown):value is {name?:string
return Object.entries(metadata).every(([key,v])=>v===undefined||typeof v==="string"&&v.length>0&&v.length<=120&&v.trim()===v&&!/[\r\n=]|https?:\/\//i.test(v)&&!/(?:^|\s)(?:sk|pk|gh[pousr]|github_pat|xox[baprs])[-_]|[A-Za-z0-9]{32}/i.test(v)&&(key!=="key"||/^[a-zA-Z0-9_.-]+$/.test(v)));
}
export interface CompanyConfirmationBinding extends SecretQuestionBinding {canonicalCommentId:string}
function orderedSecureSaveAcknowledgment(prompt:string){
const clauses=[...prompt.matchAll(/[^.!?;\n]+/g)];
for(const clause of clauses){
const save=/\b(?:save|enter|set)\s+(?:the\s+)?(?:deployment\s+(?:api\s+)?(?:key|credential|secret)|(?:secret|credential|key)\s+value|value|it)\b/i.exec(clause[0]);
if(!save||/\b(?:not|never|don.t|cancel|decline|skip|avoid|earlier|previously|formerly|may I|can I|will you)\b/i.test(clause[0].slice(0,save.index)))continue;
const after=prompt.slice(clause.index!+save.index+save[0].length);
for(const acknowledgment of after.matchAll(/\b(?:accept|confirm)\b[^.!?;\n]*/gi)){
const between=after.slice(0,acknowledgment.index),intent=acknowledgment[0];
if(/\b(?:cancel|decline|skip|avoid|do not|don.t|never)\b/i.test(between)||/\b(?:before|permission|authorize|another|other card)\b/i.test(intent))continue;
if(/^(?:accept|confirm)\s+(?:this\s+card|here\b[^.!?;\n]{0,80}\b(?:secure entry|saved|complete)|(?:that\s+)?(?:the\s+)?(?:deployment\s+(?:api\s+)?key|secure entry)\b[^.!?;\n]{0,80}\b(?:saved|complete))/i.test(intent))return true;
}
}
return false;
}
export function companySecretConfirmationHandoff(value:unknown,b:CompanyConfirmationBinding,guidance:(text:string)=>boolean,status:"pending"|"accepted"="pending"){
const row=value as Partial<RequestConfirmationInteraction>|undefined,p=row?.payload;
if(!row?.id||row.kind!=="request_confirmation"||row.status!==status||row.companyId!==b.companyId||row.issueId!==b.issueId||row.sourceRunId!==b.runId||row.createdByAgentId!==b.agentId||row.createdByUserId||row.addresseeAgentId||(row.addresseeUserId!=null&&row.addresseeUserId!==b.userId)||!row.originCommentIds?.includes(b.canonicalCommentId)||!chatCatalogHumanQuestionPolicies(row)||(row.continuationPolicy!=="wake_assignee"&&row.continuationPolicy!=="wake_assignee_on_accept")||row.dismissedAt||!p||!requestConfirmationPayloadSchema.safeParse(p).success||p.target||p.toolAction||p.secretProposal||p.connectionAuthorization||p.qualityReview
@@ -19,7 +33,7 @@ export function companySecretConfirmationHandoff(value:unknown,b:CompanyConfirma
const text=[row.title,p.prompt,p.detailsMarkdown,p.acceptLabel,p.rejectLabel].filter(Boolean).join("\n");
const completedQuestion=p.prompt.match(/\bhave\s+you\s+(?:already\s+)?(?:saved|set|entered|completed)\b[^?!\n]{0,240}\?/i)?.[0];
const completedEntry=!!completedQuestion&&/\bdeployment\s+(?:api\s+)?(?:key|credential|secret)\b/i.test(completedQuestion);
if(!guidance(text)||(!/\b(?:save|saved|set|entered)\b[^.!?\n]{0,100}\b(?:accept|confirm)\b/i.test(p.prompt)&&!completedEntry)||!/^(?:Confirm|Accept|Done|Saved)$/i.test(p.acceptLabel??""))return;
if(!guidance(text)||(!orderedSecureSaveAcknowledgment(p.prompt)&&!completedEntry)||!/^(?:Confirm|Accept|Done|Saved)$/i.test(p.acceptLabel??""))return;
const names=[...p.prompt.matchAll(/\bname\s+(?:it|the secret)\s+["“]([^"”\n]{1,120})["”]/gi)],keys=[...p.prompt.matchAll(/\b(?:set\s+)?(?:secret\s+)?key\s+`([a-zA-Z0-9_.-]{1,120})`/gi)];
if(names.length>1||keys.length>1||/\bname\s+(?:it|the secret)\b/i.test(p.prompt)&&names.length!==1||/\bset\s+(?:the\s+)?key\b/i.test(p.prompt)&&keys.length!==1||!validCompanySecretMetadata({name:names[0]?.[1],key:keys[0]?.[1]}))return;
return {interactionId:row.id,continuationPolicy:row.continuationPolicy,createdAt:String(row.createdAt),payloadHash:createHash("sha256").update(JSON.stringify(p)).digest("hex"),acceptLabel:p.acceptLabel!,prompt:p.prompt,metadata:{name:names[0]?.[1],key:keys[0]?.[1]}};