Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions server/src/addie/eval/fixed-trace-diagnostic-cli.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
export interface FixedTraceDiagnosticCliArguments {
providers?: string;
architectureArm?: string;
suite?: string;
softMaxUsd?: string;
output?: string;
validateOnly: boolean;
}

const NAMES = new Set(['providers', 'architecture-arm', 'suite', 'soft-max-usd', 'output', 'validate-only']);

/** Strict, side-effect-free parser for the diagnostic-only manual evaluator. */
/** The pinned planning artifact has no runtime controls or output surface. */
export function parseFixedTraceDiagnosticCliArguments(values: readonly string[]): FixedTraceDiagnosticCliArguments {
const seen = new Map<string, string | true>();
for (const value of values) {
if (value === '--judge-providers' || value.startsWith('--judge-providers=')) {
throw new Error('--judge-providers is unavailable_pending_trusted_coordinator');
}
const bare = value.match(/^--([a-z][a-z-]*)$/);
const assigned = value.match(/^--([a-z][a-z-]*)=(.+)$/);
const name = bare?.[1] ?? assigned?.[1];
if (!name || !NAMES.has(name)) throw new Error(`Unknown or malformed fixed-trace option: ${value}`);
if (seen.has(name)) throw new Error(`Duplicate fixed-trace option: --${name}`);
if (bare && name !== 'validate-only') throw new Error(`--${name} requires =value`);
if (assigned && name === 'validate-only' && assigned[2] !== 'true') {
throw new Error('--validate-only accepts only the bare flag or =true');
}
seen.set(name, bare ? true : assigned![2]);
}
return {
providers: typeof seen.get('providers') === 'string' ? seen.get('providers') as string : undefined,
architectureArm: typeof seen.get('architecture-arm') === 'string' ? seen.get('architecture-arm') as string : undefined,
suite: typeof seen.get('suite') === 'string' ? seen.get('suite') as string : undefined,
softMaxUsd: typeof seen.get('soft-max-usd') === 'string' ? seen.get('soft-max-usd') as string : undefined,
output: typeof seen.get('output') === 'string' ? seen.get('output') as string : undefined,
validateOnly: seen.get('validate-only') === true || seen.get('validate-only') === 'true',
};
if (values.length === 0) return { validateOnly: false };
if (values.length === 1 && values[0] === '--validate-only') return { validateOnly: true };
throw new Error('This planning-only evaluator accepts only the bare --validate-only flag');
}
51 changes: 32 additions & 19 deletions server/src/addie/eval/fixed-trace-diagnostic-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
fixedTraceResponsePricingPolicy,
isTrustedBudgetedFixedTraceProvider,
} from './fixed-trace-budget.js';
import { types } from 'node:util';
import { snapshotFixedTraceJson } from './fixed-trace-safe-snapshot.js';

export interface FixedTraceDiagnosticProviderPlan {
readonly name: string;
Expand Down Expand Up @@ -63,6 +65,17 @@ function ownDataProperty(source: unknown, name: string, owner: string): unknown
return descriptor.value;
}

function assertClosedOwnDataRecord(source: unknown, fields: readonly string[], owner: string): void {
if (typeof source !== 'object' || source === null || types.isProxy(source) || Object.getPrototypeOf(source) !== Object.prototype) {
throw new Error(`Fixed trace diagnostic ${owner} must be a plain non-Proxy object`);
}
const keys = Reflect.ownKeys(source);
if (keys.length !== fields.length || keys.some((key) => typeof key !== 'string' || !fields.includes(key))) {
throw new Error(`Fixed trace diagnostic ${owner} must contain exactly its approved fields`);
}
for (const field of fields) ownDataProperty(source, field, owner);
}

const DIAGNOSTIC_PRICING_FIELDS = [
'profileId',
'inputUsdPerMillionTokens',
Expand All @@ -75,19 +88,7 @@ const DIAGNOSTIC_PRICING_FIELDS = [
] as const;

function snapshotPricing(pricing: unknown, owner: string): FixedTracePricing {
const prototype = typeof pricing === 'object' && pricing !== null
? Object.getPrototypeOf(pricing)
: null;
if (
typeof pricing !== 'object'
|| pricing === null
|| (prototype !== Object.prototype && prototype !== null)
) throw new Error(`Fixed trace diagnostic ${owner} must be a plain pricing object`);
const keys = Reflect.ownKeys(pricing);
if (
keys.length !== DIAGNOSTIC_PRICING_FIELDS.length
|| keys.some((key) => typeof key !== 'string' || !DIAGNOSTIC_PRICING_FIELDS.includes(key as typeof DIAGNOSTIC_PRICING_FIELDS[number]))
) throw new Error(`Fixed trace diagnostic ${owner} must contain only approved pricing fields`);
assertClosedOwnDataRecord(pricing, DIAGNOSTIC_PRICING_FIELDS, owner);
// Structured cloning calls nested getters. Copy each approved data
// descriptor instead, so a price cannot change between validation and use.
return Object.freeze({
Expand All @@ -105,6 +106,10 @@ function snapshotPricing(pricing: unknown, owner: string): FixedTracePricing {
function snapshotStageConfig(config: unknown, owner: string): FixedTraceProviderStageConfig {
// Read each untrusted stage property exactly once. Later checks use only
// this detached plain object, never a caller-controlled getter or proxy.
assertClosedOwnDataRecord(config, [
'provider', 'model', 'reasoningEffort', 'maxOutputTokens', 'timeoutMs',
'maxIterations', 'transportRetries', 'samplingMode', 'temperature', 'pricing',
], owner);
const provider = ownDataProperty(config, 'provider', owner);
const model = ownDataProperty(config, 'model', owner);
const reasoningEffort = ownDataProperty(config, 'reasoningEffort', owner);
Expand Down Expand Up @@ -132,12 +137,7 @@ function snapshotStageConfig(config: unknown, owner: string): FixedTraceProvider
function snapshotBaseConfig(
config: FixedTraceDiagnosticArtifactOptions['baseConfig'],
): FixedTraceDiagnosticArtifactOptions['baseConfig'] {
const { traceSuite, toolDefinitions, ...serializable } = config;
return Object.freeze({
...structuredClone(serializable),
traceSuite: deepFreeze(structuredClone(traceSuite)),
toolDefinitions: deepFreeze(structuredClone(toolDefinitions)),
});
return snapshotFixedTraceJson(config, 'fixed trace diagnostic base config') as FixedTraceDiagnosticArtifactOptions['baseConfig'];
}

function snapshotPlans(
Expand All @@ -147,6 +147,19 @@ function snapshotPlans(
if (!Array.isArray(suppliedPlans) || suppliedPlans.length === 0) {
throw new Error('Fixed trace diagnostic run requires one or more provider plans');
}
if (types.isProxy(suppliedPlans) || Object.getPrototypeOf(suppliedPlans) !== Array.prototype || Object.getOwnPropertySymbols(suppliedPlans).length !== 0) {
throw new Error('Fixed trace diagnostic provider plans must be a plain non-Proxy array');
}
const planDescriptors = Object.getOwnPropertyDescriptors(suppliedPlans);
for (const key of Object.keys(planDescriptors)) {
if (key === 'length') continue;
if (!/^(0|[1-9][0-9]*)$/.test(key) || !('value' in planDescriptors[key]!) || !planDescriptors[key]!.enumerable) {
throw new Error('Fixed trace diagnostic provider plans contain an accessor or extra property');
}
}
for (const [index, suppliedPlan] of suppliedPlans.entries()) {
assertClosedOwnDataRecord(suppliedPlan, ['name', 'router', 'generation'], `provider plan ${index}`);
}
const plans = Object.freeze(suppliedPlans.map((suppliedPlan, index) => Object.freeze({
// Do not validate while reading: a plan accessor must not be able to
// return one identity for validation and another for execution.
Expand Down
5 changes: 3 additions & 2 deletions server/src/addie/eval/fixed-trace-evidence-prerequisite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type FixedTraceTerminalStatus =
| "malformed"
| "provider_error"
| "timeout_after_dispatch"
| "unknown_exposure"
| "not_dispatched_budget"
| "not_admitted_architecture";
type FixedTraceInvocationStage = "router" | "generation" | "judge" | "simulator";
Expand Down Expand Up @@ -254,7 +255,7 @@ type FixedTraceExtraSchemaVersion = FixedTraceAssertTrue<FixedTraceEnumIsExhaust
// @ts-expect-error invocation stages cannot admit a member outside their closed domain
type FixedTraceExtraInvocationStage = FixedTraceAssertTrue<FixedTraceEnumIsExhaustive<FixedTraceInvocationStage, ["router", "generation", "judge", "simulator", "forged"]>>;
// @ts-expect-error terminal statuses cannot admit a member outside their closed domain
type FixedTraceExtraTerminalStatus = FixedTraceAssertTrue<FixedTraceEnumIsExhaustive<FixedTraceTerminalStatus, ["complete", "ignored", "reacted", "refusal", "truncated", "empty", "malformed", "provider_error", "timeout_after_dispatch", "not_dispatched_budget", "not_admitted_architecture", "forged"]>>;
type FixedTraceExtraTerminalStatus = FixedTraceAssertTrue<FixedTraceEnumIsExhaustive<FixedTraceTerminalStatus, ["complete", "ignored", "reacted", "refusal", "truncated", "empty", "malformed", "provider_error", "timeout_after_dispatch", "unknown_exposure", "not_dispatched_budget", "not_admitted_architecture", "forged"]>>;
// @ts-expect-error finish reasons cannot admit a member outside their closed domain
type FixedTraceExtraFinishReason = FixedTraceAssertTrue<FixedTraceEnumIsExhaustive<FixedTraceFinishReason, ["stop", "tool_calls", "length", "refusal", "continue", "forged"]>>;
// @ts-expect-error completeness outcomes cannot admit a member outside their closed domain
Expand Down Expand Up @@ -310,7 +311,7 @@ export const FIXED_TRACE_SEALED_EVIDENCE_REQUIREMENTS:
},
timingAndOutcome: {
preparedAt: { type: "utc_timestamp" }, dispatchedAt: { type: "nullable_utc_timestamp" }, completedAt: { type: "nullable_utc_timestamp" }, latencyMs: { type: "nullable_number" }, timeout: { type: "boolean" },
errorCode: { type: "nullable_string" }, terminalStatus: fixedTraceEnum<FixedTraceTerminalStatus>()(["complete", "ignored", "reacted", "refusal", "truncated", "empty", "malformed", "provider_error", "timeout_after_dispatch", "not_dispatched_budget", "not_admitted_architecture"]),
errorCode: { type: "nullable_string" }, terminalStatus: fixedTraceEnum<FixedTraceTerminalStatus>()(["complete", "ignored", "reacted", "refusal", "truncated", "empty", "malformed", "provider_error", "timeout_after_dispatch", "unknown_exposure", "not_dispatched_budget", "not_admitted_architecture"]),
finishReason: fixedTraceNullableEnum<FixedTraceFinishReason>()(["stop", "tool_calls", "length", "refusal", "continue"]), outputSha256: { type: "nullable_sha256" },
},
usageAndPricing: {
Expand Down
94 changes: 79 additions & 15 deletions server/src/addie/eval/fixed-trace-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,6 @@ function fixedTraceCommonToolEnvironment(): FixedTraceCommonToolEnvironmentBindi
});
}

/**
* An evaluator-owned execution contract changed after its request snapshot was
* made. This is neither a provider failure nor a scored terminal outcome.
*/
class FixedTraceExecutionIdentityError extends Error {
constructor(message: string) {
super(message);
Expand Down Expand Up @@ -407,6 +403,43 @@ interface StageInvocationState {
latencyMs: number;
}

function providerExposures(
state: StageInvocationState,
response?: ModelResponse,
recordedExposures?: NonNullable<FixedTraceModelStageMetadata["providerExposures"]>,
): FixedTraceModelStageMetadata["providerExposures"] {
if (recordedExposures) return deepFreeze(recordedExposures.map((exposure) => ({ ...exposure })));
return deepFreeze(
state.invocations.map((prepared, index) => ({
attempt: index + 1,
preparedProvider: prepared.provider,
preparedModel: prepared.model,
returnedProvider:
response && index === state.invocations.length - 1 ? response.provider : null,
returnedModel:
response && index === state.invocations.length - 1 ? response.model : null,
})),
);
}

function hasCompleteReturnedProviderIdentities(
state: StageInvocationState,
response?: ModelResponse,
recordedExposures?: NonNullable<FixedTraceModelStageMetadata["providerExposures"]>,
): boolean {
const exposures = providerExposures(state, response, recordedExposures) ?? [];
return exposures.length === state.dispatchedCalls && exposures.every(
(exposure, index) => {
const prepared = state.invocations[index];
return exposure.attempt === index + 1 &&
prepared !== undefined &&
exposure.preparedProvider === prepared.provider &&
exposure.preparedModel === prepared.model &&
Boolean(exposure.returnedProvider && exposure.returnedModel);
},
);
}

function canonicalJson(value: unknown): string {
if (value === null || typeof value === 'boolean' || typeof value === 'string') return JSON.stringify(value);
if (typeof value === 'number') {
Expand Down Expand Up @@ -534,6 +567,7 @@ function providerStageMetadata(
response: ModelResponse,
usage: ModelUsage,
state: StageInvocationState,
recordedExposures?: NonNullable<FixedTraceModelStageMetadata["providerExposures"]>,
): FixedTraceModelStageMetadata {
// Provider responses are outside evaluator ownership. Retaining their usage
// object would let a later provider turn mutate already-recorded cost and
Expand All @@ -548,6 +582,7 @@ function providerStageMetadata(
requestedModel: config.model,
returnedProvider: response.provider,
returnedModel: response.model,
providerExposures: providerExposures(state, response, recordedExposures),
modelResolution: modelResolution(config, response),
promptSha256: promptSha256(request),
providerRequestSha256: providerRequestSha256(state.invocations),
Expand Down Expand Up @@ -575,6 +610,7 @@ function localStageMetadata(
config: FixedTraceProviderStageConfig,
state: StageInvocationState,
usage?: ModelUsage,
recordedExposures?: NonNullable<FixedTraceModelStageMetadata["providerExposures"]>,
): FixedTraceModelStageMetadata {
const recordedUsage = usage === undefined ? undefined : deepFreeze(structuredClone(usage));
return {
Expand All @@ -585,6 +621,7 @@ function localStageMetadata(
requestedModel: config.model,
returnedProvider: null,
returnedModel: null,
providerExposures: providerExposures(state, undefined, recordedExposures),
modelResolution: 'local',
promptSha256: promptSha256(request),
providerRequestSha256: providerRequestSha256(state.invocations),
Expand Down Expand Up @@ -615,6 +652,7 @@ function notRunStageMetadata(trace: FixedTraceCase): FixedTraceModelStageMetadat
requestedModel: null,
returnedProvider: null,
returnedModel: null,
providerExposures: Object.freeze([]),
modelResolution: null,
promptSha256: null,
providerRequestSha256: null,
Expand Down Expand Up @@ -986,7 +1024,9 @@ async function executeRouter(
const state = { invocations, dispatched, dispatchedCalls, latencyMs: Date.now() - startedAt };
const metadata = providerStageMetadata(request, config, response, response.usage, state);
const output = extractRouterResponseText(response.content);
const status = terminalStatusForFinishReason(response.finishReason, output);
const status = hasCompleteReturnedProviderIdentities(state, response)
? terminalStatusForFinishReason(response.finishReason, output)
: 'unknown_exposure';
if (status !== 'complete') return { request, response, plan: null, output, status, metadata };
try {
return {
Expand All @@ -1009,12 +1049,15 @@ async function executeRouter(
: timedOut && dispatched
? 'timeout_after_dispatch'
: 'provider_error';
const terminalStatus = dispatched && !hasCompleteReturnedProviderIdentities(state)
? 'unknown_exposure'
: status;
return {
request,
response: null,
plan: null,
output: fallbackOutput(status),
status,
output: fallbackOutput(terminalStatus),
status: terminalStatus,
metadata: localStageMetadata(request, config, state),
};
} finally {
Expand Down Expand Up @@ -1058,7 +1101,7 @@ export async function runFixedTraceCase(
if (architectureArm.id === 'direct_generation') {
// The evaluator's receipts and fixture facts are diagnostic only; an
// admission result can never open a direct-production dispatch path.
return {
return {
traceId: executionTrace.id,
metadata: baseMetadata(
executionTrace,
Expand All @@ -1077,8 +1120,8 @@ export async function runFixedTraceCase(
route: null,
tools: [],
rejectedToolCalls: [],
};
}
};
}
const hybridDecision = architectureArm.id === 'deterministic_policy_llm_fallback_hybrid'
? decideFixedTraceHybridRoute({
message: executionTrace.request.message,
Expand Down Expand Up @@ -1233,8 +1276,15 @@ export async function runFixedTraceCase(
result.response,
result.usage,
state,
result.providerExposures,
);
const terminalStatus = terminalStatusForFinishReason(result.response.finishReason, result.text);
const terminalStatus = hasCompleteReturnedProviderIdentities(
state,
undefined,
result.providerExposures,
) && returnedModelUsesRecordedPricing(generationConfig, result.response)
? terminalStatusForFinishReason(result.response.finishReason, result.text)
: 'unknown_exposure';
return {
traceId: executionTrace.id,
metadata: baseMetadata(executionTrace, executionConfig, toolSchemaSha256, routed.metadata, generation),
Expand Down Expand Up @@ -1262,21 +1312,35 @@ export async function runFixedTraceCase(
: timedOut && dispatched
? 'timeout_after_dispatch'
: 'provider_error';
const generation = localStageMetadata(generationRequest, generationConfig, {
const state = {
invocations,
dispatched,
dispatchedCalls,
latencyMs: Date.now() - startedAt,
}, checkpoint?.usage);
};
const generation = localStageMetadata(
generationRequest,
generationConfig,
state,
checkpoint?.usage,
checkpoint?.providerExposures,
);
const finalTerminalStatus = dispatched && !hasCompleteReturnedProviderIdentities(
state,
undefined,
checkpoint?.providerExposures,
)
? 'unknown_exposure'
: terminalStatus;
return {
traceId: executionTrace.id,
metadata: baseMetadata(executionTrace, executionConfig, toolSchemaSha256, routed.metadata, generation),
terminalStage: 'generation',
terminalStatus,
terminalStatus: finalTerminalStatus,
boundaryReason: error instanceof FixedTraceToolLoopBoundaryError ? error.reason : null,
localReplacementReason: null,
finishReason: null,
output: fallbackOutput(terminalStatus),
output: fallbackOutput(finalTerminalStatus),
flagged: true,
route,
tools: checkpoint ? [...checkpoint.tools] : [],
Expand Down
Loading
Loading