{"version":3,"sources":["../src/lib/appToolFailure.ts","../src/lib/dataSummary.ts","../src/utils/paramReaders.ts","../src/testing/conformance.ts","../src/testing/faultMatrix.ts","../src/testing/promptBudget.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["import type {\n  AppToolFailurePayload,\n  AppToolFailureResult,\n  AppToolResult,\n  AppToolResultExtra,\n  NextAction,\n} from '../types';\n\n// Written only by this builder — never smuggled in through `extra`.\nconst RESERVED_FAILURE_KEYS = new Set([\n  'success',\n  'error',\n  'outcome',\n  'message',\n  'toolName',\n  'code',\n  'nextAction',\n  'params',\n]);\n\n/**\n * Bounds an advertised wait. `delayMs: number` cannot express \"positive and sane\"\n * in the type, and both ends are harmful: a zero or negative delay reads as\n * \"call again immediately\", and an hour-long one strands the user while a page\n * fetch finishes.\n */\nexport const MIN_WAIT_DELAY_MS = 500;\nexport const MAX_WAIT_DELAY_MS = 60_000;\n\nexport function boundWaitDelay(nextAction: NextAction): NextAction {\n  if (nextAction.type !== 'WAIT') return nextAction;\n\n  const { delayMs } = nextAction;\n\n  if (!Number.isFinite(delayMs)) {\n    throw new RangeError('WAIT delayMs must be a finite number');\n  }\n\n  return {\n    type: 'WAIT',\n    delayMs: Math.min(\n      Math.max(Math.round(delayMs), MIN_WAIT_DELAY_MS),\n      MAX_WAIT_DELAY_MS,\n    ),\n  };\n}\n\nfunction sanitizeExtra(\n  extra: AppToolResultExtra | undefined,\n): AppToolResultExtra {\n  if (!extra) return {};\n\n  return Object.fromEntries(\n    Object.entries(extra).filter(([key]) => !RESERVED_FAILURE_KEYS.has(key)),\n  );\n}\n\n/**\n * A refusal the agent can act on rather than merely report.\n *\n * The request was understood and the app declined — a call that never executed\n * (a throw, a vanished iframe, a timeout) belongs to the transport's error\n * channel instead. `outcome` says which kind of refusal it is, `code` names the\n * specific one for evals and branching, `nextAction` names the only move that can\n * change the answer, and `message` is the half the agent quotes.\n */\nexport function appToolFailure(\n  message: string,\n  payload: AppToolFailurePayload,\n): AppToolFailureResult {\n  const { toolName, outcome, code, nextAction, params, extra } = payload;\n\n  return {\n    ...sanitizeExtra(extra),\n    success: false,\n    outcome,\n    toolName,\n    // `error` keeps the pre-contract runtime and the eval graders working.\n    error: message,\n    message,\n    code,\n    nextAction: boundWaitDelay(nextAction),\n    ...(params && { params }),\n  };\n}\n\n/**\n * A command whose writer was never wired up.\n *\n * Hosts pass setters optionally (`onSettingChange?.(key, value)`), so a tool can\n * be registered, do nothing, and report success — the worst outcome available,\n * because the agent tells the user a setting changed. This is a real dependency\n * failure and no argument can fix it.\n */\nexport function writerUnavailable({\n  toolName,\n  code,\n  writer,\n}: {\n  toolName: string;\n  code: string;\n  writer: string;\n}): AppToolResult {\n  return appToolFailure(\n    `This app instance cannot apply the change: its ${writer} callback is not wired up.`,\n    {\n      toolName,\n      code,\n      outcome: 'DEPENDENCY',\n      nextAction: { type: 'NONE' },\n      params: { writer },\n    },\n  );\n}\n","import type {\n  DataCoverage,\n  FieldSummary,\n  GoalSummary,\n  GroupSummary,\n  NumericSummary,\n  RatioSummary,\n  SampleRows,\n  WeightedSummary,\n} from '../types';\n\n/**\n * Default ceiling on rows returned to the agent. A data digest exists so the\n * agent does not have to page through raw rows; handing it thousands of them\n * blows the turn's context for no gain.\n */\nexport const DEFAULT_ROW_LIMIT = 200;\n\n// Serialized decimals only. `Number()` also accepts JavaScript literal syntax, so\n// a stray '0x10' would silently become 16 and '0b10' would become 2 — a data value\n// that looks like nothing of the sort must stay non-numeric.\nconst JSON_NUMBER = /^-?(0|[1-9]\\d*)(\\.\\d+)?([eE][+-]?\\d+)?$/;\n\nfunction toFiniteNumber(value: unknown): number | null {\n  if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n\n  // Numeric strings are routine in Corva payloads (unix timestamps, metric\n  // values serialized by the API), and dropping them would understate `count`.\n  if (typeof value === 'string') {\n    const trimmed = value.trim();\n\n    if (!JSON_NUMBER.test(trimmed)) return null;\n\n    const parsed = Number(trimmed);\n\n    return Number.isFinite(parsed) ? parsed : null;\n  }\n\n  return null;\n}\n\n/**\n * Aggregate raw values into one honest summary. `count` is how many values were\n * actually numeric — the agent needs it to say \"averaged over N\", and a blank\n * or non-numeric cell must not be counted as a zero.\n */\nexport function summarizeNumbers(values: readonly unknown[]): NumericSummary {\n  const numbers = values\n    .map(toFiniteNumber)\n    .filter((value): value is number => value !== null);\n\n  if (!numbers.length) {\n    return { count: 0, average: null, min: null, max: null, sum: 0 };\n  }\n\n  const sum = numbers.reduce((total, value) => total + value, 0);\n\n  return {\n    count: numbers.length,\n    average: sum / numbers.length,\n    min: Math.min(...numbers),\n    max: Math.max(...numbers),\n    sum,\n  };\n}\n\n/**\n * Weighted summary for a per-entity average — a column already holding \"average\n * connection time for this well\" cannot be averaged again across wells: wells\n * with 12 connections and 200 connections would count the same. Weighting by\n * the event count gives the figure a user computing by hand would get\n * (combined time ÷ combined event count).\n *\n * `weightedAverage` is null when no row carries both a value and a weight, so a\n * caller must fall back to `average` and say which basis it used.\n */\nexport function summarizeWeighted({\n  values,\n  weights,\n}: {\n  values: readonly unknown[];\n  weights: readonly unknown[];\n}): WeightedSummary {\n  const unweighted = summarizeNumbers(values);\n\n  const pairs = values\n    .map((value, index) => ({\n      value: toFiniteNumber(value),\n      weight: toFiniteNumber(weights[index]),\n    }))\n    .filter(\n      (pair): pair is { value: number; weight: number } =>\n        pair.value !== null && pair.weight !== null && pair.weight > 0,\n    );\n\n  // Values present but unweighable — reported, never absorbed into the figure.\n  const missingWeightCount = unweighted.count - pairs.length;\n\n  if (!pairs.length) {\n    return {\n      ...unweighted,\n      weightedAverage: null,\n      weightTotal: 0,\n      weightedCount: 0,\n      missingWeightCount,\n    };\n  }\n\n  const weightTotal = pairs.reduce((total, pair) => total + pair.weight, 0);\n  const weightedSum = pairs.reduce(\n    (total, pair) => total + pair.value * pair.weight,\n    0,\n  );\n\n  return {\n    ...unweighted,\n    weightedAverage: weightedSum / weightTotal,\n    weightTotal,\n    weightedCount: pairs.length,\n    missingWeightCount,\n  };\n}\n\n/**\n * The exact cross-entity average from a total and a count column:\n * sum(total) / sum(count). Prefer this over weighting per-entity averages —\n * `tripping_connection_time` / `tripping_connection_count` beats re-weighting\n * `tripping_connection_average`, because the means were already rounded and a\n * missing weight would drop the entity entirely.\n */\nexport function summarizeRatio({\n  rows,\n  numeratorField,\n  denominatorField,\n}: {\n  rows: readonly Record<string, unknown>[];\n  numeratorField: string;\n  denominatorField: string;\n}): RatioSummary {\n  const pairs = rows.map((row) => ({\n    numerator: toFiniteNumber(row[numeratorField]),\n    denominator: toFiniteNumber(row[denominatorField]),\n  }));\n\n  const isUsable = (pair: {\n    numerator: number | null;\n    denominator: number | null;\n  }): pair is { numerator: number; denominator: number } =>\n    pair.numerator !== null &&\n    pair.denominator !== null &&\n    pair.denominator > 0;\n\n  const usable = pairs.filter(isUsable);\n\n  // A row carrying one half of the ratio cannot contribute, and that is a gap in\n  // the figure rather than a row that never existed.\n  const missingPartCount = pairs.filter(\n    (pair) =>\n      !isUsable(pair) && (pair.numerator !== null || pair.denominator !== null),\n  ).length;\n\n  const numeratorTotal = usable.reduce(\n    (total, pair) => total + pair.numerator,\n    0,\n  );\n  const denominatorTotal = usable.reduce(\n    (total, pair) => total + pair.denominator,\n    0,\n  );\n\n  return {\n    ratio: denominatorTotal > 0 ? numeratorTotal / denominatorTotal : null,\n    numeratorTotal,\n    denominatorTotal,\n    contributingRowCount: usable.length,\n    missingPartCount,\n  };\n}\n\n/**\n * Per-field summaries over the rows the app currently has loaded.\n *\n * `weightOf` is the app's say in how a field must be aggregated: it names the\n * field carrying that field's weights (an event count, an interval length, a\n * stage count) or null for a plain mean. Only the app knows which of its metrics\n * are per-entity averages, while deciding what that means for the resulting\n * figure — and stating the basis it used — is the same everywhere, so it lives\n * here instead of being re-derived in every app.\n */\nexport function summarizeField({\n  rows,\n  field,\n  weightField,\n  ratioFields,\n}: {\n  rows: readonly Record<string, unknown>[];\n  field: string;\n  weightField?: string | null;\n  ratioFields?: { numerator: string; denominator: string } | null;\n}): FieldSummary {\n  const values = rows.map((row) => row[field]);\n  const base = summarizeNumbers(values);\n\n  if (ratioFields) {\n    const ratio = summarizeRatio({\n      rows,\n      numeratorField: ratioFields.numerator,\n      denominatorField: ratioFields.denominator,\n    });\n\n    if (ratio.ratio !== null) {\n      return {\n        ...base,\n        ...ratio,\n        ratio: ratio.ratio,\n        averageBasis: 'ratio',\n        numeratorField: ratioFields.numerator,\n        denominatorField: ratioFields.denominator,\n        isRatioComplete: ratio.missingPartCount === 0,\n      };\n    }\n  }\n\n  if (weightField) {\n    const weighted = summarizeWeighted({\n      values,\n      weights: rows.map((row) => row[weightField]),\n    });\n\n    if (weighted.weightedAverage !== null) {\n      return {\n        ...weighted,\n        weightedAverage: weighted.weightedAverage,\n        averageBasis: 'weights',\n        weightField,\n        isWeightComplete: weighted.missingWeightCount === 0,\n      };\n    }\n  }\n\n  // Neither exact path was available: an honest per-row mean, and it is the\n  // caller's job to say so when the field is a mean of means.\n  return { ...base, averageBasis: 'rows' };\n}\n\n/**\n * Per-field summaries over the rows the app currently has loaded.\n *\n * `ratioOf` and `weightOf` are the app's say in how a field must be aggregated,\n * in that order of preference: a total/count pair is exact, weighting a\n * per-entity average is a good approximation, and a plain mean is neither.\n * Only the app knows which of its metrics are per-entity averages, while what\n * that means for the resulting figure — and stating the basis it used — is the\n * same everywhere, so it lives here instead of in every app.\n */\nexport function summarizeFields({\n  rows,\n  fields,\n  weightOf,\n  weightBy,\n  ratioOf,\n}: {\n  rows: readonly Record<string, unknown>[];\n  fields: readonly string[];\n  weightOf?: (field: string) => string | null;\n  /** Simple form of `weightOf` for callers with a static mapping. */\n  weightBy?: Record<string, string>;\n  ratioOf?: (\n    field: string,\n  ) => { numerator: string; denominator: string } | null;\n}): Record<string, FieldSummary> {\n  return Object.fromEntries(\n    fields.map((field) => [\n      field,\n      summarizeField({\n        rows,\n        field,\n        weightField: weightOf?.(field) ?? weightBy?.[field] ?? null,\n        ratioFields: ratioOf?.(field) ?? null,\n      }),\n    ]),\n  );\n}\n\n/**\n * How much of what was asked the digest actually covers.\n *\n * `isComplete` is derived, never declared: it is the one field that separates\n * \"the average of everything shown\" from \"the average of page one\", and the\n * envelope reads it to decide READY against PARTIAL — so an app cannot claim\n * completeness over a set it only partly analysed. A named omission is itself\n * incompleteness, even when the counts happen to line up.\n */\nexport function buildCoverage({\n  requestedEntities,\n  analysedEntities,\n  requestedRows,\n  analysedRows,\n  omissions,\n}: {\n  requestedEntities: number;\n  analysedEntities: number;\n  requestedRows?: number;\n  analysedRows: number;\n  omissions?: readonly string[];\n}): DataCoverage {\n  const isValidCount = (value: number): boolean => Number.isInteger(value) && value >= 0;\n  const isRowsComplete = requestedRows === undefined || analysedRows === requestedRows;\n  const areEntityCountsValid =\n    isValidCount(requestedEntities) &&\n    isValidCount(analysedEntities) &&\n    analysedEntities <= requestedEntities;\n  const areRowCountsValid =\n    isValidCount(analysedRows) && (requestedRows === undefined ||\n      (isValidCount(requestedRows) && analysedRows <= requestedRows));\n\n  if (!areEntityCountsValid || !areRowCountsValid) {\n    throw new RangeError(\n      'coverage counts must be non-negative integers and analysed counts cannot exceed requested counts',\n    );\n  }\n\n  return {\n    requestedEntities,\n    analysedEntities,\n    ...(requestedRows !== undefined && { requestedRows }),\n    analysedRows,\n    isComplete:\n      analysedEntities === requestedEntities &&\n      isRowsComplete &&\n      !omissions?.length,\n    ...(omissions?.length && { omissions: [...omissions] }),\n  };\n}\n\n/**\n * Example rows, in their own block with their own counts.\n *\n * `totalAnalysed` is what the aggregates were computed over, `returned` is what\n * fitted in the turn, and `truncated` states the gap. Kept apart from the\n * aggregates because a capped list sitting beside them gets quoted as the\n * dataset — \"these are the wells\" about page one of forty.\n */\nexport function buildSampleRows<Row>({\n  rows,\n  totalAnalysed,\n  limit = DEFAULT_ROW_LIMIT,\n}: {\n  rows: readonly Row[];\n  totalAnalysed: number;\n  limit?: number;\n}): SampleRows<Row> {\n  if (!Number.isInteger(totalAnalysed) || totalAnalysed < 0) {\n    throw new RangeError('totalAnalysed must be a non-negative integer');\n  }\n\n  if (rows.length > totalAnalysed) {\n    throw new RangeError('sample rows cannot exceed totalAnalysed');\n  }\n\n  if (!Number.isFinite(limit)) {\n    throw new RangeError('sample limit must be finite');\n  }\n\n  const safeLimit = Math.max(0, Math.floor(limit));\n  const items = rows.slice(0, safeLimit);\n\n  return {\n    items,\n    returned: items.length,\n    totalAnalysed,\n    truncated: items.length < totalAnalysed,\n  };\n}\n\n/**\n * Same summaries, computed per group — \"average per well\", \"total hours per\n * operation\", \"ROP per hole section\". Grouping lives here because an agent\n * re-deriving it from returned rows can only see the rows that survived the row\n * cap, which silently turns a per-group figure into a partial one.\n *\n * Rows whose group field is empty are reported as `ungroupedRowCount` rather than\n * bucketed under a made-up key.\n */\nexport function summarizeGroups({\n  rows,\n  fields,\n  groupBy,\n  weightOf,\n  weightBy,\n  ratioOf,\n}: {\n  rows: readonly Record<string, unknown>[];\n  fields: readonly string[];\n  groupBy: string;\n  weightOf?: (field: string) => string | null;\n  weightBy?: Record<string, string>;\n  ratioOf?: (\n    field: string,\n  ) => { numerator: string; denominator: string } | null;\n}): { groups: GroupSummary[]; ungroupedRowCount: number } {\n  // Insertion-ordered, so groups come back in the order the data presented them\n  // instead of an alphabetical order nobody asked for.\n  const buckets = new Map<string, Record<string, unknown>[]>();\n  let ungroupedRowCount = 0;\n\n  rows.forEach((row) => {\n    const rawKey = row[groupBy];\n    const isGroupable =\n      typeof rawKey === 'string'\n        ? rawKey !== ''\n        : typeof rawKey === 'number' || typeof rawKey === 'boolean';\n\n    // An object would stringify to `[object Object]` and collapse unrelated rows\n    // into one bucket that reads like a real group.\n    if (!isGroupable) {\n      ungroupedRowCount += 1;\n\n      return;\n    }\n\n    const key = String(rawKey);\n    const bucket = buckets.get(key);\n\n    if (bucket) {\n      bucket.push(row);\n\n      return;\n    }\n\n    buckets.set(key, [row]);\n  });\n\n  const groups = [...buckets].map(([key, groupRows]) => ({\n    key,\n    rowCount: groupRows.length,\n    metrics: summarizeFields({\n      rows: groupRows,\n      fields,\n      weightOf,\n      weightBy,\n      ratioOf,\n    }),\n  }));\n\n  return { groups, ungroupedRowCount };\n}\n\n/**\n * A figure against the target the app displays for it. `direction` decides what\n * \"met\" means: a connection time of 4.2 beats a goal of 5, an ROP of 4.2 does\n * not, and the sign of the delta alone cannot tell them apart.\n */\nexport function summarizeGoal({\n  metric,\n  target,\n  actual,\n  direction,\n  source,\n}: {\n  metric: string;\n  target: number | null;\n  actual: number | null;\n  direction: GoalSummary['direction'];\n  source?: string;\n}): GoalSummary {\n  const isComparable = target !== null && actual !== null;\n  let isMet: boolean | null = null;\n\n  if (isComparable) {\n    isMet = direction === 'higher_is_better'\n      ? actual >= target\n      : actual <= target;\n  }\n\n  return {\n    metric,\n    target,\n    actual,\n    direction,\n    delta: isComparable ? actual - target : null,\n    isMet,\n    ...(source && { source }),\n  };\n}\n","/**\n * Typed readers for a tool's untyped `params` (`Record<string, unknown>`) — the\n * single ingress where the model's arguments are narrowed, so handlers stay\n * cast-free. Numbers are coerced from strings (the model sometimes serializes\n * numeric arguments as `\"123\"`).\n */\n\ntype Params = Record<string, unknown> | undefined;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function getString(params: Params, key: string): string | null {\n  const value = params?.[key];\n\n  return typeof value === 'string' ? value : null;\n}\n\nexport function getNumber(params: Params, key: string): number | null {\n  const value = params?.[key];\n\n  if (typeof value === 'number' && Number.isFinite(value)) return value;\n\n  if (typeof value === 'string' && value.trim()) {\n    const parsed = Number(value);\n\n    if (Number.isFinite(parsed)) return parsed;\n  }\n\n  return null;\n}\n\nexport function getBoolean(params: Params, key: string): boolean | null {\n  const value = params?.[key];\n\n  return typeof value === 'boolean' ? value : null;\n}\n\nexport function getRecord(params: Params, key: string): Record<string, unknown> | null {\n  const value = params?.[key];\n\n  return isRecord(value) ? value : null;\n}\n\n/** Numeric array with non-finite entries dropped; `[]` when absent or not an array. */\nexport function getNumberArray(params: Params, key: string): number[] {\n  const value = params?.[key];\n\n  if (!Array.isArray(value)) return [];\n\n  return value\n    .map(item => (typeof item === 'number' ? item : Number(item)))\n    .filter((item): item is number => Number.isFinite(item));\n}\n\n/** String array filtered to string entries; `[]` when absent or not an array. */\nexport function getStringArray(params: Params, key: string): string[] {\n  const value = params?.[key];\n\n  if (!Array.isArray(value)) return [];\n\n  return value.filter((item): item is string => typeof item === 'string');\n}\n\n/** Array filtered to entries that pass `guard`; `[]` when absent or not an array. */\nexport function getTypedArray<T>(\n  params: Params,\n  key: string,\n  guard: (value: unknown) => value is T,\n): T[] {\n  const value = params?.[key];\n\n  if (!Array.isArray(value)) return [];\n\n  return value.filter(guard);\n}\n","import { MAX_WAIT_DELAY_MS, MIN_WAIT_DELAY_MS } from '../lib/appToolFailure';\nimport { buildCoverage } from '../lib/dataSummary';\nimport type { AppTool, AppToolClass } from '../types';\nimport { isRecord } from '../utils/paramReaders';\n\n/** Sections every tool description carries, regardless of its class. */\nexport const COMMON_DESCRIPTION_SECTIONS = [\n  '[USE WHEN]',\n  '[DO NOT USE WHEN]',\n  '[RETURNS]',\n] as const;\n\n/**\n * Sections whose value depends on what the tool does. Requiring one universal\n * seven-section template produced boilerplate (`[READINESS]` on a command,\n * `[SIDE EFFECTS] None` on every read) and repeated the JSON parameter schema.\n */\nexport const DESCRIPTION_SECTIONS_BY_CLASS = {\n  SNAPSHOT_READ: ['[SCOPE]', '[READINESS]', '[LIMITATIONS]'],\n  UI_COMMAND: ['[CHANGES]', '[CONSTRAINTS]'],\n} as const satisfies Readonly<Record<AppToolClass, readonly string[]>>;\n\nexport function requiredDescriptionSections(toolClass: AppToolClass): readonly string[] {\n  return [\n    ...COMMON_DESCRIPTION_SECTIONS,\n    ...DESCRIPTION_SECTIONS_BY_CLASS[toolClass],\n  ];\n}\n\nconst DIGEST_BLOCKS = [\n  'scope',\n  'coverage',\n  'units',\n  'summary',\n  'provenance',\n] as const;\n\nconst DATA_OUTCOMES = new Set(['READY', 'PARTIAL', 'NOT_READY']);\nconst FAILURE_OUTCOMES = new Set([\n  'INVALID_INPUT',\n  'REJECTED',\n  'CONFLICT',\n  'DEPENDENCY',\n  'INTERNAL',\n]);\nconst NEXT_ACTION_TYPES = new Set([\n  'NONE',\n  'FIX_ARGUMENTS',\n  'ASK_USER',\n  'REFRESH_STATE',\n  'WAIT',\n  'CHANGE_SCOPE',\n  'CONFIGURE_APP',\n]);\n\n/** One contract breach, addressed to whoever wrote the tool. */\nexport type ConformanceViolation = {\n  tool: string;\n  rule: string;\n  detail: string;\n};\n\nfunction checkFailureToolName(\n  invokedToolName: string,\n  resultToolName: unknown,\n  violations: ConformanceViolation[],\n): void {\n  if (typeof resultToolName !== 'string' || !resultToolName.trim()) {\n    violations.push({\n      tool: invokedToolName,\n      rule: 'failure-tool-name-required',\n      detail: 'a refusal must carry the exact registered tool name that produced it',\n    });\n\n    return;\n  }\n\n  if (resultToolName === invokedToolName) return;\n\n  violations.push({\n    tool: invokedToolName,\n    rule: 'failure-tool-name-matches',\n    detail: `failure toolName ${resultToolName} does not match invoked tool ${invokedToolName}`,\n  });\n}\n\nfunction checkNextAction(\n  toolName: string,\n  nextAction: unknown,\n  violations: ConformanceViolation[],\n): void {\n  if (!isRecord(nextAction)) {\n    violations.push({\n      tool: toolName,\n      rule: 'next-action-required',\n      detail: 'a refusal or NOT_READY result must name the next legal move',\n    });\n\n    return;\n  }\n\n  const type = nextAction.type;\n\n  if (typeof type !== 'string' || !NEXT_ACTION_TYPES.has(type)) {\n    violations.push({\n      tool: toolName,\n      rule: 'next-action-vocabulary',\n      detail: `nextAction.type ${String(type)} is outside the closed vocabulary`,\n    });\n\n    return;\n  }\n\n  if (type !== 'WAIT') return;\n\n  const { delayMs } = nextAction;\n\n  // A WAIT with no bound invites the agent to invent a backoff, and nothing in\n  // the transport schedules the retry it implies.\n  if (\n    typeof delayMs !== 'number' ||\n    !Number.isFinite(delayMs) ||\n    delayMs < MIN_WAIT_DELAY_MS ||\n    delayMs > MAX_WAIT_DELAY_MS\n  ) {\n    violations.push({\n      tool: toolName,\n      rule: 'wait-delay-bounded',\n      detail: `WAIT must carry a delayMs within [${MIN_WAIT_DELAY_MS}, ${MAX_WAIT_DELAY_MS}], got ${String(delayMs)}`,\n    });\n  }\n}\n\nfunction checkCoverage(\n  toolName: string,\n  result: Record<string, unknown>,\n  violations: ConformanceViolation[],\n): void {\n  const coverage = result.coverage;\n\n  if (!isRecord(coverage)) {\n    violations.push({\n      tool: toolName,\n      rule: 'coverage-counts-valid',\n      detail: 'a digest must carry structured coverage counts',\n    });\n\n    return;\n  }\n\n  const {\n    requestedEntities,\n    analysedEntities,\n    requestedRows,\n    analysedRows,\n    omissions,\n  } = coverage;\n  const counts = [requestedEntities, analysedEntities, analysedRows];\n  const countsAreValid =\n    counts.every(value => Number.isInteger(value) && Number(value) >= 0) &&\n    (requestedRows === undefined ||\n      (Number.isInteger(requestedRows) && Number(requestedRows) >= 0)) &&\n    Number(analysedEntities) <= Number(requestedEntities) &&\n    (requestedRows === undefined ||\n      Number(analysedRows) <= Number(requestedRows));\n\n  if (!countsAreValid) {\n    violations.push({\n      tool: toolName,\n      rule: 'coverage-counts-valid',\n      detail:\n        'coverage counts must be non-negative integers and analysed counts cannot exceed requested counts',\n    });\n\n    return;\n  }\n\n  const derived = buildCoverage({\n    requestedEntities: Number(requestedEntities),\n    analysedEntities: Number(analysedEntities),\n    ...(requestedRows !== undefined && {\n      requestedRows: Number(requestedRows),\n    }),\n    analysedRows: Number(analysedRows),\n    ...(Array.isArray(omissions) && {\n      omissions: omissions.filter(\n        (item): item is string => typeof item === 'string',\n      ),\n    }),\n  });\n\n  if (\n    coverage.isComplete !== derived.isComplete ||\n    (result.outcome === 'READY') !== derived.isComplete\n  ) {\n    violations.push({\n      tool: toolName,\n      rule: 'outcome-matches-coverage',\n      detail:\n        'outcome and coverage.isComplete must match completeness derived from counts',\n    });\n  }\n}\n\nfunction checkDigest(\n  toolName: string,\n  result: Record<string, unknown>,\n  violations: ConformanceViolation[],\n): void {\n  DIGEST_BLOCKS.forEach((block) => {\n    if (!isRecord(result[block])) {\n      violations.push({\n        tool: toolName,\n        rule: 'digest-blocks-required',\n        detail: `a READY or PARTIAL digest must carry ${block}`,\n      });\n    }\n  });\n\n  checkCoverage(toolName, result, violations);\n\n  const sample = result.sampleRows;\n\n  if (!isRecord(sample)) return;\n\n  const { items, returned, totalAnalysed, truncated } = sample;\n\n  if (\n    !Array.isArray(items) ||\n    !Number.isInteger(returned) ||\n    !Number.isInteger(totalAnalysed) ||\n    Number(returned) < 0 ||\n    Number(totalAnalysed) < Number(returned) ||\n    items.length !== returned\n  ) {\n    violations.push({\n      tool: toolName,\n      rule: 'sample-counts-valid',\n      detail:\n        'sampleRows.returned must equal items.length and cannot exceed totalAnalysed',\n    });\n\n    return;\n  }\n\n  if (truncated !== (Number(returned) < Number(totalAnalysed))) {\n    violations.push({\n      tool: toolName,\n      rule: 'sample-truncation-honest',\n      detail:\n        'sampleRows.truncated must state whether returned is short of totalAnalysed',\n    });\n  }\n}\n\n/**\n * A command reports what it applied, and only that.\n *\n * The digest blocks are the read's vocabulary; a command carrying them is the\n * \"poses as a read\" mistake the class label exists to catch, and a command with\n * no `resultingState` leaves the agent to assume its write landed.\n */\nfunction checkCommandEcho(\n  toolName: string,\n  result: Record<string, unknown>,\n  violations: ConformanceViolation[],\n): void {\n  if (result.outcome !== 'READY') {\n    violations.push({\n      tool: toolName,\n      rule: 'command-outcome-binary',\n      detail: `a command applied its change or it refused; ${String(result.outcome)} is a read's outcome`,\n    });\n  }\n\n  if (result.resultingState === undefined) {\n    violations.push({\n      tool: toolName,\n      rule: 'command-echoes-state',\n      detail:\n        'a command returns the state actually applied, so the agent confirms the write it claims',\n    });\n  }\n\n  if ('changed' in result && !isRecord(result.changed)) {\n    violations.push({\n      tool: toolName,\n      rule: 'command-changed-is-a-diff',\n      detail: 'changed is the real diff of what moved — see diffSettings',\n    });\n  }\n\n  const posingAsRead = DIGEST_BLOCKS.filter((block) => block in result);\n\n  if (posingAsRead.length) {\n    violations.push({\n      tool: toolName,\n      rule: 'command-is-not-a-read',\n      detail: `a UI_COMMAND must not carry digest blocks (${posingAsRead.join(', ')}); the digest belongs to a SNAPSHOT_READ`,\n    });\n  }\n}\n\nfunction checkCancelledResult(\n  toolName: string,\n  record: Record<string, unknown>,\n): ConformanceViolation[] {\n  const violations: ConformanceViolation[] = [];\n\n  if (record.success !== true) {\n    violations.push({\n      tool: toolName,\n      rule: 'cancelled-is-success-shaped',\n      detail: 'a declined call is a completed execution, so success stays true',\n    });\n  }\n\n  if (record.executed !== false) {\n    violations.push({\n      tool: toolName,\n      rule: 'cancelled-states-not-executed',\n      detail: 'a cancelled result declares executed: false',\n    });\n  }\n\n  if (typeof record.code !== 'string' || !record.code) {\n    violations.push({\n      tool: toolName,\n      rule: 'cancelled-code-required',\n      detail: 'a cancelled result carries a stable code the agent can branch on',\n    });\n  }\n\n  return violations;\n}\n\n/**\n * Checks one result against the envelope. Returns the breaches rather than\n * throwing, so a suite can report every tool's findings in one pass.\n *\n * `toolClass` decides what a successful outcome has to look like, because\n * `READY` covers both a full digest and a command that did what it was asked —\n * and a command has no scope, coverage or units to report. Pass the class the\n * tool declares; without it only the shape-evident checks run.\n */\nexport function checkResultConformance(\n  toolName: string,\n  result: unknown,\n  toolClass?: AppToolClass,\n): ConformanceViolation[] {\n  const violations: ConformanceViolation[] = [];\n  if (!isRecord(result)) {\n    return [\n      {\n        tool: toolName,\n        rule: 'result-object-required',\n        detail: 'a tool result must be an object',\n      },\n    ];\n  }\n\n  const record = result;\n  const outcome = record.outcome;\n\n  // A cancelled call is neither a read nor a refusal: nothing ran, so the\n  // per-outcome field rules below have nothing to check.\n  if (outcome === 'CANCELLED') {\n    return checkCancelledResult(toolName, record);\n  }\n\n  const isDataOutcome =\n    typeof outcome === 'string' && DATA_OUTCOMES.has(outcome);\n  const isFailure =\n    typeof outcome === 'string' && FAILURE_OUTCOMES.has(outcome);\n\n  if (!isDataOutcome && !isFailure) {\n    violations.push({\n      tool: toolName,\n      rule: 'outcome-required',\n      detail: `every result carries exactly one known outcome, got ${String(outcome)}`,\n    });\n\n    return violations;\n  }\n\n  const expectedSuccess = !isFailure;\n\n  if (record.success !== expectedSuccess) {\n    violations.push({\n      tool: toolName,\n      rule: 'outcome-matches-success',\n      detail: `success ${String(record.success)} contradicts outcome ${outcome}`,\n    });\n  }\n\n  if (isFailure || outcome === 'NOT_READY') {\n    if (isFailure) checkFailureToolName(toolName, record.toolName, violations);\n\n    if (typeof record.message !== 'string' || !record.message.trim()) {\n      violations.push({\n        tool: toolName,\n        rule: 'message-required',\n        detail: 'a refusal or NOT_READY result must explain what happened',\n      });\n    }\n\n    if (typeof record.code !== 'string' || !record.code) {\n      violations.push({\n        tool: toolName,\n        rule: 'code-required',\n        detail: 'a refusal or NOT_READY result must carry a namespaced code',\n      });\n    }\n\n    checkNextAction(toolName, record.nextAction, violations);\n\n    return violations;\n  }\n\n  if (toolClass === 'UI_COMMAND') {\n    checkCommandEcho(toolName, record, violations);\n\n    return violations;\n  }\n\n  // A read that produces a file rather than a digest (`export-current-data`)\n  // fills neither block set, so the digest rules apply to a result that presents\n  // itself as one: PARTIAL is only ever `appToolData`, and a half-built digest is\n  // still worth catching under a class nobody declared.\n  const isDigest = record.outcome === 'PARTIAL' || DIGEST_BLOCKS.some((block) => block in record);\n\n  if (isDigest) checkDigest(toolName, record, violations);\n\n  // `resultingState` is not the marker — a settings read returns the snapshot\n  // under exactly that key. Reporting a diff is: a read moved nothing.\n  if (toolClass === 'SNAPSHOT_READ' && 'changed' in record) {\n    violations.push({\n      tool: toolName,\n      rule: 'read-changes-nothing',\n      detail: 'a SNAPSHOT_READ reported changed; a tool with a side effect is a UI_COMMAND',\n    });\n  }\n\n  return violations;\n}\n\nconst DOCUMENTATION_ENVELOPE_KEYS = new Set([\n  'success',\n  'outcome',\n  'message',\n  'error',\n  'code',\n  'toolName',\n  'nextAction',\n  // Written by `appToolCancelled`, never by a tool author, so demanding its prose\n  // would make every gated tool document a field it does not produce.\n  'executed',\n]);\n\nfunction collectCallerResultPaths(\n  value: unknown,\n  prefix: string,\n  paths: Set<string>,\n  nullable: Set<string>,\n): void {\n  if (Array.isArray(value)) {\n    value.forEach(item => collectCallerResultPaths(item, `${prefix}[]`, paths, nullable));\n    return;\n  }\n\n  if (!isRecord(value)) return;\n\n  Object.entries(value).forEach(([key, child]) => {\n    if (!prefix && DOCUMENTATION_ENVELOPE_KEYS.has(key)) return;\n\n    const path = prefix ? `${prefix}.${key}` : key;\n    if (Array.isArray(child)) {\n      if (!child.length || child.every(item => !isRecord(item))) paths.add(path);\n      child.forEach(item => collectCallerResultPaths(item, `${path}[]`, paths, nullable));\n      return;\n    }\n\n    if (isRecord(child)) {\n      if (!Object.keys(child).length) paths.add(path);\n      collectCallerResultPaths(child, path, paths, nullable);\n      return;\n    }\n\n    paths.add(path);\n    // An absent figure is the one thing a block description cannot imply for its\n    // members, and reading it as zero is the expensive mistake.\n    if (child === null) nullable.add(path);\n  });\n}\n\nfunction namedPathsIn(returns: string): string[] {\n  return returns.match(\n    /[A-Za-z_][A-Za-z0-9_]*(?:\\[\\])?(?:\\.[A-Za-z_][A-Za-z0-9_]*(?:\\[\\])?)*/g,\n  ) ?? [];\n}\n\nfunction parentPath(path: string): string | null {\n  const cut = path.lastIndexOf('.');\n\n  return cut > 0 ? path.slice(0, cut) : null;\n}\n\n/**\n * Documenting the block that directly contains a leaf covers that leaf. The cost of\n * `[RETURNS]` is then the number of blocks rather than the number of fields, so adding\n * a figure to a documented block costs nothing in the prompt every request pays for.\n * Only the immediate parent counts: a bare `summary` would otherwise excuse everything\n * beneath it, which is the enumeration problem inverted rather than solved.\n */\nfunction returnsCoversPath(returns: string, path: string, nullable: Set<string>): boolean {\n  const namedPaths = namedPathsIn(returns);\n\n  if (namedPaths.includes(path)) return true;\n  if (nullable.has(path)) return false;\n\n  const parent = parentPath(path);\n\n  if (!parent) return false;\n\n  return namedPaths.includes(parent);\n}\n\n/**\n * Compares every caller-visible path exercised by a result matrix with the\n * tool's `[RETURNS]` prose. It reports the complete missing-path set in one\n * pass, so adding one description key does not reveal the next key only after\n * another review round.\n *\n * Pass representative results for every success, refusal, partial, empty and\n * not-ready branch. Stable envelope fields are owned by the kit and excluded.\n * A tool-owned block such as `summary`, `resultingState` or `changed` is named\n * literally and then covers its own direct members; a member that came back `null`\n * in any exercised branch is named on its own line regardless.\n */\nexport function checkResultDocumentation(\n  tool: AppTool,\n  exercisedResults: readonly unknown[],\n): ConformanceViolation[] {\n  const description = tool.getSchema?.().description ?? tool.description;\n  const returnsIndex = description.lastIndexOf('[RETURNS]');\n  const returnsTail = returnsIndex >= 0\n    ? description.slice(returnsIndex + '[RETURNS]'.length)\n    : '';\n  const nextSection = returnsTail.search(/\\n\\[[A-Z][A-Z ]+\\]/);\n  const returns = nextSection >= 0 ? returnsTail.slice(0, nextSection) : returnsTail;\n  const paths = new Set<string>();\n  const nullable = new Set<string>();\n\n  exercisedResults.forEach(result => collectCallerResultPaths(result, '', paths, nullable));\n\n  return [...paths]\n    .filter(path => !returnsCoversPath(returns, path, nullable))\n    .sort()\n    .map(path => ({\n      tool: tool.name,\n      rule: 'description-returns-path',\n      detail: nullable.has(path)\n        ? `[RETURNS] must name ${path} on its own line and say when it is null`\n        : `[RETURNS] must name caller-visible path ${path} or the block that contains it`,\n    }));\n}\n\n/**\n * Checks the declarations a tool makes about itself, before any call.\n *\n * The class label is what makes the two structural mistakes visible — a snapshot\n * read that fetches, and a command that poses as a read — so an unlabelled tool\n * cannot be checked at all.\n */\nexport function checkToolConformance(tool: AppTool): ConformanceViolation[] {\n  const violations: ConformanceViolation[] = [];\n\n  if (!tool.toolClass) {\n    violations.push({\n      tool: tool.name,\n      rule: 'tool-class-declared',\n      detail: 'every frontend tool declares SNAPSHOT_READ or UI_COMMAND',\n    });\n  }\n\n  const description = tool.getSchema?.().description ?? tool.description;\n  const requiredSections = tool.toolClass\n    ? requiredDescriptionSections(tool.toolClass)\n    : COMMON_DESCRIPTION_SECTIONS;\n  const missing = requiredSections.filter(\n    (section) => !description.includes(section),\n  );\n\n  if (missing.length) {\n    violations.push({\n      tool: tool.name,\n      rule: 'description-sections',\n      detail: `description is missing ${missing.join(', ')}`,\n    });\n  }\n\n  if (description.includes('[GUIDANCE]')) {\n    violations.push({\n      tool: tool.name,\n      rule: 'description-guidance-section',\n      detail: '[GUIDANCE] must be distributed into routing or class-specific constraints',\n    });\n  }\n\n  if (/\\[RETURNS\\]\\s*\\{|\\b(?:string|number|boolean)\\[\\]/.test(description)) {\n    violations.push({\n      tool: tool.name,\n      rule: 'description-returns-syntax',\n      detail: '[RETURNS] must use plain language or valid JSON, not pseudo-TypeScript',\n    });\n  }\n\n  if (Boolean(tool.confirmationPolicy) !== Boolean(tool.confirmation)) {\n    violations.push({\n      tool: tool.name,\n      rule: 'confirmation-policy-coherence',\n      detail: 'confirmationPolicy and confirmation are declared together or not at all',\n    });\n  }\n\n  if (tool.confirmationPolicy && tool.toolClass === 'SNAPSHOT_READ') {\n    violations.push({\n      tool: tool.name,\n      rule: 'confirmation-on-read',\n      detail: 'a read that needs confirmation is mislabelled — gate UI_COMMAND tools',\n    });\n  }\n\n  return violations;\n}\n","export const APP_TOOL_FAULT_CAPABILITIES = [\n  'loaded-data',\n  'ui-command',\n  'host-round-trip',\n  'entity-scope',\n  'async-load',\n  'bounded-result',\n  'label-resolution',\n  'unit-decorated-labels',\n] as const;\n\nexport type AppToolFaultCapability = typeof APP_TOOL_FAULT_CAPABILITIES[number];\n\nexport const APP_TOOL_FAULT_CASES = [\n  'loading',\n  'loaded-empty',\n  'loaded-data',\n  'writer-unavailable',\n  'dropped-write',\n  'writer-rejects-after-partial',\n  'write-read-same-turn',\n  'two-writes-same-key',\n  'older-echo-after-newer-write',\n  'external-host-change',\n  'external-host-change-matches-older-intent',\n  'scope-switch',\n  'stale-entity-reference',\n  'scope-change-during-command',\n  'rejected-async-load',\n  'stale-loaded-data',\n  'result-cap-minus-one',\n  'result-cap-at-limit',\n  'result-cap-plus-one',\n  'multi-entity-cap-fairness',\n  'label-exact',\n  'label-unit-decorated',\n  'label-ambiguous',\n  'label-unknown',\n] as const;\n\nexport type AppToolFaultCase = typeof APP_TOOL_FAULT_CASES[number];\n\nconst CASES_BY_CAPABILITY = {\n  'loaded-data': ['loading', 'loaded-empty', 'loaded-data', 'stale-loaded-data'],\n  'ui-command': [\n    'writer-unavailable',\n    'dropped-write',\n    'writer-rejects-after-partial',\n    'write-read-same-turn',\n  ],\n  'host-round-trip': [\n    'two-writes-same-key',\n    'older-echo-after-newer-write',\n    'external-host-change',\n    'external-host-change-matches-older-intent',\n  ],\n  'entity-scope': [\n    'scope-switch',\n    'stale-entity-reference',\n    'scope-change-during-command',\n  ],\n  'async-load': ['rejected-async-load'],\n  'bounded-result': [\n    'result-cap-minus-one',\n    'result-cap-at-limit',\n    'result-cap-plus-one',\n    'multi-entity-cap-fairness',\n  ],\n  'label-resolution': [\n    'label-exact',\n    'label-ambiguous',\n    'label-unknown',\n  ],\n  'unit-decorated-labels': ['label-unit-decorated'],\n} as const satisfies Readonly<Record<AppToolFaultCapability, readonly AppToolFaultCase[]>>;\n\nexport type FaultMatrixViolation = {\n  rule: 'duplicate-case' | 'missing-case' | 'unknown-capability' | 'unknown-case';\n  detail: string;\n};\n\nexport type AppToolFaultProbe = {\n  case: string;\n  /** Executes the production-backed assertion for this exact defect family. */\n  run: () => void | Promise<void>;\n};\n\nfunction isFaultCapability(value: string): value is AppToolFaultCapability {\n  return APP_TOOL_FAULT_CAPABILITIES.some(capability => capability === value);\n}\n\nfunction isFaultCase(value: string): value is AppToolFaultCase {\n  return APP_TOOL_FAULT_CASES.some(testCase => testCase === value);\n}\n\nexport function requiredFaultCases(\n  capabilities: readonly AppToolFaultCapability[],\n): AppToolFaultCase[] {\n  const unknown = capabilities.find(capability => !isFaultCapability(capability));\n  if (unknown) throw new RangeError(`unknown fault-matrix capability ${String(unknown)}`);\n  return [...new Set(capabilities.flatMap(capability => CASES_BY_CAPABILITY[capability]))];\n}\n\n/** Checks declared test coverage; each app still supplies source-truth fixtures. */\nexport function checkFaultMatrixCoverage({\n  capabilities,\n  coveredCases,\n}: {\n  capabilities: readonly string[];\n  coveredCases: readonly string[];\n}): FaultMatrixViolation[] {\n  const violations: FaultMatrixViolation[] = [];\n  const invalidCapabilities = capabilities.filter(capability => !isFaultCapability(capability));\n  invalidCapabilities.forEach(capability => violations.push({\n    rule: 'unknown-capability',\n    detail: `unknown fault-matrix capability ${capability}`,\n  }));\n\n  const counts = new Map<AppToolFaultCase, number>();\n  coveredCases.forEach(testCase => {\n    if (!isFaultCase(testCase)) {\n      violations.push({\n        rule: 'unknown-case',\n        detail: `unknown fault case ${testCase}`,\n      });\n      return;\n    }\n    counts.set(testCase, (counts.get(testCase) ?? 0) + 1);\n  });\n  counts.forEach((count, testCase) => {\n    if (count > 1) violations.push({\n      rule: 'duplicate-case',\n      detail: `fault case ${testCase} is declared ${count} times`,\n    });\n  });\n\n  requiredFaultCases(\n    capabilities.filter(isFaultCapability),\n  ).forEach(testCase => {\n    if (!counts.has(testCase)) violations.push({\n      rule: 'missing-case',\n      detail: `required fault case ${testCase} is not covered`,\n    });\n  });\n\n  return violations;\n}\n\n/**\n * Validates the matrix and then executes every declared production-backed probe.\n * A declaration-only list is deliberately insufficient for the app gate.\n */\nexport async function runFaultMatrix({\n  capabilities,\n  probes,\n}: {\n  capabilities: readonly string[];\n  probes: readonly AppToolFaultProbe[];\n}): Promise<FaultMatrixViolation[]> {\n  const violations = checkFaultMatrixCoverage({\n    capabilities,\n    coveredCases: probes.map(probe => probe.case),\n  });\n  if (violations.length) return violations;\n\n  for (const probe of probes) await probe.run();\n  return [];\n}\n","import type { AppTool } from '../types';\n\nimport type { ConformanceViolation } from './conformance';\n\n/**\n * One tool, one ceiling. Set above the largest measured MANDATORY floor rather than at the\n * fleet average: parameter schemas are one line each already and `[RETURNS]` must cover the\n * whole caller-visible surface, so a ceiling under that floor would be unsatisfiable, and an\n * unsatisfiable gate gets waived rather than met.\n *\n * There is deliberately no toolset ceiling. An app with more surface legitimately has more\n * tools, and capping its total penalises functionality while fixing nothing: the cost that\n * matters is the dashboard's, where descriptors are attached per mounted instance and one\n * tool name - `get-current-state` exists in all 29 covered apps - repeats across every tile.\n * That is bounded by the host shipping a compact catalogue, not by any per-app number. The\n * toolset total is still measured, because the delivery log and that platform case both need\n * the figure.\n */\nexport const DEFAULT_TOOL_PROMPT_MAX_BYTES = 6000;\n\nexport type PromptBudget = {\n  perTool: Array<{ tool: string; bytes: number }>;\n  bytes: number;\n  maxToolBytes: number;\n  withinBudget: boolean;\n};\n\nconst encoder = new TextEncoder();\n\nfunction sizeOf(value: unknown): number {\n  if (value === null || value === undefined) return 0;\n  const serialized = typeof value === 'string' ? value : JSON.stringify(value);\n\n  return serialized === undefined ? 0 : encoder.encode(serialized).byteLength;\n}\n\n/**\n * Every byte the agent is billed for on each request: the schema the host serializes,\n * not the source that produced it. `getSchema()` wins where a tool builds its own,\n * because that is what actually ships.\n */\nexport function measurePromptBytes(tool: AppTool): number {\n  const schema = tool.getSchema?.();\n\n  return (\n    sizeOf(tool.name) +\n    sizeOf(schema?.description ?? tool.description) +\n    sizeOf(tool.shortDescription) +\n    sizeOf(tool.tags) +\n    sizeOf(schema?.parameters ?? tool.parameters)\n  );\n}\n\nexport function measureToolsetPromptBudget(\n  tools: readonly AppTool[],\n  { maxToolBytes = DEFAULT_TOOL_PROMPT_MAX_BYTES }: { maxToolBytes?: number } = {},\n): PromptBudget {\n  const perTool = tools.map((tool) => ({\n    tool: tool.name,\n    bytes: measurePromptBytes(tool),\n  }));\n\n  return {\n    perTool,\n    bytes: perTool.reduce((total, entry) => total + entry.bytes, 0),\n    maxToolBytes,\n    withinBudget: perTool.every((entry) => entry.bytes <= maxToolBytes),\n  };\n}\n\n/**\n * A description is a permanent tax on every request in the conversation, so an oversized\n * one is a contract breach like any other. The breach is per tool: the toolset total is\n * reported by `measureToolsetPromptBudget` for the record, but it is not a verdict.\n */\nexport function checkPromptBudget(\n  tools: readonly AppTool[],\n  options: { maxToolBytes?: number } = {},\n): ConformanceViolation[] {\n  const budget = measureToolsetPromptBudget(tools, options);\n\n  return budget.perTool\n    .filter((entry) => entry.bytes > budget.maxToolBytes)\n    .map((entry) => ({\n      tool: entry.tool,\n      rule: 'prompt-budget-per-tool',\n      detail: `prompt surface is ${entry.bytes} bytes, over the ${budget.maxToolBytes} ceiling`,\n    }));\n}\n"]}