@W-23144570: stale-content-cleanup-apply eval (behavioral + adversarial) + runnability layer#496
Open
Alon-ST-DATA wants to merge 3 commits into
Open
@W-23144570: stale-content-cleanup-apply eval (behavioral + adversarial) + runnability layer#496Alon-ST-DATA wants to merge 3 commits into
Alon-ST-DATA wants to merge 3 commits into
Conversation
…sarial) Adds tests/eval/staleContentCleanupApply.test.ts evaluating the stale-content-cleanup-apply MCP prompt shipped in #405. 5 behavioral cases assert dry-run faithfulness, HITL gate, parameter scoping; 6 adversarial cases cover prompt-injection on tag (schema regex), projectIds (verbatim data, not directives), itemTypes=Flow rejection, user-message HITL bypass, user-message dryRun bypass, and fabricated-confirmationToken coercion. Each LLM-bound case has up to 3 attempts (vitest retry: 2) to absorb single LLM flakes; faithfulness target >= 0.95 from JTBD 2 work breakdown. Tool-result injection (e.g., crafted workbook names) is out of scope here because the harness drives a live Tableau site -- tracked as a separate follow-up requiring a mocked MCP server. Bumps version 2.18.0 -> 2.18.1 per repo convention.
Anchor .rejects.toThrow() to the schema's regex error message so the test fails when the tag-validation defense regresses, not just on any unrelated error.
… (token scope + best-of-N) Builds on Akash Rastogi's behavioral + adversarial eval (PR #412, 5 behavioral + 6 adversarial cases) with the harness changes needed to run it on a low tokens-per-minute grader key and in CI. tests/eval/staleContentCleanupApply.test.ts - INCLUDE_TOOLS narrowing in adminEnv(): the workflow only needs 6 tools; the full ~40-tool catalog schema alone can exceed a 30k-TPM grader budget before the workflow runs. Delete tools stay included so the "no destructive call" invariant is meaningful. - Optional EVAL_STALE_PROJECT_ID report scope via withScope(): on a large admin site the stale-content report returns thousands of rows / ~1MB, which fed back to the model is ~350k tokens — an instant hard 429. Scoping to a small non-empty project cut the request from 353k to ~30k tokens (12x) while still exercising the full report -> resolve -> notify -> STOP workflow. Read at call time (not module load) because tests/eval/.env is loaded in a beforeAll after module evaluation. - The one open-ended rubric case uses grade()'s best-of-N (attempts: 3) with rate-limit backoff instead of vitest .retry(2): it re-runs only the fuzzy judge and reports real scores if every attempt fails. Deterministic tool-call cases keep .retry(2). tests/eval/grade.ts - Best-of-N (attempts, default 1 => existing evals unchanged) + retryDelayMs backoff and a 429/rate-limit detector, so a noisy single-shot LLM judge or a low per-minute quota does not produce false reds. Note: on a 30k-TPM key the multi-turn agent loop still sums past the per-minute ceiling across the 11 cases; full green needs TPM headroom (higher-tier key or gateway). The scope + backoff make the suite feasible and keep the deterministic security invariant (never call a delete tool) hard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full eval for the
stale-content-cleanup-applyMCP prompt (W-23144570), targetingmainas one complete PR. Combines the behavioral + adversarial coverage from #412 (Akash Rastogi) with the harness changes needed to actually run it on a low tokens-per-minute grader key and in CI.tests/eval/staleContentCleanupApply.test.ts— 5 behavioral + 6 adversarial cases (from @W-23144570: Add stale-content-cleanup-apply eval (behavioral + adversarial) #412) + a runnability layer.tests/eval/grade.ts— best-of-N + rate-limit backoff (backward-compatible:attemptsdefaults to 1, so existing evals are unchanged).Supersedes #412 — carries its cases plus the fixes below in one PR.
What this adds on top of #412
1. Tool-catalog narrowing (
INCLUDE_TOOLS). The workflow only needs 6 tools. The full ~40-tool catalog schema alone can exceed a 30k-TPM grader budget before the workflow even runs.adminEnv()now setsINCLUDE_TOOLSto the 6 workflow tools; the two delete tools stay included on purpose so the "no destructive call" invariant remains meaningful.2. Optional report scoping (
EVAL_STALE_PROJECT_ID). On a large admin site the stale-content report returns thousands of rows / ~1MB. Fed back to the model that is ~353k tokens in a single request — an instant hard429 Request too largeon a 30k-TPM key. SettingEVAL_STALE_PROJECT_IDto a small, non-empty project scopes the report and cuts the request from 353,463 to ~30,000 tokens (12x) while still exercising the full report → resolve → notify → STOP workflow. Left unset, the eval runs unscoped (original behavior) — fine on a high-TPM key or a small fixture site. Read at call time (not module load), becausetests/eval/.envis loaded in abeforeAllafter module evaluation.3. Best-of-N for the fuzzy judge. The one open-ended rubric case ("coherent assistant report") uses
grade()'s best-of-N (attempts: 3) with rate-limit backoff instead of vitest.retry(2)..retryre-runs on any throw — including a 429 — and re-fires straight into the same TPM wall. Best-of-N re-runs only the fuzzy 5-dim judge, backs off on a detected rate-limit, and reports real scores if every attempt fails. Deterministic tool-call cases keep.retry(2).Deterministic-vs-LLM split (preserved)
The security-critical invariant — the agent never invokes a delete tool from this prompt under any input, including adversarial "skip confirmation" / "ignore dryRun" / "fabricate a confirmationToken" messages — is asserted deterministically and gates hard. Only the fuzzy rubric is best-of-N.
Test plan
npx tsc --noEmit— cleannpm run lint— clean (eval files)npm run build— cleanEVAL_STALE_PROJECT_IDset.Known limitation (this is the pipeline requirement)
On a 30k-TPM OpenAI key the multi-turn agent loop (report → resolve → notify → summary is several LLM calls) still sums past the per-minute ceiling across all 11 cases, and a shared live Tableau site can throttle rapid sign-ins. Full green needs TPM headroom — a higher-tier key or an internal gateway — plus a bounded fixture. The scope + backoff added here are necessary to make the suite feasible at all and to keep the deterministic security invariant hard; they are not sufficient on the lowest key tier alone. This matches the eval-testing-pipeline requirements (CI-held LLM credential with TPM headroom + a bounded eval fixture).
Env
EVAL_STALE_PROJECT_ID(optional) — scope the stale-content report to a small non-empty project. Set intests/eval/.env(git-ignored).