@tangle-network/agent-eval 0.137.0 → 0.138.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 (150) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +33 -0
  3. package/dist/analyst/index.d.ts +473 -39
  4. package/dist/analyst/index.d.ts.map +1 -1
  5. package/dist/analyst/index.js +11 -593
  6. package/dist/analyst/index.js.map +1 -1
  7. package/dist/{analyze-runs-PVtnfjvA.d.ts → analyze-runs-CPYxfPWT.d.ts} +5 -5
  8. package/dist/{analyze-runs-PVtnfjvA.d.ts.map → analyze-runs-CPYxfPWT.d.ts.map} +1 -1
  9. package/dist/{benchmark-YDrpumqB.js → benchmark-D8dkki-J.js} +299 -159
  10. package/dist/benchmark-D8dkki-J.js.map +1 -0
  11. package/dist/{benchmark-CHX4orG7.d.ts → benchmark-DlQgU_XI.d.ts} +67 -15
  12. package/dist/benchmark-DlQgU_XI.d.ts.map +1 -0
  13. package/dist/benchmark-command-CMqVqReF.js +4332 -0
  14. package/dist/benchmark-command-CMqVqReF.js.map +1 -0
  15. package/dist/benchmarks/index.d.ts +1 -1
  16. package/dist/benchmarks/index.js +1 -1
  17. package/dist/{benchmarks-DCLkQOmc.js → benchmarks-BJ_xK5rQ.js} +4 -3
  18. package/dist/{benchmarks-DCLkQOmc.js.map → benchmarks-BJ_xK5rQ.js.map} +1 -1
  19. package/dist/campaign/index.d.ts +5 -5
  20. package/dist/campaign/index.js +3 -3
  21. package/dist/{campaign-lgObcHFC.js → campaign-BIBS-NHV.js} +16 -9
  22. package/dist/campaign-BIBS-NHV.js.map +1 -0
  23. package/dist/cli.js +9 -2
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{client-C8L6h6Wf.d.ts → client-BwPKohkJ.d.ts} +4 -4
  26. package/dist/{client-C8L6h6Wf.d.ts.map → client-BwPKohkJ.d.ts.map} +1 -1
  27. package/dist/{completion-verifier-DSyRNVzU.d.ts → completion-verifier-B4-IMYcS.d.ts} +3 -3
  28. package/dist/{completion-verifier-DSyRNVzU.d.ts.map → completion-verifier-B4-IMYcS.d.ts.map} +1 -1
  29. package/dist/contract/index.d.ts +10 -10
  30. package/dist/contract/index.js +8 -8
  31. package/dist/control.d.ts +2 -2
  32. package/dist/{cost-ledger-D2o6JOrL.d.ts → cost-ledger-B1D3COAc.d.ts} +5 -4
  33. package/dist/{cost-ledger-D2o6JOrL.d.ts.map → cost-ledger-B1D3COAc.d.ts.map} +1 -1
  34. package/dist/{cost-ledger-D-5_-dhi.js → cost-ledger-CHDLA0Ss.js} +90 -45
  35. package/dist/cost-ledger-CHDLA0Ss.js.map +1 -0
  36. package/dist/{default-registry-Dc5D_Loc.d.ts → default-registry-PUhIVRWz.d.ts} +18 -5
  37. package/dist/default-registry-PUhIVRWz.d.ts.map +1 -0
  38. package/dist/{default-registry-CLXbRt0f.js → default-registry-lp5R0lve.js} +1503 -258
  39. package/dist/default-registry-lp5R0lve.js.map +1 -0
  40. package/dist/{eval-campaign-CHqfLnff.js → eval-campaign-9MozgKL7.js} +2 -2
  41. package/dist/{eval-campaign-CHqfLnff.js.map → eval-campaign-9MozgKL7.js.map} +1 -1
  42. package/dist/exact-types-Dpw2LeHA.d.ts +234 -0
  43. package/dist/exact-types-Dpw2LeHA.d.ts.map +1 -0
  44. package/dist/{extract-usage-p-56bh8q.js → extract-usage-CS391dOE.js} +2 -2
  45. package/dist/{extract-usage-p-56bh8q.js.map → extract-usage-CS391dOE.js.map} +1 -1
  46. package/dist/{feedback-trajectory-N_F0PwHz.d.ts → feedback-trajectory-CoNep7rl.d.ts} +3 -2
  47. package/dist/feedback-trajectory-CoNep7rl.d.ts.map +1 -0
  48. package/dist/fuzz.d.ts +1 -1
  49. package/dist/fuzz.js +1 -1
  50. package/dist/hosted/index.d.ts +3 -3
  51. package/dist/{index-U3RHOShi.d.ts → index-B2-IxCMB.d.ts} +2 -2
  52. package/dist/{index-U3RHOShi.d.ts.map → index-B2-IxCMB.d.ts.map} +1 -1
  53. package/dist/{index-BnP1QJUv.d.ts → index-CjVYlVBK.d.ts} +5 -5
  54. package/dist/{index-BnP1QJUv.d.ts.map → index-CjVYlVBK.d.ts.map} +1 -1
  55. package/dist/{index-C-Pr4OWg.d.ts → index-D0cxAdaV.d.ts} +11 -10
  56. package/dist/index-D0cxAdaV.d.ts.map +1 -0
  57. package/dist/index-DEb46kc6.d.ts.map +1 -1
  58. package/dist/{index-DRNl6g_N.d.ts → index-sMN_hI4E.d.ts} +3 -3
  59. package/dist/{index-DRNl6g_N.d.ts.map → index-sMN_hI4E.d.ts.map} +1 -1
  60. package/dist/index.d.ts +24 -23
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +19 -353
  63. package/dist/index.js.map +1 -1
  64. package/dist/{insight-report-B9ooYH_g.d.ts → insight-report-CXd8VBDR.d.ts} +4 -4
  65. package/dist/{insight-report-B9ooYH_g.d.ts.map → insight-report-CXd8VBDR.d.ts.map} +1 -1
  66. package/dist/{integrity-CKxosZ5Z.d.ts → integrity-B-MLFz0I.d.ts} +2 -2
  67. package/dist/{integrity-CKxosZ5Z.d.ts.map → integrity-B-MLFz0I.d.ts.map} +1 -1
  68. package/dist/ledger-core/index.js +1 -1
  69. package/dist/{ledger-core-t6sItivm.js → ledger-core-C0Yx1I14.js} +220 -27
  70. package/dist/ledger-core-C0Yx1I14.js.map +1 -0
  71. package/dist/{llm-client-DKB25jV8.js → llm-client-Cj3c7PEm.js} +5 -5
  72. package/dist/llm-client-Cj3c7PEm.js.map +1 -0
  73. package/dist/meta-eval/index.d.ts +2 -2
  74. package/dist/multishot/index.d.ts +2 -2
  75. package/dist/openapi.json +1 -1
  76. package/dist/{proposal-findings-DCawte-y.js → proposal-findings-2GIUo1et.js} +2 -68
  77. package/dist/proposal-findings-2GIUo1et.js.map +1 -0
  78. package/dist/{registry-BdM7SuTr.d.ts → registry-C4yJTza7.d.ts} +60 -6
  79. package/dist/registry-C4yJTza7.d.ts.map +1 -0
  80. package/dist/{release-report-CofgVNZt.d.ts → release-report-CoyvyLBs.d.ts} +3 -3
  81. package/dist/{release-report-CofgVNZt.d.ts.map → release-report-CoyvyLBs.d.ts.map} +1 -1
  82. package/dist/{replay-Bju0T8Ls.js → replay-Cb-4Vf0k.js} +8 -7
  83. package/dist/replay-Cb-4Vf0k.js.map +1 -0
  84. package/dist/{replay-K8FaC0CB.d.ts → replay-DbIYwso6.d.ts} +7 -7
  85. package/dist/{replay-K8FaC0CB.d.ts.map → replay-DbIYwso6.d.ts.map} +1 -1
  86. package/dist/reporting.d.ts +4 -4
  87. package/dist/{researcher-Da0Wj-bt.d.ts → researcher-BCeOEjtR.d.ts} +5 -5
  88. package/dist/{researcher-Da0Wj-bt.d.ts.map → researcher-BCeOEjtR.d.ts.map} +1 -1
  89. package/dist/{reward-hacking-CQ3hTCO3.d.ts → reward-hacking-sE2l_NV6.d.ts} +2 -2
  90. package/dist/{reward-hacking-CQ3hTCO3.d.ts.map → reward-hacking-sE2l_NV6.d.ts.map} +1 -1
  91. package/dist/rl.d.ts +5 -5
  92. package/dist/rl.js +1 -1
  93. package/dist/rollout/index.d.ts +1 -1
  94. package/dist/{rubric-predictive-validity-C4sztLR3.d.ts → rubric-predictive-validity-w2klGv1u.d.ts} +2 -2
  95. package/dist/{rubric-predictive-validity-C4sztLR3.d.ts.map → rubric-predictive-validity-w2klGv1u.d.ts.map} +1 -1
  96. package/dist/{run-evidence-BDIircdA.d.ts → run-evidence-CbE0A8Xg.d.ts} +3 -3
  97. package/dist/{run-evidence-BDIircdA.d.ts.map → run-evidence-CbE0A8Xg.d.ts.map} +1 -1
  98. package/dist/{run-record-BPCa2rQ8.d.ts → run-record-DwHMk1Ai.d.ts} +2 -2
  99. package/dist/{run-record-BPCa2rQ8.d.ts.map → run-record-DwHMk1Ai.d.ts.map} +1 -1
  100. package/dist/{semantic-concept-judge-Bz64IckK.js → semantic-concept-judge-DYXDPZW0.js} +11 -5
  101. package/dist/semantic-concept-judge-DYXDPZW0.js.map +1 -0
  102. package/dist/{server-KjXZZUDX.js → server-DLEvyW2z.js} +3 -3
  103. package/dist/{server-KjXZZUDX.js.map → server-DLEvyW2z.js.map} +1 -1
  104. package/dist/single-run-lock-D_bS5xhj.js +318 -0
  105. package/dist/single-run-lock-D_bS5xhj.js.map +1 -0
  106. package/dist/{skill-usage-CFDLLlhF.d.ts → skill-usage-Bv3G4VkA.d.ts} +18 -8
  107. package/dist/skill-usage-Bv3G4VkA.d.ts.map +1 -0
  108. package/dist/{skillopt-optimization-method-f4o9sUT4.js → skillopt-optimization-method-CjKMZy0d.js} +7 -182
  109. package/dist/skillopt-optimization-method-CjKMZy0d.js.map +1 -0
  110. package/dist/{skillopt-optimization-method-BpbnlvAZ.d.ts → skillopt-optimization-method-CzfnA8O-.d.ts} +10 -10
  111. package/dist/{skillopt-optimization-method-BpbnlvAZ.d.ts.map → skillopt-optimization-method-CzfnA8O-.d.ts.map} +1 -1
  112. package/dist/{statistics-_7P642CN.d.ts → statistics-mf70aXKp.d.ts} +2 -2
  113. package/dist/{statistics-_7P642CN.d.ts.map → statistics-mf70aXKp.d.ts.map} +1 -1
  114. package/dist/{tools-DZk2Jn64.js → store-otlp-BenKynPE.js} +4 -192
  115. package/dist/store-otlp-BenKynPE.js.map +1 -0
  116. package/dist/{summary-report-DHipz9Kx.d.ts → summary-report-BKinV4yD.d.ts} +3 -3
  117. package/dist/{summary-report-DHipz9Kx.d.ts.map → summary-report-BKinV4yD.d.ts.map} +1 -1
  118. package/dist/tools-DZGdROtG.js +255 -0
  119. package/dist/tools-DZGdROtG.js.map +1 -0
  120. package/dist/traces.d.ts +5 -5
  121. package/dist/traces.js +4 -3
  122. package/dist/{types-CTvKfr5F.d.ts → types-5q2T25iW.d.ts} +2 -2
  123. package/dist/{types-CTvKfr5F.d.ts.map → types-5q2T25iW.d.ts.map} +1 -1
  124. package/dist/{types-CKswbJGO.d.ts → types-BtJhn8v6.d.ts} +4 -4
  125. package/dist/{types-CKswbJGO.d.ts.map → types-BtJhn8v6.d.ts.map} +1 -1
  126. package/dist/{types-CTGbIm57.d.ts → types-zFYez3PK.d.ts} +5 -5
  127. package/dist/{types-CTGbIm57.d.ts.map → types-zFYez3PK.d.ts.map} +1 -1
  128. package/dist/wire/index.d.ts +3 -3
  129. package/dist/wire/index.js +1 -1
  130. package/docs/trace-analysis.md +123 -3
  131. package/package.json +5 -3
  132. package/dist/benchmark-CHX4orG7.d.ts.map +0 -1
  133. package/dist/benchmark-YDrpumqB.js.map +0 -1
  134. package/dist/campaign-lgObcHFC.js.map +0 -1
  135. package/dist/concurrency-MUjT7VjM.js +0 -109
  136. package/dist/concurrency-MUjT7VjM.js.map +0 -1
  137. package/dist/cost-ledger-D-5_-dhi.js.map +0 -1
  138. package/dist/default-registry-CLXbRt0f.js.map +0 -1
  139. package/dist/default-registry-Dc5D_Loc.d.ts.map +0 -1
  140. package/dist/feedback-trajectory-N_F0PwHz.d.ts.map +0 -1
  141. package/dist/index-C-Pr4OWg.d.ts.map +0 -1
  142. package/dist/ledger-core-t6sItivm.js.map +0 -1
  143. package/dist/llm-client-DKB25jV8.js.map +0 -1
  144. package/dist/proposal-findings-DCawte-y.js.map +0 -1
  145. package/dist/registry-BdM7SuTr.d.ts.map +0 -1
  146. package/dist/replay-Bju0T8Ls.js.map +0 -1
  147. package/dist/semantic-concept-judge-Bz64IckK.js.map +0 -1
  148. package/dist/skill-usage-CFDLLlhF.d.ts.map +0 -1
  149. package/dist/skillopt-optimization-method-f4o9sUT4.js.map +0 -1
  150. package/dist/tools-DZk2Jn64.js.map +0 -1
@@ -1,10 +1,13 @@
1
- import { i as CostLedger } from "./cost-ledger-D-5_-dhi.js";
2
- import { f as maximumChargeForLlmRequest, l as costReceiptFromLlm, n as LlmClient, s as callLlm, u as costReceiptFromLlmError } from "./llm-client-DKB25jV8.js";
1
+ import { i as CostLedger } from "./cost-ledger-CHDLA0Ss.js";
2
+ import { f as maximumChargeForLlmRequest, l as costReceiptFromLlm, n as LlmClient, s as callLlm, u as costReceiptFromLlmError } from "./llm-client-Cj3c7PEm.js";
3
3
  import { LLM_CONTEXT_TOKENS, LLM_INPUT_TOKEN_ATTR_KEYS, LLM_OUTPUT_TOKEN_ATTR_KEYS, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
4
4
  import { t as executionTrackByLane } from "./execution-tracks-CpgFPpS5.js";
5
- import { O as spanEpochMillis, a as runTraceAnalysisLoop, r as buildTraceAnalystTools } from "./tools-DZk2Jn64.js";
6
- import { c as makeFinding, o as combineAbortSignals } from "./proposal-findings-DCawte-y.js";
5
+ import { D as spanEpochMillis } from "./store-otlp-BenKynPE.js";
6
+ import { f as validateUsageSettlementTimeout, l as assertValidAnalystUsageReceipt, m as makeFinding, u as settleUsageReceiptFromCostLedger } from "./single-run-lock-D_bS5xhj.js";
7
+ import { _ as canonicalString, v as hashCanonical } from "./ledger-core-C0Yx1I14.js";
8
+ import { a as runTraceAnalysisLoop, r as buildTraceAnalystTools } from "./tools-DZGdROtG.js";
7
9
  import { t as analyzeSupervisorRunIntegrity } from "./integrity-CCXTftiL.js";
10
+ import { o as combineAbortSignals } from "./proposal-findings-2GIUo1et.js";
8
11
  import { ai } from "@ax-llm/ax";
9
12
  import { z } from "zod";
10
13
  import { randomUUID } from "node:crypto";
@@ -551,6 +554,11 @@ function behavioralAnalyst(options = {}) {
551
554
  inputKind: "trace-store",
552
555
  cost: { kind: "deterministic" },
553
556
  version: "2.0.0",
557
+ executionConfig: {
558
+ kind: "behavioral-efficiency",
559
+ max_traces: maxTraces,
560
+ max_evidence_refs_per_finding: maxEvidenceRefsPerFinding
561
+ },
554
562
  async analyze(store, context) {
555
563
  const analyzedTraceIds = await listTraceIds(store, maxTraces, context.signal);
556
564
  const findingsById = /* @__PURE__ */ new Map();
@@ -692,6 +700,7 @@ function boundOutputTokens(request, limit) {
692
700
  const maxTokens = requested === void 0 ? limit : Math.min(requested, limit);
693
701
  return {
694
702
  ...request,
703
+ ...request.functionCall === void 0 && !request.functions?.length ? { functionCall: "none" } : {},
695
704
  modelConfig: {
696
705
  ...request.modelConfig,
697
706
  maxTokens,
@@ -800,6 +809,221 @@ function assertPositiveInteger(value, field) {
800
809
  if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`meterAxChatService: ${field} must be a positive integer`);
801
810
  }
802
811
  //#endregion
812
+ //#region src/ledger-core/deep-freeze.ts
813
+ /** Freeze a detached canonical-JSON graph. Canonicalization has already ruled out cycles.
814
+ *
815
+ * Lives outside canonical.ts so the analyst-benchmark implementation digest,
816
+ * which covers canonical.ts, stays bound to the published benchmark evidence. */
817
+ function deepFreezeCanonicalJson(value) {
818
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
819
+ Object.freeze(value);
820
+ for (const nested of Object.values(value)) deepFreezeCanonicalJson(nested);
821
+ }
822
+ return value;
823
+ }
824
+ //#endregion
825
+ //#region src/analyst/exact-types.ts
826
+ /** Canonical identity for any live component admitted to an exact run. */
827
+ function snapshotExactExecutionComponentIdentity(value, context) {
828
+ let detached;
829
+ try {
830
+ detached = JSON.parse(canonicalString(value));
831
+ } catch (cause) {
832
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
833
+ }
834
+ const parsed = componentIdentitySchema.safeParse(detached);
835
+ if (!parsed.success) throw new TypeError(`${context} requires non-empty id/version and object config`);
836
+ return deepFreezeCanonicalJson({
837
+ id: parsed.data.id,
838
+ version: parsed.data.version,
839
+ config_digest: hashCanonical(parsed.data.config)
840
+ });
841
+ }
842
+ const nonEmptyString = z.string().min(1);
843
+ const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
844
+ const finiteNonnegative$1 = z.number().finite().nonnegative();
845
+ const nonnegativeSafeInteger$1 = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
846
+ const positiveTimeout = z.number().int().positive().max(2147483647);
847
+ const componentSnapshotSchema = z.strictObject({
848
+ id: nonEmptyString,
849
+ version: nonEmptyString,
850
+ config_digest: digest
851
+ });
852
+ const componentIdentitySchema = z.strictObject({
853
+ id: nonEmptyString,
854
+ version: nonEmptyString,
855
+ config: z.record(z.string(), z.unknown())
856
+ });
857
+ const deterministicCostSchema = z.strictObject({
858
+ kind: z.literal("deterministic"),
859
+ est_usd_per_run: finiteNonnegative$1.optional(),
860
+ models: z.array(nonEmptyString).optional()
861
+ });
862
+ const llmCostSchema = z.strictObject({
863
+ kind: z.literal("llm"),
864
+ est_usd_per_run: finiteNonnegative$1.optional(),
865
+ models: z.array(nonEmptyString).optional(),
866
+ settlement_timeout_ms: nonnegativeSafeInteger$1.optional()
867
+ });
868
+ const requirementsSchema = z.strictObject({
869
+ min_shots: nonnegativeSafeInteger$1.optional(),
870
+ capabilities: z.array(nonEmptyString).optional()
871
+ }).nullable();
872
+ const analystSnapshotSchema = z.strictObject({
873
+ id: nonEmptyString,
874
+ version: nonEmptyString,
875
+ input_kind: z.enum([
876
+ "trace-store",
877
+ "artifact-dir",
878
+ "run-record",
879
+ "judge-input",
880
+ "custom"
881
+ ]),
882
+ cost: z.discriminatedUnion("kind", [deterministicCostSchema, llmCostSchema]),
883
+ requirements: requirementsSchema,
884
+ execution_config_digest: digest
885
+ });
886
+ const allocationsSchema = z.record(nonEmptyString, z.union([finiteNonnegative$1, z.null()]));
887
+ const weightsSchema = z.record(nonEmptyString, finiteNonnegative$1);
888
+ const budgetSnapshotSchema = z.discriminatedUnion("kind", [
889
+ z.strictObject({ kind: z.literal("none") }),
890
+ z.strictObject({
891
+ kind: z.literal("equal"),
892
+ total_usd: finiteNonnegative$1,
893
+ allocations_usd: allocationsSchema
894
+ }),
895
+ z.strictObject({
896
+ kind: z.literal("weighted"),
897
+ total_usd: finiteNonnegative$1,
898
+ weights: weightsSchema,
899
+ allocations_usd: allocationsSchema
900
+ })
901
+ ]);
902
+ const priorFindingsSchema = z.discriminatedUnion("kind", [
903
+ z.strictObject({ kind: z.literal("none") }),
904
+ z.strictObject({
905
+ kind: z.literal("ordered"),
906
+ count: nonnegativeSafeInteger$1,
907
+ digest
908
+ }),
909
+ z.strictObject({
910
+ kind: z.literal("by_analyst"),
911
+ keys: z.array(nonEmptyString),
912
+ count: nonnegativeSafeInteger$1,
913
+ digest
914
+ })
915
+ ]);
916
+ const exactRunPolicySchema = z.strictObject({
917
+ budget: budgetSnapshotSchema,
918
+ total_timeout_ms: positiveTimeout.nullable(),
919
+ signal_provided: z.boolean(),
920
+ cost_ledger: componentSnapshotSchema.nullable(),
921
+ cost_phase: nonEmptyString.nullable(),
922
+ tags: z.record(z.string(), z.string()).nullable(),
923
+ prior_findings: priorFindingsSchema,
924
+ chain_findings: z.boolean(),
925
+ missing_input_mode: z.enum(["skip", "abort"]),
926
+ registry_hooks: componentSnapshotSchema.nullable(),
927
+ registry_chat: componentSnapshotSchema.nullable()
928
+ });
929
+ const exactExecutionPlanSchema = z.strictObject({
930
+ schema_version: z.literal("1.0.0"),
931
+ analysts: z.array(analystSnapshotSchema).min(1),
932
+ policy: exactRunPolicySchema,
933
+ digest
934
+ }).superRefine((plan, context) => {
935
+ const issue = (path, message) => context.addIssue({
936
+ code: "custom",
937
+ path,
938
+ message
939
+ });
940
+ const analystIds = plan.analysts.map((analyst) => analyst.id);
941
+ if (new Set(analystIds).size !== analystIds.length) issue(["analysts"], "analyst ids must be unique");
942
+ if (plan.policy.cost_ledger === null && plan.policy.cost_phase !== null) issue(["policy", "cost_phase"], "cost phase requires a cost ledger");
943
+ if (plan.policy.prior_findings.kind === "by_analyst" && plan.policy.prior_findings.keys.some((key, index, keys) => index > 0 && key <= keys[index - 1])) issue([
944
+ "policy",
945
+ "prior_findings",
946
+ "keys"
947
+ ], "keys must be sorted and unique");
948
+ const budget = plan.policy.budget;
949
+ if (budget.kind === "none") return;
950
+ const allocationIds = Object.keys(budget.allocations_usd).sort();
951
+ const selectedIds = [...analystIds].sort();
952
+ if (allocationIds.length !== selectedIds.length || allocationIds.some((id, index) => id !== selectedIds[index])) {
953
+ issue([
954
+ "policy",
955
+ "budget",
956
+ "allocations_usd"
957
+ ], "allocations must name every analyst and no others");
958
+ return;
959
+ }
960
+ const runnableIds = analystIds.filter((id) => budget.allocations_usd[id] !== null);
961
+ const epsilon = Math.max(1, budget.total_usd) * Number.EPSILON * 8;
962
+ if (runnableIds.length === 0) return;
963
+ if (budget.kind === "weighted") {
964
+ const weightIds = Object.keys(budget.weights).sort();
965
+ if (weightIds.length !== selectedIds.length || weightIds.some((id, index) => id !== selectedIds[index])) {
966
+ issue([
967
+ "policy",
968
+ "budget",
969
+ "weights"
970
+ ], "weights must name every analyst and no others");
971
+ return;
972
+ }
973
+ const totalWeight = runnableIds.reduce((sum, id) => sum + (budget.weights[id] ?? 0), 0);
974
+ if (totalWeight === 0) {
975
+ issue([
976
+ "policy",
977
+ "budget",
978
+ "weights"
979
+ ], "runnable analysts must have positive total weight");
980
+ return;
981
+ }
982
+ for (const id of runnableIds) {
983
+ const expected = budget.total_usd * (budget.weights[id] ?? 0) / totalWeight;
984
+ if (Math.abs((budget.allocations_usd[id] ?? 0) - expected) > epsilon) issue([
985
+ "policy",
986
+ "budget",
987
+ "allocations_usd",
988
+ id
989
+ ], "allocation does not match the weighted policy");
990
+ }
991
+ return;
992
+ }
993
+ const expected = budget.total_usd / runnableIds.length;
994
+ for (const id of runnableIds) if (Math.abs((budget.allocations_usd[id] ?? 0) - expected) > epsilon) issue([
995
+ "policy",
996
+ "budget",
997
+ "allocations_usd",
998
+ id
999
+ ], "allocation does not match the equal policy");
1000
+ });
1001
+ /**
1002
+ * Canonicalize and validate the one exact-plan representation shared by execution and archival.
1003
+ * Unknown fields fail at every level; the returned graph is detached and deeply frozen.
1004
+ */
1005
+ function snapshotExactExecutionPlan(value, context = "exact analyst execution plan") {
1006
+ let detached;
1007
+ try {
1008
+ detached = JSON.parse(canonicalString(value));
1009
+ } catch (cause) {
1010
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
1011
+ }
1012
+ const parsed = exactExecutionPlanSchema.safeParse(detached);
1013
+ if (!parsed.success) {
1014
+ const issue = parsed.error.issues[0];
1015
+ const path = issue?.path.length ? ` ${issue.path.join(".")}` : "";
1016
+ throw new TypeError(`${context}${path}: ${issue?.message ?? "is invalid"}`);
1017
+ }
1018
+ const expectedDigest = hashCanonical({
1019
+ schema_version: parsed.data.schema_version,
1020
+ analysts: parsed.data.analysts,
1021
+ policy: parsed.data.policy
1022
+ });
1023
+ if (parsed.data.digest !== expectedDigest) throw new TypeError(`${context} digest does not match its content`);
1024
+ return deepFreezeCanonicalJson(parsed.data);
1025
+ }
1026
+ //#endregion
803
1027
  //#region src/analyst/finding-subject.ts
804
1028
  /**
805
1029
  * Typed `FindingSubject` — the canonical grammar every analyst kind emits.
@@ -1311,6 +1535,7 @@ function parseFindingWithSchema(schema, row, log) {
1311
1535
  }
1312
1536
  function evidenceKindFromUri(uri) {
1313
1537
  if (uri.startsWith("span://")) return "span";
1538
+ if (/^trace:\/\/[^/]+\/span\/[^/]+$/.test(uri)) return "span";
1314
1539
  if (uri.startsWith("event://")) return "event";
1315
1540
  if (uri.startsWith("finding://")) return "finding";
1316
1541
  if (uri.startsWith("metric://")) return "metric";
@@ -1424,49 +1649,6 @@ async function structureFindings(opts) {
1424
1649
  outcome: "extraction_failed"
1425
1650
  };
1426
1651
  }
1427
- /** Convert one ledger channel's complete call set into one analyst receipt. */
1428
- function usageReceiptFromCostLedger(ledger, filter = "analyst") {
1429
- const resolvedFilter = typeof filter === "string" ? { channel: filter } : filter;
1430
- const summary = ledger.summary(resolvedFilter);
1431
- const receipts = ledger.list(resolvedFilter);
1432
- const hasReasoningUsage = receipts.some((receipt) => receipt.reasoningTokens !== void 0);
1433
- const hasCacheWriteUsage = receipts.some((receipt) => receipt.cacheWriteTokens !== void 0);
1434
- const cost = summary.costProvenance;
1435
- return {
1436
- calls: summary.totalCalls + summary.pendingCalls,
1437
- tokens: summary.usageComplete ? {
1438
- input: summary.inputTokens,
1439
- output: summary.outputTokens,
1440
- ...hasReasoningUsage ? { reasoning: summary.reasoningTokens ?? 0 } : {},
1441
- ...summary.cachedTokens > 0 ? { cached: summary.cachedTokens } : {},
1442
- ...hasCacheWriteUsage ? { cacheWrite: summary.cacheWriteTokens ?? 0 } : {}
1443
- } : null,
1444
- cost,
1445
- ...cost.kind === "uncaptured" ? { knownCostUsd: summary.totalCostUsd } : {}
1446
- };
1447
- }
1448
- /** Wait a bounded time for late provider receipts, then take one immutable snapshot. */
1449
- async function settleUsageReceiptFromCostLedger(ledger, options = {}) {
1450
- const { timeoutMs: requestedTimeoutMs, ...requestedFilter } = options;
1451
- const filter = {
1452
- channel: requestedFilter.channel ?? "analyst",
1453
- ...requestedFilter.phase === void 0 ? {} : { phase: requestedFilter.phase },
1454
- ...requestedFilter.tags === void 0 ? {} : { tags: requestedFilter.tags }
1455
- };
1456
- const timeoutMs = validateUsageSettlementTimeout(requestedTimeoutMs);
1457
- const waitResult = ledger.summary(filter).pendingCalls === 0 ? true : ledger.waitForIdle ? await ledger.waitForIdle({ timeoutMs }) : false;
1458
- const pendingCalls = ledger.summary(filter).pendingCalls;
1459
- return {
1460
- settled: waitResult && pendingCalls === 0,
1461
- pendingCalls,
1462
- receipt: usageReceiptFromCostLedger(ledger, filter)
1463
- };
1464
- }
1465
- function validateUsageSettlementTimeout(timeoutMs) {
1466
- const resolved = timeoutMs ?? 5e3;
1467
- if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > 2147483647) throw new TypeError("settlementTimeoutMs must be a non-negative safe integer no greater than 2147483647");
1468
- return resolved;
1469
- }
1470
1652
  //#endregion
1471
1653
  //#region src/analyst/kind-factory.ts
1472
1654
  /**
@@ -1484,6 +1666,8 @@ function createTraceAnalystKind(spec, opts) {
1484
1666
  const minimumEvidenceCitations = spec.minimumEvidenceCitations ?? 1;
1485
1667
  if (!Number.isInteger(minimumEvidenceCitations) || minimumEvidenceCitations < 1) throw new TypeError("minimumEvidenceCitations must be a positive integer");
1486
1668
  const settlementTimeoutMs = validateUsageSettlementTimeout(opts.settlementTimeoutMs);
1669
+ const maxOutputTokens = spec.maxOutputTokens ?? 4096;
1670
+ const aiIdentity = opts.aiIdentity === void 0 ? null : snapshotExactExecutionComponentIdentity(opts.aiIdentity, "createTraceAnalystKind aiIdentity");
1487
1671
  return {
1488
1672
  id: spec.id,
1489
1673
  description: spec.description,
@@ -1493,8 +1677,29 @@ function createTraceAnalystKind(spec, opts) {
1493
1677
  settlement_timeout_ms: settlementTimeoutMs
1494
1678
  },
1495
1679
  version,
1680
+ executionConfig: {
1681
+ kind: "trace-analyst",
1682
+ model,
1683
+ ai_identity: aiIdentity,
1684
+ actor_description_digest: hashCanonical(spec.actorDescription.trim()),
1685
+ max_subqueries: spec.subqueries?.maxCalls ?? 0,
1686
+ max_parallel_subqueries: spec.subqueries?.maxParallel ?? 2,
1687
+ max_turns: spec.maxTurns ?? 12,
1688
+ max_runtime_chars: spec.maxRuntimeChars ?? 6e3,
1689
+ max_output_tokens: maxOutputTokens,
1690
+ minimum_evidence_citations: minimumEvidenceCitations,
1691
+ require_structured_findings: spec.requireStructuredFindings ?? false,
1692
+ prepare_context: spec.prepareContext === void 0 ? "disabled" : "version-bound",
1693
+ post_process: spec.postProcess === void 0 ? "disabled" : "version-bound",
1694
+ recovery: opts.recovery === void 0 ? null : {
1695
+ base_url: opts.recovery.baseUrl,
1696
+ model: opts.recovery.model ?? model,
1697
+ api_key_provided: opts.recovery.apiKey !== void 0,
1698
+ fetch_implementation: opts.recovery.fetchImpl === void 0 ? "global" : "version-bound"
1699
+ },
1700
+ settlement_timeout_ms: settlementTimeoutMs
1701
+ },
1496
1702
  async analyze(store, ctx) {
1497
- const maxOutputTokens = spec.maxOutputTokens ?? 4096;
1498
1703
  const costLedger = ctx.costLedger ?? new CostLedger(ctx.budgetUsd);
1499
1704
  const costTags = {
1500
1705
  ...ctx.tags ?? {},
@@ -1511,7 +1716,10 @@ function createTraceAnalystKind(spec, opts) {
1511
1716
  tags: costTags
1512
1717
  });
1513
1718
  try {
1514
- const tools = spec.buildTools(store);
1719
+ const preparedContext = await spec.prepareContext?.(store, ctx);
1720
+ if (preparedContext !== void 0 && typeof preparedContext !== "string") throw new TypeError(`Trace analyst '${spec.id}' prepareContext must return a string`);
1721
+ const tools = preparedContext === void 0 ? spec.buildTools(store) : [];
1722
+ const analysisMode = preparedContext === void 0 ? "tool-loop" : "prepared-context";
1515
1723
  const maxSubqueries = spec.subqueries?.maxCalls ?? 0;
1516
1724
  const maxParallel = spec.subqueries?.maxParallel ?? 2;
1517
1725
  const priorContext = renderPriorFindings(ctx.priorFindings);
@@ -1520,9 +1728,11 @@ function createTraceAnalystKind(spec, opts) {
1520
1728
  ctx.log?.(`analyst.kind ${spec.id} forward`, {
1521
1729
  max_subqueries: maxSubqueries,
1522
1730
  tool_count: tools.length,
1731
+ analysis_mode: analysisMode,
1732
+ prepared_context_chars: preparedContext?.length ?? 0,
1523
1733
  tags: ctx.tags
1524
1734
  });
1525
- const { report, findings: submittedFindings } = await runTraceAnalysisLoop({
1735
+ const completed = await runTraceAnalysisLoop({
1526
1736
  id: spec.id,
1527
1737
  description: spec.description,
1528
1738
  prompt: actorDescription,
@@ -1535,8 +1745,10 @@ function createTraceAnalystKind(spec, opts) {
1535
1745
  maxParallelSubqueries: maxParallel,
1536
1746
  maxTurns: spec.maxTurns ?? 12,
1537
1747
  maxRuntimeChars: spec.maxRuntimeChars ?? 6e3,
1748
+ ...preparedContext !== void 0 ? { context: preparedContext } : {},
1538
1749
  ...ctx.signal ? { signal: ctx.signal } : {}
1539
1750
  });
1751
+ const { report, findings: submittedFindings } = completed;
1540
1752
  const expectedSubjects = KIND_EXPECTED_SUBJECTS[spec.id];
1541
1753
  const out = [];
1542
1754
  const rawRows = submittedFindings;
@@ -1586,7 +1798,10 @@ function createTraceAnalystKind(spec, opts) {
1586
1798
  if (!parsed) continue;
1587
1799
  const postProcessed = processRow(parsed);
1588
1800
  if (!postProcessed) continue;
1589
- out.push(toAnalystFinding(spec, version, postProcessed));
1801
+ out.push(toAnalystFinding(spec, version, postProcessed, {
1802
+ analysis_mode: analysisMode,
1803
+ analysis_turn_count: completed.turnCount
1804
+ }));
1590
1805
  }
1591
1806
  ctx.log?.(`analyst.kind ${spec.id} done`, {
1592
1807
  emitted: rawRows.length,
@@ -1623,6 +1838,7 @@ function createTraceAnalystKind(spec, opts) {
1623
1838
  });
1624
1839
  }
1625
1840
  if (out.length === 0) {
1841
+ if (spec.requireStructuredFindings) throw new Error(`Trace analyst '${spec.id}' produced no valid structured findings after ${completed.turnCount} turns: ${truncateForContext(report, 600)}`);
1626
1842
  const fallback = processRow({
1627
1843
  claim: "Analyst produced a diagnosis but no structured findings — see report.",
1628
1844
  rationale: report.slice(0, 1500),
@@ -1633,7 +1849,11 @@ function createTraceAnalystKind(spec, opts) {
1633
1849
  excerpt: report.slice(0, 2e3)
1634
1850
  }]
1635
1851
  });
1636
- if (fallback) out.push(toAnalystFinding(spec, version, fallback, { outcome: "extraction_failed" }));
1852
+ if (fallback) out.push(toAnalystFinding(spec, version, fallback, {
1853
+ analysis_mode: analysisMode,
1854
+ analysis_turn_count: completed.turnCount,
1855
+ outcome: "extraction_failed"
1856
+ }));
1637
1857
  else throw new Error(`Trace analyst '${spec.id}' produced a substantive report, but no finding satisfied its acceptance rules`);
1638
1858
  }
1639
1859
  }
@@ -1792,6 +2012,10 @@ var ControlIntegrityAnalyst = class {
1792
2012
  est_usd_per_run: 0
1793
2013
  };
1794
2014
  version = "2.0.0";
2015
+ executionConfig = {
2016
+ kind: "control-integrity",
2017
+ produced_at_source: "tags.producedAt-or-system-clock"
2018
+ };
1795
2019
  async analyze(input, ctx) {
1796
2020
  const findings = emitControlIntegrityFindings(input, ctx.tags?.producedAt ?? (/* @__PURE__ */ new Date()).toISOString());
1797
2021
  ctx.log?.(`control-integrity: ${findings.length} finding(s)`, { input: "nodes" in input ? "SupervisorRunTree" : "SupervisorRunSources" });
@@ -1827,6 +2051,13 @@ const TOOL_NAMES_BY_GROUP = {
1827
2051
  "queryTraces",
1828
2052
  "viewSpans",
1829
2053
  "searchSpan"
2054
+ ]),
2055
+ singleTrace: /* @__PURE__ */ new Set([
2056
+ "getDatasetOverview",
2057
+ "viewTrace",
2058
+ "viewSpans",
2059
+ "searchTrace",
2060
+ "searchSpan"
1830
2061
  ])
1831
2062
  };
1832
2063
  /**
@@ -2017,6 +2248,483 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
2017
2248
  IMPROVEMENT_KIND_SPEC
2018
2249
  ];
2019
2250
  //#endregion
2251
+ //#region src/feedback-trajectory-review.ts
2252
+ /** Bind an analyst finding's complete canonical JSON content to a stable digest. */
2253
+ function analystFindingDigest(finding) {
2254
+ return hashCanonical(snapshotAnalystFinding(finding, "analyst finding"));
2255
+ }
2256
+ /** Bind the complete analyst result to one immutable review target. */
2257
+ function analystRunDigest(run) {
2258
+ return hashCanonical(snapshotAnalystRun(run, "analyst run"));
2259
+ }
2260
+ function snapshotAnalystRun(value, context = "analyst run") {
2261
+ const snapshot = snapshotAnalystRunRecord(value, context);
2262
+ if (snapshot.execution_plan !== void 0) return sealExactAnalystRunReceipt(snapshot, context);
2263
+ if (snapshot.completion !== void 0) throw new TypeError(`${context} completion is valid only for an exact run`);
2264
+ return snapshot;
2265
+ }
2266
+ /** Canonicalize, validate, and deeply freeze one complete or failed exact-run receipt. */
2267
+ function snapshotExactAnalystRunReceipt(value, context = "exact analyst run receipt") {
2268
+ return sealExactAnalystRunReceipt(snapshotAnalystRunRecord(value, context), context);
2269
+ }
2270
+ function snapshotAnalystRunRecord(value, context) {
2271
+ let snapshot;
2272
+ try {
2273
+ snapshot = JSON.parse(canonicalString(value));
2274
+ } catch (cause) {
2275
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
2276
+ }
2277
+ if (!isRecord(snapshot)) throw new TypeError(`${context} must be an object`);
2278
+ assertOnlyKeys(snapshot, [
2279
+ "run_id",
2280
+ "correlation_id",
2281
+ "started_at",
2282
+ "ended_at",
2283
+ "findings",
2284
+ "per_analyst",
2285
+ "total_cost_usd",
2286
+ "total_cost_provenance",
2287
+ "execution_plan",
2288
+ "completion"
2289
+ ], context);
2290
+ requiredString(snapshot.run_id, `${context} run_id`);
2291
+ requiredString(snapshot.correlation_id, `${context} correlation_id`);
2292
+ canonicalTimestamp(snapshot.started_at, `${context} started_at`);
2293
+ canonicalTimestamp(snapshot.ended_at, `${context} ended_at`);
2294
+ snapshot.findings = snapshotAnalystFindings(snapshot.findings, `${context} findings`);
2295
+ if (!Array.isArray(snapshot.per_analyst)) throw new TypeError(`${context} per_analyst must be an array`);
2296
+ for (const [index, summary] of snapshot.per_analyst.entries()) assertAnalystRunSummary(summary, `${context} per_analyst ${index}`);
2297
+ if (typeof snapshot.total_cost_usd !== "number" || !Number.isFinite(snapshot.total_cost_usd) || snapshot.total_cost_usd < 0) throw new TypeError(`${context} total_cost_usd must be a finite non-negative number`);
2298
+ if (snapshot.total_cost_provenance !== void 0) assertCostProvenance(snapshot.total_cost_provenance, `${context} total_cost_provenance`);
2299
+ return snapshot;
2300
+ }
2301
+ function assertAnalystRunSummary(value, context) {
2302
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2303
+ assertOnlyKeys(value, [
2304
+ "analyst_id",
2305
+ "status",
2306
+ "reason",
2307
+ "findings_count",
2308
+ "latency_ms",
2309
+ "usage",
2310
+ "allocated_budget_usd",
2311
+ "error"
2312
+ ], context);
2313
+ requiredString(value.analyst_id, `${context} analyst_id`);
2314
+ if (value.status !== "ok" && value.status !== "skipped" && value.status !== "failed") throw new TypeError(`${context} status is invalid`);
2315
+ if (value.reason !== void 0) requiredString(value.reason, `${context} reason`);
2316
+ if (value.status === "skipped" && value.reason === void 0) throw new TypeError(`${context} skipped summary requires reason`);
2317
+ nonnegativeSafeInteger(value.findings_count, `${context} findings_count`);
2318
+ finiteNonnegative(value.latency_ms, `${context} latency_ms`);
2319
+ assertAnalystUsageReceipt(value.usage, `${context} usage`);
2320
+ if (value.allocated_budget_usd !== void 0 && value.allocated_budget_usd !== null) finiteNonnegative(value.allocated_budget_usd, `${context} allocated_budget_usd`);
2321
+ if (value.error !== void 0) {
2322
+ if (value.status !== "failed" || !isRecord(value.error)) throw new TypeError(`${context} error is valid only for failed summaries`);
2323
+ assertOnlyKeys(value.error, ["class", "message"], `${context} error`);
2324
+ requiredString(value.error.class, `${context} error class`);
2325
+ requiredString(value.error.message, `${context} error message`);
2326
+ } else if (value.status === "failed") throw new TypeError(`${context} failed summary requires error`);
2327
+ }
2328
+ function assertAnalystUsageReceipt(value, context) {
2329
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2330
+ assertOnlyKeys(value, [
2331
+ "calls",
2332
+ "tokens",
2333
+ "cost",
2334
+ "knownCostUsd"
2335
+ ], context);
2336
+ for (const field of [
2337
+ "calls",
2338
+ "tokens",
2339
+ "cost"
2340
+ ]) if (!Object.hasOwn(value, field)) throw new TypeError(`${context} ${field} is required`);
2341
+ if (value.tokens !== null) {
2342
+ if (!isRecord(value.tokens)) throw new TypeError(`${context} tokens must be an object or null`);
2343
+ assertOnlyKeys(value.tokens, [
2344
+ "input",
2345
+ "output",
2346
+ "reasoning",
2347
+ "cached",
2348
+ "cacheWrite"
2349
+ ], `${context} tokens`);
2350
+ }
2351
+ assertCostProvenance(value.cost, `${context} cost`);
2352
+ assertValidAnalystUsageReceipt(value, context);
2353
+ }
2354
+ function assertCostProvenance(value, context) {
2355
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2356
+ assertOnlyKeys(value, ["kind", "usd"], context);
2357
+ if (value.kind === "uncaptured") {
2358
+ if (value.usd !== null) throw new TypeError(`${context} uncaptured usd must be null`);
2359
+ return;
2360
+ }
2361
+ if (value.kind !== "observed" && value.kind !== "estimated") throw new TypeError(`${context} kind is invalid`);
2362
+ finiteNonnegative(value.usd, `${context} usd`);
2363
+ }
2364
+ function sealExactAnalystRunReceipt(run, context) {
2365
+ if (run.execution_plan === void 0) throw new TypeError(`${context} exact run requires execution_plan`);
2366
+ const plan = snapshotExactExecutionPlan(run.execution_plan, `${context} execution_plan`);
2367
+ const completion = snapshotExactRunCompletion(run.completion, `${context} completion`);
2368
+ run.execution_plan = plan;
2369
+ run.completion = completion;
2370
+ const summaries = run.per_analyst;
2371
+ const findings = run.findings;
2372
+ const planned = plan.analysts.map((analyst) => analyst.id);
2373
+ const completed = summaries.map((summary) => summary.analyst_id);
2374
+ if (!completed.every((analystId, index) => analystId === planned[index]) || completion.status === "complete" && completed.length !== planned.length) throw new TypeError(completion.status === "complete" ? `${context} complete receipt must contain every execution_plan analyst in exact order` : `${context} failed receipt per_analyst must be an execution_plan prefix`);
2375
+ const completedIds = new Set(completed);
2376
+ for (const finding of findings) if (!completedIds.has(finding.analyst_id)) throw new TypeError(`${context} finding names an analyst absent from per_analyst`);
2377
+ for (const summary of summaries) {
2378
+ const actual = findings.filter((finding) => finding.analyst_id === summary.analyst_id).length;
2379
+ if (summary.findings_count !== actual) throw new TypeError(`${context} findings_count does not match findings for "${summary.analyst_id}"`);
2380
+ const hasAllocation = Object.hasOwn(summary, "allocated_budget_usd");
2381
+ if (summary.status === "skipped") {
2382
+ if (hasAllocation) throw new TypeError(`${context} skipped summary "${summary.analyst_id}" cannot report an allocated budget`);
2383
+ continue;
2384
+ }
2385
+ const allocation = summary.allocated_budget_usd;
2386
+ if (!(hasAllocation && (plan.policy.budget.kind === "none" ? allocation === null : typeof allocation === "number" && plan.policy.budget.allocations_usd[summary.analyst_id] !== null && plan.policy.budget.allocations_usd[summary.analyst_id] !== void 0 && allocation <= plan.policy.budget.allocations_usd[summary.analyst_id]))) throw new TypeError(`${context} summary "${summary.analyst_id}" allocation does not match its execution plan`);
2387
+ }
2388
+ let knownCost = 0;
2389
+ for (const summary of summaries) {
2390
+ const amount = summary.usage.cost.kind === "uncaptured" ? summary.usage.knownCostUsd ?? 0 : summary.usage.cost.usd ?? 0;
2391
+ knownCost = finiteNonnegative(knownCost + amount, `${context} aggregate known cost`);
2392
+ }
2393
+ if (run.total_cost_usd !== knownCost) throw new TypeError(`${context} total_cost_usd does not match per_analyst usage`);
2394
+ if (run.total_cost_provenance === void 0) throw new TypeError(`${context} exact run requires total_cost_provenance`);
2395
+ const costs = summaries.map((summary) => summary.usage.cost);
2396
+ const expectedProvenance = costs.some((cost) => cost.kind === "uncaptured") ? {
2397
+ kind: "uncaptured",
2398
+ usd: null
2399
+ } : {
2400
+ kind: costs.some((cost) => cost.kind === "estimated") ? "estimated" : "observed",
2401
+ usd: costs.reduce((sum, cost) => finiteNonnegative(sum + (cost.usd ?? 0), `${context} aggregate captured cost`), 0)
2402
+ };
2403
+ if (hashCanonical(run.total_cost_provenance) !== hashCanonical(expectedProvenance)) throw new TypeError(`${context} total_cost_provenance does not match per_analyst usage`);
2404
+ return deepFreezeCanonicalJson(run);
2405
+ }
2406
+ function snapshotExactRunCompletion(value, context) {
2407
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2408
+ if (value.status === "complete") {
2409
+ assertOnlyKeys(value, ["status"], context);
2410
+ return value;
2411
+ }
2412
+ if (value.status !== "failed") throw new TypeError(`${context} status must be complete or failed`);
2413
+ assertOnlyKeys(value, ["status", "error"], context);
2414
+ if (!isRecord(value.error)) throw new TypeError(`${context} failed receipt requires error`);
2415
+ assertOnlyKeys(value.error, ["class", "message"], `${context} error`);
2416
+ requiredString(value.error.class, `${context} error class`);
2417
+ requiredString(value.error.message, `${context} error message`);
2418
+ return value;
2419
+ }
2420
+ function finiteNonnegative(value, context) {
2421
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new TypeError(`${context} must be a non-negative finite number`);
2422
+ return value;
2423
+ }
2424
+ function nonnegativeSafeInteger(value, context) {
2425
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${context} must be a non-negative safe integer`);
2426
+ return value;
2427
+ }
2428
+ function snapshotAnalystFindings(value, context = "analyst run findings") {
2429
+ if (!Array.isArray(value)) throw new TypeError(`${context} must be an array`);
2430
+ const findings = value.map((finding, index) => snapshotAnalystFinding(finding, `${context} finding ${index}`));
2431
+ assertUniqueFindingIds(findings.map((finding) => finding.finding_id));
2432
+ return findings;
2433
+ }
2434
+ function readAnalystReview(trajectory) {
2435
+ const analystAttempts = trajectory.attempts.filter((attempt) => isRecord(attempt.artifact) && attempt.artifact.type === "analyst-run");
2436
+ const analysis = isRecord(trajectory.metadata?.analysis) ? trajectory.metadata.analysis : void 0;
2437
+ if (analystAttempts.length === 0) {
2438
+ if (analysis?.kind === "analyst-run") throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing its archived run`);
2439
+ return;
2440
+ }
2441
+ if (analystAttempts.length !== 1) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" must contain exactly one archived run`);
2442
+ if (analysis?.kind !== "analyst-run") throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing review state`);
2443
+ const artifact = analystAttempts[0].artifact;
2444
+ if (!isRecord(artifact)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" has an invalid archived run`);
2445
+ const runId = requiredString(artifact.analystRunId, `analyst trajectory "${trajectory.id}" run id`);
2446
+ if (analysis.runId !== runId) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" run identity does not match its review state`);
2447
+ const artifactRunDigest = requiredDigest(artifact.runDigest, `analyst trajectory "${trajectory.id}" archived run digest`);
2448
+ const storedRunDigest = requiredDigest(analysis.runDigest, `analyst trajectory "${trajectory.id}" review run digest`);
2449
+ if (artifactRunDigest !== storedRunDigest) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" run digest does not match its review state`);
2450
+ const findings = snapshotAnalystFindings(artifact.findings, `analyst trajectory "${trajectory.id}"`);
2451
+ const findingIds = findings.map((finding) => finding.finding_id);
2452
+ const analystIds = stringArray(artifact.analystIds, `analyst trajectory "${trajectory.id}" analyst ids`);
2453
+ const attemptMetadata = analystAttempts[0].metadata;
2454
+ if (!isRecord(attemptMetadata)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing archived run metadata`);
2455
+ const archivedRun = snapshotAnalystRun({
2456
+ run_id: runId,
2457
+ correlation_id: artifact.correlationId,
2458
+ started_at: analysis.startedAt,
2459
+ ended_at: analysis.endedAt,
2460
+ findings,
2461
+ per_analyst: attemptMetadata.perAnalyst,
2462
+ total_cost_usd: analysis.knownCostUsd,
2463
+ ...analysis.costProvenance === void 0 ? {} : { total_cost_provenance: analysis.costProvenance },
2464
+ ...artifact.executionPlan === void 0 ? {} : {
2465
+ execution_plan: artifact.executionPlan,
2466
+ completion: artifact.completion
2467
+ }
2468
+ }, `analyst trajectory "${trajectory.id}" archived run`);
2469
+ const knownAnalystIds = new Set(analystIds);
2470
+ for (const [index, finding] of findings.entries()) if (!knownAnalystIds.has(finding.analyst_id)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" omits generating analyst "${finding.analyst_id}" at finding ${index}`);
2471
+ const reviewDecisions = validateAnalystReviewDecisions({
2472
+ runId,
2473
+ runDigest: storedRunDigest,
2474
+ findings,
2475
+ analystIds,
2476
+ decisions: analysis.reviewDecisions,
2477
+ requireComplete: true
2478
+ });
2479
+ const expectedRunDigest = analystRunDigest(archivedRun);
2480
+ if (storedRunDigest !== expectedRunDigest) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" archived run digest mismatch`);
2481
+ return {
2482
+ runId,
2483
+ runDigest: expectedRunDigest,
2484
+ findings,
2485
+ findingIds,
2486
+ analystIds,
2487
+ reviewDecisions
2488
+ };
2489
+ }
2490
+ function completedAnalystReviewQuality(review) {
2491
+ const findingDecisions = review.reviewDecisions.filter((decision) => decision.verdict !== "completeness_assessed");
2492
+ const completeness = review.reviewDecisions.filter((decision) => decision.verdict === "completeness_assessed");
2493
+ if (completeness.length !== 1) throw new TypeError("feedbackTrajectoryToOptimizerRow: analyst run requires exactly one independent completeness_assessed decision");
2494
+ const confirmed = findingDecisions.filter((decision) => decision.verdict === "confirmed").length;
2495
+ const rejected = findingDecisions.length - confirmed;
2496
+ const emitted = review.findingIds.length;
2497
+ const missed = completeness[0].missedIssues.length;
2498
+ const precision = emitted === 0 ? 1 : confirmed / emitted;
2499
+ const recallDenominator = confirmed + missed;
2500
+ const recall = recallDenominator === 0 ? 1 : confirmed / recallDenominator;
2501
+ return {
2502
+ precision,
2503
+ recall,
2504
+ f1: precision + recall === 0 ? 0 : 2 * precision * recall / (precision + recall),
2505
+ counts: {
2506
+ emitted,
2507
+ confirmed,
2508
+ rejected,
2509
+ missed
2510
+ }
2511
+ };
2512
+ }
2513
+ function validateAnalystReviewDecisions(input) {
2514
+ if (!Array.isArray(input.decisions)) throw new TypeError("analyst review decisions must be an array");
2515
+ const findings = snapshotAnalystFindings(input.findings);
2516
+ const expectedRunDigest = requiredDigest(input.runDigest, "analyst review run digest");
2517
+ const findingsById = new Map(findings.map((finding) => [finding.finding_id, finding]));
2518
+ const generatingAnalystIds = new Set(input.analystIds);
2519
+ const seenFindingIds = /* @__PURE__ */ new Set();
2520
+ let completenessCount = 0;
2521
+ const decisions = input.decisions.map((value, index) => {
2522
+ if (!isRecord(value)) throw new TypeError(`analyst review decision ${index} must be an object`);
2523
+ const source = requiredString(value.source, `analyst review decision ${index} source`);
2524
+ if (!isAnalystReviewSource(source)) throw new TypeError(`analyst review decision ${index} source must be user, judge, environment, metric, or policy`);
2525
+ const reviewerId = requiredString(value.reviewerId, `analyst review decision ${index} reviewerId`);
2526
+ if (generatingAnalystIds.has(reviewerId)) throw new TypeError(`analyst review decision ${index} reviewerId must differ from the generating analyst`);
2527
+ const reviewId = requiredString(value.reviewId, `analyst review decision ${index} reviewId`);
2528
+ if (reviewId === input.runId) throw new TypeError(`analyst review decision ${index} reviewId must identify an independent review`);
2529
+ const reason = requiredString(value.reason, `analyst review decision ${index} reason`);
2530
+ const decidedAt = canonicalTimestamp(value.decidedAt, `analyst review decision ${index} decidedAt`);
2531
+ const runDigest = requiredDigest(value.runDigest, `analyst review decision ${index} runDigest`);
2532
+ if (runDigest !== expectedRunDigest) throw new TypeError(`analyst review decision ${index} run digest mismatch`);
2533
+ if (value.verdict === "completeness_assessed") {
2534
+ assertOnlyKeys(value, [
2535
+ "runDigest",
2536
+ "verdict",
2537
+ "missedIssues",
2538
+ "source",
2539
+ "reviewerId",
2540
+ "reviewId",
2541
+ "reason",
2542
+ "decidedAt"
2543
+ ], `analyst review decision ${index}`);
2544
+ completenessCount += 1;
2545
+ if (completenessCount > 1) throw new TypeError("duplicate completeness_assessed analyst review decision");
2546
+ return {
2547
+ runDigest,
2548
+ verdict: "completeness_assessed",
2549
+ missedIssues: validateMissedIssues(value.missedIssues, findingsById, `analyst review decision ${index}`),
2550
+ source,
2551
+ reviewerId,
2552
+ reviewId,
2553
+ reason,
2554
+ decidedAt
2555
+ };
2556
+ }
2557
+ if (value.verdict !== "confirmed" && value.verdict !== "rejected") throw new TypeError(`analyst review decision ${index} verdict must be confirmed, rejected, or completeness_assessed`);
2558
+ assertOnlyKeys(value, [
2559
+ "runDigest",
2560
+ "findingId",
2561
+ "findingDigest",
2562
+ "verdict",
2563
+ "source",
2564
+ "reviewerId",
2565
+ "reviewId",
2566
+ "reason",
2567
+ "decidedAt"
2568
+ ], `analyst review decision ${index}`);
2569
+ const findingId = requiredString(value.findingId, `analyst review decision ${index} findingId`);
2570
+ const finding = findingsById.get(findingId);
2571
+ if (!finding) throw new TypeError(`analyst review decision references unknown finding id "${findingId}"`);
2572
+ if (seenFindingIds.has(findingId)) throw new TypeError(`duplicate analyst review decision for finding id "${findingId}"`);
2573
+ seenFindingIds.add(findingId);
2574
+ const findingDigest = requiredString(value.findingDigest, `analyst review decision ${index} findingDigest`);
2575
+ const expectedDigest = analystFindingDigest(finding);
2576
+ if (findingDigest !== expectedDigest) throw new TypeError(`analyst review decision ${index} digest mismatch for finding id "${findingId}"`);
2577
+ return {
2578
+ runDigest,
2579
+ findingId,
2580
+ findingDigest: expectedDigest,
2581
+ verdict: value.verdict,
2582
+ source,
2583
+ reviewerId,
2584
+ reviewId,
2585
+ reason,
2586
+ decidedAt
2587
+ };
2588
+ });
2589
+ if (input.requireComplete) {
2590
+ const missing = findings.map((finding) => finding.finding_id).filter((findingId) => !seenFindingIds.has(findingId));
2591
+ if (missing.length > 0) throw new TypeError(`feedbackTrajectoryToOptimizerRow: missing independent decisions for finding ids: ${missing.join(", ")}`);
2592
+ if (completenessCount !== 1) throw new TypeError("feedbackTrajectoryToOptimizerRow: analyst run requires exactly one independent completeness_assessed decision");
2593
+ }
2594
+ return decisions;
2595
+ }
2596
+ function assertUniqueFindingIds(findingIds) {
2597
+ const seen = /* @__PURE__ */ new Set();
2598
+ for (const findingId of findingIds) {
2599
+ if (findingId.trim().length === 0) throw new TypeError("analyst finding id must not be empty");
2600
+ if (seen.has(findingId)) throw new TypeError(`analyst run contains duplicate finding id "${findingId}"`);
2601
+ seen.add(findingId);
2602
+ }
2603
+ }
2604
+ function snapshotAnalystFinding(value, context) {
2605
+ let snapshot;
2606
+ try {
2607
+ snapshot = JSON.parse(canonicalString(value));
2608
+ } catch (cause) {
2609
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
2610
+ }
2611
+ assertAnalystFinding(snapshot, context);
2612
+ return snapshot;
2613
+ }
2614
+ function assertAnalystFinding(value, context) {
2615
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2616
+ assertOnlyKeys(value, [
2617
+ "schema_version",
2618
+ "finding_id",
2619
+ "analyst_id",
2620
+ "produced_at",
2621
+ "severity",
2622
+ "area",
2623
+ "claim",
2624
+ "rationale",
2625
+ "evidence_refs",
2626
+ "recommended_action",
2627
+ "validation_plan",
2628
+ "confidence",
2629
+ "subject",
2630
+ "derived_from_judge",
2631
+ "metadata"
2632
+ ], context);
2633
+ if (value.schema_version !== "1.0.0") throw new TypeError(`${context} schema_version must be "1.0.0"`);
2634
+ requiredString(value.finding_id, `${context} finding_id`);
2635
+ requiredString(value.analyst_id, `${context} analyst_id`);
2636
+ canonicalTimestamp(value.produced_at, `${context} produced_at`);
2637
+ if (value.severity !== "critical" && value.severity !== "high" && value.severity !== "medium" && value.severity !== "low" && value.severity !== "info") throw new TypeError(`${context} severity is invalid`);
2638
+ requiredString(value.area, `${context} area`);
2639
+ requiredString(value.claim, `${context} claim`);
2640
+ optionalString(value.rationale, `${context} rationale`);
2641
+ value.evidence_refs = validateEvidenceRefs(value.evidence_refs, `${context} evidence_refs`);
2642
+ optionalString(value.recommended_action, `${context} recommended_action`);
2643
+ optionalString(value.validation_plan, `${context} validation_plan`);
2644
+ if (typeof value.confidence !== "number" || !Number.isFinite(value.confidence) || value.confidence < 0 || value.confidence > 1) throw new TypeError(`${context} confidence must be a finite number from 0 through 1`);
2645
+ optionalString(value.subject, `${context} subject`);
2646
+ if (value.derived_from_judge !== void 0 && typeof value.derived_from_judge !== "boolean") throw new TypeError(`${context} derived_from_judge must be a boolean`);
2647
+ if (value.metadata !== void 0 && !isRecord(value.metadata)) throw new TypeError(`${context} metadata must be an object`);
2648
+ }
2649
+ function validateMissedIssues(value, findingsById, context) {
2650
+ if (!Array.isArray(value)) throw new TypeError(`${context} missedIssues must be an array`);
2651
+ const seen = /* @__PURE__ */ new Set();
2652
+ return value.map((issue, index) => {
2653
+ const issueContext = `${context} missedIssues ${index}`;
2654
+ if (!isRecord(issue)) throw new TypeError(`${issueContext} must be an object`);
2655
+ assertOnlyKeys(issue, [
2656
+ "id",
2657
+ "reason",
2658
+ "evidence"
2659
+ ], issueContext);
2660
+ const id = requiredString(issue.id, `${issueContext} id`);
2661
+ if (findingsById.has(id)) throw new TypeError(`${issueContext} id "${id}" is already an emitted finding id`);
2662
+ if (seen.has(id)) throw new TypeError(`duplicate missed issue id "${id}"`);
2663
+ seen.add(id);
2664
+ return {
2665
+ id,
2666
+ reason: requiredString(issue.reason, `${issueContext} reason`),
2667
+ ...issue.evidence === void 0 ? {} : { evidence: validateEvidenceRefs(issue.evidence, `${issueContext} evidence`) }
2668
+ };
2669
+ });
2670
+ }
2671
+ function validateEvidenceRefs(value, context) {
2672
+ if (!Array.isArray(value)) throw new TypeError(`${context} must be an array`);
2673
+ return value.map((evidence, index) => {
2674
+ const evidenceContext = `${context} ${index}`;
2675
+ if (!isRecord(evidence)) throw new TypeError(`${evidenceContext} must be an object`);
2676
+ assertOnlyKeys(evidence, [
2677
+ "kind",
2678
+ "uri",
2679
+ "excerpt"
2680
+ ], evidenceContext);
2681
+ if (evidence.kind !== "span" && evidence.kind !== "event" && evidence.kind !== "artifact" && evidence.kind !== "finding" && evidence.kind !== "metric") throw new TypeError(`${evidenceContext} kind is invalid`);
2682
+ const uri = requiredString(evidence.uri, `${evidenceContext} uri`);
2683
+ const excerpt = evidence.excerpt;
2684
+ optionalString(excerpt, `${evidenceContext} excerpt`);
2685
+ return {
2686
+ kind: evidence.kind,
2687
+ uri,
2688
+ ...excerpt === void 0 ? {} : { excerpt }
2689
+ };
2690
+ });
2691
+ }
2692
+ function assertOnlyKeys(value, allowed, name) {
2693
+ const allowedKeys = new Set(allowed);
2694
+ const unexpected = Object.keys(value).filter((key) => !allowedKeys.has(key));
2695
+ if (unexpected.length > 0) throw new TypeError(`${name} contains unknown fields: ${unexpected.sort().join(", ")}`);
2696
+ }
2697
+ function stringArray(value, name) {
2698
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new TypeError(`${name} must be an array of strings`);
2699
+ const strings = value.map((item) => requiredString(item, name));
2700
+ if (new Set(strings).size !== strings.length) throw new TypeError(`${name} must contain unique values`);
2701
+ return strings;
2702
+ }
2703
+ function requiredString(value, name) {
2704
+ if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string`);
2705
+ return value;
2706
+ }
2707
+ function requiredDigest(value, name) {
2708
+ const digest = requiredString(value, name);
2709
+ if (!/^sha256:[a-f0-9]{64}$/.test(digest)) throw new TypeError(`${name} must be a sha256 digest`);
2710
+ return digest;
2711
+ }
2712
+ function optionalString(value, name) {
2713
+ if (value !== void 0 && typeof value !== "string") throw new TypeError(`${name} must be a string`);
2714
+ }
2715
+ function canonicalTimestamp(value, name) {
2716
+ const timestamp = requiredString(value, name);
2717
+ const parsed = new Date(timestamp);
2718
+ if (Number.isNaN(parsed.valueOf()) || parsed.toISOString() !== timestamp) throw new TypeError(`${name} must be a canonical ISO 8601 UTC timestamp`);
2719
+ return timestamp;
2720
+ }
2721
+ function isAnalystReviewSource(value) {
2722
+ return value === "user" || value === "judge" || value === "environment" || value === "metric" || value === "policy";
2723
+ }
2724
+ function isRecord(value) {
2725
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2726
+ }
2727
+ //#endregion
2020
2728
  //#region src/analyst/registry.ts
2021
2729
  /**
2022
2730
  * AnalystRegistry — orchestrate N analysts against one run.
@@ -2035,6 +2743,17 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
2035
2743
  * (equal split vs weighted vs custom) lives in `BudgetPolicy`. Both
2036
2744
  * have sensible defaults; consumers override only what they need.
2037
2745
  */
2746
+ /** A post-start exact-run failure; completed work remains attached for accounting and review. */
2747
+ var ExactAnalystRunExecutionError = class extends Error {
2748
+ name = "ExactAnalystRunExecutionError";
2749
+ result;
2750
+ constructor(message, result, options) {
2751
+ super(message, options);
2752
+ const snapshot = snapshotExactAnalystRunReceipt(result, "ExactAnalystRunExecutionError result");
2753
+ if (snapshot.completion.status !== "failed") throw new TypeError("ExactAnalystRunExecutionError result must be a failed receipt");
2754
+ this.result = snapshot;
2755
+ }
2756
+ };
2038
2757
  var AnalystRegistry = class {
2039
2758
  analysts = /* @__PURE__ */ new Map();
2040
2759
  options;
@@ -2042,12 +2761,15 @@ var AnalystRegistry = class {
2042
2761
  this.options = options;
2043
2762
  }
2044
2763
  register(analyst) {
2045
- if (!analyst.id) throw new Error("AnalystRegistry.register: analyst.id is required");
2046
- if (this.analysts.has(analyst.id)) throw new Error(`AnalystRegistry.register: duplicate analyst id "${analyst.id}"`);
2047
- if (!analyst.version) throw new Error(`AnalystRegistry.register: analyst "${analyst.id}" must declare a version`);
2048
- if (analyst.cost.kind === "deterministic" && analyst.cost.settlement_timeout_ms !== void 0) throw new TypeError(`AnalystRegistry.register: deterministic analyst "${analyst.id}" cannot declare settlement_timeout_ms`);
2049
- if (analyst.cost.settlement_timeout_ms !== void 0) validateUsageSettlementTimeout(analyst.cost.settlement_timeout_ms);
2050
- this.analysts.set(analyst.id, analyst);
2764
+ const id = analyst.id;
2765
+ const version = analyst.version;
2766
+ const cost = analyst.cost;
2767
+ if (!id) throw new Error("AnalystRegistry.register: analyst.id is required");
2768
+ if (this.analysts.has(id)) throw new Error(`AnalystRegistry.register: duplicate analyst id "${id}"`);
2769
+ if (!version) throw new Error(`AnalystRegistry.register: analyst "${id}" must declare a version`);
2770
+ if (cost.kind === "deterministic" && cost.settlement_timeout_ms !== void 0) throw new TypeError(`AnalystRegistry.register: deterministic analyst "${id}" cannot declare settlement_timeout_ms`);
2771
+ if (cost.settlement_timeout_ms !== void 0) validateUsageSettlementTimeout(cost.settlement_timeout_ms);
2772
+ this.analysts.set(id, analyst);
2051
2773
  }
2052
2774
  list() {
2053
2775
  return Array.from(this.analysts.values()).map((a) => ({
@@ -2061,6 +2783,15 @@ var AnalystRegistry = class {
2061
2783
  for await (const ev of this.runStream(runId, inputs, runOpts)) if (ev.type === "run-completed") return ev.result;
2062
2784
  throw new Error("AnalystRegistry.run: stream completed without run-completed event");
2063
2785
  }
2786
+ /** Run exactly the ordered analysts and complete policy supplied by the caller. */
2787
+ async runExact(runId, inputs, runOpts) {
2788
+ for await (const ev of this.runExactStream(runId, inputs, runOpts)) if (ev.type === "run-completed") return ev.result;
2789
+ throw new Error("AnalystRegistry.runExact: stream completed without run-completed event");
2790
+ }
2791
+ /** Streaming counterpart to {@link runExact}. */
2792
+ async *runExactStream(runId, inputs, runOpts) {
2793
+ for await (const event of this.executePlanStream(this.normalizeExactPlan(runId, inputs, runOpts))) yield event;
2794
+ }
2064
2795
  /**
2065
2796
  * Streaming counterpart to `run()`. Emits `AnalystRunEvent` values
2066
2797
  * in real time — `run-started`, then per-analyst `skipped` /
@@ -2073,50 +2804,124 @@ var AnalystRegistry = class {
2073
2804
  * replacement.
2074
2805
  */
2075
2806
  async *runStream(runId, inputs, runOpts = {}) {
2807
+ yield* this.executePlanStream(this.normalizeLegacyPlan(runId, inputs, runOpts));
2808
+ }
2809
+ normalizeLegacyPlan(runId, inputs, runOpts) {
2810
+ const timeoutMs = validateTimeout(runOpts.timeoutMs) ?? null;
2811
+ const budget = runOpts.budget ?? this.options.defaultBudget;
2812
+ validateBudgetPolicy(budget);
2813
+ return {
2814
+ runId,
2815
+ prepared: this.selectAnalysts(runOpts).map((analyst) => ({
2816
+ analyst,
2817
+ input: this.routeInput(analyst, inputs)
2818
+ })),
2819
+ budget: budget ? {
2820
+ kind: "dynamic",
2821
+ policy: budget
2822
+ } : { kind: "none" },
2823
+ totalTimeoutMs: timeoutMs,
2824
+ signal: runOpts.signal ?? null,
2825
+ costLedger: runOpts.costLedger ?? null,
2826
+ costPhase: runOpts.costPhase ?? null,
2827
+ tags: runOpts.tags ?? null,
2828
+ priorFindings: runOpts.priorFindings ?? null,
2829
+ chainFindings: runOpts.chainFindings ?? false,
2830
+ hooks: this.options.hooks ?? {},
2831
+ chat: this.options.chat,
2832
+ log: this.options.log ?? (() => {}),
2833
+ executionSnapshot: void 0
2834
+ };
2835
+ }
2836
+ normalizeExactPlan(runId, inputs, runOpts) {
2837
+ const exactRunId = snapshotExactRunId(runId);
2838
+ const exact = snapshotExactRegistryRunOpts(runOpts);
2839
+ const selected = normalizeExactAnalysts(this.selectExactAnalysts(exact.analystIds));
2840
+ const registryChat = this.options.chat;
2841
+ const registryChatIdentity = this.options.chatIdentity;
2842
+ const registryHooks = this.options.hooks;
2843
+ const registryHooksIdentity = this.options.hooksIdentity;
2844
+ if (exact.useRegistryChat && registryChat === void 0) throw new TypeError("ExactRegistryRunOpts.useRegistryChat is true but the registry has no chat client");
2845
+ if (exact.applyRegistryHooks && !hasRegistryHooks(registryHooks)) throw new TypeError("ExactRegistryRunOpts.applyRegistryHooks is true but the registry has no lifecycle hooks");
2846
+ const inputSnapshot = snapshotAnalystRunInputChannels(inputs);
2847
+ const prepared = selected.map((analyst) => ({
2848
+ analyst,
2849
+ input: this.routeInput(analyst, inputSnapshot)
2850
+ }));
2851
+ if (exact.missingInputMode === "abort") {
2852
+ const missing = prepared.find((candidate) => candidate.input.kind === "missing")?.analyst;
2853
+ if (missing) throw new TypeError(`ExactRegistryRunOpts.missingInputMode abort preflight found no "${missing.inputKind}" input for "${missing.id}"`);
2854
+ }
2855
+ const hooksIdentity = exact.applyRegistryHooks && registryHooks ? requireExactComponentIdentity(registryHooksIdentity, "registry hooks") : null;
2856
+ const chatIdentity = exact.useRegistryChat ? requireExactComponentIdentity(registryChatIdentity, "registry chat") : null;
2857
+ const costLedgerIdentity = exact.costLedger === null ? null : requireExactComponentIdentity(exact.costLedgerIdentity ?? void 0, "cost ledger");
2858
+ const executionSnapshot = exactExecutionSnapshot(selected, exact, exactFixedBudgets(exact.budget, prepared.filter((candidate) => candidate.input.kind === "present").map((candidate) => candidate.analyst), selected), costLedgerIdentity, hooksIdentity, chatIdentity);
2859
+ return {
2860
+ runId: exactRunId,
2861
+ prepared,
2862
+ budget: { kind: "none" },
2863
+ totalTimeoutMs: exact.totalTimeoutMs,
2864
+ signal: exact.signal,
2865
+ costLedger: exact.costLedger,
2866
+ costPhase: exact.costPhase,
2867
+ tags: exact.tags,
2868
+ priorFindings: exact.priorFindings,
2869
+ chainFindings: exact.chainFindings,
2870
+ hooks: exact.applyRegistryHooks && registryHooks ? snapshotHooks(registryHooks) : {},
2871
+ chat: exact.useRegistryChat && registryChat ? snapshotChat(registryChat) : void 0,
2872
+ log: () => {},
2873
+ executionSnapshot
2874
+ };
2875
+ }
2876
+ async *executePlanStream(plan) {
2877
+ const exact = plan.executionSnapshot !== void 0;
2878
+ if (exact && plan.signal?.aborted) throw abortReason(plan.signal);
2076
2879
  const correlationId = `ar_${randomUUID().slice(0, 12)}`;
2077
- const log = this.options.log ?? (() => {});
2078
- const hooks = this.options.hooks ?? {};
2880
+ const log = plan.log;
2079
2881
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2080
2882
  const started = Date.now();
2081
- const timeoutMs = validateTimeout(runOpts.timeoutMs);
2082
- const deadlineMs = timeoutMs === void 0 ? void 0 : started + timeoutMs;
2083
- const timeoutSignal = timeoutMs === void 0 ? void 0 : AbortSignal.timeout(timeoutMs);
2084
- const runSignal = combineAbortSignals(runOpts.signal, timeoutSignal);
2085
- const selected = this.selectAnalysts(runOpts);
2086
- const budget = runOpts.budget ?? this.options.defaultBudget;
2087
- validateBudgetPolicy(budget);
2088
- yield {
2883
+ const timeoutSignal = plan.totalTimeoutMs === null ? void 0 : AbortSignal.timeout(plan.totalTimeoutMs);
2884
+ const runSignal = combineAbortSignals(plan.signal ?? void 0, timeoutSignal);
2885
+ const deadlineMs = plan.totalTimeoutMs === null ? void 0 : started + plan.totalTimeoutMs;
2886
+ const runnable = plan.prepared.filter((candidate) => candidate.input.kind === "present").map((candidate) => candidate.analyst);
2887
+ let remainingUsd = plan.budget.kind === "dynamic" ? plan.budget.policy.totalUsd : void 0;
2888
+ const weights = plan.budget.kind === "dynamic" ? plan.budget.policy.weights : void 0;
2889
+ const totalWeight = weights && plan.budget.kind === "dynamic" && plan.budget.policy.totalUsd != null && !plan.budget.policy.allocate && runnable.length > 0 ? runnable.reduce((sum, analyst) => sum + analystWeight(weights, analyst.id), 0) : void 0;
2890
+ if (totalWeight === 0) throw new Error("BudgetPolicy.weights must allocate positive weight to a runnable analyst");
2891
+ const upstreamFindings = [];
2892
+ yield snapshotExecutionEvent({
2089
2893
  type: "run-started",
2090
- run_id: runId,
2894
+ run_id: plan.runId,
2091
2895
  correlation_id: correlationId,
2092
2896
  started_at: startedAt,
2093
- analyst_ids: selected.map((a) => a.id)
2094
- };
2095
- const summaries = [];
2096
- const allFindings = [];
2097
- let totalCost = 0;
2098
- let remainingUsd = budget?.totalUsd;
2099
- const runnableAnalysts = selected.filter((a) => this.routeInput(a, inputs).kind !== "missing");
2100
- const runnableCount = runnableAnalysts.length;
2101
- const weights = budget?.weights;
2102
- const totalWeight = weights && budget?.totalUsd != null && !budget.allocate && runnableCount > 0 ? runnableAnalysts.reduce((sum, analyst) => sum + analystWeight(weights, analyst.id), 0) : void 0;
2103
- if (totalWeight === 0) throw new Error("BudgetPolicy.weights must allocate positive weight to a runnable analyst");
2104
- for (const analyst of selected) {
2897
+ analyst_ids: plan.prepared.map(({ analyst }) => analyst.id),
2898
+ ...plan.executionSnapshot === void 0 ? {} : { execution_plan: plan.executionSnapshot }
2899
+ }, exact);
2900
+ const executions = [];
2901
+ let executionFailure;
2902
+ for (const { analyst, input } of plan.prepared) {
2105
2903
  const t0 = Date.now();
2106
2904
  if (runSignal?.aborted) {
2107
2905
  const summary = abortedBeforeStartSummary(analyst, runSignal);
2108
- summaries.push(summary);
2906
+ executions.push({
2907
+ summary,
2908
+ findings: [],
2909
+ budgetDebitUsd: 0
2910
+ });
2109
2911
  log(`[analyst] skip ${analyst.id} — run aborted`, {
2110
- runId,
2912
+ runId: plan.runId,
2111
2913
  reason: summary.reason
2112
2914
  });
2113
- yield {
2915
+ yield snapshotExecutionEvent({
2114
2916
  type: "analyst-skipped",
2115
2917
  summary
2116
- };
2918
+ }, exact);
2919
+ if (exact) {
2920
+ executionFailure = abortReason(runSignal);
2921
+ break;
2922
+ }
2117
2923
  continue;
2118
2924
  }
2119
- const input = this.routeInput(analyst, inputs);
2120
2925
  if (input.kind === "missing") {
2121
2926
  const summary = {
2122
2927
  analyst_id: analyst.id,
@@ -2126,189 +2931,295 @@ var AnalystRegistry = class {
2126
2931
  latency_ms: 0,
2127
2932
  usage: zeroUsage()
2128
2933
  };
2129
- summaries.push(summary);
2934
+ const execution = {
2935
+ summary,
2936
+ findings: [],
2937
+ budgetDebitUsd: 0
2938
+ };
2939
+ executions.push(execution);
2130
2940
  log(`[analyst] skip ${analyst.id} — missing input`, {
2131
- runId,
2941
+ runId: plan.runId,
2132
2942
  kind: analyst.inputKind
2133
2943
  });
2134
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
2135
- analyst,
2136
- summary,
2137
- findings: [],
2138
- runId
2139
- }) : void 0, runSignal);
2140
- yield {
2944
+ const hookValues = snapshotAfterHookValues(summary, [], exact);
2945
+ try {
2946
+ await waitForHook(plan.hooks.onAfterAnalyze ? () => plan.hooks.onAfterAnalyze?.({
2947
+ analyst,
2948
+ summary: hookValues.summary,
2949
+ findings: hookValues.findings,
2950
+ runId: plan.runId
2951
+ }) : void 0, runSignal);
2952
+ } catch (error) {
2953
+ if (!exact) throw error;
2954
+ executionFailure = error;
2955
+ }
2956
+ yield snapshotExecutionEvent({
2141
2957
  type: "analyst-skipped",
2142
2958
  summary
2143
- };
2959
+ }, exact);
2960
+ if (executionFailure !== void 0) break;
2144
2961
  continue;
2145
2962
  }
2146
- const perBudget = allocateBudget(budget, {
2963
+ const allocatedUsd = plan.executionSnapshot === void 0 ? allocateBudget(plan.budget.kind === "dynamic" ? plan.budget.policy : void 0, {
2147
2964
  analyst,
2148
2965
  remainingUsd,
2149
- runningCount: runnableCount,
2966
+ runningCount: runnable.length,
2150
2967
  totalWeight
2151
- });
2968
+ }) : exactPlannedAllocation(plan.executionSnapshot, analyst.id);
2969
+ const budgetCeilingUsd = plan.executionSnapshot === void 0 ? remainingUsd : allocatedUsd;
2152
2970
  const usageReceipts = [];
2971
+ const contextTags = plan.tags === null ? void 0 : { ...plan.tags };
2972
+ const priorFindings = selectPriorFindings(plan.priorFindings ?? void 0, analyst.id);
2973
+ const chainedFindings = plan.chainFindings && upstreamFindings.length > 0 ? [...upstreamFindings] : void 0;
2153
2974
  const ctx = {
2154
- runId,
2975
+ runId: plan.runId,
2155
2976
  correlationId,
2156
2977
  deadlineMs,
2157
- budgetUsd: perBudget,
2158
- costLedger: runOpts.costLedger,
2159
- costPhase: runOpts.costPhase,
2160
- chat: this.options.chat,
2161
- tags: runOpts.tags,
2162
- log: (msg, fields) => log(`[${analyst.id}] ${msg}`, {
2163
- runId,
2978
+ budgetUsd: allocatedUsd,
2979
+ costLedger: plan.costLedger ?? void 0,
2980
+ costPhase: plan.costPhase ?? void 0,
2981
+ chat: plan.chat,
2982
+ tags: contextTags,
2983
+ log: (message, fields) => log(`[${analyst.id}] ${message}`, {
2984
+ runId: plan.runId,
2164
2985
  correlationId,
2165
2986
  ...fields
2166
2987
  }),
2167
2988
  signal: runSignal,
2168
- priorFindings: selectPriorFindings(runOpts.priorFindings, analyst.id),
2169
- upstreamFindings: runOpts.chainFindings && allFindings.length > 0 ? [...allFindings] : void 0,
2989
+ priorFindings,
2990
+ upstreamFindings: chainedFindings,
2170
2991
  recordUsage: (receipt) => {
2171
- assertValidUsageReceipt(receipt);
2172
- usageReceipts.push(receipt);
2992
+ if (!exact) {
2993
+ assertValidAnalystUsageReceipt(receipt);
2994
+ usageReceipts.push(receipt);
2995
+ return;
2996
+ }
2997
+ usageReceipts.push(snapshotUsageReceiptOnce(receipt, `AnalystRegistry.runExact analyst "${analyst.id}" usage`));
2173
2998
  }
2174
2999
  };
2175
- await waitForHook(hooks.onBeforeAnalyze ? () => hooks.onBeforeAnalyze?.({
2176
- analyst,
2177
- ctx,
2178
- runId
2179
- }) : void 0, runSignal);
3000
+ if (exact) {
3001
+ if (contextTags) deepFreezeCanonicalJson(contextTags);
3002
+ if (priorFindings) deepFreezeCanonicalJson(priorFindings);
3003
+ if (chainedFindings) deepFreezeCanonicalJson(chainedFindings);
3004
+ Object.freeze(ctx);
3005
+ }
3006
+ try {
3007
+ await waitForHook(plan.hooks.onBeforeAnalyze ? () => plan.hooks.onBeforeAnalyze?.({
3008
+ analyst,
3009
+ ctx,
3010
+ runId: plan.runId
3011
+ }) : void 0, runSignal);
3012
+ } catch (error) {
3013
+ if (!exact) throw error;
3014
+ executionFailure = error;
3015
+ break;
3016
+ }
2180
3017
  if (runSignal?.aborted) {
2181
3018
  const summary = abortedBeforeStartSummary(analyst, runSignal, Date.now() - t0);
2182
- summaries.push(summary);
2183
- log(`[analyst] skip ${analyst.id} — run aborted`, {
2184
- runId,
2185
- reason: summary.reason
3019
+ executions.push({
3020
+ summary,
3021
+ findings: [],
3022
+ budgetDebitUsd: 0
2186
3023
  });
2187
- yield {
3024
+ yield snapshotExecutionEvent({
2188
3025
  type: "analyst-skipped",
2189
3026
  summary
2190
- };
3027
+ }, exact);
3028
+ if (exact) {
3029
+ executionFailure = abortReason(runSignal);
3030
+ break;
3031
+ }
2191
3032
  continue;
2192
3033
  }
2193
- const effectiveBudget = validateEffectiveBudget(ctx.budgetUsd, remainingUsd, analyst.id);
2194
- yield {
3034
+ let effectiveBudget;
3035
+ try {
3036
+ effectiveBudget = validateEffectiveBudget(ctx.budgetUsd, budgetCeilingUsd, analyst.id);
3037
+ } catch (error) {
3038
+ if (!exact) throw error;
3039
+ executionFailure = error;
3040
+ break;
3041
+ }
3042
+ const analystContext = exact ? ctx : { ...ctx };
3043
+ const executionSignal = exact ? ctx.signal : runSignal;
3044
+ yield snapshotExecutionEvent({
2195
3045
  type: "analyst-started",
2196
3046
  analyst_id: analyst.id,
2197
3047
  started_at: new Date(t0).toISOString()
2198
- };
3048
+ }, exact);
2199
3049
  let findings;
2200
3050
  let summary;
3051
+ let lifecycleFailure;
3052
+ let analysisFailure;
2201
3053
  try {
2202
3054
  if (runSignal?.aborted) throw abortReason(runSignal);
2203
- findings = await waitForOperation(analyst.analyze(input.value, ctx), runSignal, analystAbortGraceMs(analyst));
2204
- const latency = Date.now() - t0;
2205
- const usage = resolveUsage(analyst, usageReceipts);
2206
- const cost = knownCostUsd(usage);
2207
- totalCost += cost;
2208
- if (typeof remainingUsd === "number") remainingUsd = Math.max(0, remainingUsd - budgetDebit(usage, effectiveBudget));
2209
- allFindings.push(...findings);
3055
+ findings = snapshotExecutionFindings(await waitForOperation(analyst.analyze(input.value, analystContext), executionSignal, analystAbortGraceMs(analyst)), exact, `AnalystRegistry.runExact analyst "${analyst.id}" findings`);
3056
+ } catch (error) {
3057
+ const cause = error instanceof Error ? error : new Error(String(error));
3058
+ analysisFailure = cause;
3059
+ let hookFindings = [];
3060
+ if (!executionSignal?.aborted) try {
3061
+ hookFindings = snapshotExecutionFindings(await waitForHook(plan.hooks.onError ? () => plan.hooks.onError?.({
3062
+ analyst,
3063
+ error: cause,
3064
+ runId: plan.runId
3065
+ }) : void 0, executionSignal) ?? [], exact, `AnalystRegistry.runExact analyst "${analyst.id}" onError findings`);
3066
+ } catch (error) {
3067
+ lifecycleFailure = error;
3068
+ }
3069
+ findings = hookFindings;
3070
+ }
3071
+ let usage;
3072
+ try {
3073
+ usage = resolveUsage(analyst, usageReceipts, exact);
3074
+ } catch (error) {
3075
+ if (!exact) throw error;
3076
+ executionFailure = error;
3077
+ break;
3078
+ }
3079
+ if (analysisFailure === void 0) {
2210
3080
  summary = {
2211
3081
  analyst_id: analyst.id,
2212
3082
  status: "ok",
2213
3083
  findings_count: findings.length,
2214
- latency_ms: latency,
2215
- usage
3084
+ latency_ms: Date.now() - t0,
3085
+ usage,
3086
+ ...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {}
2216
3087
  };
2217
- summaries.push(summary);
2218
3088
  log(`[analyst] ok ${analyst.id}`, {
2219
- runId,
3089
+ runId: plan.runId,
2220
3090
  findings: findings.length,
2221
- latency_ms: latency,
2222
- cost_usd: cost,
3091
+ latency_ms: summary.latency_ms,
3092
+ cost_usd: knownCostUsd(usage),
2223
3093
  cost_kind: usage.cost.kind,
2224
3094
  input_tokens: usage.tokens?.input ?? null,
2225
3095
  output_tokens: usage.tokens?.output ?? null
2226
3096
  });
2227
- if (effectiveBudget !== void 0 && usage.cost.kind === "uncaptured") log(`[analyst] WARN ${analyst.id} USD cost uncaptured; budget not reconciled`, {
2228
- runId,
2229
- budget_usd: effectiveBudget,
2230
- cost_captured: false
2231
- });
2232
- } catch (err) {
2233
- const latency = Date.now() - t0;
2234
- const e = err instanceof Error ? err : new Error(String(err));
2235
- const hookFindings = runSignal?.aborted ? [] : await hooks.onError?.({
2236
- analyst,
2237
- error: e,
2238
- runId
2239
- }) ?? [];
2240
- if (hookFindings.length) allFindings.push(...hookFindings);
2241
- const usage = resolveUsage(analyst, usageReceipts);
2242
- const cost = knownCostUsd(usage);
2243
- totalCost += cost;
2244
- if (typeof remainingUsd === "number") remainingUsd = Math.max(0, remainingUsd - budgetDebit(usage, effectiveBudget));
2245
- const summary = {
3097
+ } else {
3098
+ const errorClass = analysisFailure.constructor.name || "Error";
3099
+ const errorMessage = exact && analysisFailure.message.length === 0 ? "Analyst failed without an error message" : analysisFailure.message;
3100
+ summary = {
2246
3101
  analyst_id: analyst.id,
2247
3102
  status: "failed",
2248
- findings_count: hookFindings.length,
2249
- latency_ms: latency,
3103
+ findings_count: findings.length,
3104
+ latency_ms: Date.now() - t0,
2250
3105
  usage,
3106
+ ...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {},
2251
3107
  error: {
2252
- class: e.constructor.name,
2253
- message: e.message
3108
+ class: errorClass,
3109
+ message: errorMessage
2254
3110
  }
2255
3111
  };
2256
- summaries.push(summary);
2257
3112
  log(`[analyst] FAIL ${analyst.id}`, {
2258
- runId,
2259
- error_class: e.constructor.name,
2260
- error: e.message,
2261
- cost_usd: cost,
3113
+ runId: plan.runId,
3114
+ error_class: errorClass,
3115
+ error: errorMessage,
3116
+ cost_usd: knownCostUsd(usage),
2262
3117
  cost_kind: usage.cost.kind
2263
3118
  });
2264
- if (effectiveBudget !== void 0 && usage.cost.kind === "uncaptured") log(`[analyst] WARN ${analyst.id} — USD cost uncaptured; budget not reconciled`, {
2265
- runId,
2266
- budget_usd: effectiveBudget,
2267
- cost_captured: false
2268
- });
2269
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
2270
- analyst,
2271
- summary,
2272
- findings: hookFindings,
2273
- runId
2274
- }) : void 0, runSignal);
2275
- yield {
2276
- type: "analyst-completed",
2277
- summary,
2278
- findings: hookFindings
2279
- };
2280
- continue;
2281
3119
  }
2282
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
3120
+ logUncapturedBudgetWarning({
2283
3121
  analyst,
3122
+ runId: plan.runId,
3123
+ budgetUsd: effectiveBudget,
3124
+ usage,
3125
+ log
3126
+ });
3127
+ const execution = {
2284
3128
  summary,
2285
3129
  findings,
2286
- runId
2287
- }) : void 0, runSignal);
2288
- yield {
3130
+ budgetDebitUsd: budgetDebit(summary.usage, effectiveBudget)
3131
+ };
3132
+ if (exact) try {
3133
+ executionCost([...executions, execution], true);
3134
+ } catch (error) {
3135
+ executionFailure = error;
3136
+ break;
3137
+ }
3138
+ executions.push(execution);
3139
+ if (plan.budget.kind === "dynamic" && remainingUsd !== void 0) remainingUsd = Math.max(0, remainingUsd - execution.budgetDebitUsd);
3140
+ if (plan.chainFindings) upstreamFindings.push(...findings);
3141
+ if (lifecycleFailure !== void 0) {
3142
+ if (!exact) throw lifecycleFailure;
3143
+ executionFailure = lifecycleFailure;
3144
+ break;
3145
+ }
3146
+ const hookValues = snapshotAfterHookValues(summary, findings, exact);
3147
+ try {
3148
+ await waitForHook(plan.hooks.onAfterAnalyze ? () => plan.hooks.onAfterAnalyze?.({
3149
+ analyst,
3150
+ summary: hookValues.summary,
3151
+ findings: hookValues.findings,
3152
+ runId: plan.runId
3153
+ }) : void 0, executionSignal);
3154
+ } catch (error) {
3155
+ if (!exact) throw error;
3156
+ executionFailure = error;
3157
+ break;
3158
+ }
3159
+ yield snapshotExecutionEvent({
2289
3160
  type: "analyst-completed",
2290
3161
  summary,
2291
3162
  findings
2292
- };
3163
+ }, exact);
3164
+ if (exact && runSignal?.aborted) {
3165
+ executionFailure = abortReason(runSignal);
3166
+ break;
3167
+ }
2293
3168
  }
2294
- const result = {
2295
- run_id: runId,
3169
+ const summaries = executions.map(({ summary }) => summary);
3170
+ const findings = executions.flatMap((execution) => execution.findings);
3171
+ const cost = executionCost(executions, exact);
3172
+ const baseResult = {
3173
+ run_id: plan.runId,
2296
3174
  correlation_id: correlationId,
2297
3175
  started_at: startedAt,
2298
3176
  ended_at: (/* @__PURE__ */ new Date()).toISOString(),
2299
- findings: allFindings,
3177
+ findings,
2300
3178
  per_analyst: summaries,
2301
- total_cost_usd: totalCost,
2302
- total_cost_provenance: aggregateCostProvenance(summaries.map((summary) => summary.usage?.cost ?? {
2303
- kind: "uncaptured",
2304
- usd: null
2305
- }))
2306
- };
2307
- await waitForHook(hooks.onComplete ? () => hooks.onComplete?.({ result }) : void 0, runSignal);
2308
- yield {
2309
- type: "run-completed",
2310
- result
3179
+ total_cost_usd: cost.known,
3180
+ total_cost_provenance: cost.provenance
2311
3181
  };
3182
+ if (plan.executionSnapshot === void 0) {
3183
+ await waitForHook(plan.hooks.onComplete ? () => plan.hooks.onComplete?.({ result: baseResult }) : void 0, runSignal);
3184
+ yield {
3185
+ type: "run-completed",
3186
+ result: baseResult
3187
+ };
3188
+ return;
3189
+ }
3190
+ let completeResult;
3191
+ if (executionFailure === void 0) try {
3192
+ completeResult = snapshotExactAnalystRunReceipt({
3193
+ ...baseResult,
3194
+ execution_plan: plan.executionSnapshot,
3195
+ completion: { status: "complete" }
3196
+ }, "AnalystRegistry.runExact result");
3197
+ await waitForHook(plan.hooks.onComplete ? () => plan.hooks.onComplete?.({ result: completeResult }) : void 0, runSignal);
3198
+ } catch (error) {
3199
+ executionFailure = error;
3200
+ }
3201
+ if (runSignal?.aborted) executionFailure ??= abortReason(runSignal);
3202
+ if (executionFailure === void 0 && completeResult) {
3203
+ yield snapshotExecutionEvent({
3204
+ type: "run-completed",
3205
+ result: completeResult
3206
+ }, true);
3207
+ return;
3208
+ }
3209
+ const cause = executionFailure instanceof Error ? executionFailure : new Error(String(executionFailure));
3210
+ const errorClass = cause.constructor.name || "Error";
3211
+ const errorMessage = cause.message.trim().length === 0 ? "Exact analyst run failed without a message" : cause.message;
3212
+ throw new ExactAnalystRunExecutionError(`exact analyst run failed after starting: ${errorMessage}; partial result is attached`, {
3213
+ ...baseResult,
3214
+ execution_plan: plan.executionSnapshot,
3215
+ completion: {
3216
+ status: "failed",
3217
+ error: {
3218
+ class: errorClass,
3219
+ message: errorMessage
3220
+ }
3221
+ }
3222
+ }, { cause });
2312
3223
  }
2313
3224
  selectAnalysts(opts) {
2314
3225
  let candidates = Array.from(this.analysts.values());
@@ -2322,34 +3233,377 @@ var AnalystRegistry = class {
2322
3233
  }
2323
3234
  return candidates;
2324
3235
  }
3236
+ selectExactAnalysts(ids) {
3237
+ return ids.map((id) => {
3238
+ const analyst = this.analysts.get(id);
3239
+ if (!analyst) throw new Error(`ExactRegistryRunOpts.analystIds names unknown analyst "${id}"`);
3240
+ return {
3241
+ registeredId: id,
3242
+ analyst
3243
+ };
3244
+ });
3245
+ }
2325
3246
  routeInput(analyst, inputs) {
2326
3247
  switch (analyst.inputKind) {
2327
- case "trace-store": return inputs.traceStore ? {
2328
- kind: "present",
2329
- value: inputs.traceStore
2330
- } : { kind: "missing" };
2331
- case "artifact-dir": return inputs.artifactDir ? {
2332
- kind: "present",
2333
- value: inputs.artifactDir
2334
- } : { kind: "missing" };
2335
- case "run-record": return inputs.runRecord ? {
2336
- kind: "present",
2337
- value: inputs.runRecord
2338
- } : { kind: "missing" };
2339
- case "judge-input": return inputs.judgeInput ? {
2340
- kind: "present",
2341
- value: inputs.judgeInput
2342
- } : { kind: "missing" };
3248
+ case "trace-store": {
3249
+ const value = inputs.traceStore;
3250
+ return value ? {
3251
+ kind: "present",
3252
+ value
3253
+ } : { kind: "missing" };
3254
+ }
3255
+ case "artifact-dir": {
3256
+ const value = inputs.artifactDir;
3257
+ return value ? {
3258
+ kind: "present",
3259
+ value
3260
+ } : { kind: "missing" };
3261
+ }
3262
+ case "run-record": {
3263
+ const value = inputs.runRecord;
3264
+ return value ? {
3265
+ kind: "present",
3266
+ value
3267
+ } : { kind: "missing" };
3268
+ }
3269
+ case "judge-input": {
3270
+ const value = inputs.judgeInput;
3271
+ return value ? {
3272
+ kind: "present",
3273
+ value
3274
+ } : { kind: "missing" };
3275
+ }
2343
3276
  case "custom": {
2344
- const v = inputs.custom?.[analyst.id];
2345
- return v !== void 0 ? {
3277
+ const value = inputs.custom?.[analyst.id];
3278
+ return value !== void 0 ? {
2346
3279
  kind: "present",
2347
- value: v
3280
+ value
2348
3281
  } : { kind: "missing" };
2349
3282
  }
2350
3283
  }
2351
3284
  }
2352
3285
  };
3286
+ const exactRunFields = [
3287
+ "analystIds",
3288
+ "budget",
3289
+ "totalTimeoutMs",
3290
+ "signal",
3291
+ "costLedger",
3292
+ "costLedgerIdentity",
3293
+ "costPhase",
3294
+ "tags",
3295
+ "priorFindings",
3296
+ "chainFindings",
3297
+ "missingInputMode",
3298
+ "applyRegistryHooks",
3299
+ "useRegistryChat"
3300
+ ];
3301
+ const exactNonEmptyString = z.string().min(1);
3302
+ const exactFiniteNonnegative = z.number().finite().nonnegative();
3303
+ const exactBudgetSchema = z.discriminatedUnion("kind", [z.strictObject({
3304
+ kind: z.literal("equal"),
3305
+ totalUsd: exactFiniteNonnegative
3306
+ }), z.strictObject({
3307
+ kind: z.literal("weighted"),
3308
+ totalUsd: exactFiniteNonnegative,
3309
+ weights: z.record(exactNonEmptyString, exactFiniteNonnegative)
3310
+ })]);
3311
+ const exactRunDataSchema = z.strictObject({
3312
+ analystIds: z.array(exactNonEmptyString).min(1),
3313
+ budget: exactBudgetSchema.nullable(),
3314
+ totalTimeoutMs: z.number().int().positive().max(2147483647).nullable(),
3315
+ costLedgerIdentity: z.unknown().nullable(),
3316
+ costPhase: exactNonEmptyString.nullable(),
3317
+ tags: z.record(z.string(), z.string()).nullable(),
3318
+ chainFindings: z.boolean(),
3319
+ missingInputMode: z.enum(["skip", "abort"]),
3320
+ applyRegistryHooks: z.boolean(),
3321
+ useRegistryChat: z.boolean()
3322
+ }).superRefine((policy, context) => {
3323
+ const issue = (path, message) => context.addIssue({
3324
+ code: "custom",
3325
+ path,
3326
+ message
3327
+ });
3328
+ if (new Set(policy.analystIds).size !== policy.analystIds.length) issue(["analystIds"], "must not contain duplicates");
3329
+ if (policy.budget?.kind === "weighted" && Object.values(policy.budget.weights).every((weight) => weight === 0)) issue(["budget", "weights"], "must allocate positive weight to at least one analyst");
3330
+ if (policy.budget?.kind === "weighted") {
3331
+ const selected = [...policy.analystIds].sort();
3332
+ const weighted = Object.keys(policy.budget.weights).sort();
3333
+ if (selected.length !== weighted.length || selected.some((id, index) => id !== weighted[index])) issue(["budget", "weights"], "must name every selected analyst and no others");
3334
+ }
3335
+ });
3336
+ /** Validate the canonical exact-run policy before any analyst can start. */
3337
+ function assertExactRegistryRunOpts(value) {
3338
+ snapshotExactRegistryRunOpts(value);
3339
+ }
3340
+ function snapshotExactRunId(value) {
3341
+ if (typeof value !== "string" || value.length === 0) throw new TypeError("AnalystRegistry.runExact: runId must be a non-empty string");
3342
+ return canonicalJsonSnapshot(value, "AnalystRegistry.runExact runId");
3343
+ }
3344
+ function snapshotAnalystRunInputChannels(inputs) {
3345
+ if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) throw new TypeError("AnalystRegistry.runExact: inputs must be an object");
3346
+ const traceStore = inputs.traceStore;
3347
+ const artifactDir = inputs.artifactDir;
3348
+ const runRecord = inputs.runRecord;
3349
+ const judgeInput = inputs.judgeInput;
3350
+ const custom = inputs.custom;
3351
+ return Object.freeze({
3352
+ traceStore,
3353
+ artifactDir,
3354
+ runRecord,
3355
+ judgeInput,
3356
+ custom
3357
+ });
3358
+ }
3359
+ /**
3360
+ * Read the untrusted caller object once, then validate and execute only this frozen snapshot.
3361
+ * Functions and resource handles retain identity; all data fields are copied canonically.
3362
+ */
3363
+ function snapshotExactRegistryRunOpts(value) {
3364
+ const captured = readOwnFields(value, exactRunFields, "ExactRegistryRunOpts");
3365
+ const missing = exactRunFields.find((field) => !Object.hasOwn(captured, field));
3366
+ if (missing) throw new TypeError(`ExactRegistryRunOpts.${missing} must be supplied explicitly`);
3367
+ const { signal, costLedger, priorFindings, ...rawData } = captured;
3368
+ const data = canonicalJsonSnapshot(rawData, "ExactRegistryRunOpts");
3369
+ const parsed = exactRunDataSchema.safeParse(data);
3370
+ if (!parsed.success) {
3371
+ const issue = parsed.error.issues[0];
3372
+ if (issue?.code === "unrecognized_keys" && issue.path.join(".") === "budget") {
3373
+ const required = isPlainRecord(data.budget) && data.budget.kind === "weighted" ? "kind, totalUsd, weights" : "kind, totalUsd";
3374
+ throw new TypeError(`ExactRegistryRunOpts.budget must contain exactly ${required}`);
3375
+ }
3376
+ const path = issue?.path.length ? `.${issue.path.join(".")}` : "";
3377
+ throw new TypeError(`ExactRegistryRunOpts${path}: ${issue?.message ?? "is invalid"}`);
3378
+ }
3379
+ if (signal !== null && (!signal || typeof signal !== "object" || typeof signal.addEventListener !== "function")) throw new TypeError("ExactRegistryRunOpts.signal must be an AbortSignal or null");
3380
+ if (costLedger !== null && (!costLedger || typeof costLedger !== "object")) throw new TypeError("ExactRegistryRunOpts.costLedger must be a CostLedgerHandle or null");
3381
+ if (costLedger === null && parsed.data.costLedgerIdentity !== null) throw new TypeError("ExactRegistryRunOpts.costLedgerIdentity must be null without costLedger");
3382
+ if (costLedger !== null && parsed.data.costLedgerIdentity === null) throw new TypeError("ExactRegistryRunOpts.costLedgerIdentity is required with costLedger");
3383
+ if (costLedger === null && parsed.data.costPhase !== null) throw new TypeError("ExactRegistryRunOpts.costPhase requires a non-null costLedger");
3384
+ return Object.freeze({
3385
+ ...deepFreezeCanonicalJson(parsed.data),
3386
+ signal,
3387
+ costLedger,
3388
+ priorFindings: snapshotExactPriorFindings(priorFindings)
3389
+ });
3390
+ }
3391
+ function snapshotExactPriorFindings(value) {
3392
+ if (value === null) return null;
3393
+ if (Array.isArray(value)) return snapshotAnalystFindings(value, "ExactRegistryRunOpts.priorFindings");
3394
+ if (!isPlainRecord(value)) throw new TypeError("ExactRegistryRunOpts.priorFindings must be an array, a findings record, or null");
3395
+ const result = {};
3396
+ for (const [key, findings] of Object.entries(value)) {
3397
+ if (!Array.isArray(findings)) throw new TypeError(`ExactRegistryRunOpts.priorFindings.${key} must be an array`);
3398
+ result[key] = snapshotAnalystFindings(findings, `ExactRegistryRunOpts.priorFindings.${key}`);
3399
+ }
3400
+ return deepFreezeCanonicalJson(result);
3401
+ }
3402
+ function normalizeExactAnalysts(selections) {
3403
+ return selections.map(({ registeredId, analyst }) => {
3404
+ const exactAnalyst = analyst;
3405
+ const id = analyst.id;
3406
+ const description = analyst.description;
3407
+ const inputKind = analyst.inputKind;
3408
+ const rawCostValue = analyst.cost;
3409
+ const requiresValue = analyst.requires;
3410
+ const version = analyst.version;
3411
+ const executionConfigValue = exactAnalyst.executionConfig;
3412
+ const analyzeValue = analyst.analyze;
3413
+ if (id !== registeredId) throw new TypeError(`AnalystRegistry.runExact: registered analyst "${registeredId}" changed id to "${id}"`);
3414
+ if (executionConfigValue === void 0) throw new TypeError(`AnalystRegistry.runExact: analyst "${id}" must declare executionConfig`);
3415
+ const executionConfig = canonicalJsonSnapshot(executionConfigValue, `AnalystRegistry.runExact analyst "${id}" executionConfig`);
3416
+ if (!isPlainRecord(executionConfig)) throw new TypeError(`AnalystRegistry.runExact analyst "${id}" executionConfig must be an object`);
3417
+ const rawCost = canonicalJsonSnapshot(rawCostValue, `AnalystRegistry.runExact analyst "${id}" cost`);
3418
+ const cost = rawCost.kind === "llm" ? Object.freeze({
3419
+ ...rawCost,
3420
+ settlement_timeout_ms: validateUsageSettlementTimeout(rawCost.settlement_timeout_ms)
3421
+ }) : rawCost;
3422
+ const requires = requiresValue === void 0 ? void 0 : canonicalJsonSnapshot(requiresValue, `AnalystRegistry.runExact analyst "${id}" requirements`);
3423
+ const analyze = analyzeValue.bind(analyst);
3424
+ return Object.freeze({
3425
+ id,
3426
+ description,
3427
+ inputKind,
3428
+ cost,
3429
+ ...requires === void 0 ? {} : { requires },
3430
+ version,
3431
+ executionConfig,
3432
+ analyze
3433
+ });
3434
+ });
3435
+ }
3436
+ function hasRegistryHooks(hooks) {
3437
+ return Boolean(hooks && (hooks.onBeforeAnalyze || hooks.onAfterAnalyze || hooks.onError || hooks.onComplete));
3438
+ }
3439
+ function snapshotHooks(hooks) {
3440
+ const onBeforeAnalyze = hooks.onBeforeAnalyze;
3441
+ const onAfterAnalyze = hooks.onAfterAnalyze;
3442
+ const onError = hooks.onError;
3443
+ const onComplete = hooks.onComplete;
3444
+ return Object.freeze({
3445
+ ...onBeforeAnalyze === void 0 ? {} : { onBeforeAnalyze: onBeforeAnalyze.bind(hooks) },
3446
+ ...onAfterAnalyze === void 0 ? {} : { onAfterAnalyze: onAfterAnalyze.bind(hooks) },
3447
+ ...onError === void 0 ? {} : { onError: onError.bind(hooks) },
3448
+ ...onComplete === void 0 ? {} : { onComplete: onComplete.bind(hooks) }
3449
+ });
3450
+ }
3451
+ function snapshotChat(chat) {
3452
+ const transport = chat.transport;
3453
+ const defaultModel = chat.defaultModel;
3454
+ const maximumAttempts = chat.maximumAttempts;
3455
+ const call = chat.chat;
3456
+ return Object.freeze({
3457
+ transport,
3458
+ ...defaultModel === void 0 ? {} : { defaultModel },
3459
+ ...maximumAttempts === void 0 ? {} : { maximumAttempts },
3460
+ chat: call.bind(chat)
3461
+ });
3462
+ }
3463
+ function requireExactComponentIdentity(value, label) {
3464
+ if (value === void 0) throw new TypeError(`AnalystRegistry.runExact: ${label} requires a versioned identity`);
3465
+ return snapshotExactExecutionComponentIdentity(value, `AnalystRegistry.runExact ${label} identity`);
3466
+ }
3467
+ function exactExecutionSnapshot(analysts, opts, allocations, costLedger, hooks, chat) {
3468
+ const priorFindings = exactPriorFindingsSnapshot(opts.priorFindings);
3469
+ const budget = opts.budget === null ? { kind: "none" } : opts.budget.kind === "equal" ? {
3470
+ kind: "equal",
3471
+ total_usd: opts.budget.totalUsd,
3472
+ allocations_usd: { ...allocations }
3473
+ } : {
3474
+ kind: "weighted",
3475
+ total_usd: opts.budget.totalUsd,
3476
+ weights: { ...opts.budget.weights },
3477
+ allocations_usd: { ...allocations }
3478
+ };
3479
+ const material = {
3480
+ schema_version: "1.0.0",
3481
+ analysts: analysts.map((analyst) => ({
3482
+ id: analyst.id,
3483
+ version: analyst.version,
3484
+ input_kind: analyst.inputKind,
3485
+ cost: analyst.cost,
3486
+ requirements: analyst.requires ?? null,
3487
+ execution_config_digest: hashCanonical(analyst.executionConfig)
3488
+ })),
3489
+ policy: {
3490
+ budget,
3491
+ total_timeout_ms: opts.totalTimeoutMs,
3492
+ signal_provided: opts.signal !== null,
3493
+ cost_ledger: costLedger,
3494
+ cost_phase: opts.costPhase,
3495
+ tags: opts.tags === null ? null : { ...opts.tags },
3496
+ prior_findings: priorFindings,
3497
+ chain_findings: opts.chainFindings,
3498
+ missing_input_mode: opts.missingInputMode,
3499
+ registry_hooks: hooks,
3500
+ registry_chat: chat
3501
+ }
3502
+ };
3503
+ return snapshotExactExecutionPlan({
3504
+ ...material,
3505
+ digest: hashCanonical(material)
3506
+ }, "AnalystRegistry.runExact execution plan");
3507
+ }
3508
+ function exactPriorFindingsSnapshot(findings) {
3509
+ if (findings === null) return { kind: "none" };
3510
+ if (Array.isArray(findings)) return {
3511
+ kind: "ordered",
3512
+ count: findings.length,
3513
+ digest: hashCanonical(findings)
3514
+ };
3515
+ const record = findings;
3516
+ const keys = Object.keys(record).sort();
3517
+ return {
3518
+ kind: "by_analyst",
3519
+ keys,
3520
+ count: keys.reduce((sum, key) => sum + (record[key]?.length ?? 0), 0),
3521
+ digest: hashCanonical(record)
3522
+ };
3523
+ }
3524
+ function canonicalJsonSnapshot(value, label) {
3525
+ let snapshot;
3526
+ try {
3527
+ snapshot = JSON.parse(canonicalString(value));
3528
+ } catch (cause) {
3529
+ throw new TypeError(`${label} must be canonical JSON`, { cause });
3530
+ }
3531
+ return deepFreezeCanonicalJson(snapshot);
3532
+ }
3533
+ function snapshotUsageReceiptOnce(receipt, context) {
3534
+ const data = readOwnFields(receipt, [
3535
+ "calls",
3536
+ "tokens",
3537
+ "cost",
3538
+ "knownCostUsd"
3539
+ ], context);
3540
+ data.tokens = data.tokens === null ? null : readOwnFields(data.tokens, [
3541
+ "input",
3542
+ "output",
3543
+ "reasoning",
3544
+ "cached",
3545
+ "cacheWrite"
3546
+ ], `${context} tokens`);
3547
+ data.cost = readOwnFields(data.cost, ["kind", "usd"], `${context} cost`);
3548
+ const snapshot = canonicalJsonSnapshot(data, context);
3549
+ assertValidAnalystUsageReceipt(snapshot, context);
3550
+ return snapshot;
3551
+ }
3552
+ function readOwnFields(value, fields, context) {
3553
+ if (!isPlainRecord(value)) throw new TypeError(`${context} must be a plain object`);
3554
+ const unexpected = Object.keys(value).filter((key) => !fields.includes(key));
3555
+ if (unexpected.length > 0) throw new TypeError(`${context} contains unknown fields: ${unexpected.sort().join(", ")}`);
3556
+ return Object.fromEntries(fields.flatMap((field) => Object.hasOwn(value, field) ? [[field, value[field]]] : []));
3557
+ }
3558
+ function isPlainRecord(value) {
3559
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3560
+ const prototype = Object.getPrototypeOf(value);
3561
+ return prototype === Object.prototype || prototype === null;
3562
+ }
3563
+ function exactFixedBudgets(exact, runnable, selected) {
3564
+ if (exact === null) return {};
3565
+ const allocations = Object.fromEntries(selected.map((analyst) => [analyst.id, null]));
3566
+ if (runnable.length === 0) return deepFreezeCanonicalJson(allocations);
3567
+ if (exact.kind === "equal") {
3568
+ const each = exact.totalUsd / runnable.length;
3569
+ for (const analyst of runnable) allocations[analyst.id] = each;
3570
+ return deepFreezeCanonicalJson(allocations);
3571
+ }
3572
+ const totalWeight = runnable.reduce((sum, analyst) => sum + exact.weights[analyst.id], 0);
3573
+ if (totalWeight === 0) throw new Error("ExactRegistryRunOpts weighted budget must allocate positive weight to a runnable analyst");
3574
+ for (const analyst of runnable) allocations[analyst.id] = exact.totalUsd * exact.weights[analyst.id] / totalWeight;
3575
+ return deepFreezeCanonicalJson(allocations);
3576
+ }
3577
+ function snapshotExecutionFindings(findings, exact, context) {
3578
+ return exact ? deepFreezeCanonicalJson(snapshotAnalystFindings(findings, context)) : findings;
3579
+ }
3580
+ function snapshotAfterHookValues(summary, findings, exact) {
3581
+ if (!exact) return {
3582
+ summary,
3583
+ findings
3584
+ };
3585
+ return {
3586
+ summary: canonicalJsonSnapshot(summary, "AnalystRegistry.runExact onAfterAnalyze summary"),
3587
+ findings: deepFreezeCanonicalJson(snapshotAnalystFindings(findings, "AnalystRegistry.runExact onAfterAnalyze findings"))
3588
+ };
3589
+ }
3590
+ function exactPlannedAllocation(plan, analystId) {
3591
+ const budget = plan.policy.budget;
3592
+ if (budget.kind === "none") return void 0;
3593
+ const allocated = budget.allocations_usd[analystId];
3594
+ return allocated === null ? void 0 : allocated;
3595
+ }
3596
+ function snapshotExecutionEvent(event, exact) {
3597
+ return exact ? canonicalJsonSnapshot(event, "AnalystRegistry.runExact event") : event;
3598
+ }
3599
+ function logUncapturedBudgetWarning(args) {
3600
+ if (args.budgetUsd === void 0 || args.usage.cost.kind !== "uncaptured") return;
3601
+ args.log(`[analyst] WARN ${args.analyst.id} — USD cost uncaptured; budget not reconciled`, {
3602
+ runId: args.runId,
3603
+ budget_usd: args.budgetUsd,
3604
+ cost_captured: false
3605
+ });
3606
+ }
2353
3607
  function validateTimeout(timeoutMs) {
2354
3608
  if (timeoutMs === void 0) return void 0;
2355
3609
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new TypeError("RegistryRunOpts.timeoutMs must be a positive safe integer no greater than 2147483647");
@@ -2479,8 +3733,8 @@ function zeroUsage() {
2479
3733
  }
2480
3734
  };
2481
3735
  }
2482
- function resolveUsage(analyst, receipts) {
2483
- if (receipts.length > 0) return mergeUsageReceipts(receipts);
3736
+ function resolveUsage(analyst, receipts, exact = false) {
3737
+ if (receipts.length > 0) return mergeUsageReceipts(receipts, exact);
2484
3738
  if (analyst.cost.kind === "deterministic") return zeroUsage();
2485
3739
  return {
2486
3740
  calls: null,
@@ -2491,24 +3745,21 @@ function resolveUsage(analyst, receipts) {
2491
3745
  }
2492
3746
  };
2493
3747
  }
2494
- function mergeUsageReceipts(receipts) {
2495
- const calls = receipts.every((receipt) => receipt.calls !== null) ? receipts.reduce((sum, receipt) => sum + (receipt.calls ?? 0), 0) : null;
2496
- const tokens = receipts.every((receipt) => receipt.tokens !== null) ? receipts.reduce((sum, receipt) => ({
2497
- input: sum.input + (receipt.tokens?.input ?? 0),
2498
- output: sum.output + (receipt.tokens?.output ?? 0),
2499
- ...sum.reasoning !== void 0 || receipt.tokens?.reasoning !== void 0 ? { reasoning: (sum.reasoning ?? 0) + (receipt.tokens?.reasoning ?? 0) } : {},
2500
- ...sum.cached !== void 0 || receipt.tokens?.cached !== void 0 ? { cached: (sum.cached ?? 0) + (receipt.tokens?.cached ?? 0) } : {},
2501
- ...sum.cacheWrite !== void 0 || receipt.tokens?.cacheWrite !== void 0 ? { cacheWrite: (sum.cacheWrite ?? 0) + (receipt.tokens?.cacheWrite ?? 0) } : {}
2502
- }), {
2503
- input: 0,
2504
- output: 0
2505
- }) : null;
2506
- const cost = aggregateCostProvenance(receipts.map((receipt) => receipt.cost));
3748
+ function mergeUsageReceipts(receipts, exact = false) {
3749
+ const calls = receipts.every((receipt) => receipt.calls !== null) ? usageSum(receipts.map((receipt) => receipt.calls ?? 0), exact, "calls", true) : null;
3750
+ const tokens = receipts.every((receipt) => receipt.tokens !== null) ? Object.fromEntries([
3751
+ "input",
3752
+ "output",
3753
+ "reasoning",
3754
+ "cached",
3755
+ "cacheWrite"
3756
+ ].flatMap((field) => field === "input" || field === "output" || receipts.some((receipt) => receipt.tokens?.[field] !== void 0) ? [[field, usageSum(receipts.map((receipt) => receipt.tokens?.[field] ?? 0), exact, `tokens.${field}`, true)]] : [])) : null;
3757
+ const cost = aggregateCostProvenance(receipts.map((receipt) => receipt.cost), exact);
2507
3758
  return {
2508
3759
  calls,
2509
3760
  tokens,
2510
3761
  cost,
2511
- ...cost.kind === "uncaptured" ? { knownCostUsd: receipts.reduce((sum, receipt) => sum + knownCostUsd(receipt), 0) } : {}
3762
+ ...cost.kind === "uncaptured" ? { knownCostUsd: usageSum(receipts.map(knownCostUsd), exact, "known cost") } : {}
2512
3763
  };
2513
3764
  }
2514
3765
  function knownCostUsd(receipt) {
@@ -2518,12 +3769,12 @@ function budgetDebit(receipt, allocatedUsd) {
2518
3769
  const known = knownCostUsd(receipt);
2519
3770
  return receipt.cost.kind === "uncaptured" && allocatedUsd !== void 0 ? Math.max(known, allocatedUsd) : known;
2520
3771
  }
2521
- function aggregateCostProvenance(costs) {
3772
+ function aggregateCostProvenance(costs, exact = false) {
2522
3773
  if (costs.some((cost) => cost.kind === "uncaptured")) return {
2523
3774
  kind: "uncaptured",
2524
3775
  usd: null
2525
3776
  };
2526
- const usd = costs.reduce((sum, cost) => sum + (cost.usd ?? 0), 0);
3777
+ const usd = usageSum(costs.map((cost) => cost.usd ?? 0), exact, "captured cost");
2527
3778
  return costs.some((cost) => cost.kind === "estimated") ? {
2528
3779
  kind: "estimated",
2529
3780
  usd
@@ -2532,24 +3783,17 @@ function aggregateCostProvenance(costs) {
2532
3783
  usd
2533
3784
  };
2534
3785
  }
2535
- function assertValidUsageReceipt(receipt) {
2536
- if (receipt.calls !== null && (!Number.isInteger(receipt.calls) || receipt.calls < 0)) throw new Error("AnalystContext.recordUsage: calls must be a non-negative integer or null");
2537
- if (receipt.tokens) {
2538
- assertNonNegativeFinite(receipt.tokens.input, "tokens.input");
2539
- assertNonNegativeFinite(receipt.tokens.output, "tokens.output");
2540
- if (receipt.tokens.reasoning !== void 0) {
2541
- assertNonNegativeFinite(receipt.tokens.reasoning, "tokens.reasoning");
2542
- if (receipt.tokens.reasoning > receipt.tokens.output) throw new Error("AnalystContext.recordUsage: tokens.reasoning must not exceed tokens.output");
2543
- }
2544
- if (receipt.tokens.cached !== void 0) assertNonNegativeFinite(receipt.tokens.cached, "tokens.cached");
2545
- if (receipt.tokens.cacheWrite !== void 0) assertNonNegativeFinite(receipt.tokens.cacheWrite, "tokens.cacheWrite");
2546
- }
2547
- if (receipt.cost.kind !== "uncaptured") assertNonNegativeFinite(receipt.cost.usd, "cost.usd");
2548
- else if (receipt.cost.usd !== null) throw new Error("AnalystContext.recordUsage: uncaptured cost.usd must be null");
2549
- if (receipt.knownCostUsd !== void 0) assertNonNegativeFinite(receipt.knownCostUsd, "knownCostUsd");
3786
+ function executionCost(executions, exact) {
3787
+ const usages = executions.map((execution) => execution.summary.usage);
3788
+ return {
3789
+ known: usageSum(usages.map(knownCostUsd), exact, "run known cost"),
3790
+ provenance: aggregateCostProvenance(usages.map((usage) => usage.cost), exact)
3791
+ };
2550
3792
  }
2551
- function assertNonNegativeFinite(value, field) {
2552
- if (!Number.isFinite(value) || value < 0) throw new Error(`AnalystContext.recordUsage: ${field} must be a non-negative finite number`);
3793
+ function usageSum(values, exact, field, integer = false) {
3794
+ const sum = values.reduce((total, value) => total + value, 0);
3795
+ if (exact && (integer ? !Number.isSafeInteger(sum) : !Number.isFinite(sum))) throw new RangeError(`exact analyst usage ${field} aggregate ${integer ? "exceeds a safe integer" : "is not finite"}`);
3796
+ return sum;
2553
3797
  }
2554
3798
  /**
2555
3799
  * Resolve the `priorFindings` slice an analyst sees.
@@ -2583,12 +3827,13 @@ function buildDefaultAnalystRegistry(opts = {}) {
2583
3827
  const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
2584
3828
  for (const spec of kinds) registry.register(createTraceAnalystKind(spec, {
2585
3829
  ai: opts.ai,
2586
- model: opts.model
3830
+ model: opts.model,
3831
+ aiIdentity: opts.aiIdentity
2587
3832
  }));
2588
3833
  }
2589
3834
  return registry;
2590
3835
  }
2591
3836
  //#endregion
2592
- export { FindingSubjectStringSchema as A, parseRawFinding as C, FINDING_SUBJECT_GRAMMAR_PROMPT as D, stripCodeFences as E, behavioralAnalyst as F, deriveEfficiencyFindings as I, computeTraceMetrics as L, findingSubjectGrammarPromptFor as M, parseFindingSubject as N, FINDING_SUBJECT_KINDS as O, renderFindingSubject as P, createChatClient as R, evidenceRefsFromRawFinding as S, coerceToFindingRows as T, structureFindings as _, KNOWLEDGE_GAP_KIND_SPEC as a, RawAnalystEvidenceSchema as b, buildTraceToolsForGroup as c, emitControlIntegrityFindings as d, createTraceAnalystKind as f, validateUsageSettlementTimeout as g, settleUsageReceiptFromCostLedger as h, KNOWLEDGE_POISONING_KIND_SPEC as i, KIND_EXPECTED_SUBJECTS as j, FINDING_SUBJECT_SYNTAX as k, CONTROL_INTEGRITY_ANALYST as l, renderUpstreamFindings as m, AnalystRegistry as n, IMPROVEMENT_KIND_SPEC as o, renderPriorFindings as p, DEFAULT_TRACE_ANALYST_KINDS as r, FAILURE_MODE_KIND_SPEC as s, buildDefaultAnalystRegistry as t, ControlIntegrityAnalyst as u, ANALYST_SEVERITIES as v, coerceJson as w, RawAnalystFindingSchema as x, RAW_FINDING_SCHEMA_PROMPT as y, createAnalystAi as z };
3837
+ export { parseRawFinding as A, parseFindingSubject as B, renderUpstreamFindings as C, RawAnalystEvidenceSchema as D, RAW_FINDING_SCHEMA_PROMPT as E, FINDING_SUBJECT_KINDS as F, createChatClient as G, behavioralAnalyst as H, FINDING_SUBJECT_SYNTAX as I, createAnalystAi as K, FindingSubjectStringSchema as L, coerceToFindingRows as M, stripCodeFences as N, RawAnalystFindingSchema as O, FINDING_SUBJECT_GRAMMAR_PROMPT as P, KIND_EXPECTED_SUBJECTS as R, renderPriorFindings as S, ANALYST_SEVERITIES as T, deriveEfficiencyFindings as U, renderFindingSubject as V, computeTraceMetrics as W, buildTraceToolsForGroup as _, analystFindingDigest as a, emitControlIntegrityFindings as b, completedAnalystReviewQuality as c, validateAnalystReviewDecisions as d, DEFAULT_TRACE_ANALYST_KINDS as f, FAILURE_MODE_KIND_SPEC as g, IMPROVEMENT_KIND_SPEC as h, assertExactRegistryRunOpts as i, coerceJson as j, evidenceRefsFromRawFinding as k, readAnalystReview as l, KNOWLEDGE_GAP_KIND_SPEC as m, AnalystRegistry as n, analystRunDigest as o, KNOWLEDGE_POISONING_KIND_SPEC as p, ExactAnalystRunExecutionError as r, assertUniqueFindingIds as s, buildDefaultAnalystRegistry as t, snapshotAnalystRun as u, CONTROL_INTEGRITY_ANALYST as v, structureFindings as w, createTraceAnalystKind as x, ControlIntegrityAnalyst as y, findingSubjectGrammarPromptFor as z };
2593
3838
 
2594
- //# sourceMappingURL=default-registry-CLXbRt0f.js.map
3839
+ //# sourceMappingURL=default-registry-lp5R0lve.js.map