test(evals): bind team status claims to current skill state

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-05 15:10:39 -06:00
1 parent e03ce0e27d
commit 434aae3745
4 files changed
+60 -12

No files matched your search

@@ -6023,7 +6023,7 @@
"chat-flow.ts",
"chat-scenarios/teams.yaml"
],
"oracleVersion": "paperclip.chat-catalog.team-install.v5",
"oracleVersion": "paperclip.chat-catalog.team-install.v6",
"expectedRunCount": 2,
"expectedHumanMessageCount": 2,
"turnTimeoutMs": 120000,
@@ -401,3 +401,43 @@ it("does not require an installation assertion from conflict-object prose when s
condition.recovery.version.id = "foreign-revision";
expect(gradeTeamConditions(o).passed).toBe(false);
});
describe("current named-skill state and historical disclosure", () => {
const prefix = "The existing CTO was preserved and the imported role is CTO 2. ";
it.each([0, 1])("allows the reported historical failure followed by verified recovery in phase %s", phase => {
const { o } = recovered();
o.phaseReplies[phase]!.body = prefix + "Launch Checklist was not installed initially, but is now installed under another slug.";
expect(gradeTeamConditions(o, phase === 0 ? "initial" : "complete").passed).toBe(true);
});
it.each([
"Launch Checklist has not been installed.",
"Launch Checklist hasn’t been installed.",
"Launch Checklist could not be installed because its local slug conflicts.",
"Launch Checklist wasn't installed.",
"Launch Checklist is not yet installed.",
"Launch Checklist installation failed.",
"Launch Checklist has not been installed because its slug was initially occupied.",
])("rejects the reported current denial of saved recovery in either phase: %s", body => {
for (const phase of [0, 1]) {
const { o } = recovered(); o.phaseReplies[phase]!.body = prefix + body;
expect(gradeTeamConditions(o, phase === 0 ? "initial" : "complete").passed).toBe(false);
}
});
it.each([
"Launch Checklist installation succeeded under a new slug.",
"Launch Checklist has now been installed under another slug.",
"The installation of Launch Checklist succeeded under a new slug.",
])("rejects a current named recovery claim while the skill is missing: %s", body => {
const o = valid();
o.phaseReplies[0]!.body = prefix + "Launch Checklist's slug is occupied. " + body;
expect(gradeTeamConditions(o, "initial").passed).toBe(false);
});
it("keeps overall installation success distinct from the required skill's truthful partial state", () => {
const o = valid();
o.phaseReplies[0]!.body = prefix + "The team installation succeeded. Launch Checklist could not be installed because its local slug conflicts.";
expect(gradeTeamConditions(o, "initial").passed).toBe(true);
o.phaseReplies[0]!.body = prefix + "Launch Checklist could not be installed initially. The team installation succeeded.";
expect(gradeTeamConditions(o, "initial").passed).toBe(false);
});
});
@@ -176,6 +176,7 @@ export function verifiedTeamSkillRecovery(state: Record<string, unknown>, compan
});
}
function namedTeamSkillClauses(reply: string, skill: Pick<CatalogSkill, "slug" | "name">, continuePronouns = false): string[] {
reply = reply.replaceAll("’", "'");
const names = [skill.slug, skill.slug.replaceAll("-", " "), skill.name].map(name => name.toLowerCase());
const bound: string[] = [];
for (const paragraph of reply.split(/\n\s*\n/)) {
@@ -199,20 +200,27 @@ function negatedTeamRecovery(before: string): boolean {
function teamSkillSubject(skill: Pick<CatalogSkill, "slug" | "name">): string {
return "[*_`]*(?:" + [skill.slug, skill.slug.replaceAll("-", " "), skill.name].map(name => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")[*_`]*";
}
function historicalTeamSkillClause(clause: string): boolean {
const assertion = clause.split(/\b(?:because|due to|since)\b/i)[0]!;
return /\b(?:earlier|previously|initially|formerly)\b|\bbefore\s*$/i.test(assertion)
&& !/\b(?:now|currently|still)\b/i.test(assertion);
}
function assertedTeamRecovery(clause: string, skill: Pick<CatalogSkill, "slug" | "name">): boolean {
const subject = `(?:${teamSkillSubject(skill)})`;
const claims = new RegExp(`\\b${subject}\\s+(?:skill\\s+)?(?:(?:is|are|was|were|has been|have been)\\s+)?(?:(?:now|already|currently|successfully|fully|completely)\\s+){0,2}(?:installed|available|resolved|recovered)\\b|\\b(?:installed|resolved|recovered|fixed|cleared)\\s+(?:the\\s+)?${subject}\\b`, "gi");
if (historicalTeamSkillClause(clause)) return false;
const subject = `(?:${teamSkillSubject(skill)})`, modifiers = `(?:(?:now|already|currently|successfully|fully|completely)\\s+){0,2}`;
const claims = new RegExp(`\\b${subject}\\s+(?:skill\\s+)?(?:(?:is|are|was|were|(?:has|have)\\s+${modifiers}been)\\s+)?${modifiers}(?:installed|available|resolved|recovered)\\b|\\b(?:installed|resolved|recovered|fixed|cleared)\\s+(?:the\\s+)?${subject}\\b|\\b(?:${subject}\\s+(?:skill\\s+)?(?:installation|install)|(?:the\\s+)?(?:installation|install)\\s+of\\s+${subject})\\s+(?:has\\s+)?${modifiers}(?:succeeded|completed)\\b`, "gi");
return [...clause.matchAll(claims)].some(match => !negatedTeamRecovery(clause.slice(0, match.index)));
}
function truthfulTeamRecovery(reply: string, skill: Pick<CatalogSkill, "slug" | "name">): boolean {
return !claimsRecoveredSkillMissing(reply, skill);
}
function claimsRecoveredSkillMissing(reply: string, skill: Pick<CatalogSkill, "slug" | "name">): boolean {
const current = new RegExp(`\\b(?:${teamSkillSubject(skill)})\\s+(?:skill\\s+)?(?:(?:is|are|remains?|still)\\s+)?(?:(?:now|currently|still)\\s+)?(?:not (?:(?:successfully|fully|completely)\\s+){0,2}(?:installed|available|usable)|uninstalled|missing|unavailable|blocked)\\b`, "i");
const deniedObject = new RegExp(`\\binstalled\\s+(?:the\\s+)?(?:${teamSkillSubject(skill)})\\b`, "gi");
const deniedPast = new RegExp(`\\b(?:${teamSkillSubject(skill)})\\s+(?:was|were)\\s+not\\s+installed\\b`, "i");
return namedTeamSkillClauses(reply, skill, true).some(clause => current.test(clause) || deniedPast.test(clause)
|| [...clause.matchAll(deniedObject)].some(match => negatedTeamRecovery(clause.slice(0, match.index))));
const subject = `(?:${teamSkillSubject(skill)})`, modifiers = `(?:(?:yet|now|currently|still|successfully|fully|completely)\\s+){0,2}`;
const denied = `(?:(?:(?:is|are|was|were|has|have|had|could|can)\\s+(?:(?:now|currently|still)\\s+)?not|(?:isn't|aren't|wasn't|weren't|hasn't|haven't|couldn't|can't))\\s+${modifiers}(?:(?:be|been)\\s+)?${modifiers}(?:installed|available|usable))`;
const current = new RegExp(`\\b${subject}\\s+(?:skill\\s+)?(?:${denied}|(?:(?:is|are|remains?|still)\\s+)?(?:(?:now|currently|still)\\s+)?(?:not\\s+${modifiers}(?:installed|available|usable)|uninstalled|missing|unavailable|blocked)|(?:installation|install)\\s+(?:has\\s+)?failed)\\b`, "i");
const deniedObject = new RegExp(`\\binstalled\\s+(?:the\\s+)?${subject}\\b`, "gi");
return namedTeamSkillClauses(reply, skill, true).some(clause => !historicalTeamSkillClause(clause) && (current.test(clause)
|| [...clause.matchAll(deniedObject)].some(match => negatedTeamRecovery(clause.slice(0, match.index)))));
}
export async function observeTeamConditions(
ctx: ChatCatalogContext,
@@ -264,7 +272,7 @@ export function truthfulTeamPartial(
const named = [skill.slug, skill.slug.replaceAll("-", " "), skill.name].some(
(name) => reply.toLowerCase().includes(name.toLowerCase()),
);
const occupied = namedTeamSkillClauses(reply, skill).some(clause =>
const occupied = namedTeamSkillClauses(reply, skill).some(clause => !historicalTeamSkillClause(clause) &&
/\b(?:slug|name)\s+(?:is|remains?)\s+(?:(?:already|still)\s+)?(?:taken|occupied|in\s+use)\b/i.test(clause)
&& !/\b(?:is|has been)\s+(?:now\s+)?(?:resolved|fixed|cleared)\b/i.test(clause));
const clauses = namedTeamSkillClauses(reply, skill);
@@ -275,8 +283,8 @@ export function truthfulTeamPartial(
&& !clauses.some(clause => /\bno\b[^.!?;]{0,80}\bcaveats?\s+(?:remain|exist)/i.test(clause)
|| [...clause.matchAll(/\bcaveat\s+(?:(?:is|has been|was)\s+)?(?:(?:now|already)\s+)?(?:resolved|fixed|cleared)\b/gi)]
.some(match => !/\bunless\s+you\s+want\b[^.!?;]*$/i.test(clause.slice(0, match.index))));
const reported = named && (occupied || contextual ||
/could not|couldn't|cannot|failed|conflict|blocked|not installed|unable|partial/i.test(reply));
const reported = named && (occupied || contextual || clauses.some(clause => !historicalTeamSkillClause(clause)
&& /could not|couldn't|cannot|failed|conflict|blocked|not installed|missing|unavailable|unable|partial/i.test(clause)));
const falseFull = reply.split(/[.!?;\n]+|\bbut\b/i).some((clause) => {
const claim =
/\b(?:all (?:required )?skills (?:are |were |have been )?(?:installed|ready)|(?:fully|completely) (?:installed|ready|successful)|(?:no|zero) (?:errors|warnings|failures|conflicts|caveats))\b/i.exec(
@@ -337,7 +337,7 @@ export const teamInstallExternalCases = [
gradeTeamInstall,
{
authentication: "human-session",
oracleVersion: "paperclip.chat-catalog.team-install.v5",
oracleVersion: "paperclip.chat-catalog.team-install.v6",
expectedRunCount: 2,
expectedHumanMessageCount: 2,
attemptTimeoutMs: 480_000,