modelrig 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/adapters/anthropic.d.ts +4 -4
  3. package/dist/adapters/anthropic.d.ts.map +1 -1
  4. package/dist/adapters/anthropic.js +8 -1
  5. package/dist/adapters/deepseek.d.ts +2 -4
  6. package/dist/adapters/deepseek.d.ts.map +1 -1
  7. package/dist/adapters/deepseek.js +9 -5
  8. package/dist/adapters/gemini.d.ts +4 -1
  9. package/dist/adapters/gemini.d.ts.map +1 -1
  10. package/dist/adapters/gemini.js +16 -1
  11. package/dist/adapters/grok.d.ts +2 -4
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +8 -4
  14. package/dist/adapters/history-mapping.d.ts +126 -0
  15. package/dist/adapters/history-mapping.d.ts.map +1 -0
  16. package/dist/adapters/history-mapping.js +230 -0
  17. package/dist/adapters/openai-compat.d.ts +2 -4
  18. package/dist/adapters/openai-compat.d.ts.map +1 -1
  19. package/dist/adapters/openai-compat.js +9 -5
  20. package/dist/adapters/openai.d.ts +2 -4
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +8 -4
  23. package/dist/adapters/responses-api.d.ts.map +1 -1
  24. package/dist/adapters/responses-api.js +9 -4
  25. package/dist/artifacts/artifact.d.ts.map +1 -1
  26. package/dist/artifacts/artifact.js +79 -0
  27. package/dist/artifacts/conversation.d.ts +128 -0
  28. package/dist/artifacts/conversation.d.ts.map +1 -0
  29. package/dist/artifacts/conversation.js +172 -0
  30. package/dist/artifacts/index.d.ts +1 -1
  31. package/dist/artifacts/index.d.ts.map +1 -1
  32. package/dist/artifacts/media.d.ts +46 -0
  33. package/dist/artifacts/media.d.ts.map +1 -0
  34. package/dist/artifacts/media.js +88 -0
  35. package/dist/artifacts/run-context.d.ts +61 -2
  36. package/dist/artifacts/run-context.d.ts.map +1 -1
  37. package/dist/artifacts/run-context.js +183 -12
  38. package/dist/artifacts/scrub.d.ts +3 -0
  39. package/dist/artifacts/scrub.d.ts.map +1 -1
  40. package/dist/bakeoff/engine.d.ts.map +1 -1
  41. package/dist/bakeoff/engine.js +34 -7
  42. package/dist/bakeoff/eval-cases.d.ts +5 -0
  43. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  44. package/dist/bakeoff/eval-cases.js +10 -1
  45. package/dist/bakeoff/replay.d.ts +15 -0
  46. package/dist/bakeoff/replay.d.ts.map +1 -1
  47. package/dist/bakeoff/replay.js +19 -1
  48. package/dist/bakeoff/report.d.ts.map +1 -1
  49. package/dist/bakeoff/report.js +9 -2
  50. package/dist/bakeoff/turn-replay.d.ts +93 -0
  51. package/dist/bakeoff/turn-replay.d.ts.map +1 -0
  52. package/dist/bakeoff/turn-replay.js +150 -0
  53. package/dist/bakeoff/types.d.ts +12 -0
  54. package/dist/bakeoff/types.d.ts.map +1 -1
  55. package/dist/feedback/grade.d.ts +7 -2
  56. package/dist/feedback/grade.d.ts.map +1 -1
  57. package/dist/feedback/grade.js +14 -0
  58. package/dist/feedback/outcome.d.ts +103 -0
  59. package/dist/feedback/outcome.d.ts.map +1 -0
  60. package/dist/feedback/outcome.js +175 -0
  61. package/dist/gateway/history-bridge.d.ts +32 -0
  62. package/dist/gateway/history-bridge.d.ts.map +1 -0
  63. package/dist/gateway/history-bridge.js +64 -0
  64. package/dist/gateway/serve-client.d.ts.map +1 -1
  65. package/dist/gateway/serve-client.js +6 -0
  66. package/dist/index.d.ts +44 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +106 -6
  69. package/dist/init/observe.d.ts.map +1 -1
  70. package/dist/init/observe.js +3 -3
  71. package/dist/registry/build.d.ts +4 -0
  72. package/dist/registry/build.d.ts.map +1 -1
  73. package/dist/registry/generation-models.d.ts +68 -0
  74. package/dist/registry/generation-models.d.ts.map +1 -0
  75. package/dist/registry/generation-models.js +168 -0
  76. package/dist/registry/model-kind.d.ts +34 -0
  77. package/dist/registry/model-kind.d.ts.map +1 -0
  78. package/dist/registry/model-kind.js +40 -0
  79. package/dist/registry/pricing-shape.d.ts +29 -1
  80. package/dist/registry/pricing-shape.d.ts.map +1 -1
  81. package/dist/registry/pricing-shape.js +9 -0
  82. package/dist/registry/pricing.d.ts +26 -0
  83. package/dist/registry/pricing.d.ts.map +1 -1
  84. package/dist/registry/pricing.js +29 -0
  85. package/dist/registry/rows.d.ts.map +1 -1
  86. package/dist/registry/rows.js +3 -1
  87. package/dist/registry/unit-pricing.d.ts +32 -0
  88. package/dist/registry/unit-pricing.d.ts.map +1 -0
  89. package/dist/registry/unit-pricing.js +62 -0
  90. package/dist/routes/input-loader.d.ts +38 -0
  91. package/dist/routes/input-loader.d.ts.map +1 -0
  92. package/dist/routes/input-loader.js +80 -0
  93. package/dist/routes/loader.d.ts.map +1 -1
  94. package/dist/routes/loader.js +3 -0
  95. package/dist/routes/resolved-bundle.d.ts +2 -0
  96. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  97. package/dist/routes/resolved-bundle.js +3 -0
  98. package/dist/run/cost.d.ts +28 -0
  99. package/dist/run/cost.d.ts.map +1 -1
  100. package/dist/run/cost.js +74 -0
  101. package/dist/run/dispatch.d.ts +4 -1
  102. package/dist/run/dispatch.d.ts.map +1 -1
  103. package/dist/run/dispatch.js +3 -0
  104. package/dist/run/limits.d.ts +88 -0
  105. package/dist/run/limits.d.ts.map +1 -0
  106. package/dist/run/limits.js +155 -0
  107. package/dist/run/messages.d.ts +199 -0
  108. package/dist/run/messages.d.ts.map +1 -0
  109. package/dist/run/messages.js +430 -0
  110. package/dist/run/raw-finalize.d.ts +5 -2
  111. package/dist/run/raw-finalize.d.ts.map +1 -1
  112. package/dist/run/raw-finalize.js +8 -5
  113. package/dist/run/raw-request.d.ts.map +1 -1
  114. package/dist/run/raw-request.js +2 -1
  115. package/dist/run/raw.d.ts +10 -4
  116. package/dist/run/raw.d.ts.map +1 -1
  117. package/dist/run/raw.js +11 -3
  118. package/dist/run/record-attempt.d.ts.map +1 -1
  119. package/dist/run/record-attempt.js +16 -6
  120. package/dist/run/record-call.d.ts +13 -2
  121. package/dist/run/record-call.d.ts.map +1 -1
  122. package/dist/run/record-call.js +50 -7
  123. package/dist/run/run.d.ts.map +1 -1
  124. package/dist/run/run.js +12 -8
  125. package/dist/telemetry/attempt-meta.d.ts +49 -1
  126. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  127. package/dist/telemetry/attempt-meta.js +45 -1
  128. package/dist/telemetry/exporter.d.ts.map +1 -1
  129. package/dist/telemetry/exporter.js +2 -1
  130. package/dist/telemetry/grade-mappers.d.ts +1 -1
  131. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  132. package/dist/telemetry/grade-mappers.js +7 -1
  133. package/dist/telemetry/grade-task.d.ts +49 -0
  134. package/dist/telemetry/grade-task.d.ts.map +1 -0
  135. package/dist/telemetry/grade-task.js +85 -0
  136. package/dist/telemetry/grades-sql.d.ts +16 -1
  137. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  138. package/dist/telemetry/grades-sql.js +64 -4
  139. package/dist/telemetry/ingest-protocol.d.ts +24 -1
  140. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  141. package/dist/telemetry/ingest-protocol.js +72 -43
  142. package/dist/telemetry/otel-content.d.ts +31 -0
  143. package/dist/telemetry/otel-content.d.ts.map +1 -0
  144. package/dist/telemetry/otel-content.js +106 -0
  145. package/dist/telemetry/otel-hierarchy.d.ts +57 -0
  146. package/dist/telemetry/otel-hierarchy.d.ts.map +1 -0
  147. package/dist/telemetry/otel-hierarchy.js +155 -0
  148. package/dist/telemetry/otel-normalize.d.ts +15 -1
  149. package/dist/telemetry/otel-normalize.d.ts.map +1 -1
  150. package/dist/telemetry/otel-normalize.js +24 -39
  151. package/dist/telemetry/sinks.d.ts +4 -1
  152. package/dist/telemetry/sinks.d.ts.map +1 -1
  153. package/dist/telemetry/sinks.js +53 -34
  154. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  155. package/dist/telemetry/store-migrations.js +8 -0
  156. package/dist/telemetry/store.d.ts +9 -0
  157. package/dist/telemetry/store.d.ts.map +1 -1
  158. package/dist/telemetry/store.js +10 -0
  159. package/dist/telemetry/uniform-rows.d.ts +43 -0
  160. package/dist/telemetry/uniform-rows.d.ts.map +1 -0
  161. package/dist/telemetry/uniform-rows.js +139 -0
  162. package/dist/types/failures-teaching.d.ts.map +1 -1
  163. package/dist/types/failures-teaching.js +11 -0
  164. package/dist/types/failures.d.ts +1 -1
  165. package/dist/types/failures.d.ts.map +1 -1
  166. package/dist/types/failures.js +2 -0
  167. package/dist/types/provider.d.ts +12 -0
  168. package/dist/types/provider.d.ts.map +1 -1
  169. package/dist/types/route.d.ts +13 -0
  170. package/dist/types/route.d.ts.map +1 -1
  171. package/dist/types/telemetry.d.ts +14 -1
  172. package/dist/types/telemetry.d.ts.map +1 -1
  173. package/docs/agent-skill/SKILL.md +16 -3
  174. package/docs/how-it-fits.md +3 -3
  175. package/docs/instrumentation.md +15 -6
  176. package/docs/llms.txt +55 -7
  177. package/docs/mcp-oracle.md +15 -4
  178. package/docs/quickstart.md +6 -5
  179. package/package.json +4 -2
  180. package/registry/registry.json +456 -0
  181. package/templates/creative-approval.json +81 -0
  182. package/templates/support-conversation.json +82 -0
@@ -12,6 +12,7 @@ exports.formatDisagreementSummary = formatDisagreementSummary;
12
12
  exports.formatSwapDirection = formatSwapDirection;
13
13
  exports.printMatrix = printMatrix;
14
14
  exports.runEvalCaseBakeoffCli = runEvalCaseBakeoffCli;
15
+ const turn_replay_1 = require("./turn-replay");
15
16
  const node_fs_1 = require("node:fs");
16
17
  const node_os_1 = require("node:os");
17
18
  const node_path_1 = require("node:path");
@@ -75,7 +76,7 @@ function bakeoffHeadline(artifact) {
75
76
  : "cost n/a";
76
77
  const ceiling = fmtCeiling(artifact);
77
78
  const r = p.regret;
78
- return (`${p.variant} — regret ${fmtPts(r.regretMean)} pts ` +
79
+ return (`${p.variant}${arm?.armLabel !== undefined ? ` (${arm.armLabel})` : ""} — regret ${fmtPts(r.regretMean)} pts ` +
79
80
  `(CI ${fmtPts(r.regretCi95[0])}…${fmtPts(r.regretCi95[1])}, n=${r.nPaired}) vs reference ${ref.model}, ` +
80
81
  `${cost}${ceiling !== null ? ` vs ceiling ${ceiling}` : ""} — proposed`);
81
82
  }
@@ -178,7 +179,7 @@ function printMatrix(artifact, cacheAdjusted = []) {
178
179
  header.push("pairwise-win");
179
180
  const rows = artifact.variants.map((m) => {
180
181
  const base = [
181
- m.isReference === true ? `${m.variant} [ref]` : m.variant,
182
+ m.isReference === true ? `${m.variant} [ref]` : m.armLabel !== undefined ? `${m.variant} (${m.armLabel})` : m.variant, // ARC-β R3-1
182
183
  String(m.samples),
183
184
  `${(m.conformRate * 100).toFixed(0)}%`,
184
185
  `[${(m.conformCi95[0] * 100).toFixed(0)},${(m.conformCi95[1] * 100).toFixed(0)}]`,
@@ -225,6 +226,12 @@ function printMatrix(artifact, cacheAdjusted = []) {
225
226
  // byte-identical to the pre-N6 matrix output.
226
227
  for (const l of formatDisagreementSummary(artifact))
227
228
  console.log(l);
229
+ for (const m of artifact.variants)
230
+ for (const l of (0, turn_replay_1.formatIncomparable)(m.incomparable ?? []))
231
+ console.log(`[${m.variant}] ${l}`); // ARC-β M3(c)
232
+ for (const m of artifact.variants)
233
+ for (const l of (0, turn_replay_1.formatFallbackServed)(m.servedByFallback ?? []))
234
+ console.log(`[${m.variant}] ${l}`); // ARC-β R3-1
228
235
  // Cache-adjusted section (spec §5.4) — printed only when the caller supplied
229
236
  // simulator rows; absent ⇒ the matrix output is byte-identical to pre-WS-B.
230
237
  for (const l of formatCacheAdjustedSection(cacheAdjusted))
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Turn replay — ARC-β (agent-archetypes spec §4, builder-prompt-02 §1.7).
3
+ *
4
+ * "Would a cheaper model have answered THIS turn as well, given the same
5
+ * conversation?" A messages route's capture row holds the history up to and
6
+ * including the turn's last user/tool message (run/messages.ts capture codec)
7
+ * and the served reply. The bake-off replays that history against each
8
+ * candidate, which answers the FINAL turn only, graded by the route's existing
9
+ * grader path with the regret bound unchanged. Replay NEVER continues a
10
+ * conversation past the captured turn — there is no simulated next user turn
11
+ * (spec §9 refuses user simulators).
12
+ *
13
+ * M15 GUARD (unruled → conservative, roadmap §2): turn replay re-sends real
14
+ * end-user text to candidate models and graders, so it runs only for routes
15
+ * whose capture is EXPLICITLY on under the existing capture posture
16
+ * (`effectiveCapture`: the control-plane flip wins, else the YAML flag).
17
+ * Otherwise the bake-off refuses with a teaching 422.
18
+ */
19
+ import { RouteConfigError } from "../routes/loader";
20
+ import type { TaskSettingsResolver } from "../settings/task-settings";
21
+ import type { CaptureRow } from "../telemetry/capture";
22
+ import type { RouteBundle } from "../types/route";
23
+ import type { HistoryMessage } from "../run/messages";
24
+ import type { CaptureReplayInput } from "../run/messages";
25
+ import type { RigFailure } from "../types/failures";
26
+ import type { ReplaySampleResult } from "./replay";
27
+ /** The bake-off refused a messages route because capture is not explicitly on.
28
+ * A RouteConfigError (refused before any spend), carrying the HTTP status a
29
+ * server surface should answer with. */
30
+ export declare class TurnReplayRefusedError extends RouteConfigError {
31
+ readonly httpStatus = 422;
32
+ constructor(route: string);
33
+ }
34
+ /** Throw {@link TurnReplayRefusedError} for a messages route whose capture is
35
+ * not explicitly on; a no-op for every variables route (byte-identical). */
36
+ export declare function assertTurnReplayAllowed(route: Pick<RouteBundle, "route" | "inputKind" | "capture">, settings: TaskSettingsResolver | null): void;
37
+ /** One replayable turn of a captured conversation. */
38
+ export interface TurnReplayCase {
39
+ /** The route variables the system template renders from. */
40
+ readonly variables: Record<string, unknown>;
41
+ /** The conversation up to and including the turn's last user/tool message. */
42
+ readonly history: readonly HistoryMessage[];
43
+ /** The reply that was served for this turn (the capture's output). */
44
+ readonly reference: string;
45
+ /** The live call's tools (R2-1), when it declared any. */
46
+ readonly tools?: CaptureReplayInput["tools"];
47
+ readonly toolChoice?: CaptureReplayInput["toolChoice"];
48
+ }
49
+ /** The turn a capture row encodes, or null for a variables (non-messages) row. */
50
+ export declare function turnReplayCase(capture: Pick<CaptureRow, "variablesJson" | "outputText">): TurnReplayCase | null;
51
+ /** The incomparable marker for a replay failure, or null for an ordinary one. */
52
+ export declare function incomparableFrom(failure: RigFailure): {
53
+ readonly model: string;
54
+ readonly reason: string;
55
+ } | null;
56
+ /** One "n cases incomparable for <model>: reason" line's data. */
57
+ export interface IncomparableSummary {
58
+ readonly model: string;
59
+ readonly reason: string;
60
+ readonly n: number;
61
+ }
62
+ /**
63
+ * Split an arm's samples into the COMPARABLE ones (scored, paired for regret)
64
+ * and a per-(model, reason) count of the incomparable ones. An incomparable
65
+ * sample is neither a pass nor a failure: dropping it from both sides of the
66
+ * pairing keeps it from biasing the verdict.
67
+ */
68
+ export declare function splitIncomparable(samples: readonly ReplaySampleResult[]): {
69
+ readonly comparable: readonly ReplaySampleResult[];
70
+ readonly incomparable: readonly IncomparableSummary[];
71
+ };
72
+ /** The report lines: "n cases incomparable for <model>: reason". */
73
+ export declare function formatIncomparable(rows: readonly IncomparableSummary[]): string[];
74
+ /** The `history_skipped` value on a replayed call's telemetry rows, if any. */
75
+ export declare function skippedTagOf(rows: readonly {
76
+ readonly tagsJson: string;
77
+ }[]): string | undefined;
78
+ /** One "n cases served by <servedBy> (<skipped> skipped: <reason>)" row. */
79
+ export interface FallbackServed {
80
+ readonly servedBy: string;
81
+ /** The skipped candidates, `provider/model=code` comma-joined (the tag value). */
82
+ readonly skipped: string;
83
+ readonly n: number;
84
+ }
85
+ /** Count an arm's comparable samples answered by a fallback, grouped by
86
+ * (served model, skipped set). Empty when nothing was skipped. */
87
+ export declare function fallbackServed(samples: readonly ReplaySampleResult[]): FallbackServed[];
88
+ /** "<first> → <fallback…> (fallback served n/N)" — the label an arm's numbers
89
+ * carry when any of its cases was served by a fallback. */
90
+ export declare function fallbackArmLabel(first: string, rows: readonly FallbackServed[], total: number): string;
91
+ /** The report lines for an arm's fallback-served cases. */
92
+ export declare function formatFallbackServed(rows: readonly FallbackServed[]): string[];
93
+ //# sourceMappingURL=turn-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-replay.d.ts","sourceRoot":"","sources":["../../src/bakeoff/turn-replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * Turn replay — ARC-β (agent-archetypes spec §4, builder-prompt-02 §1.7).
4
+ *
5
+ * "Would a cheaper model have answered THIS turn as well, given the same
6
+ * conversation?" A messages route's capture row holds the history up to and
7
+ * including the turn's last user/tool message (run/messages.ts capture codec)
8
+ * and the served reply. The bake-off replays that history against each
9
+ * candidate, which answers the FINAL turn only, graded by the route's existing
10
+ * grader path with the regret bound unchanged. Replay NEVER continues a
11
+ * conversation past the captured turn — there is no simulated next user turn
12
+ * (spec §9 refuses user simulators).
13
+ *
14
+ * M15 GUARD (unruled → conservative, roadmap §2): turn replay re-sends real
15
+ * end-user text to candidate models and graders, so it runs only for routes
16
+ * whose capture is EXPLICITLY on under the existing capture posture
17
+ * (`effectiveCapture`: the control-plane flip wins, else the YAML flag).
18
+ * Otherwise the bake-off refuses with a teaching 422.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.TurnReplayRefusedError = void 0;
22
+ exports.assertTurnReplayAllowed = assertTurnReplayAllowed;
23
+ exports.turnReplayCase = turnReplayCase;
24
+ exports.incomparableFrom = incomparableFrom;
25
+ exports.splitIncomparable = splitIncomparable;
26
+ exports.formatIncomparable = formatIncomparable;
27
+ exports.skippedTagOf = skippedTagOf;
28
+ exports.fallbackServed = fallbackServed;
29
+ exports.fallbackArmLabel = fallbackArmLabel;
30
+ exports.formatFallbackServed = formatFallbackServed;
31
+ const loader_1 = require("../routes/loader");
32
+ const task_settings_1 = require("../settings/task-settings");
33
+ const messages_1 = require("../run/messages");
34
+ /** The bake-off refused a messages route because capture is not explicitly on.
35
+ * A RouteConfigError (refused before any spend), carrying the HTTP status a
36
+ * server surface should answer with. */
37
+ class TurnReplayRefusedError extends loader_1.RouteConfigError {
38
+ constructor(route) {
39
+ super(`turn replay refused for route "${route}": it serves conversations (input: messages), and replaying them ` +
40
+ "re-sends end-user text to candidate models — allowed only where capture is explicitly on. Turn capture on " +
41
+ "for this route (`capture: true`, or the console capture flip), let it record real turns, then re-run the " +
42
+ "bake-off (see messages-lane.md#turn-replay)");
43
+ this.httpStatus = 422;
44
+ this.name = "TurnReplayRefusedError";
45
+ }
46
+ }
47
+ exports.TurnReplayRefusedError = TurnReplayRefusedError;
48
+ /** Throw {@link TurnReplayRefusedError} for a messages route whose capture is
49
+ * not explicitly on; a no-op for every variables route (byte-identical). */
50
+ function assertTurnReplayAllowed(route, settings) {
51
+ if (route.inputKind !== "messages")
52
+ return;
53
+ if (!(0, task_settings_1.effectiveCapture)(settings, route.route, route.capture))
54
+ throw new TurnReplayRefusedError(route.route);
55
+ }
56
+ /** The turn a capture row encodes, or null for a variables (non-messages) row. */
57
+ function turnReplayCase(capture) {
58
+ const parsed = (0, messages_1.parseCaptureInput)(capture.variablesJson);
59
+ if (parsed.messages === undefined)
60
+ return null;
61
+ return {
62
+ variables: parsed.input,
63
+ history: parsed.messages,
64
+ reference: capture.outputText,
65
+ ...(parsed.tools !== undefined ? { tools: parsed.tools } : {}),
66
+ ...(parsed.toolChoice !== undefined ? { toolChoice: parsed.toolChoice } : {}),
67
+ };
68
+ }
69
+ // ── Incomparable cases (architect ruling M3(c), 2026-10-24) ──────────────────
70
+ /** The incomparable marker for a replay failure, or null for an ordinary one. */
71
+ function incomparableFrom(failure) {
72
+ if (!failure.message.startsWith(messages_1.HISTORY_INCOMPATIBLE_PREFIX))
73
+ return null;
74
+ const model = failure.provider !== undefined && failure.model !== undefined ? `${failure.provider}/${failure.model}` : "every candidate of the arm";
75
+ return { model, reason: failure.message.slice(messages_1.HISTORY_INCOMPATIBLE_PREFIX.length).replace(/^:\s*/, "") };
76
+ }
77
+ /**
78
+ * Split an arm's samples into the COMPARABLE ones (scored, paired for regret)
79
+ * and a per-(model, reason) count of the incomparable ones. An incomparable
80
+ * sample is neither a pass nor a failure: dropping it from both sides of the
81
+ * pairing keeps it from biasing the verdict.
82
+ */
83
+ function splitIncomparable(samples) {
84
+ const counts = new Map();
85
+ const comparable = [];
86
+ for (const s of samples) {
87
+ if (s.incomparable === undefined) {
88
+ comparable.push(s);
89
+ continue;
90
+ }
91
+ const key = `${s.incomparable.model}\u0000${s.incomparable.reason}`;
92
+ const prev = counts.get(key);
93
+ counts.set(key, { model: s.incomparable.model, reason: s.incomparable.reason, n: (prev?.n ?? 0) + 1 });
94
+ }
95
+ return { comparable, incomparable: [...counts.values()] };
96
+ }
97
+ /** The report lines: "n cases incomparable for <model>: reason". */
98
+ function formatIncomparable(rows) {
99
+ return rows.map((r) => `${r.n} case${r.n === 1 ? "" : "s"} incomparable for ${r.model}: ${r.reason}`);
100
+ }
101
+ // ── Fallback attribution (QA R3-1, 2026-10-24) ────────────────────────────────
102
+ // Under ruling R2-2 a multi-candidate arm whose first candidate cannot take a
103
+ // tool-loop history is served by a later one. The bake-off must say so — an
104
+ // arm's numbers are never presented under a model that never saw the turn.
105
+ /** The `history_skipped` value on a replayed call's telemetry rows, if any. */
106
+ function skippedTagOf(rows) {
107
+ for (const r of rows) {
108
+ try {
109
+ const v = JSON.parse(r.tagsJson)[messages_1.HISTORY_SKIPPED_TAG];
110
+ if (typeof v === "string" && v !== "")
111
+ return v;
112
+ }
113
+ catch {
114
+ // a malformed tags blob carries no skip
115
+ }
116
+ }
117
+ return undefined;
118
+ }
119
+ /** Count an arm's comparable samples answered by a fallback, grouped by
120
+ * (served model, skipped set). Empty when nothing was skipped. */
121
+ function fallbackServed(samples) {
122
+ const counts = new Map();
123
+ for (const s of samples) {
124
+ if (s.historySkipped === undefined || s.servedBy === undefined)
125
+ continue;
126
+ const key = `${s.servedBy}\u0000${s.historySkipped}`;
127
+ counts.set(key, { servedBy: s.servedBy, skipped: s.historySkipped, n: (counts.get(key)?.n ?? 0) + 1 });
128
+ }
129
+ return [...counts.values()];
130
+ }
131
+ /** "<first> → <fallback…> (fallback served n/N)" — the label an arm's numbers
132
+ * carry when any of its cases was served by a fallback. */
133
+ function fallbackArmLabel(first, rows, total) {
134
+ const servedBy = [...new Set(rows.map((r) => r.servedBy))].join(", ");
135
+ const n = rows.reduce((a, r) => a + r.n, 0);
136
+ return `${first} → ${servedBy} (fallback served ${n}/${total})`;
137
+ }
138
+ /** The report lines for an arm's fallback-served cases. */
139
+ function formatFallbackServed(rows) {
140
+ return rows.map((r) => {
141
+ const skipped = r.skipped
142
+ .split(",")
143
+ .map((entry) => {
144
+ const at = entry.lastIndexOf("=");
145
+ return at > 0 ? `${entry.slice(0, at)} skipped: ${entry.slice(at + 1)}` : `${entry} skipped`;
146
+ })
147
+ .join("; ");
148
+ return `${r.n} case${r.n === 1 ? "" : "s"} served by ${r.servedBy} (${skipped})`;
149
+ });
150
+ }
@@ -4,6 +4,7 @@
4
4
  * effective $ per 1K conformant outputs (F1-corrected); RB-1 adds per-sample
5
5
  * value accuracy, paired regret vs a reference arm, and an objective-aware gate.
6
6
  */
7
+ import type { FallbackServed, IncomparableSummary } from "./turn-replay";
7
8
  import type { ObjectiveMode } from "../types/route";
8
9
  import type { PairwiseStats } from "./pairwise";
9
10
  import type { DisagreementReport } from "./disagreement";
@@ -55,6 +56,17 @@ export interface VariantMetrics {
55
56
  * winning variant so the mirror/console read the artifact's decision rather
56
57
  * than recomputing a (mode-blind) winner client-side. */
57
58
  readonly proposed?: boolean;
59
+ /** ARC-β ruling M3(c): captured turns this arm's model could not accept
60
+ * natively — excluded from every number above and from regret; reported as
61
+ * "n cases incomparable for <model>: reason". Absent ⇒ none. */
62
+ readonly incomparable?: readonly IncomparableSummary[];
63
+ /** ARC-β R3-1: cases this arm's ladder answered with a FALLBACK because its
64
+ * earlier candidates were skipped for the history ("n cases served by
65
+ * <provider/model> (<skipped> skipped: <reason>)"). Absent ⇒ none. */
66
+ readonly servedByFallback?: readonly FallbackServed[];
67
+ /** ARC-β R3-1: present with `servedByFallback` — "<first> → <fallback>
68
+ * (fallback served n/N)", the label these numbers must be shown under. */
69
+ readonly armLabel?: string;
58
70
  /** RB-1: this arm's regret vs the reference; null on the reference arm and
59
71
  * when there is no reference. */
60
72
  readonly regret?: RegretStats | null;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  * (`rig.feedback` / `artifact.evaluate` / `run.evaluate`) and their adapters were
12
12
  * removed in GP-3 — every judgment now builds a grade directly.
13
13
  */
14
- import type { GradeCriterion, GradeRow, GradeSubject } from "../types/telemetry";
14
+ import type { GradeCriterion, GradeKind, GradeRow, GradeSubject } from "../types/telemetry";
15
15
  /** Options for `rig.grade(subject, opts)`. `score` is the 0–1 primitive and is
16
16
  * required; everything else is optional display / provenance. */
17
17
  export interface GradeOpts {
@@ -26,7 +26,9 @@ export interface GradeOpts {
26
26
  };
27
27
  /** Written guidance saved for a later improvement path (was note/explanation). */
28
28
  readonly comment?: string;
29
- readonly kind: "human" | "model-judge" | "deterministic";
29
+ /** `outcome` (ARC-3) is normally written through `rig.outcome`, which derives
30
+ * the subject, `grader = outcome:<name>` and the score for you. */
31
+ readonly kind: GradeKind;
30
32
  /** Optional specific grader name (e.g. "gpt-5-judge", "schema-check"). */
31
33
  readonly grader?: string;
32
34
  /** Provenance (e.g. "sdk", "console", a review-agent name). */
@@ -38,6 +40,9 @@ export interface GradeOpts {
38
40
  * the scrub + dedup/drop-empty-id/clamp guard at persist; this is carried
39
41
  * verbatim. */
40
42
  readonly criteria?: readonly GradeCriterion[];
43
+ /** ARC-3: the raw business value — allowed ONLY on `kind: "outcome"` (a value
44
+ * on any other kind throws: it would be refused server-side). */
45
+ readonly value?: number;
41
46
  /** The grade's own org — server-supplied ONLY, already authorized (T3). Omit
42
47
  * from application code (a rig has one org and the exporter knows it). */
43
48
  readonly orgId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../src/feedback/grade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjF;iEACiE;AACjE,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;6EACyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,eAAe,CAAC;IACzD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;mBAKe;IACf,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C;8EAC0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAQD;oFACoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CA2B9E;AAED;;+DAE+D;AAC/D,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,GAAG;IACzE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;CACzD,CAQA"}
1
+ {"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../src/feedback/grade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE5F;iEACiE;AACjE,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;6EACyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;uEACmE;IACnE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;mBAKe;IACf,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C;qEACiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAQD;oFACoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAoC9E;AAED;;+DAE+D;AAC/D,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,GAAG;IACzE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;CACzD,CAiBA"}
@@ -32,6 +32,11 @@ function buildGradeRow(subject, opts) {
32
32
  if (!Number.isFinite(opts.score) || opts.score < 0 || opts.score > 1) {
33
33
  throw new Error(`grade score must be in [0, 1], got ${opts.score}`);
34
34
  }
35
+ if (opts.value !== undefined && (opts.kind !== "outcome" || !Number.isFinite(opts.value))) {
36
+ throw new Error(opts.kind !== "outcome"
37
+ ? `grade value is only allowed on kind "outcome" (got kind "${opts.kind}") — put the number in score (0–1)`
38
+ : `outcome value must be a finite number, got ${String(opts.value)}`);
39
+ }
35
40
  const { scaleMax, scaleLabel } = scaleFields(opts.scale);
36
41
  return {
37
42
  id: (0, node_crypto_1.randomUUID)(),
@@ -51,6 +56,8 @@ function buildGradeRow(subject, opts) {
51
56
  // a single-score grade's row is byte-identical to pre-F4). Structural
52
57
  // normalization (dedup/clamp/scrub) is the server's job at persist.
53
58
  ...(opts.criteria !== undefined ? { criteria: opts.criteria } : {}),
59
+ // ARC-3: spread-iff so a non-outcome row is byte-identical to pre-ARC.
60
+ ...(opts.value !== undefined ? { value: opts.value } : {}),
54
61
  };
55
62
  }
56
63
  /** Build the rig's `grade` surface over a grade writer (the telemetry store).
@@ -59,6 +66,13 @@ function buildGradeRow(subject, opts) {
59
66
  function makeGradeApi(store) {
60
67
  return {
61
68
  grade(subject, opts) {
69
+ // L4: an outcome is written through rig.outcome (it derives the subject,
70
+ // the outcome:<name> grader, the score and the idempotent id) — a bare
71
+ // rig.grade({ kind: "outcome" }) would be dropped server-side, silently.
72
+ if (opts.kind === "outcome") {
73
+ throw new Error('rig.grade does not record outcomes — use rig.outcome({ key, name, score }) ' +
74
+ "(it files the outcome under outcome:<name> with an idempotent id)");
75
+ }
62
76
  const row = buildGradeRow(subject, opts);
63
77
  store.recordGrade(row);
64
78
  return row;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * ARC-3 — outcomes: the delayed business ground truth, written as a GRADE.
3
+ *
4
+ * An outcome (resolved, reopened, booked, approved, CTR …) arrives hours to
5
+ * weeks after the call that earned it. It is stored as ONE `grades` row with
6
+ * `kind: "outcome"`, `grader: "outcome:<name>"`, `score` 0–1 (the primitive the
7
+ * verdict is derived from) and an optional raw `value` (00089). No new table,
8
+ * no pending store: a conversation-keyed outcome is stored
9
+ * `subject_kind = "run"`, `subject_id = <episode_key>` (the 00070 precedent) and
10
+ * the console joins it lazily at read time — so an outcome may arrive before,
11
+ * during or after its telemetry and nothing is ever dropped.
12
+ *
13
+ * This module is the ONE definition of the key mapping, the name rule, the
14
+ * score derivation and the idempotent id — shared by `rig.outcome` (SDK),
15
+ * `POST /v1/outcomes` (server) and the oracle's `record_outcome` (MCP), so the
16
+ * three surfaces cannot disagree about what an outcome is.
17
+ *
18
+ * An outcome is a RECEIPT, never a calibration gate (ARC-D3): outcome agreement
19
+ * never moves a grader to CALIBRATED on its own.
20
+ */
21
+ import type { GradeRow, GradeSubject } from "../types/telemetry";
22
+ /** What the outcome is ABOUT. A `conversation` key is the caller's own
23
+ * conversation id (= the run's `episode_key`); the others are ModelRig ids. */
24
+ export type OutcomeKey = {
25
+ readonly run: string;
26
+ } | {
27
+ readonly conversation: string;
28
+ } | {
29
+ readonly inference: string;
30
+ } | {
31
+ readonly artifact: string;
32
+ };
33
+ export interface OutcomeInput {
34
+ readonly key: OutcomeKey;
35
+ /** `^[a-z][a-z0-9_.-]{0,63}$` — e.g. `resolved`, `meeting_booked`, `ctr.7d`. */
36
+ readonly name: string;
37
+ /** 0–1, or a boolean (true ⇒ 1, false ⇒ 0). Required unless `value` and
38
+ * `scale.max` are both given, in which case score = clamp(value / max, 0, 1). */
39
+ readonly score?: number | boolean;
40
+ /** The raw business number (revenue, CTR, handle time). Stored as-is. */
41
+ readonly value?: number;
42
+ /** Display scale; `max` also drives the score derivation from `value`. */
43
+ readonly scale?: {
44
+ readonly max: number;
45
+ } | {
46
+ readonly label: string;
47
+ };
48
+ /** When the outcome happened (ISO-8601). Default: now. */
49
+ readonly at?: string;
50
+ /** Retry-safe id: the same (org, name, idempotencyKey) always yields the same
51
+ * grade id — UUIDv5(org, `${name}:${idempotencyKey}`), identical on the SDK and
52
+ * the API paths — so a retry never duplicates, and two DIFFERENT outcome names
53
+ * sent under one key (one webhook event) both land. One key = one outcome per name. */
54
+ readonly idempotencyKey?: string;
55
+ /** Optional free-text note (scrubbed server-side like every grade comment). */
56
+ readonly comment?: string;
57
+ }
58
+ /** The name rule — lowercase identifier, ≤64 chars. */
59
+ export declare const OUTCOME_NAME_PATTERN: RegExp;
60
+ export declare const OUTCOME_GRADER_PREFIX = "outcome:";
61
+ /** A teaching validation failure (the server maps it to a 422). */
62
+ export declare class OutcomeValidationError extends Error {
63
+ readonly fixHint: string;
64
+ constructor(message: string, fixHint: string);
65
+ }
66
+ /** The grade subject an outcome key maps to. `conversation` → a RUN subject whose
67
+ * id is the episode key (00070 precedent; resolved lazily at read time). */
68
+ export declare function outcomeSubject(key: OutcomeKey): GradeSubject;
69
+ /** Validate the outcome name; returns the grader name `outcome:<name>`. */
70
+ export declare function outcomeGraderName(name: string): string;
71
+ /** Derive the 0–1 score: an explicit score (or boolean) wins; else value/scale.max
72
+ * clamped to [0, 1]; else a teaching error. */
73
+ export declare function deriveOutcomeScore(input: Pick<OutcomeInput, "score" | "value" | "scale">): number;
74
+ /** RFC 4122 UUIDv5 (SHA-1, name-based) — deterministic, so an idempotency key
75
+ * maps to the same grade id on every retry. `namespace` must be a UUID. */
76
+ export declare function uuidV5(name: string, namespace: string): string;
77
+ /**
78
+ * THE id function for an idempotent outcome (fix round 1, L3) — shared by
79
+ * `rig.outcome` (SDK), `POST /v1/outcomes` and `/v1/ingest`:
80
+ * UUIDv5(namespace = org id, name = `${name}:${idempotencyKey}`). The SDK does
81
+ * not know its org locally, so it mints a PROVISIONAL id (null org → a fixed
82
+ * namespace) and ships the idempotency key on the wire; the server re-derives
83
+ * the id with the key's org at ingest, so both doors land on the same row.
84
+ */
85
+ export declare function outcomeGradeId(orgId: string | null, name: string, idempotencyKey: string): string;
86
+ /** Build the outcome's GradeRow. `orgId` keys the idempotent id (and names the
87
+ * grade's org on a server write); `source` is provenance ("sdk", "outcomes-api", …). */
88
+ export declare function buildOutcomeGradeRow(input: OutcomeInput, ctx: {
89
+ readonly orgId?: string | null;
90
+ readonly source: string;
91
+ }): GradeRow;
92
+ /** `rig.outcome` — record one outcome locally; the existing grade exporter ships
93
+ * it (no new exporter). Returns the subject it was filed under. Throws an
94
+ * {@link OutcomeValidationError} on bad input (it is caller code, not telemetry). */
95
+ export declare function makeOutcomeApi(store: {
96
+ recordGrade(row: GradeRow): void;
97
+ }): {
98
+ outcome(input: OutcomeInput): {
99
+ readonly id: string;
100
+ readonly subject: GradeSubject;
101
+ };
102
+ };
103
+ //# sourceMappingURL=outcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../src/feedback/outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE;+EAC+E;AAC/E,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;qFACiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB;;;2FAGuF;IACvF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,QAA6B,CAAC;AAC/D,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAIhD,mEAAmE;AACnE,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK7C;AAED;4EAC4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,CA6B5D;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED;+CAC+C;AAC/C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,MAAM,CAyBjG;AAED;2EAC2E;AAC3E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAS9D;AAMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAGjG;AAcD;wFACwF;AACxF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/D,QAAQ,CA8BV;AAED;;qFAEqF;AACrF,wBAAgB,cAAc,CAAC,KAAK,EAAE;IAAE,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,GAAG;IAC3E,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;KAAE,CAAC;CACvF,CAQA"}