modelrig 0.11.0 → 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 (199) hide show
  1. package/CHANGELOG.md +82 -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/cli.d.ts.map +1 -1
  56. package/dist/cli.js +1 -0
  57. package/dist/config.d.ts +18 -0
  58. package/dist/config.d.ts.map +1 -1
  59. package/dist/config.js +23 -3
  60. package/dist/doctor-preflight.d.ts.map +1 -1
  61. package/dist/doctor-preflight.js +6 -1
  62. package/dist/feedback/grade.d.ts +7 -2
  63. package/dist/feedback/grade.d.ts.map +1 -1
  64. package/dist/feedback/grade.js +14 -0
  65. package/dist/feedback/outcome.d.ts +103 -0
  66. package/dist/feedback/outcome.d.ts.map +1 -0
  67. package/dist/feedback/outcome.js +175 -0
  68. package/dist/gateway/history-bridge.d.ts +32 -0
  69. package/dist/gateway/history-bridge.d.ts.map +1 -0
  70. package/dist/gateway/history-bridge.js +64 -0
  71. package/dist/gateway/serve-client.d.ts.map +1 -1
  72. package/dist/gateway/serve-client.js +6 -0
  73. package/dist/index.d.ts +44 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +110 -9
  76. package/dist/init/init-cli.d.ts.map +1 -1
  77. package/dist/init/init-cli.js +21 -4
  78. package/dist/init/observe-cli.js +1 -1
  79. package/dist/init/observe.d.ts +7 -1
  80. package/dist/init/observe.d.ts.map +1 -1
  81. package/dist/init/observe.js +74 -22
  82. package/dist/init/starter.d.ts +36 -0
  83. package/dist/init/starter.d.ts.map +1 -0
  84. package/dist/init/starter.js +85 -0
  85. package/dist/registry/build.d.ts +4 -0
  86. package/dist/registry/build.d.ts.map +1 -1
  87. package/dist/registry/generation-models.d.ts +68 -0
  88. package/dist/registry/generation-models.d.ts.map +1 -0
  89. package/dist/registry/generation-models.js +168 -0
  90. package/dist/registry/model-kind.d.ts +34 -0
  91. package/dist/registry/model-kind.d.ts.map +1 -0
  92. package/dist/registry/model-kind.js +40 -0
  93. package/dist/registry/pricing-shape.d.ts +29 -1
  94. package/dist/registry/pricing-shape.d.ts.map +1 -1
  95. package/dist/registry/pricing-shape.js +9 -0
  96. package/dist/registry/pricing.d.ts +26 -0
  97. package/dist/registry/pricing.d.ts.map +1 -1
  98. package/dist/registry/pricing.js +29 -0
  99. package/dist/registry/rows.d.ts.map +1 -1
  100. package/dist/registry/rows.js +3 -1
  101. package/dist/registry/unit-pricing.d.ts +32 -0
  102. package/dist/registry/unit-pricing.d.ts.map +1 -0
  103. package/dist/registry/unit-pricing.js +62 -0
  104. package/dist/routes/input-loader.d.ts +38 -0
  105. package/dist/routes/input-loader.d.ts.map +1 -0
  106. package/dist/routes/input-loader.js +80 -0
  107. package/dist/routes/loader.d.ts.map +1 -1
  108. package/dist/routes/loader.js +3 -0
  109. package/dist/routes/resolved-bundle.d.ts +2 -0
  110. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  111. package/dist/routes/resolved-bundle.js +3 -0
  112. package/dist/run/cost.d.ts +28 -0
  113. package/dist/run/cost.d.ts.map +1 -1
  114. package/dist/run/cost.js +74 -0
  115. package/dist/run/dispatch.d.ts +4 -1
  116. package/dist/run/dispatch.d.ts.map +1 -1
  117. package/dist/run/dispatch.js +3 -0
  118. package/dist/run/limits.d.ts +88 -0
  119. package/dist/run/limits.d.ts.map +1 -0
  120. package/dist/run/limits.js +155 -0
  121. package/dist/run/messages.d.ts +199 -0
  122. package/dist/run/messages.d.ts.map +1 -0
  123. package/dist/run/messages.js +430 -0
  124. package/dist/run/raw-finalize.d.ts +5 -2
  125. package/dist/run/raw-finalize.d.ts.map +1 -1
  126. package/dist/run/raw-finalize.js +8 -5
  127. package/dist/run/raw-request.d.ts.map +1 -1
  128. package/dist/run/raw-request.js +2 -1
  129. package/dist/run/raw.d.ts +10 -4
  130. package/dist/run/raw.d.ts.map +1 -1
  131. package/dist/run/raw.js +11 -3
  132. package/dist/run/rawonly.d.ts +29 -5
  133. package/dist/run/rawonly.d.ts.map +1 -1
  134. package/dist/run/rawonly.js +54 -19
  135. package/dist/run/record-attempt.d.ts.map +1 -1
  136. package/dist/run/record-attempt.js +16 -6
  137. package/dist/run/record-call.d.ts +13 -2
  138. package/dist/run/record-call.d.ts.map +1 -1
  139. package/dist/run/record-call.js +50 -7
  140. package/dist/run/run.d.ts.map +1 -1
  141. package/dist/run/run.js +12 -8
  142. package/dist/telemetry/attempt-meta.d.ts +49 -1
  143. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  144. package/dist/telemetry/attempt-meta.js +45 -1
  145. package/dist/telemetry/exporter.d.ts.map +1 -1
  146. package/dist/telemetry/exporter.js +2 -1
  147. package/dist/telemetry/grade-mappers.d.ts +1 -1
  148. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  149. package/dist/telemetry/grade-mappers.js +7 -1
  150. package/dist/telemetry/grade-task.d.ts +49 -0
  151. package/dist/telemetry/grade-task.d.ts.map +1 -0
  152. package/dist/telemetry/grade-task.js +85 -0
  153. package/dist/telemetry/grades-sql.d.ts +16 -1
  154. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  155. package/dist/telemetry/grades-sql.js +64 -4
  156. package/dist/telemetry/ingest-protocol.d.ts +24 -1
  157. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  158. package/dist/telemetry/ingest-protocol.js +72 -43
  159. package/dist/telemetry/otel-content.d.ts +31 -0
  160. package/dist/telemetry/otel-content.d.ts.map +1 -0
  161. package/dist/telemetry/otel-content.js +106 -0
  162. package/dist/telemetry/otel-hierarchy.d.ts +57 -0
  163. package/dist/telemetry/otel-hierarchy.d.ts.map +1 -0
  164. package/dist/telemetry/otel-hierarchy.js +155 -0
  165. package/dist/telemetry/otel-normalize.d.ts +15 -1
  166. package/dist/telemetry/otel-normalize.d.ts.map +1 -1
  167. package/dist/telemetry/otel-normalize.js +24 -39
  168. package/dist/telemetry/sinks.d.ts +4 -1
  169. package/dist/telemetry/sinks.d.ts.map +1 -1
  170. package/dist/telemetry/sinks.js +53 -34
  171. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  172. package/dist/telemetry/store-migrations.js +8 -0
  173. package/dist/telemetry/store.d.ts +9 -0
  174. package/dist/telemetry/store.d.ts.map +1 -1
  175. package/dist/telemetry/store.js +10 -0
  176. package/dist/telemetry/uniform-rows.d.ts +43 -0
  177. package/dist/telemetry/uniform-rows.d.ts.map +1 -0
  178. package/dist/telemetry/uniform-rows.js +139 -0
  179. package/dist/types/failures-teaching.d.ts.map +1 -1
  180. package/dist/types/failures-teaching.js +11 -0
  181. package/dist/types/failures.d.ts +1 -1
  182. package/dist/types/failures.d.ts.map +1 -1
  183. package/dist/types/failures.js +2 -0
  184. package/dist/types/provider.d.ts +12 -0
  185. package/dist/types/provider.d.ts.map +1 -1
  186. package/dist/types/route.d.ts +13 -0
  187. package/dist/types/route.d.ts.map +1 -1
  188. package/dist/types/telemetry.d.ts +14 -1
  189. package/dist/types/telemetry.d.ts.map +1 -1
  190. package/docs/agent-skill/SKILL.md +122 -12
  191. package/docs/how-it-fits.md +4 -4
  192. package/docs/instrumentation.md +148 -44
  193. package/docs/llms.txt +143 -13
  194. package/docs/mcp-oracle.md +15 -4
  195. package/docs/quickstart.md +129 -16
  196. package/package.json +4 -2
  197. package/registry/registry.json +456 -0
  198. package/templates/creative-approval.json +81 -0
  199. package/templates/support-conversation.json +82 -0
@@ -26,7 +26,7 @@
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.normalizeOtlpTraces = normalizeOtlpTraces;
28
28
  exports.looksLikeLogsPayload = looksLikeLogsPayload;
29
- const node_crypto_1 = require("node:crypto");
29
+ const otel_hierarchy_1 = require("./otel-hierarchy");
30
30
  // ── value coercion (learnings §1.2, ported fresh) ────────────────────────────
31
31
  /** OTLP AnyValue → plain JS. Numeric strings coerce EXCEPT NaN/±Infinity which
32
32
  * stay as their string form (invalid JSON must never enter the envelope). int64
@@ -296,22 +296,6 @@ function classify(map) {
296
296
  return "chat";
297
297
  return "span";
298
298
  }
299
- // ── structure helpers ────────────────────────────────────────────────────────
300
- const WS_RE = /\s+/g;
301
- /** span name → step_key: trimmed, whitespace-collapsed, length-capped, never
302
- * empty (the step PK forbids it). */
303
- function toStepKey(name) {
304
- const raw = typeof name === "string" ? name.trim().replace(WS_RE, " ") : "";
305
- if (raw === "")
306
- return "span";
307
- return raw.slice(0, 200);
308
- }
309
- /** 32-hex trace id → canonical UUID string (runs.id is uuid; 16 bytes ≡ a UUID).
310
- * A trace id that is not 16 bytes gets a deterministic UUID derived from it. */
311
- function traceIdToRunUuid(traceHex) {
312
- const hex = traceHex.length === 32 ? traceHex : (0, node_crypto_1.createHash)("sha256").update(traceHex).digest("hex").slice(0, 32);
313
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
314
- }
315
299
  function bump(drops, reason, by = 1) {
316
300
  drops[reason] = (drops[reason] ?? 0) + by;
317
301
  }
@@ -374,7 +358,7 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
374
358
  }
375
359
  const { map, keys } = attrMap(span.attributes);
376
360
  const timing = resolveTiming(span, nowMs, drops);
377
- const runId = traceIdToRunUuid(traceHex);
361
+ const runId = (0, otel_hierarchy_1.traceIdToRunUuid)(traceHex);
378
362
  // ── envelope (identity/structure/usage ONLY; NO content) ──
379
363
  const operation = classify(map);
380
364
  const isAgentAggregate = AGENT_OPERATIONS.has(operation);
@@ -383,10 +367,14 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
383
367
  ? "unknown"
384
368
  : (firstAttr(map, PROVIDER_CHAIN) ?? "unknown");
385
369
  const usage = isAgentAggregate ? undefined : extractUsage(map);
370
+ const parentHex = toHexId(span.parentSpanId ?? span.parent_span_id, 8);
371
+ const operationClass = (0, otel_hierarchy_1.operationClassOf)(operation);
386
372
  const envelope = {
387
373
  model,
388
374
  provider,
389
375
  operation,
376
+ operation_class: operationClass,
377
+ parent_span_id: parentHex,
390
378
  ...(usage !== undefined ? { usage } : {}),
391
379
  scope: { name: scopeName, version: scopeVersion },
392
380
  };
@@ -397,7 +385,7 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
397
385
  const failureClass = statusCode === 2 ? "attested_error" : null;
398
386
  const step = {
399
387
  run_id: runId,
400
- step_key: toStepKey(span.name),
388
+ step_key: (0, otel_hierarchy_1.toStepKey)(span.name),
401
389
  attempt_no: 1, // assigned per (trace, step_key) below
402
390
  task: null,
403
391
  provenance: "attested",
@@ -413,6 +401,13 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
413
401
  const acc = runs.get(runId);
414
402
  const endMs = timing.endedAt !== null ? Date.parse(timing.endedAt) : timing.startMs;
415
403
  const episodeKey = firstAttr(map, EPISODE_CHAIN) ?? null;
404
+ const conversation = firstAttr(map, ["gen_ai.conversation.id"]) !== undefined;
405
+ const pendingStep = {
406
+ step,
407
+ stepKey: step.step_key,
408
+ startMs: timing.startMs,
409
+ span: { id: spanHex, parent: parentHex, operation, operationClass },
410
+ };
416
411
  if (acc === undefined) {
417
412
  runs.set(runId, {
418
413
  runId,
@@ -423,7 +418,8 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
423
418
  minStartMs: timing.startMs,
424
419
  maxEndMs: endMs,
425
420
  anyError: failureClass !== null,
426
- pending: [{ step, stepKey: step.step_key, startMs: timing.startMs }],
421
+ conversation,
422
+ pending: [pendingStep],
427
423
  });
428
424
  }
429
425
  else {
@@ -432,13 +428,17 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
432
428
  acc.anyError = acc.anyError || failureClass !== null;
433
429
  if (acc.episodeKey === null && episodeKey !== null)
434
430
  acc.episodeKey = episodeKey;
435
- acc.pending.push({ step, stepKey: step.step_key, startMs: timing.startMs });
431
+ acc.conversation = acc.conversation || conversation;
432
+ acc.pending.push(pendingStep);
436
433
  }
437
434
  }
438
435
  }
439
436
  }
440
437
  const runRows = [];
441
438
  for (const acc of runs.values()) {
439
+ // ARC-6: a conversation trace (gen_ai.conversation.id + invoke_agent spans)
440
+ // numbers its generations into ARC-α turn steps and marks the run.
441
+ const turned = acc.conversation ? (0, otel_hierarchy_1.numberTurns)(acc.pending) : { pending: acc.pending, turns: 0 };
442
442
  runRows.push({
443
443
  id: acc.runId,
444
444
  pipeline: acc.pipeline,
@@ -449,25 +449,10 @@ function normalizeOtlpTraces(data, nowMs = Date.now()) {
449
449
  ended_at: new Date(acc.maxEndMs).toISOString(),
450
450
  // The trace has completed (we only see finished spans); status is honest.
451
451
  status: acc.anyError ? "failed" : "succeeded",
452
+ ...(turned.turns > 0 ? { meta: { v: 1, trigger: "conversation" } } : {}),
452
453
  });
453
- // Assign attempt_no per (run, step_key): repeated same-named spans become
454
- // attempts in start-time (then span-id) order — deterministic within a
455
- // trace. v0 limitation: a trace split across batches is not deduped (no
456
- // seen-store); one trace = one batch is the common, correct case.
457
- const perKey = new Map();
458
- for (const p of acc.pending) {
459
- const list = perKey.get(p.stepKey) ?? [];
460
- list.push({ step: p.step, startMs: p.startMs });
461
- perKey.set(p.stepKey, list);
462
- }
463
- for (const list of perKey.values()) {
464
- list.sort((a, b) => a.startMs !== b.startMs
465
- ? a.startMs - b.startMs
466
- : (a.step.span_id ?? "") < (b.step.span_id ?? "")
467
- ? -1
468
- : 1);
469
- list.forEach(({ step }, i) => steps.push({ ...step, attempt_no: i + 1 }));
470
- }
454
+ // attempt_no per (run, step_key) — the hierarchy module (otel-hierarchy.ts).
455
+ steps.push(...(0, otel_hierarchy_1.assignAttempts)(turned.pending));
471
456
  }
472
457
  return { runs: runRows, steps, drops, spanCount, scopeNames: [...scopeNames] };
473
458
  }
@@ -57,7 +57,10 @@ export declare class SinkSendError extends Error {
57
57
  * poison-skip candidate). Derived once, from the status, so the classification
58
58
  * lives in exactly one place. */
59
59
  readonly retriable: boolean;
60
- constructor(message: string, status: number | undefined);
60
+ /** COV-1c: the server's 422 `reason_code` — a PostgREST/PG error CODE
61
+ * (`PGRST102`, `23514`) and nothing else, shape-checked; null when absent. */
62
+ readonly reasonCode: string | null;
63
+ constructor(message: string, status: number | undefined, reasonCode?: string | null);
61
64
  }
62
65
  export interface DirectSinkConfig {
63
66
  readonly supabaseUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../../src/telemetry/sinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAmBrE;6CAC6C;AAC7C,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AAED,MAAM,WAAW,aAAa;IAC5B;;yDAEqD;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAChC;;6EAEyE;IACzE,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;qCAEiC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS;CAMxD;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CA0CxE;AAMD,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;4EACwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC;;;wDAGoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD;;oDAEgD;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAsElE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,eAAe,GAAG,kBAAkB,EAClD,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACzC,aAAa,CA2Bf"}
1
+ {"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../../src/telemetry/sinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAc,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAmBrE;6CAC6C;AAC7C,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AAED,MAAM,WAAW,aAAa;IAC5B;;yDAEqD;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAChC;;6EAEyE;IACzE,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;qCAEiC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;kFAC8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,GAAE,MAAM,GAAG,IAAW;CAO1F;AAcD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CAkDxE;AAMD,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;4EACwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC;;;wDAGoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD;;oDAEgD;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CA4ElE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,eAAe,GAAG,kBAAkB,EAClD,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACzC,aAAa,CA2Bf"}
@@ -66,55 +66,72 @@ function __resetApiSinkWarningsForTest() {
66
66
  * gate), so the body a failed write returns is logged nowhere.
67
67
  */
68
68
  class SinkSendError extends Error {
69
- constructor(message, status) {
69
+ constructor(message, status, reasonCode = null) {
70
70
  super(message);
71
71
  this.name = "SinkSendError";
72
72
  if (status !== undefined)
73
73
  this.status = status;
74
74
  this.retriable = status === undefined || status >= 500 || status === 429;
75
+ this.reasonCode = reasonCode;
75
76
  }
76
77
  }
77
78
  exports.SinkSendError = SinkSendError;
79
+ /** The 422 body's `reason_code`, iff it is code-shaped — never any other body text. */
80
+ async function reasonCodeOf(response) {
81
+ if (response.status !== 422)
82
+ return null;
83
+ const body = (await response.json().catch(() => null));
84
+ const code = body?.reason_code;
85
+ return typeof code === "string" && /^(PGRST\d{3}|[0-9A-Z]{5})$/.test(code) ? code : null;
86
+ }
78
87
  function createDirectSink(config) {
79
88
  const base = config.supabaseUrl.replace(/\/$/, "");
80
89
  const fetchFn = config.fetchFn ?? fetch;
81
90
  return {
82
91
  mode: "direct",
83
92
  async send(batch) {
84
- for (const write of (0, ingest_protocol_1.stampBatch)(batch, config.tenancy)) {
85
- let response;
86
- try {
87
- response = await fetchFn(
88
- // S5: the upsert names a real unique constraint. A single-argument
89
- // upsert targets the primary key and 23505s later.
90
- `${base}/rest/v1/${write.table}?on_conflict=${encodeURIComponent(write.onConflict)}`, {
91
- method: "POST",
92
- headers: {
93
- "content-type": "application/json",
94
- apikey: config.serviceRoleKey,
95
- authorization: `Bearer ${config.serviceRoleKey}`,
96
- prefer: write.merge
97
- ? "resolution=merge-duplicates,return=minimal"
98
- : "resolution=ignore-duplicates,return=minimal",
99
- },
100
- body: JSON.stringify(write.rows),
101
- });
102
- }
103
- catch {
104
- // Network-level throw — no status, so TRANSIENT (the exporter retries
105
- // forever). The raw error can carry the control-plane host, so it is
106
- // NOT echoed (C2); the table + "network error" is the whole message.
107
- throw new SinkSendError(`sink POST ${write.table} → network error`, undefined);
108
- }
109
- if (!response.ok) {
110
- // Status + table only — never the PostgREST body (it carries the
111
- // control-plane host and, on some failures, header fragments; C2).
112
- // The status is what the exporter classifies on (spec §2.3.1).
113
- throw new SinkSendError(`sink POST ${write.table} → ${response.status}`, response.status);
114
- }
115
- }
93
+ await sendWrites((0, ingest_protocol_1.stampBatch)(batch, config.tenancy));
94
+ // GRADE-RAW S2-1: step-task registration is best-effort and AFTER the
95
+ // batch landed — a refused registration never fails (or re-sends) it.
96
+ const registration = (0, ingest_protocol_1.stepTaskRegistration)(batch, config.tenancy);
97
+ if (registration !== null)
98
+ await sendWrites([registration]).catch(() => undefined);
116
99
  },
117
100
  };
101
+ async function sendWrites(writes) {
102
+ for (const write of writes) {
103
+ let response;
104
+ try {
105
+ response = await fetchFn(
106
+ // S5: the upsert names a real unique constraint. A single-argument
107
+ // upsert targets the primary key and 23505s later.
108
+ `${base}/rest/v1/${write.table}?on_conflict=${encodeURIComponent(write.onConflict)}`, {
109
+ method: "POST",
110
+ headers: {
111
+ "content-type": "application/json",
112
+ apikey: config.serviceRoleKey,
113
+ authorization: `Bearer ${config.serviceRoleKey}`,
114
+ prefer: write.merge
115
+ ? "resolution=merge-duplicates,return=minimal"
116
+ : "resolution=ignore-duplicates,return=minimal",
117
+ },
118
+ body: JSON.stringify(write.rows),
119
+ });
120
+ }
121
+ catch {
122
+ // Network-level throw — no status, so TRANSIENT (the exporter retries
123
+ // forever). The raw error can carry the control-plane host, so it is
124
+ // NOT echoed (C2); the table + "network error" is the whole message.
125
+ throw new SinkSendError(`sink POST ${write.table} → network error`, undefined);
126
+ }
127
+ if (!response.ok) {
128
+ // Status + table only — never the PostgREST body (it carries the
129
+ // control-plane host and, on some failures, header fragments; C2).
130
+ // The status is what the exporter classifies on (spec §2.3.1).
131
+ throw new SinkSendError(`sink POST ${write.table} → ${response.status}`, response.status);
132
+ }
133
+ }
134
+ }
118
135
  }
119
136
  /** Below this, gzip costs more than it saves. */
120
137
  const GZIP_MIN_BYTES = 1024;
@@ -166,7 +183,9 @@ function createApiSink(config) {
166
183
  // someone else's log aggregator. The status is the exporter's transient-
167
184
  // vs-poison signal: the server sends 422 for a deterministic control-plane
168
185
  // rejection, 503 for a transient one (spec §2.2/§2.3.1).
169
- throw new SinkSendError(`ingest POST → ${response.status}`, response.status);
186
+ // COV-1c: a 422 may name the PostgREST/PG code (shape-checked, code only).
187
+ const reasonCode = await reasonCodeOf(response);
188
+ throw new SinkSendError(`ingest POST → ${response.status}${reasonCode !== null ? ` (${reasonCode})` : ""}`, response.status, reasonCode);
170
189
  }
171
190
  // A good send clears the auth-failure latch, so a later key breakage
172
191
  // re-alerts instead of staying silent behind a once-per-process guard.
@@ -1 +1 @@
1
- {"version":3,"file":"store-migrations.d.ts","sourceRoot":"","sources":["../../src/telemetry/store-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C;sCACsC;AACtC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAgM9D"}
1
+ {"version":3,"file":"store-migrations.d.ts","sourceRoot":"","sources":["../../src/telemetry/store-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C;sCACsC;AACtC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAwM9D"}
@@ -206,4 +206,12 @@ function runStoreMigrations(db) {
206
206
  }
207
207
  db.pragma("user_version = 16");
208
208
  }
209
+ // v17 (ARC-3, mirrors 00089): `grades.kind` gains 'outcome' (table rebuild —
210
+ // SQLite cannot ALTER a CHECK) and the nullable `value` column. The rebuild
211
+ // copies each row's ROWID too, so the exporter's `:grades` rowid watermark is
212
+ // exactly as valid after the rung as before. See migrateGradesToV17.
213
+ if (db.pragma("user_version", { simple: true }) < 17) {
214
+ (0, grades_sql_1.migrateGradesToV17)(db);
215
+ db.pragma("user_version = 17");
216
+ }
209
217
  }
@@ -52,6 +52,15 @@ export interface ExportableTelemetryStore extends TelemetryStore {
52
52
  * non-null inference_id, or null (E4 chunk A: bake-off rehydration resolves an
53
53
  * eval case's source step to the inference that keys its captured input). */
54
54
  getStepInferenceId(runId: string, stepKey: string): string | null;
55
+ /** ARC-1 conversation re-entry: the newest `running` run for (pipeline,
56
+ * episode_key) in THIS local store, with its recorded step keys (the caller
57
+ * derives the turn counter from `turn:<n>.<k>` keys), or null. Local only —
58
+ * cross-process re-entry is out of scope (duplicate runs per episode are
59
+ * tolerated, 00031). */
60
+ findOpenRun?(pipeline: string, episodeKey: string): {
61
+ readonly row: RunRow;
62
+ readonly stepKeys: readonly string[];
63
+ } | null;
55
64
  listEnvelopes(): EnvelopeRow[];
56
65
  /** Local exporter watermark (per source instance), 0 when unset. */
57
66
  getExportWatermark(sourceId: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,EACP,YAAY,EACZ,cAAc,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAA2B,KAAK,cAAc,EAAuB,MAAM,UAAU,CAAC;AAU7F;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC9D,qEAAqE;IACrE,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACpG;kEAC8D;IAC9D,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjC;;wDAEoD;IACpD,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IAC5F;mEAC+D;IAC/D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxF,mEAAmE;IACnE,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1F;;iFAE6E;IAC7E,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClE,aAAa,IAAI,WAAW,EAAE,CAAC;IAC/B,oEAAoE;IACpE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;CAC3E;AAgSD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CA6P3E"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,EACP,YAAY,EACZ,cAAc,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAA2B,KAAK,cAAc,EAAuB,MAAM,UAAU,CAAC;AAU7F;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC9D,qEAAqE;IACrE,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACpG;kEAC8D;IAC9D,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjC;;wDAEoD;IACpD,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IAC5F;mEAC+D;IAC/D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxF,mEAAmE;IACnE,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1F;;iFAE6E;IAC7E,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClE;;;;4BAIwB;IACxB,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1H,aAAa,IAAI,WAAW,EAAE,CAAC;IAC/B,oEAAoE;IACpE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;CAC3E;AAgSD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAyQ3E"}
@@ -403,6 +403,16 @@ function openTelemetryStore(dbPath) {
403
403
  .all(afterRowid, limit);
404
404
  return rows.map((r) => ({ rowid: r.__rowid, row: toStepRow(r) }));
405
405
  },
406
+ findOpenRun(pipeline, episodeKey) {
407
+ const r = db
408
+ .prepare("SELECT * FROM runs WHERE pipeline = ? AND episode_key = ? AND status = 'running' " +
409
+ "ORDER BY started_at DESC LIMIT 1")
410
+ .get(pipeline, episodeKey);
411
+ if (r === undefined)
412
+ return null;
413
+ const keys = db.prepare("SELECT DISTINCT step_key FROM steps WHERE run_id = ?").all(r.id);
414
+ return { row: toRunRow(r), stepKeys: keys.map((k) => k.step_key) };
415
+ },
406
416
  getStepInferenceId(runId, stepKey) {
407
417
  const r = db
408
418
  .prepare("SELECT inference_id FROM steps WHERE run_id = ? AND step_key = ? AND inference_id IS NOT NULL " +
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Uniform key sets for `stampBatch`'s bulk writes (COV-1c). Extracted from
3
+ * ingest-protocol.ts so stampBatch stays under the LOC cap (P1).
4
+ *
5
+ * A PostgREST bulk insert requires every row in the array to carry the SAME
6
+ * keys — one row that omits a key the others carry fails the whole POST with
7
+ * PGRST102 ("All object keys must match") before any SQL runs. The wire is
8
+ * spread-iff-present by design (an older SDK's payload stays byte-identical),
9
+ * so rows in one batch legitimately differ: a web-search surcharge row carries
10
+ * `meta: null` (no `meta` key after validation) beside metered rows that carry
11
+ * one. Two remedies, chosen by the write's conflict policy:
12
+ *
13
+ * - merge=false (an immutable fact: inferences, steps, artifacts) — FILL every
14
+ * absent optional column with its DB default (null, or the NOT NULL column's
15
+ * default). Safe because ignore-duplicates never rewrites a landed row.
16
+ * - merge=true (a mutable projection: tasks, runs) — SPLIT into one write per
17
+ * key set (the routes precedent). Filling would null out a column a merge
18
+ * upsert must leave untouched.
19
+ *
20
+ * `undefined` values count as absent: JSON.stringify drops the key, so a row
21
+ * holding `{ meta: undefined }` reaches PostgREST without `meta`.
22
+ */
23
+ import type { Tenancy, WireInference } from "./ingest-protocol";
24
+ /** Every `inferences` column the wire can carry, plus the stamped tenancy. ONE
25
+ * list — the uniform key set of every inferences write. */
26
+ export declare const INFERENCE_WRITE_COLUMNS: readonly ["org_id", "project_id", "source_id", "id", "ts", "route", "route_version", "provider", "model", "requested_tier", "served_tier", "tokens_in", "tokens_out", "tokens_cached", "cost_estimate_usd", "latency_ms", "ttfb_ms", "failure_class", "attempts", "tags", "fingerprint_cell", "served_variant", "repaired_by", "pricing_missing", "tokens_cache_write", "tokens_cache_write_1h", "cache_key", "prefix_fingerprint", "call_id", "attempt_seq", "candidate_index", "meta", "mask"];
27
+ export type InferenceWriteColumn = (typeof INFERENCE_WRITE_COLUMNS)[number];
28
+ /** A wire inference → its `inferences` row with EXACTLY `INFERENCE_WRITE_COLUMNS`
29
+ * as keys. Tenancy is stamped from the principal and never read from the row
30
+ * (T3) — the list order puts it first, and the wire type cannot carry it. */
31
+ export declare function normalizeInferenceRow(row: WireInference, tenancy: Tenancy): Record<string, unknown>;
32
+ /** merge=false fill: for each of `optional` that ANY row in the write carries,
33
+ * give the rows lacking it an explicit null, so rows that differ only in those
34
+ * spread-iff-present columns share one key set. A write where no row carries
35
+ * the column is returned key-for-key unchanged (byte-identical to pre-COV-1c). */
36
+ export declare function fillNulls(rows: readonly Record<string, unknown>[], optional: readonly string[]): Record<string, unknown>[];
37
+ /** The key set a row reaches PostgREST with (undefined-valued keys dropped, as
38
+ * JSON.stringify drops them), sorted — the PGRST102 comparison. */
39
+ export declare function wireKeySet(row: Record<string, unknown>): string;
40
+ /** merge=true split: group rows by key set, first-seen order, so each bulk POST
41
+ * is uniform without writing a null a merge upsert would apply. */
42
+ export declare function splitByKeySet(rows: readonly Record<string, unknown>[]): Record<string, unknown>[][];
43
+ //# sourceMappingURL=uniform-rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uniform-rows.d.ts","sourceRoot":"","sources":["../../src/telemetry/uniform-rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEhE;2DAC2D;AAC3D,eAAO,MAAM,uBAAuB,keAkC1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAwB5E;;6EAE6E;AAC7E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAanG;AAED;;;kFAGkF;AAClF,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACxC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAO3B;AAED;mEACmE;AACnE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAK/D;AAED;mEACmE;AACnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CASnG"}
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ /**
3
+ * Uniform key sets for `stampBatch`'s bulk writes (COV-1c). Extracted from
4
+ * ingest-protocol.ts so stampBatch stays under the LOC cap (P1).
5
+ *
6
+ * A PostgREST bulk insert requires every row in the array to carry the SAME
7
+ * keys — one row that omits a key the others carry fails the whole POST with
8
+ * PGRST102 ("All object keys must match") before any SQL runs. The wire is
9
+ * spread-iff-present by design (an older SDK's payload stays byte-identical),
10
+ * so rows in one batch legitimately differ: a web-search surcharge row carries
11
+ * `meta: null` (no `meta` key after validation) beside metered rows that carry
12
+ * one. Two remedies, chosen by the write's conflict policy:
13
+ *
14
+ * - merge=false (an immutable fact: inferences, steps, artifacts) — FILL every
15
+ * absent optional column with its DB default (null, or the NOT NULL column's
16
+ * default). Safe because ignore-duplicates never rewrites a landed row.
17
+ * - merge=true (a mutable projection: tasks, runs) — SPLIT into one write per
18
+ * key set (the routes precedent). Filling would null out a column a merge
19
+ * upsert must leave untouched.
20
+ *
21
+ * `undefined` values count as absent: JSON.stringify drops the key, so a row
22
+ * holding `{ meta: undefined }` reaches PostgREST without `meta`.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.INFERENCE_WRITE_COLUMNS = void 0;
26
+ exports.normalizeInferenceRow = normalizeInferenceRow;
27
+ exports.fillNulls = fillNulls;
28
+ exports.wireKeySet = wireKeySet;
29
+ exports.splitByKeySet = splitByKeySet;
30
+ /** Every `inferences` column the wire can carry, plus the stamped tenancy. ONE
31
+ * list — the uniform key set of every inferences write. */
32
+ exports.INFERENCE_WRITE_COLUMNS = [
33
+ "org_id",
34
+ "project_id",
35
+ "source_id",
36
+ "id",
37
+ "ts",
38
+ "route",
39
+ "route_version",
40
+ "provider",
41
+ "model",
42
+ "requested_tier",
43
+ "served_tier",
44
+ "tokens_in",
45
+ "tokens_out",
46
+ "tokens_cached",
47
+ "cost_estimate_usd",
48
+ "latency_ms",
49
+ "ttfb_ms",
50
+ "failure_class",
51
+ "attempts",
52
+ "tags",
53
+ "fingerprint_cell",
54
+ "served_variant",
55
+ "repaired_by",
56
+ "pricing_missing",
57
+ "tokens_cache_write",
58
+ "tokens_cache_write_1h",
59
+ "cache_key",
60
+ "prefix_fingerprint",
61
+ "call_id",
62
+ "attempt_seq",
63
+ "candidate_index",
64
+ "meta",
65
+ "mask",
66
+ ];
67
+ // Compile-time drift lock: a WireInference field missing from the list above
68
+ // fails tsc here, rather than being silently dropped from the write.
69
+ const _everyWireFieldListed = true;
70
+ void _everyWireFieldListed;
71
+ /** The value written for an ABSENT column: the NOT NULL columns' DB defaults
72
+ * (00001 / 00005 / 00049), null for every nullable one (00002/00003/00007/
73
+ * 00049/00054/00056/00062 add them nullable with no default). Writing the
74
+ * default explicitly is what the omitted key produced before. */
75
+ const ABSENT_DEFAULTS = {
76
+ tokens_in: 0,
77
+ tokens_out: 0,
78
+ tokens_cached: 0,
79
+ cost_estimate_usd: 0,
80
+ latency_ms: 0,
81
+ attempts: {},
82
+ tags: {},
83
+ pricing_missing: false,
84
+ tokens_cache_write: 0,
85
+ tokens_cache_write_1h: 0,
86
+ };
87
+ /** A wire inference → its `inferences` row with EXACTLY `INFERENCE_WRITE_COLUMNS`
88
+ * as keys. Tenancy is stamped from the principal and never read from the row
89
+ * (T3) — the list order puts it first, and the wire type cannot carry it. */
90
+ function normalizeInferenceRow(row, tenancy) {
91
+ const source = {
92
+ ...row,
93
+ org_id: tenancy.org_id,
94
+ project_id: tenancy.project_id,
95
+ source_id: tenancy.source_id,
96
+ };
97
+ const out = {};
98
+ for (const col of exports.INFERENCE_WRITE_COLUMNS) {
99
+ const v = source[col];
100
+ out[col] = v !== undefined ? v : (ABSENT_DEFAULTS[col] ?? null);
101
+ }
102
+ return out;
103
+ }
104
+ /** merge=false fill: for each of `optional` that ANY row in the write carries,
105
+ * give the rows lacking it an explicit null, so rows that differ only in those
106
+ * spread-iff-present columns share one key set. A write where no row carries
107
+ * the column is returned key-for-key unchanged (byte-identical to pre-COV-1c). */
108
+ function fillNulls(rows, optional) {
109
+ const carried = optional.filter((k) => rows.some((r) => r[k] !== undefined));
110
+ return rows.map((r) => {
111
+ const out = { ...r };
112
+ for (const k of carried)
113
+ if (out[k] === undefined)
114
+ out[k] = null;
115
+ return out;
116
+ });
117
+ }
118
+ /** The key set a row reaches PostgREST with (undefined-valued keys dropped, as
119
+ * JSON.stringify drops them), sorted — the PGRST102 comparison. */
120
+ function wireKeySet(row) {
121
+ return Object.keys(row)
122
+ .filter((k) => row[k] !== undefined)
123
+ .sort()
124
+ .join(",");
125
+ }
126
+ /** merge=true split: group rows by key set, first-seen order, so each bulk POST
127
+ * is uniform without writing a null a merge upsert would apply. */
128
+ function splitByKeySet(rows) {
129
+ const groups = new Map();
130
+ for (const r of rows) {
131
+ const key = wireKeySet(r);
132
+ const g = groups.get(key);
133
+ if (g === undefined)
134
+ groups.set(key, [r]);
135
+ else
136
+ g.push(r);
137
+ }
138
+ return [...groups.values()];
139
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"failures-teaching.d.ts","sourceRoot":"","sources":["../../src/types/failures-teaching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,eAAe,GAAG,IAAI,CAAC;CAC5E;AAID,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,CAuK5E,CAAC;AAiBF;6EAC6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;CAClD;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,gBAAgB,CAU3D;AAED;gEACgE;AAChE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAapE"}
1
+ {"version":3,"file":"failures-teaching.d.ts","sourceRoot":"","sources":["../../src/types/failures-teaching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,eAAe,GAAG,IAAI,CAAC;CAC5E;AAID,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,CAqL5E,CAAC;AAiBF;6EAC6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;CAClD;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,gBAAgB,CAU3D;AAED;gEACgE;AAChE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAapE"}
@@ -157,6 +157,17 @@ exports.FAILURE_TEACHING = {
157
157
  "falling through, at the cost of one extra call.",
158
158
  }),
159
159
  },
160
+ limit_exceeded: {
161
+ class: "limit_exceeded",
162
+ what: "The run reached one of the limits you set on it (maxUsd, maxCalls, maxWallMs or " +
163
+ "maxRepeatToolCall), so ModelRig refused the next call in-process and ended the run as failed " +
164
+ "with failure_reason `limit:<which>` (e.g. `limit:max_usd`). Nothing was sent to a provider for the refused call.",
165
+ fixHint: "Look at the run before raising the limit: a stuck tool loop (the same tool with the same " +
166
+ "arguments again and again) or a runaway agent looks exactly like an under-sized limit. If the " +
167
+ "run was legitimately bigger, raise that one limit on run.start({ limits }) or " +
168
+ "rig.conversation(id, { limits }).",
169
+ docsUrl: `${DOCS_BASE}#limit_exceeded`,
170
+ },
160
171
  };
161
172
  /** Provider → the env var that keys it. Mirrors config.ts's PROVIDER_KEY_ENV;
162
173
  * kept as a local lookup so this table has no import cycle with config. */
@@ -4,7 +4,7 @@
4
4
  * errors never cross the adapter boundary. Retry budgets are per-class and
5
5
  * non-fungible (see run/retry.ts).
6
6
  */
7
- export type FailureClass = "content_invalid" | "capacity_shed" | "network" | "refusal" | "cache_invalid" | "timeout" | "config_auth" | "budget_exhausted" | "invariant_violation" | "quality_rejected";
7
+ export type FailureClass = "content_invalid" | "capacity_shed" | "network" | "refusal" | "cache_invalid" | "timeout" | "config_auth" | "budget_exhausted" | "invariant_violation" | "quality_rejected" | "limit_exceeded";
8
8
  export interface RigFailure {
9
9
  readonly class: FailureClass;
10
10
  readonly message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../../src/types/failures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,eAAe,GACf,SAAS,GACT,SAAS,GACT,eAAe,GACf,SAAS,GACT,aAAa,GACb,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,YAAY,CAGrD,CAAC;AAeH;kFACkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;gBAEjB,OAAO,EAAE,UAAU;CAKhC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIhE"}
1
+ {"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../../src/types/failures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,eAAe,GACf,SAAS,GACT,SAAS,GACT,eAAe,GACf,SAAS,GACT,aAAa,GACb,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,gBAAgB,CAAC;AAErB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,YAAY,CAIrD,CAAC;AAgBH;kFACkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;gBAEjB,OAAO,EAAE,UAAU;CAKhC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIhE"}
@@ -11,6 +11,7 @@ exports.isRigFailure = isRigFailure;
11
11
  exports.TERMINAL_CLASSES = new Set([
12
12
  "budget_exhausted",
13
13
  "invariant_violation",
14
+ "limit_exceeded",
14
15
  ]);
15
16
  const FAILURE_CLASSES = new Set([
16
17
  "content_invalid",
@@ -23,6 +24,7 @@ const FAILURE_CLASSES = new Set([
23
24
  "budget_exhausted",
24
25
  "invariant_violation",
25
26
  "quality_rejected",
27
+ "limit_exceeded",
26
28
  ]);
27
29
  /** Error subclass for the rare paths that must throw (config errors, terminal
28
30
  * failures surfacing to the caller). The failure travels intact on `.failure`. */
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { RigFailure } from "./failures";
7
7
  import type { AttemptMetaV1 } from "../telemetry/attempt-meta";
8
+ import type { HistoryMessage } from "../run/messages";
8
9
  export type ProviderId = "gemini" | "openai" | "deepseek" | "anthropic" | "grok" | "deepinfra" | "fireworks" | "zai";
9
10
  /** Runtime enumeration of every ProviderId — the single list a raw-model lane
10
11
  * (`provider/model`) validates against (http gateway spec §3.3). Kept in sync
@@ -171,6 +172,17 @@ export interface AdapterRequest {
171
172
  readonly tools?: readonly ToolDefinition[];
172
173
  /** Tool-selection directive; only meaningful alongside `tools`. */
173
174
  readonly toolChoice?: ToolChoice;
175
+ /**
176
+ * ARC-β messages lane (agent-archetypes spec §4) — the conversation history,
177
+ * validated by `run/messages.ts`. Identical semantics in every adapter:
178
+ * - ABSENT ⇒ today's single-turn code path, BYTE-IDENTICAL request (pinned by
179
+ * adapters/one-turn-parity.test.ts).
180
+ * - PRESENT ⇒ provider messages = system(`systemPrompt`) + map(history), and a
181
+ * NON-EMPTY `userPrompt` is appended as a final user message (a route
182
+ * template's final instruction); an empty `userPrompt` appends nothing.
183
+ * Per-family mapping lives in adapters/history-mapping.ts.
184
+ */
185
+ readonly history?: readonly HistoryMessage[];
174
186
  }
175
187
  export interface AdapterUsage {
176
188
  /**