Files
PaperClipAI/tests/runner-e2e/types.ts
T
DottaandPaperclip 53732c811b fix(evals): classify latency from observation bounds (#16829)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Product E2E probes measure when acknowledgements and replies become
visible.
> - A poll gives a time interval for an event.
> - The first positive poll can occur after a target that the event
could still meet.
> - This pull request compares the full interval with a strict or
inclusive target.
> - Reports retain the raw observations and the original verdict when
the timing verdict changes.

## Linked Issues or Issue Description

**What happened?**

A historical acknowledgement was absent at 1901 ms and first present at
2427 ms. A report marked the 2000 ms target as failed. The interval
crosses that target, so the available evidence cannot give a pass or
fail verdict.

**Expected behavior**

Return pass when the full interval meets the target. Return fail when
the full interval rules out the target. Return inconclusive for overlap,
invalid evidence, or insufficient evidence. Respect strict and inclusive
targets and open and closed endpoints.

**Steps to reproduce**

Run the deterministic timing tests. Use the acknowledgement interval
`(1901, 2427] ms` with `<= 2000 ms`. Compare the late reply interval
`(52965, 53334] ms` with `<= 30000 ms`. The first is inconclusive. The
second fails.

**Paperclip version or commit**

The source includes master at
`14afc12124017d4ec30d0f4bfd6892f8ce475d21`.

Related PR:
[#13710](https://github.com/paperclipai/paperclip/pull/13710) changed
chat latency and session reuse. This change corrects probe evidence and
report verdicts.

## What Changed

- Add a shared interval classifier with explicit endpoint and target
operators.
- Start observation before dispatch. Use a monotonic clock. Bound
asynchronous reads by request start and completion.
- Measure the first visible response separately from message creation
and final settlement. Share browser reads with an optional
acknowledgement probe.
- Keep raw bounds, clock resolution, message metadata, failed reads, and
original verdicts in retained report evidence.
- Add deterministic coverage for threshold equality, missing data,
edited messages, asynchronous reads, teardown, report correction, and a
finite-domain oracle.
- Bound observer shutdown to one second across pending and final reads.
Retain timeout counts. Ignore late completions and start no more reads
after shutdown.
- Document the timing contract in the existing Product E2E guide.
- Keep current startup reuse evidence after the master merge. Update
four stale test expectations for qualified Copilot admission. Keep the
explicit suite and provider restrictions.

## Verification

- Current head: `c42e87e6d3b4b347e7cb176f56819c1d0abe106d`.
- Passed: `pnpm test:e2e:runner:unit` (3,287 Vitest tests and 128 native
completion tests; two explicit skips).
- Passed: `pnpm test:e2e:runner:typecheck`, `pnpm -r typecheck`, `pnpm
build`, `pnpm check:token-gates`, and `git diff origin/master --check`.
- Passed: [exact-head
CI](https://github.com/paperclipai/paperclip/actions/runs/39607140418),
including both required checks (`ci / verify` and `ci / e2e`), all
general and serialized test jobs, all eight browser shards, build,
canary dry run, and the clean public npm-install smoke.
- Greptile reviewed the complete current diff and returned [5/5 with no
actionable
issues](https://github.com/paperclipai/paperclip/pull/16829#issuecomment-6266704814).
All review threads are resolved.
- The redundant local `pnpm test:run` was stopped after the exact-head
CI test jobs passed. It did not produce a complete local verdict; the
full repository test evidence is the CI run above.
- The [earlier broad
run](https://github.com/paperclipai/paperclip/actions/runs/39249282597)
failed on the old head. Its full test command timed out after 15
minutes. Five E2E support failures came from ambient `NODE_PATH` and a
missing historical Git object. Current master supplies the
closed-environment launcher and retained source fixture. The full
support suite passes with these fixes.
- After the current master merge, four stale Copilot assertions and the
nested unit launcher failed. Copilot is now qualified. Those assertions
now require no qualification override and retain the rejected
suite/provider cases. The full documented unit command passes.
- The first local typecheck after the merge used stale generated package
declarations and failed. The typechecks pass after `pnpm build`.
- These checks use synthetic timing evidence. They make no live provider
calls and claim no new live latency measurement.

## Risks

- The browser diagnostic depends on the existing message and receipt DOM
markers. Controlled tests cover visibility, receipt separation,
concurrent reads, and teardown.
- Corrected timing verdicts can become inconclusive. The report keeps
the original verdict and does not change the overall workflow grade.
- This change adds no database migration or product surface.

## Model Used

- OpenAI GPT-6 Codex. Model family: GPT-6. This session does not expose
a more specific model ID or context window size. The agent used
reasoning, shell tools, code inspection, and deterministic tests. The
earlier implementation session did not retain its exact model ID.

## 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-21 08:31:22 -05:00

465 lines
14 KiB
TypeScript

export const CREDENTIAL_NAMES = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"OPENROUTER_API_KEY",
"KIMI_MODEL_API_KEY",
"XAI_API_KEY",
"GROK_AUTH_JSON",
"CODEX_AUTH_JSON",
"GEMINI_API_KEY",
"DAYTONA_API_KEY",
"GITHUB_TOKEN",
"CURSOR_AUTH_TOKEN",
"COPILOT_GITHUB_TOKEN",
] as const;
export type CredentialName = (typeof CREDENTIAL_NAMES)[number];
export type RunnerGeneration = "legacy" | "native";
export type RunnerEnvironmentId = "local" | "daytona";
export type RunnerTaskWorkMode = "standard" | "planning" | "ask";
export type RunnerTaskFlow =
| "case_handoff"
| "task_workspaces"
| "repository_skills"
| "provider_connection"
| "plan_task_guidance"
| "blocker_guidance"
| "public_mcp"
| "everyday_workflow"
| "context_integrity"
| "continuation_accounting"
| "continuation"
| "first_task"
| "agent_chat"
| "governed_tool_review"
| "single_turn"
| "plan_revision_acceptance"
| "question_resume_completion"
| "plan_approval_completion"
| "warm_three_turn"
| "instruction_persistence"
| "pi_native"
| "pi_controls"
| "native_active_stop"
| "copilot_protection"
| "native_provider_loss"
| "cursor_native";
export interface SecretReference {
type: "secret_ref";
secretId: string;
version: "latest";
}
export type SecretReferenceMap = Partial<
Record<CredentialName, SecretReference>
>;
export interface AgentFixtureBuildInput {
environmentId: string;
environmentFixtureId: RunnerEnvironmentId;
workspacePath: string;
secretRefs: SecretReferenceMap;
executionId: string;
}
export interface EnvironmentFixtureBuildInput {
secretRefs: SecretReferenceMap;
daytonaImage?: string;
executionId: string;
}
export interface RunnerProfileFixture {
id: string;
label: string;
generation: RunnerGeneration;
groups: readonly string[];
adapterType: string;
provider: string;
model: string;
modelQualification: {
source:
| "adapter_constant"
| "qualified_runner_profile"
| "candidate_runner_profile"
| "openrouter_rankings_snapshot";
qualificationId: string;
};
qualificationCandidate?: "cursor" | "copilot" | "pi";
ranking?: {
rank: number;
canonicalModelId: string;
snapshotId: string;
capturedAt: string;
sourceUrl: string;
};
credential: Exclude<CredentialName, "DAYTONA_API_KEY">;
supportedEnvironments: readonly RunnerEnvironmentId[];
expectedRuntimeMode: RunnerGeneration;
expectedRuntimeMetadata: {
adapterType: string;
provider: string;
};
buildAgent(input: AgentFixtureBuildInput): Record<string, unknown>;
}
export interface EnvironmentFixture {
id: RunnerEnvironmentId;
/** Distinguishes materially different configurations that share a provider ID. */
configurationKey?: string;
label: string;
groups: readonly string[];
driver: "local" | "sandbox";
provider: "local" | "daytona";
credential?: "DAYTONA_API_KEY";
lifecycle: {
setup: "instance_managed" | "create_via_api";
probe: "run_context_via_api";
cleanup: "instance_shutdown" | "delete_via_api_and_destroy_leases";
};
expectedExecutionTarget: {
kind: "local" | "remote";
transport?: "sandbox";
};
buildEnvironment(
input: EnvironmentFixtureBuildInput,
): Record<string, unknown>;
}
export type Matcher =
| { kind: "message_exact"; expected: string }
| { kind: "message_contains"; expected: string }
| { kind: "message_occurrences"; expected: string; count: number }
| { kind: "message_regex"; pattern: string; flags?: string }
| { kind: "message_ordered"; expected: readonly string[] }
| { kind: "issue_status"; expected: string }
| { kind: "run_status"; expected: string }
| { kind: "runtime_mode"; expected: RunnerGeneration }
| { kind: "environment"; expected: RunnerEnvironmentId }
| { kind: "file_exists"; path: string }
| { kind: "file_exact"; path: string; expected: string }
| { kind: "file_contains"; path: string; expected: string }
| { kind: "artifact_exists"; name: string; mimeType?: string }
| { kind: "artifact_exact"; name: string; expected: string; mimeType?: string }
| { kind: "json_path"; path: string; expected: unknown }
| { kind: "json_schema"; schema: Record<string, unknown> };
export interface RunnerTaskFixture {
requiredCredentials?: readonly CredentialName[];
id: string;
label: string;
groups: readonly string[];
workMode: RunnerTaskWorkMode;
flow: RunnerTaskFlow;
expectedRunCount: number;
/** Optional lower bound; expectedRunCount remains the maximum/cost estimate. */
minimumExpectedRunCount?: number;
/** Admit only the first attempt, including provider or infrastructure failures. */
automaticRetryPolicy?: "single_attempt";
attemptTimeoutMs: Readonly<Record<RunnerEnvironmentId, number>>;
expectedTerminalState: {
issue: "done" | "in_review" | "blocked" | "in_progress";
/** Only the Copilot denial flow admits this with a verified recovery settlement. */
alternativeIssue?: "blocked";
run: "succeeded" | "failed" | "cancelled";
};
buildTitle(nonce: string): string;
buildPrompt(nonce: string): string;
buildVisibleMarker(nonce: string): string;
buildRevisionRequest?(nonce: string): string;
buildFollowupMessages?(nonce: string): readonly [string, string];
turnTimeoutMs?: number;
buildQuestionAnswer?(nonce: string): {
optionLabel: string;
expectedMarker: string;
};
/** Restart the isolated Paperclip server after the waiting turn settles. */
restartServerBeforeQuestionAnswer?: boolean;
toolReviewDecision?: "approve" | "decline" | "always" | "restart";
buildPlanMarkers?(nonce: string): {
draft: string;
revised: string;
};
buildMatchers(nonce: string, execution: MatrixExecution): readonly Matcher[];
}
export interface MatrixExecution {
id: string;
suite: RunnerSuiteFixture;
suiteDefinitionHash: string;
profile: RunnerProfileFixture;
environment: EnvironmentFixture;
task: RunnerTaskFixture;
groups: readonly string[];
requiredCredentials: readonly CredentialName[];
}
export interface RunnerSuiteFixture {
id: string;
label: string;
description: string;
groups: readonly string[];
profiles: readonly RunnerProfileFixture[];
environments: readonly EnvironmentFixture[];
tasks: readonly RunnerTaskFixture[];
excludedExecutionIds?: readonly string[];
expectedMatrixSize: number;
definitionMetadata?: Readonly<Record<string, unknown>>;
/** Requires an explicit suite or execution ID; excluded from scheduled --all. */
manualOnly?: boolean;
}
export interface MatrixJob {
executionId: string;
suiteId: string;
profileId: string;
credentialName: Exclude<CredentialName, "DAYTONA_API_KEY">;
environmentId: RunnerEnvironmentId;
caseId: string;
timeoutMinutes: number;
needsDaytona: boolean;
}
export type FailureClass =
| "candidate_failure"
| "provider_variance"
| "transient_infrastructure"
| "permanent_infrastructure"
| "secret_leak"
| "cleanup_failure";
export type RunnerE2ECostStatus =
| "reported"
| "estimated"
| "partial"
| "unpriced"
| "unavailable"
| "not_metered";
export interface RunnerE2ERuntimeUsage {
provider: RunnerEnvironmentId;
/** Sum of the selected Paperclip heartbeat-run spans. */
agentRunDurationMs: number;
/** Sum of provider lease windows when the environment exposes leases. */
leaseDurationMs: number | null;
leaseCount: number;
cpuCores?: number;
memoryGiB?: number;
diskGiB?: number;
estimatedListCostUsd?: number;
costStatus: "estimated" | "unavailable" | "not_metered";
costSource:
| "daytona_public_list_price"
| "provider_cost_unavailable"
| "local_not_metered";
pricingAsOf?: string;
pricingUrl?: string;
}
export interface RunnerE2EBillingSummary {
assistant?: import("./public-mcp-model.js").AssistantUsage;
llm: {
runCount: number;
runsWithTokenUsage: number;
runsWithReportedCost: number;
inputTokens: number;
outputTokens: number;
cachedInputTokens: number;
totalTokens: number;
reportedCostUsd: number;
costStatus: Exclude<RunnerE2ECostStatus, "estimated" | "not_metered">;
};
runtime: RunnerE2ERuntimeUsage;
/** Provider-reported model spend only; never includes unknown/unpriced runs. */
reportedCostUsd: number;
/** Public-list-price estimate for metered execution infrastructure. */
estimatedRuntimeCostUsd: number;
/** Separately recorded post-processing judge usage; absent when not judged. */
judge?: { inputTokens: number | null; outputTokens: number | null; estimatedCostUsd: number | null; reservedCostUsd: number };
/** Reported model subtotal plus runtime and judge list-price estimates. */
observedAndEstimatedCostUsd: number | null;
complete: boolean;
}
export interface RunnerE2EResult {
schema: "paperclip.runner-e2e.result/v1" | "paperclip.runner-e2e.result/v2";
executionId: string;
suiteId?: string;
suiteDefinitionHash?: string;
source?: {
sha: string | null;
ref: string | null;
workflowRunUrl: string | null;
};
rankingSnapshot?: {
snapshotId: string;
capturedAt: string;
sourceUrl: string;
rank: number;
canonicalModelId: string;
};
attempt: number;
status: "passed" | "failed";
failureClass?: FailureClass;
error?: string;
profileId: string;
environmentId: RunnerEnvironmentId;
caseId: string;
provider: string;
model: string;
runtimeMode: RunnerGeneration;
issueId?: string;
issueIdentifier?: string | null;
runIds?: string[];
latencyProbes?: import("./latency-timing.js").LatencyResult[];
startupReuse?: Array<{ runId: string } & import("../../packages/adapter-utils/src/startup-reuse.js").StartupReuseSummary>;
turnTimings?: Array<{
turn: number;
submittedAt: string;
runStartedAt: string | null;
runFinishedAt: string | null;
schedulerLatencyMs: number | null;
runDurationMs: number | null;
responseLatencyMs: number | null;
runId: string;
leaseAcquisitionOutcome: "created" | "resumed" | "replacement" | "unknown";
}>;
startedAt: string;
finishedAt: string;
durationMs: number;
usage?: Record<string, unknown> | null;
runtimeUsage?: RunnerE2ERuntimeUsage;
billing?: RunnerE2EBillingSummary;
matcherResults?: Array<{
matcher: Matcher;
passed: boolean;
detail: string;
}>;
screenshots?: Array<{
id: string;
label: string;
file: string;
publication?: "public-runner-fixture";
/** Absent in historical results; new captures bind the exact PNG bytes. */
sha256?: string;
}>;
firstTask?: import("./first-task-scoring.js").FirstTaskEvidence;
providerConnection?: import("./connection-evidence.js").ConnectionEvidence;
completionQuality?: import("./completion-quality.js").CompletionQualityRecord[];
firstTaskQuality?: import("./first-task-quality.js").FirstTaskQuality;
/** External assistant API calls, separate from the team's heartbeat executions. */
publicMcp?: import("./public-mcp-model.js").AssistantUsage;
cleanup: "not_started" | "passed" | "failed";
}
export interface RunnerE2ESuiteSummary {
suiteId: string;
suiteDefinitionHash: string;
expected: number;
selected: number;
executed: number;
passed: number;
failed: number;
incomplete?: number;
retries: number;
cleanupPassed: boolean;
complete: boolean;
durationMs: number;
billing: RunnerE2EAggregateBillingSummary;
}
export interface RunnerE2EJudgeBillingSummary {
attempts: number;
inputTokens: number;
outputTokens: number;
estimatedCostUsd: number | null;
reservedCostUsd: number;
attemptsWithUnknownUsage: number;
}
export interface RunnerE2EAggregateBillingSummary {
assistant?: { requests: number; inputTokens: number; outputTokens: number; cachedInputTokens: number; estimatedCostUsd: number };
judge?: RunnerE2EJudgeBillingSummary;
testCount: number;
agentRunDurationMs: number;
leaseDurationMs: number;
llm: RunnerE2EBillingSummary["llm"];
reportedLlmCostUsd: number;
estimatedRuntimeCostUsd: number;
observedAndEstimatedCostUsd: number | null;
testsWithCompleteBilling: number;
}
export interface RunnerE2ECampaign {
schema: "paperclip.runner-e2e.campaign/v2";
campaignId: string;
generatedAt: string;
source: {
sha: string | null;
ref: string | null;
workflowRunUrl: string | null;
eventName: string | null;
};
expected: string[];
complete: boolean;
selected: number;
executed: number;
passed: number;
failed: number;
incomplete?: number;
retries: number;
cleanupPassed: boolean;
rankingSnapshots: Array<{
snapshotId: string;
capturedAt: string;
sourceUrl: string;
}>;
billing: RunnerE2EAggregateBillingSummary;
suites: RunnerE2ESuiteSummary[];
results: RunnerE2EResult[];
}
export interface RunnerE2EHistoryExecution {
executionId: string;
suiteId: string;
profileId: string;
environmentId: RunnerEnvironmentId;
caseId: string;
provider: string;
model: string;
status: "passed" | "failed" | "incomplete";
durationMs: number;
attempt: number;
cleanup: RunnerE2EResult["cleanup"];
billing: RunnerE2EBillingSummary;
}
export interface RunnerE2EHistoryCampaign {
campaignId: string;
generatedAt: string;
source: RunnerE2ECampaign["source"];
complete: boolean;
selected: number;
executed: number;
passed: number;
failed: number;
incomplete?: number;
retries: number;
cleanupPassed: boolean;
publicUrl: string;
billing: RunnerE2EAggregateBillingSummary;
suites: RunnerE2ESuiteSummary[];
executions: RunnerE2EHistoryExecution[];
}
export interface RunnerE2EHistoryIndex {
schema: "paperclip.runner-e2e.history/v1";
updatedAt: string;
latestCampaignId: string | null;
latestGreenCampaignId: string | null;
latestBySuite: Record<string, string>;
latestGreenBySuite: Record<string, string>;
campaigns: RunnerE2EHistoryCampaign[];
}