From 871586433d0301db9bb75053ee92990c8a9820e5 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 4 Nov 2026 19:09:33 -0600 Subject: [PATCH] fix(evals): bind installation claims in both directions Co-Authored-By: Paperclip --- .../external-team-conditional.test.ts | 9 +++++++++ .../chat-catalog-domains/external-team-conditional.ts | 9 +++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts index 0b93c584aa3..0e12f8bfb23 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts @@ -268,3 +268,12 @@ it("rejects a later same-skill current recovery claim while saved state still sh o.phaseReplies[0]!.body = "The existing CTO was preserved and the imported role is CTO 2. Launch Checklist's slug is occupied. The other three required skills installed."; expect(gradeTeamConditions(o, "initial").passed).toBe(true); }); + +it("rejects named verb-object installation claims but keeps genuine noninstallation and other-skill success", () => { + const o=valid(); + const start="The existing CTO was preserved and the imported role is CTO 2. Launch Checklist's slug is occupied. "; + o.phaseReplies[0]!.body=start+"We installed Launch Checklist under another slug."; + expect(gradeTeamConditions(o,"initial").passed).toBe(false); + o.phaseReplies[0]!.body=start+"We have not installed Launch Checklist. The other three required skills installed."; + expect(gradeTeamConditions(o,"initial").passed).toBe(true); +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts index 2a706186271..108bf43005d 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts @@ -188,9 +188,12 @@ function namedTeamSkillClauses(reply: string, skill: Pick): boolean { return namedTeamSkillClauses(reply, skill).some(clause => [...clause.matchAll(/\b(?:installed|available|resolved|recovered)\b/gi)].some(match => - !/\b(?:not|never|isn.t|aren.t|wasn.t|weren.t)\s+(?:(?:be|been|yet|currently|still)\s+){0,2}$/i.test(clause.slice(0, match.index)))) + !negatedTeamRecovery(clause.slice(0, match.index)))) && !claimsRecoveredSkillMissing(reply, skill); } function claimsRecoveredSkillMissing(reply: string, skill: Pick): boolean { @@ -269,7 +272,9 @@ export function truthfulTeamPartial( const subjects = [skill.slug, skill.slug.replaceAll("-", " "), skill.name] .map(name => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"); const currentRecovery = new RegExp("(?:\\b(?:" + subjects + ")[*_`]*|^(?:it|(?:this|that) skill))\\s+(?:skill\\s+)?(?:is|has been)\\s+(?:(?:now|already|currently)\\s+)?(?:installed|available|resolved|recovered)\\b", "i"); - return reported && !falseFull && !namedTeamSkillClauses(reply, skill, true).some(clause => currentRecovery.test(clause)); + const installedObject = new RegExp("\\binstalled\\s+(?:the\\s+)?[*_`]*(?:" + subjects + ")\\b", "gi"); + return reported && !falseFull && !namedTeamSkillClauses(reply, skill, true).some(clause => + currentRecovery.test(clause) || [...clause.matchAll(installedObject)].some(claim => !negatedTeamRecovery(clause.slice(0, claim.index)))); } export function gradeTeamConditions( o: ExternalObservation,