Files
PaperClipAI/tests/runner-e2e/chat-catalog-contract.test.ts
T
DottaandClaude Opus 5.5 c5e6ab5ca8 test(evals): retarget instance-settings from the removed Agent Chat toggle to the Cases experiment (#20897)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The chat catalog eval `instance-operations.instance-settings` checks
that a person who asks the chat agent to turn on an instance experiment
gets the supported operator path, and that the setting really works
> - The case asked "Enable Agent Chat on this instance." Agent Chat is
always on since #19360, and the catalog says so (#20169), so the agent
correctly answered "already on" and the scripted human could not find
the removed toggle
> - The premise is obsolete, so this pull request changes it explicitly
to the Cases experiment (`enableCases`), which still exists and has a
real route gate
> - It also replaces a reply-wording regex with structural checks
> - The benefit is that the case measures its original promise on
today's product

## Linked Issues or Issue Description

**What happened**
In the full chat-catalog run, the case failed in the human journey: the
locator `getByRole('switch', { name: 'Toggle agent chat experimental
setting' })` found no element. The agent's reply ("Agent Chat is already
enabled ... there's no toggle to change") was correct for the current
product. The grader also used reply regexes
(`experimental|enableAgentChat`, `operator|admin|human|settings`, and a
negative "I have enabled" pattern).

**Expected behavior**
The case tests an instance experiment a person can still turn on. The
agent cannot change instance settings, does not change the setting, and
links Experimental settings. The person follows that rendered link,
turns the feature on, sees it persist, and the feature's page opens.
Grading reads saved state and rendered links, not wording.

**Steps to reproduce**
1. Run
`chat-features.runner-opencode.local.instance-operations.instance-settings`
on master.
2. See the agent answer that Agent Chat is always on.
3. See the human step time out on the missing Agent Chat switch.

Refs #19360, #20169. Product companion: #20894 (the denial and the
catalog name the Experimental settings page). It is cherry-picked here
so the paid run measures it.

## What Changed

- Premise change (explicit): query "Enable Agent Chat on this instance."
becomes "Turn on Cases for this instance."; context and expected outcome
follow. Recorded in the manifest `changedFields`, pinned in the manifest
test, inventory digest updated.
- Setup: refuses a hidden Cases setting, starts Cases off (cleanup
restores the target's value), and probes GET/PATCH experimental settings
with the agent's own key (403 `board_access_required`, setting
unchanged).
- Human journey: checks that Cases is still off after the agent's turn
and that `/<prefix>/cases` redirects to the dashboard. Then follows the
Experimental settings link that the reply rendered (no typed-URL
fallback), turns Cases on, checks it after reload, and opens the Cases
page.
- Grader: the `supported-human-path` regex check is replaced by
`agent-links-experimental-settings` (rendered link to the page, and the
person followed it). Saved-state checks cover the off route, the save,
persistence and the open Cases page.
- `oracleVersion` is now
`paperclip.chat-catalog.privileged.instance-settings.v2`. `sourceFiles`
follow the code.
- Calibration: the valid reply and a reworded reply pass. Each missing
state, a missing link, a wrong page, a foreign origin and a literal
`<prefix>` link fail.

## Verification

- `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts
tests/runner-e2e/chat-catalog-domains/privileged-companions.test.ts
tests/runner-e2e/chat-catalog-domains/privileged-budget.test.ts
tests/runner-e2e/chat-catalog-contract.test.ts
tests/runner-e2e/chat-catalog-consistency.test.ts`: 25 passed.
- `node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs`: 10
passed.
- `node tests/runner-e2e/source-sdk-public-inputs.mjs <HEAD> --check`:
unchanged.
- `npx tsc --noEmit -p tests/runner-e2e`: no errors in changed files.
- The paid end-to-end run is not done yet.

## Risks

- The case now depends on the Cases experiment. If Cases becomes always
on or is removed, the case needs another retarget.
- The agent can still say something wrong in prose. The grader cannot
detect a false "I turned it on" claim without wording checks, so a human
reads the reply. The saved state proves that the agent did not change
the setting.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 14:02:01 -06:00

40 lines
3.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { validateChatCatalogGrade } from "./chat-catalog-contract.js";
import { chatCatalogScenarios, chatCatalogInventoryDigest } from "./chat-catalog-inventory.js";
import { runnerMatrix } from "./catalog.js";
import { parseRunnerSelectors, selectRunnerExecutions } from "./selectors.js";
import { chatCatalogCases } from "./chat-catalog-registry.js";
describe("catalog oracle contract", () => {
it("accepts an independently checked result and rejects missing or contradictory evidence", () => {
const checks = [{ id: "saved-owner", passed: true, detail: "Persisted assignee matches the requested agent" }];
expect(validateChatCatalogGrade({ passed: true, checks }).passed).toBe(true);
expect(validateChatCatalogGrade({ passed: false, checks: [{ ...checks[0]!, passed: false }] }).passed).toBe(false);
for (const value of [{ passed: true, checks: [] }, { passed: true, checks: [{ ...checks[0]!, passed: false }] },
{ passed: true, checks: [...checks, ...checks] }]) expect(() => validateChatCatalogGrade(value)).toThrow();
});
it("discovers each concrete implementation explicitly with its exact query and bounds", () => {
for (const implementation of chatCatalogCases) {
const cells = runnerMatrix.filter(cell => cell.suite.id === "chat-features" && cell.task.id === implementation.scenarioId);
expect(cells).toHaveLength(1);
const cell = cells[0]!;
expect(cell.task.buildPrompt("nonce")).toBe(chatCatalogScenarios.find(row => row.id === implementation.scenarioId)!.query);
expect(cell.task.automaticRetryPolicy).toBe("single_attempt");
expect(selectRunnerExecutions(parseRunnerSelectors(["--id", cell.id])).map(row => row.id)).toEqual([cell.id]);
expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(row => row.id === cell.id)).toBe(false);
}
});
it("keeps every frozen scenario in the denominator independently of executable readiness", () => {
expect(chatCatalogScenarios).toHaveLength(244);
expect(new Set(chatCatalogScenarios.map(row => row.feature)).size).toBe(35);
expect(new Set(chatCatalogScenarios.flatMap(row => row.capabilities)).size).toBe(191);
// agent-chat.chat-disabled retargeted to the Conference Room gate (Agent Chat is always on since #19360);
// agents.restore-instructions retargeted to no saved history (instruction saves keep none since #14420).
// access.manage-membership retargeted to read-and-route; activity.routine-audit builds real same-morning history.
// gateways-profiles.create-gateway and rotate-token: agents keep delegated gateway/token authority and never receive token values.
// instance-operations.instance-settings retargeted from the removed Agent Chat toggle (always on since #19360) to the Cases experiment.
expect(chatCatalogInventoryDigest).toBe("4e313bc5f35d2e7d0504e14f6039666345e38d734c7fd449e7bb84c783a5451a");
for (const implementation of chatCatalogCases) expect(chatCatalogScenarios.some(row => row.id === implementation.scenarioId)).toBe(true);
});
});