@tangle-network/agent-eval 0.136.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.
- package/CHANGELOG.md +86 -1
- package/README.md +37 -2
- package/dist/{agent-profile-cell-OhuTee9n.js → agent-profile-cell-CbfBm2g6.js} +2 -2
- package/dist/{agent-profile-cell-OhuTee9n.js.map → agent-profile-cell-CbfBm2g6.js.map} +1 -1
- package/dist/{agent-profile-cell-CCm3l2v2.d.ts → agent-profile-cell-Cw0PVwDr.d.ts} +2 -2
- package/dist/{agent-profile-cell-CCm3l2v2.d.ts.map → agent-profile-cell-Cw0PVwDr.d.ts.map} +1 -1
- package/dist/analyst/index.d.ts +574 -18
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +25 -5
- package/dist/analyst/index.js.map +1 -1
- package/dist/{analyze-runs-jjCmF8pU.js → analyze-runs-BScZvqMV.js} +6 -6
- package/dist/{analyze-runs-jjCmF8pU.js.map → analyze-runs-BScZvqMV.js.map} +1 -1
- package/dist/{analyze-runs-Cda5Xkj1.d.ts → analyze-runs-CPYxfPWT.d.ts} +6 -6
- package/dist/{analyze-runs-Cda5Xkj1.d.ts.map → analyze-runs-CPYxfPWT.d.ts.map} +1 -1
- package/dist/{baseline-BUeFcgrn.js → baseline-C-GocmIW.js} +2 -2
- package/dist/{baseline-BUeFcgrn.js.map → baseline-C-GocmIW.js.map} +1 -1
- package/dist/benchmark-D8dkki-J.js +554 -0
- package/dist/benchmark-D8dkki-J.js.map +1 -0
- package/dist/benchmark-DlQgU_XI.d.ts +236 -0
- package/dist/benchmark-DlQgU_XI.d.ts.map +1 -0
- package/dist/benchmark-command-CMqVqReF.js +4332 -0
- package/dist/benchmark-command-CMqVqReF.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-Dfgm9ts5.js → benchmarks-BJ_xK5rQ.js} +4 -3
- package/dist/{benchmarks-Dfgm9ts5.js.map → benchmarks-BJ_xK5rQ.js.map} +1 -1
- package/dist/builder-eval/index.js +2 -2
- package/dist/campaign/index.d.ts +6 -6
- package/dist/campaign/index.js +4 -4
- package/dist/{campaign-Dz8uQnhC.js → campaign-BIBS-NHV.js} +219 -78
- package/dist/campaign-BIBS-NHV.js.map +1 -0
- package/dist/cli.js +9 -2
- package/dist/cli.js.map +1 -1
- package/dist/client-BwPKohkJ.d.ts +202 -0
- package/dist/client-BwPKohkJ.d.ts.map +1 -0
- package/dist/completion-verifier-B4-IMYcS.d.ts +240 -0
- package/dist/completion-verifier-B4-IMYcS.d.ts.map +1 -0
- package/dist/contract/index.d.ts +10 -9
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +13 -13
- package/dist/control.d.ts +2 -2
- package/dist/control.js +1 -1
- package/dist/{cost-ledger-fGS_u_O1.d.ts → cost-ledger-B1D3COAc.d.ts} +6 -5
- package/dist/{cost-ledger-fGS_u_O1.d.ts.map → cost-ledger-B1D3COAc.d.ts.map} +1 -1
- package/dist/{cost-ledger-DHAjwNj7.js → cost-ledger-CHDLA0Ss.js} +91 -46
- package/dist/cost-ledger-CHDLA0Ss.js.map +1 -0
- package/dist/{dataset-BvtnC8Dc.d.ts → dataset-v_Y5902-.d.ts} +2 -2
- package/dist/{dataset-BvtnC8Dc.d.ts.map → dataset-v_Y5902-.d.ts.map} +1 -1
- package/dist/{default-registry-Brxr728w.d.ts → default-registry-PUhIVRWz.d.ts} +77 -138
- package/dist/default-registry-PUhIVRWz.d.ts.map +1 -0
- package/dist/{default-registry-CHmdy2An.js → default-registry-lp5R0lve.js} +1617 -291
- package/dist/default-registry-lp5R0lve.js.map +1 -0
- package/dist/{errors-8YnH8WlF.js → errors-D-LKuDhb.js} +8 -2
- package/dist/errors-D-LKuDhb.js.map +1 -0
- package/dist/{errors-CEk209JS.d.ts → errors-DkfjIDvD.d.ts} +9 -3
- package/dist/errors-DkfjIDvD.d.ts.map +1 -0
- package/dist/{eval-campaign-Cc8WZJ6b.js → eval-campaign-9MozgKL7.js} +6 -6
- package/dist/{eval-campaign-Cc8WZJ6b.js.map → eval-campaign-9MozgKL7.js.map} +1 -1
- package/dist/exact-types-Dpw2LeHA.d.ts +234 -0
- package/dist/exact-types-Dpw2LeHA.d.ts.map +1 -0
- package/dist/{extract-usage-DIQpN-ww.js → extract-usage-CS391dOE.js} +3 -3
- package/dist/{extract-usage-DIQpN-ww.js.map → extract-usage-CS391dOE.js.map} +1 -1
- package/dist/{feedback-trajectory-CVaeREXV.d.ts → feedback-trajectory-CoNep7rl.d.ts} +91 -3
- package/dist/feedback-trajectory-CoNep7rl.d.ts.map +1 -0
- package/dist/fuzz.d.ts +1 -1
- package/dist/fuzz.js +2 -2
- package/dist/hosted/index.d.ts +3 -2
- package/dist/hosted/index.d.ts.map +1 -1
- package/dist/{index-AbhwHp0V.d.ts → index-B2-IxCMB.d.ts} +2 -2
- package/dist/{index-AbhwHp0V.d.ts.map → index-B2-IxCMB.d.ts.map} +1 -1
- package/dist/index-BipJlj-C.d.ts +316 -0
- package/dist/index-BipJlj-C.d.ts.map +1 -0
- package/dist/{index-CQsJcqch.d.ts → index-CjVYlVBK.d.ts} +5 -5
- package/dist/{index-CQsJcqch.d.ts.map → index-CjVYlVBK.d.ts.map} +1 -1
- package/dist/{index-B4Fjfo5U.d.ts → index-D0cxAdaV.d.ts} +89 -317
- package/dist/index-D0cxAdaV.d.ts.map +1 -0
- package/dist/{index-DuhJaaiH.d.ts → index-DEb46kc6.d.ts} +2 -2
- package/dist/index-DEb46kc6.d.ts.map +1 -0
- package/dist/{index-C2fkZhv_.d.ts → index-sMN_hI4E.d.ts} +3 -3
- package/dist/{index-C2fkZhv_.d.ts.map → index-sMN_hI4E.d.ts.map} +1 -1
- package/dist/index.d.ts +30 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -35
- package/dist/index.js.map +1 -1
- package/dist/{client-DcvgkaZi.d.ts → insight-report-CXd8VBDR.d.ts} +5 -203
- package/dist/insight-report-CXd8VBDR.d.ts.map +1 -0
- package/dist/{integrity-rmVhXWA7.d.ts → integrity-B-MLFz0I.d.ts} +3 -3
- package/dist/{integrity-rmVhXWA7.d.ts.map → integrity-B-MLFz0I.d.ts.map} +1 -1
- package/dist/integrity-CCXTftiL.js +1360 -0
- package/dist/integrity-CCXTftiL.js.map +1 -0
- package/dist/{integrity-BzRbCHzi.js → integrity-fdt8XPAv.js} +2 -2
- package/dist/{integrity-BzRbCHzi.js.map → integrity-fdt8XPAv.js.map} +1 -1
- package/dist/ledger-core/index.d.ts +1 -1
- package/dist/ledger-core/index.js +1 -1
- package/dist/{ledger-core-DAKFKRzi.js → ledger-core-C0Yx1I14.js} +303 -110
- package/dist/ledger-core-C0Yx1I14.js.map +1 -0
- package/dist/{llm-client-DHx8pzyJ.js → llm-client-Cj3c7PEm.js} +6 -6
- package/dist/llm-client-Cj3c7PEm.js.map +1 -0
- package/dist/meta-eval/index.d.ts +2 -2
- package/dist/meta-eval/index.js +3 -3
- package/dist/{mint-DyRUc9k6.js → mint-Ctwk079K.js} +4 -4
- package/dist/{mint-DyRUc9k6.js.map → mint-Ctwk079K.js.map} +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/{paired-arms-BbFKrAU-.js → paired-arms-iZ08VFMN.js} +3 -3
- package/dist/{paired-arms-BbFKrAU-.js.map → paired-arms-iZ08VFMN.js.map} +1 -1
- package/dist/pipelines/index.js +2 -2
- package/dist/profile-cell.d.ts +1 -1
- package/dist/profile-cell.js +1 -1
- package/dist/{proposal-findings-DCawte-y.js → proposal-findings-2GIUo1et.js} +2 -68
- package/dist/proposal-findings-2GIUo1et.js.map +1 -0
- package/dist/{propose-review-control-SQ-n9-We.js → propose-review-control-DLXz4FCX.js} +2 -2
- package/dist/{propose-review-control-SQ-n9-We.js.map → propose-review-control-DLXz4FCX.js.map} +1 -1
- package/dist/registry-C4yJTza7.d.ts +178 -0
- package/dist/registry-C4yJTza7.d.ts.map +1 -0
- package/dist/{release-report-DooPguBc.js → release-report-B5XPBvAU.js} +4 -4
- package/dist/{release-report-DooPguBc.js.map → release-report-B5XPBvAU.js.map} +1 -1
- package/dist/{release-report-DpBxGGI1.d.ts → release-report-CoyvyLBs.d.ts} +4 -4
- package/dist/{release-report-DpBxGGI1.d.ts.map → release-report-CoyvyLBs.d.ts.map} +1 -1
- package/dist/{replay-C6wRg47C.js → replay-Cb-4Vf0k.js} +249 -8
- package/dist/replay-Cb-4Vf0k.js.map +1 -0
- package/dist/{replay-BRfMIs81.d.ts → replay-DbIYwso6.d.ts} +227 -52
- package/dist/replay-DbIYwso6.d.ts.map +1 -0
- package/dist/reporting.d.ts +4 -4
- package/dist/reporting.js +4 -4
- package/dist/{researcher-Doo95b50.d.ts → researcher-BCeOEjtR.d.ts} +6 -7
- package/dist/researcher-BCeOEjtR.d.ts.map +1 -0
- package/dist/{reward-hacking-a-kYs0-i.js → reward-hacking-GyN0kMd8.js} +3 -3
- package/dist/{reward-hacking-a-kYs0-i.js.map → reward-hacking-GyN0kMd8.js.map} +1 -1
- package/dist/{reward-hacking-D-QqXvg-.d.ts → reward-hacking-sE2l_NV6.d.ts} +2 -2
- package/dist/{reward-hacking-D-QqXvg-.d.ts.map → reward-hacking-sE2l_NV6.d.ts.map} +1 -1
- package/dist/rl.d.ts +6 -6
- package/dist/rl.js +9 -9
- package/dist/rollout/index.d.ts +1 -1
- package/dist/rollout/index.js +3 -3
- package/dist/{rollout-DLSUIWLu.js → rollout-DQFl0UXA.js} +2 -2
- package/dist/{rollout-DLSUIWLu.js.map → rollout-DQFl0UXA.js.map} +1 -1
- package/dist/{rubric-predictive-validity-BJf-8ejY.js → rubric-predictive-validity-BRR632r1.js} +2 -2
- package/dist/{rubric-predictive-validity-BJf-8ejY.js.map → rubric-predictive-validity-BRR632r1.js.map} +1 -1
- package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts → rubric-predictive-validity-w2klGv1u.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts.map → rubric-predictive-validity-w2klGv1u.d.ts.map} +1 -1
- package/dist/{run-evidence-DokQtX0-.d.ts → run-evidence-CbE0A8Xg.d.ts} +3 -3
- package/dist/{run-evidence-DokQtX0-.d.ts.map → run-evidence-CbE0A8Xg.d.ts.map} +1 -1
- package/dist/{run-record-DcObtIGh.d.ts → run-record-DwHMk1Ai.d.ts} +4 -4
- package/dist/{run-record-DcObtIGh.d.ts.map → run-record-DwHMk1Ai.d.ts.map} +1 -1
- package/dist/{run-record-BIwU2wdV.js → run-record-vRgqWmJw.js} +3 -3
- package/dist/{run-record-BIwU2wdV.js.map → run-record-vRgqWmJw.js.map} +1 -1
- package/dist/{semantic-concept-judge-Btozx3Vc.js → semantic-concept-judge-DYXDPZW0.js} +12 -6
- package/dist/semantic-concept-judge-DYXDPZW0.js.map +1 -0
- package/dist/{server-Bz3WQJs6.js → server-DLEvyW2z.js} +3 -3
- package/dist/{server-Bz3WQJs6.js.map → server-DLEvyW2z.js.map} +1 -1
- package/dist/single-run-lock-D_bS5xhj.js +318 -0
- package/dist/single-run-lock-D_bS5xhj.js.map +1 -0
- package/dist/{skill-usage-BDQVPIG1.d.ts → skill-usage-Bv3G4VkA.d.ts} +36 -48
- package/dist/skill-usage-Bv3G4VkA.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-0UmPD6aP.js → skillopt-optimization-method-CjKMZy0d.js} +10 -185
- package/dist/skillopt-optimization-method-CjKMZy0d.js.map +1 -0
- package/dist/{skillopt-optimization-method-CwSYkv35.d.ts → skillopt-optimization-method-CzfnA8O-.d.ts} +11 -12
- package/dist/skillopt-optimization-method-CzfnA8O-.d.ts.map +1 -0
- package/dist/{statistics-CnGCLLqc.js → statistics-ByxzSiOM.js} +2 -2
- package/dist/{statistics-CnGCLLqc.js.map → statistics-ByxzSiOM.js.map} +1 -1
- package/dist/{statistics-CKOqre5S.d.ts → statistics-mf70aXKp.d.ts} +2 -2
- package/dist/{statistics-CKOqre5S.d.ts.map → statistics-mf70aXKp.d.ts.map} +1 -1
- package/dist/store-otlp-BenKynPE.js +1688 -0
- package/dist/store-otlp-BenKynPE.js.map +1 -0
- package/dist/{summary-report-BEk8OFLs.js → summary-report-9A5y7EsK.js} +4 -4
- package/dist/{summary-report-BEk8OFLs.js.map → summary-report-9A5y7EsK.js.map} +1 -1
- package/dist/{summary-report-CPMINBqs.d.ts → summary-report-BKinV4yD.d.ts} +3 -3
- package/dist/{summary-report-CPMINBqs.d.ts.map → summary-report-BKinV4yD.d.ts.map} +1 -1
- package/dist/supervisor-run/index.d.ts +3 -2
- package/dist/supervisor-run/index.js +3 -2
- package/dist/{supervisor-run-Dr5HnTup.js → supervisor-run-B2EWUmQY.js} +28 -454
- package/dist/supervisor-run-B2EWUmQY.js.map +1 -0
- package/dist/{test-graded-scenario-BsqWLmPt.js → test-graded-scenario-JHcKQNpq.js} +2 -2
- package/dist/{test-graded-scenario-BsqWLmPt.js.map → test-graded-scenario-JHcKQNpq.js.map} +1 -1
- package/dist/tools-DZGdROtG.js +255 -0
- package/dist/tools-DZGdROtG.js.map +1 -0
- package/dist/traces.d.ts +6 -7
- package/dist/traces.js +6 -6
- package/dist/types-5q2T25iW.d.ts +804 -0
- package/dist/types-5q2T25iW.d.ts.map +1 -0
- package/dist/{types-DVjczBM9.d.ts → types-BtJhn8v6.d.ts} +260 -6
- package/dist/types-BtJhn8v6.d.ts.map +1 -0
- package/dist/{index-CyC1BTmn.d.ts → types-Dea6tiVI.d.ts} +16 -238
- package/dist/types-Dea6tiVI.d.ts.map +1 -0
- package/dist/{types-DiWLru6Z.d.ts → types-zFYez3PK.d.ts} +5 -5
- package/dist/{types-DiWLru6Z.d.ts.map → types-zFYez3PK.d.ts.map} +1 -1
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.js +1 -1
- package/docs/feedback-trajectories.md +100 -1
- package/docs/trace-analysis.md +494 -58
- package/package.json +9 -3
- package/dist/analyst-BkTS3C58.d.ts +0 -89
- package/dist/analyst-BkTS3C58.d.ts.map +0 -1
- package/dist/analyst-j5je5J7c.js +0 -152
- package/dist/analyst-j5je5J7c.js.map +0 -1
- package/dist/campaign-Dz8uQnhC.js.map +0 -1
- package/dist/client-DcvgkaZi.d.ts.map +0 -1
- package/dist/concurrency-MUjT7VjM.js +0 -109
- package/dist/concurrency-MUjT7VjM.js.map +0 -1
- package/dist/cost-ledger-DHAjwNj7.js.map +0 -1
- package/dist/default-registry-Brxr728w.d.ts.map +0 -1
- package/dist/default-registry-CHmdy2An.js.map +0 -1
- package/dist/errors-8YnH8WlF.js.map +0 -1
- package/dist/errors-CEk209JS.d.ts.map +0 -1
- package/dist/feedback-trajectory-CVaeREXV.d.ts.map +0 -1
- package/dist/index-B4Fjfo5U.d.ts.map +0 -1
- package/dist/index-CyC1BTmn.d.ts.map +0 -1
- package/dist/index-DuhJaaiH.d.ts.map +0 -1
- package/dist/ledger-core-DAKFKRzi.js.map +0 -1
- package/dist/llm-client-BiK4HW0u.d.ts +0 -290
- package/dist/llm-client-BiK4HW0u.d.ts.map +0 -1
- package/dist/llm-client-DHx8pzyJ.js.map +0 -1
- package/dist/proposal-findings-DCawte-y.js.map +0 -1
- package/dist/raw-provider-sink-BU29Sh8h.d.ts +0 -134
- package/dist/raw-provider-sink-BU29Sh8h.d.ts.map +0 -1
- package/dist/replay-BRfMIs81.d.ts.map +0 -1
- package/dist/replay-C6wRg47C.js.map +0 -1
- package/dist/researcher-Doo95b50.d.ts.map +0 -1
- package/dist/semantic-concept-judge-Btozx3Vc.js.map +0 -1
- package/dist/skill-usage-BDQVPIG1.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-0UmPD6aP.js.map +0 -1
- package/dist/skillopt-optimization-method-CwSYkv35.d.ts.map +0 -1
- package/dist/store-CxJry_cs.d.ts +0 -229
- package/dist/store-CxJry_cs.d.ts.map +0 -1
- package/dist/supervisor-run-Dr5HnTup.js.map +0 -1
- package/dist/tools-D8yTtNSN.js +0 -1190
- package/dist/tools-D8yTtNSN.js.map +0 -1
- package/dist/types-Cc3qbqzj.d.ts +0 -387
- package/dist/types-Cc3qbqzj.d.ts.map +0 -1
- package/dist/types-DVjczBM9.d.ts.map +0 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { i as CostLedger } from "./cost-ledger-
|
|
2
|
-
import { f as maximumChargeForLlmRequest, l as costReceiptFromLlm, n as LlmClient, s as callLlm, u as costReceiptFromLlmError } from "./llm-client-
|
|
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 {
|
|
6
|
-
import {
|
|
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";
|
|
9
|
+
import { t as analyzeSupervisorRunIntegrity } from "./integrity-CCXTftiL.js";
|
|
10
|
+
import { o as combineAbortSignals } from "./proposal-findings-2GIUo1et.js";
|
|
7
11
|
import { ai } from "@ax-llm/ax";
|
|
8
12
|
import { z } from "zod";
|
|
9
13
|
import { randomUUID } from "node:crypto";
|
|
@@ -134,11 +138,8 @@ function resolveModel(req, defaultModel) {
|
|
|
134
138
|
/**
|
|
135
139
|
* Deterministic behavioral metrics over OTLP spans — pure arithmetic, no LLM.
|
|
136
140
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* growth, output decay, tool monoculture, missing self-verification — computed
|
|
140
|
-
* here once, in TypeScript, with zero model judgment. A finding that falls out
|
|
141
|
-
* of arithmetic is trivially model-agnostic and cannot hallucinate the trend.
|
|
141
|
+
* It computes token growth, output decay, tool monoculture, and missing
|
|
142
|
+
* self-verification once in TypeScript with no model judgment.
|
|
142
143
|
*
|
|
143
144
|
* General, not trace-specific: the detectors key off token trajectories and
|
|
144
145
|
* tool usage present in any agentic OTLP trace, not any one benchmark.
|
|
@@ -472,18 +473,9 @@ function everyAdjacent(values, predicate) {
|
|
|
472
473
|
//#endregion
|
|
473
474
|
//#region src/analyst/behavioral-analyst.ts
|
|
474
475
|
/**
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
* context bloat, output decay, tool monoculture, missing self-verification —
|
|
479
|
-
* directly from arithmetic over spans (`computeTraceMetrics`).
|
|
480
|
-
*
|
|
481
|
-
* Why it matters: these findings are model-agnostic BY CONSTRUCTION (no model
|
|
482
|
-
* in the loop), so they cannot return 0 on a weak model the way the Ax-RLM
|
|
483
|
-
* does — and they are strictly more reliable than HALO, which spends tokens
|
|
484
|
-
* re-deriving the same numbers and can hallucinate the trend. The agentic
|
|
485
|
-
* RLM kinds remain for SEMANTIC findings that genuinely need a model; this
|
|
486
|
-
* analyst owns the behavioral class.
|
|
476
|
+
* Deterministic behavioral analysis over arithmetic in trace spans.
|
|
477
|
+
* This pass is cheap and repeatable; semantic analysis remains the job of
|
|
478
|
+
* model-backed analysts. Relative quality requires a labeled comparison.
|
|
487
479
|
*/
|
|
488
480
|
const RECOMMENDED_ACTION = {
|
|
489
481
|
"monotonic-input-growth": "Inspect context assembly; if prior history is repeatedly included, summarize completed work before the next model call.",
|
|
@@ -491,19 +483,44 @@ const RECOMMENDED_ACTION = {
|
|
|
491
483
|
"single-tool-dependency": "Test whether an inspect or verification tool improves outcomes after the repeated call fails or returns no progress.",
|
|
492
484
|
"no-self-verification": "After state-changing actions, require an observable check before the agent proceeds."
|
|
493
485
|
};
|
|
494
|
-
const ANALYST_ID = "efficiency-behavioral";
|
|
486
|
+
const ANALYST_ID$1 = "efficiency-behavioral";
|
|
487
|
+
const DEFAULT_MAX_TRACES = 1e3;
|
|
488
|
+
const DEFAULT_MAX_EVIDENCE_REFS = 20;
|
|
489
|
+
const TRACE_PAGE_SIZE = 200;
|
|
495
490
|
const AGGREGATE_CLAIM = {
|
|
496
491
|
"monotonic-input-growth": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed input tokens grow from zero to nonzero or to at least 3x their initial value across at least 3 serial model calls without a decrease.`,
|
|
497
492
|
"output-length-decay": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed output tokens decrease while input tokens increased monotonically across at least 3 serial model calls.`,
|
|
498
493
|
"single-tool-dependency": (observed, analyzed) => `${observed}/${analyzed} analyzed traces used only one named tool across at least 3 tool calls.`,
|
|
499
494
|
"no-self-verification": (observed, analyzed) => `${observed}/${analyzed} analyzed traces had at least 3 tool calls without a verification-named tool call.`
|
|
500
495
|
};
|
|
496
|
+
async function listTraceIds(store, maxTraces, signal) {
|
|
497
|
+
const traceIds = /* @__PURE__ */ new Set();
|
|
498
|
+
let offset = 0;
|
|
499
|
+
let expectedTotal;
|
|
500
|
+
while (true) {
|
|
501
|
+
signal?.throwIfAborted();
|
|
502
|
+
const page = await store.queryTraces({
|
|
503
|
+
limit: TRACE_PAGE_SIZE,
|
|
504
|
+
offset
|
|
505
|
+
});
|
|
506
|
+
if (expectedTotal === void 0) expectedTotal = page.total;
|
|
507
|
+
if (page.total !== expectedTotal) throw new Error(`behavioralAnalyst: trace count changed during pagination (${expectedTotal} to ${page.total})`);
|
|
508
|
+
if (page.total > maxTraces) throw new RangeError(`behavioralAnalyst: ${page.total} traces exceed maxTraces=${maxTraces}; filter the store or raise the explicit limit`);
|
|
509
|
+
for (const trace of page.traces) traceIds.add(trace.trace_id);
|
|
510
|
+
if (traceIds.size > maxTraces) throw new RangeError(`behavioralAnalyst: more than maxTraces=${maxTraces} unique traces were returned`);
|
|
511
|
+
if (!page.has_more) break;
|
|
512
|
+
if (page.traces.length === 0) throw new Error("behavioralAnalyst: trace store returned an empty page with has_more=true");
|
|
513
|
+
offset += page.traces.length;
|
|
514
|
+
}
|
|
515
|
+
if (traceIds.size !== expectedTotal) throw new Error(`behavioralAnalyst: pagination returned ${traceIds.size}/${expectedTotal ?? 0} unique traces`);
|
|
516
|
+
return [...traceIds].sort();
|
|
517
|
+
}
|
|
501
518
|
/**
|
|
502
519
|
* Map computed signals → structured AnalystFindings. Pure: no LLM, no clock
|
|
503
520
|
* dependence beyond `produced_at` (overridable for deterministic tests).
|
|
504
521
|
*/
|
|
505
522
|
function deriveEfficiencyFindings(metrics, opts = {}) {
|
|
506
|
-
const analystId = opts.analystId ?? ANALYST_ID;
|
|
523
|
+
const analystId = opts.analystId ?? ANALYST_ID$1;
|
|
507
524
|
const traceId = metrics.traceId;
|
|
508
525
|
return metrics.signals.map((sig) => makeFinding({
|
|
509
526
|
analyst_id: analystId,
|
|
@@ -528,18 +545,25 @@ function deriveEfficiencyFindings(metrics, opts = {}) {
|
|
|
528
545
|
}));
|
|
529
546
|
}
|
|
530
547
|
/** The deterministic behavioral/efficiency analyst (no LLM, any-model). */
|
|
531
|
-
function behavioralAnalyst() {
|
|
548
|
+
function behavioralAnalyst(options = {}) {
|
|
549
|
+
const maxTraces = positiveInteger(options.maxTraces ?? DEFAULT_MAX_TRACES, "maxTraces");
|
|
550
|
+
const maxEvidenceRefsPerFinding = positiveInteger(options.maxEvidenceRefsPerFinding ?? DEFAULT_MAX_EVIDENCE_REFS, "maxEvidenceRefsPerFinding");
|
|
532
551
|
return {
|
|
533
|
-
id: ANALYST_ID,
|
|
552
|
+
id: ANALYST_ID$1,
|
|
534
553
|
description: "Deterministic behavioral/efficiency findings over OTLP spans — token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.",
|
|
535
554
|
inputKind: "trace-store",
|
|
536
555
|
cost: { kind: "deterministic" },
|
|
537
556
|
version: "2.0.0",
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
557
|
+
executionConfig: {
|
|
558
|
+
kind: "behavioral-efficiency",
|
|
559
|
+
max_traces: maxTraces,
|
|
560
|
+
max_evidence_refs_per_finding: maxEvidenceRefsPerFinding
|
|
561
|
+
},
|
|
562
|
+
async analyze(store, context) {
|
|
563
|
+
const analyzedTraceIds = await listTraceIds(store, maxTraces, context.signal);
|
|
541
564
|
const findingsById = /* @__PURE__ */ new Map();
|
|
542
565
|
for (const traceId of analyzedTraceIds) {
|
|
566
|
+
context.signal?.throwIfAborted();
|
|
543
567
|
const viewed = await store.viewTrace({ trace_id: traceId });
|
|
544
568
|
if (viewed.trace_id !== traceId) throw new Error(`behavioralAnalyst: requested trace '${traceId}', received '${viewed.trace_id}'`);
|
|
545
569
|
if (!viewed.spans) throw new Error(`behavioralAnalyst: trace '${traceId}' is oversized; complete spans are required`);
|
|
@@ -550,30 +574,39 @@ function behavioralAnalyst() {
|
|
|
550
574
|
if (!current) {
|
|
551
575
|
findingsById.set(finding.finding_id, {
|
|
552
576
|
finding,
|
|
553
|
-
|
|
577
|
+
observedTraceCount: 1,
|
|
578
|
+
evidenceTraceIds: [traceId],
|
|
554
579
|
evidence: [...finding.evidence_refs]
|
|
555
580
|
});
|
|
556
581
|
continue;
|
|
557
582
|
}
|
|
558
|
-
current.
|
|
559
|
-
current.evidence.
|
|
583
|
+
current.observedTraceCount += 1;
|
|
584
|
+
if (current.evidence.length < maxEvidenceRefsPerFinding) {
|
|
585
|
+
current.evidenceTraceIds.push(traceId);
|
|
586
|
+
current.evidence.push(...finding.evidence_refs);
|
|
587
|
+
}
|
|
560
588
|
}
|
|
561
589
|
}
|
|
562
|
-
return [...findingsById.values()].map(({ finding,
|
|
590
|
+
return [...findingsById.values()].map(({ finding, observedTraceCount, evidenceTraceIds, evidence }) => ({
|
|
563
591
|
...finding,
|
|
564
|
-
claim: AGGREGATE_CLAIM[finding.subject](
|
|
565
|
-
rationale: `${
|
|
592
|
+
claim: AGGREGATE_CLAIM[finding.subject](observedTraceCount, analyzedTraceIds.length),
|
|
593
|
+
rationale: `${observedTraceCount}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,
|
|
566
594
|
evidence_refs: evidence,
|
|
567
595
|
metadata: {
|
|
568
596
|
deterministic: true,
|
|
569
|
-
|
|
570
|
-
|
|
597
|
+
evidence_trace_ids: evidenceTraceIds,
|
|
598
|
+
omitted_evidence_trace_count: observedTraceCount - evidenceTraceIds.length,
|
|
599
|
+
observed_trace_count: observedTraceCount,
|
|
571
600
|
analyzed_trace_count: analyzedTraceIds.length
|
|
572
601
|
}
|
|
573
602
|
}));
|
|
574
603
|
}
|
|
575
604
|
};
|
|
576
605
|
}
|
|
606
|
+
function positiveInteger(value, name) {
|
|
607
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`behavioralAnalyst: ${name} must be a positive safe integer`);
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
577
610
|
//#endregion
|
|
578
611
|
//#region src/analyst/ax-cost-service.ts
|
|
579
612
|
/**
|
|
@@ -667,6 +700,7 @@ function boundOutputTokens(request, limit) {
|
|
|
667
700
|
const maxTokens = requested === void 0 ? limit : Math.min(requested, limit);
|
|
668
701
|
return {
|
|
669
702
|
...request,
|
|
703
|
+
...request.functionCall === void 0 && !request.functions?.length ? { functionCall: "none" } : {},
|
|
670
704
|
modelConfig: {
|
|
671
705
|
...request.modelConfig,
|
|
672
706
|
maxTokens,
|
|
@@ -775,6 +809,221 @@ function assertPositiveInteger(value, field) {
|
|
|
775
809
|
if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`meterAxChatService: ${field} must be a positive integer`);
|
|
776
810
|
}
|
|
777
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
|
|
778
1027
|
//#region src/analyst/finding-subject.ts
|
|
779
1028
|
/**
|
|
780
1029
|
* Typed `FindingSubject` — the canonical grammar every analyst kind emits.
|
|
@@ -1286,6 +1535,7 @@ function parseFindingWithSchema(schema, row, log) {
|
|
|
1286
1535
|
}
|
|
1287
1536
|
function evidenceKindFromUri(uri) {
|
|
1288
1537
|
if (uri.startsWith("span://")) return "span";
|
|
1538
|
+
if (/^trace:\/\/[^/]+\/span\/[^/]+$/.test(uri)) return "span";
|
|
1289
1539
|
if (uri.startsWith("event://")) return "event";
|
|
1290
1540
|
if (uri.startsWith("finding://")) return "finding";
|
|
1291
1541
|
if (uri.startsWith("metric://")) return "metric";
|
|
@@ -1399,49 +1649,6 @@ async function structureFindings(opts) {
|
|
|
1399
1649
|
outcome: "extraction_failed"
|
|
1400
1650
|
};
|
|
1401
1651
|
}
|
|
1402
|
-
/** Convert one ledger channel's complete call set into one analyst receipt. */
|
|
1403
|
-
function usageReceiptFromCostLedger(ledger, filter = "analyst") {
|
|
1404
|
-
const resolvedFilter = typeof filter === "string" ? { channel: filter } : filter;
|
|
1405
|
-
const summary = ledger.summary(resolvedFilter);
|
|
1406
|
-
const receipts = ledger.list(resolvedFilter);
|
|
1407
|
-
const hasReasoningUsage = receipts.some((receipt) => receipt.reasoningTokens !== void 0);
|
|
1408
|
-
const hasCacheWriteUsage = receipts.some((receipt) => receipt.cacheWriteTokens !== void 0);
|
|
1409
|
-
const cost = summary.costProvenance;
|
|
1410
|
-
return {
|
|
1411
|
-
calls: summary.totalCalls + summary.pendingCalls,
|
|
1412
|
-
tokens: summary.usageComplete ? {
|
|
1413
|
-
input: summary.inputTokens,
|
|
1414
|
-
output: summary.outputTokens,
|
|
1415
|
-
...hasReasoningUsage ? { reasoning: summary.reasoningTokens ?? 0 } : {},
|
|
1416
|
-
...summary.cachedTokens > 0 ? { cached: summary.cachedTokens } : {},
|
|
1417
|
-
...hasCacheWriteUsage ? { cacheWrite: summary.cacheWriteTokens ?? 0 } : {}
|
|
1418
|
-
} : null,
|
|
1419
|
-
cost,
|
|
1420
|
-
...cost.kind === "uncaptured" ? { knownCostUsd: summary.totalCostUsd } : {}
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
/** Wait a bounded time for late provider receipts, then take one immutable snapshot. */
|
|
1424
|
-
async function settleUsageReceiptFromCostLedger(ledger, options = {}) {
|
|
1425
|
-
const { timeoutMs: requestedTimeoutMs, ...requestedFilter } = options;
|
|
1426
|
-
const filter = {
|
|
1427
|
-
channel: requestedFilter.channel ?? "analyst",
|
|
1428
|
-
...requestedFilter.phase === void 0 ? {} : { phase: requestedFilter.phase },
|
|
1429
|
-
...requestedFilter.tags === void 0 ? {} : { tags: requestedFilter.tags }
|
|
1430
|
-
};
|
|
1431
|
-
const timeoutMs = validateUsageSettlementTimeout(requestedTimeoutMs);
|
|
1432
|
-
const waitResult = ledger.summary(filter).pendingCalls === 0 ? true : ledger.waitForIdle ? await ledger.waitForIdle({ timeoutMs }) : false;
|
|
1433
|
-
const pendingCalls = ledger.summary(filter).pendingCalls;
|
|
1434
|
-
return {
|
|
1435
|
-
settled: waitResult && pendingCalls === 0,
|
|
1436
|
-
pendingCalls,
|
|
1437
|
-
receipt: usageReceiptFromCostLedger(ledger, filter)
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
function validateUsageSettlementTimeout(timeoutMs) {
|
|
1441
|
-
const resolved = timeoutMs ?? 5e3;
|
|
1442
|
-
if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > 2147483647) throw new TypeError("settlementTimeoutMs must be a non-negative safe integer no greater than 2147483647");
|
|
1443
|
-
return resolved;
|
|
1444
|
-
}
|
|
1445
1652
|
//#endregion
|
|
1446
1653
|
//#region src/analyst/kind-factory.ts
|
|
1447
1654
|
/**
|
|
@@ -1459,6 +1666,8 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1459
1666
|
const minimumEvidenceCitations = spec.minimumEvidenceCitations ?? 1;
|
|
1460
1667
|
if (!Number.isInteger(minimumEvidenceCitations) || minimumEvidenceCitations < 1) throw new TypeError("minimumEvidenceCitations must be a positive integer");
|
|
1461
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");
|
|
1462
1671
|
return {
|
|
1463
1672
|
id: spec.id,
|
|
1464
1673
|
description: spec.description,
|
|
@@ -1468,8 +1677,29 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1468
1677
|
settlement_timeout_ms: settlementTimeoutMs
|
|
1469
1678
|
},
|
|
1470
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
|
+
},
|
|
1471
1702
|
async analyze(store, ctx) {
|
|
1472
|
-
const maxOutputTokens = spec.maxOutputTokens ?? 4096;
|
|
1473
1703
|
const costLedger = ctx.costLedger ?? new CostLedger(ctx.budgetUsd);
|
|
1474
1704
|
const costTags = {
|
|
1475
1705
|
...ctx.tags ?? {},
|
|
@@ -1486,7 +1716,10 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1486
1716
|
tags: costTags
|
|
1487
1717
|
});
|
|
1488
1718
|
try {
|
|
1489
|
-
const
|
|
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";
|
|
1490
1723
|
const maxSubqueries = spec.subqueries?.maxCalls ?? 0;
|
|
1491
1724
|
const maxParallel = spec.subqueries?.maxParallel ?? 2;
|
|
1492
1725
|
const priorContext = renderPriorFindings(ctx.priorFindings);
|
|
@@ -1495,9 +1728,11 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1495
1728
|
ctx.log?.(`analyst.kind ${spec.id} forward`, {
|
|
1496
1729
|
max_subqueries: maxSubqueries,
|
|
1497
1730
|
tool_count: tools.length,
|
|
1731
|
+
analysis_mode: analysisMode,
|
|
1732
|
+
prepared_context_chars: preparedContext?.length ?? 0,
|
|
1498
1733
|
tags: ctx.tags
|
|
1499
1734
|
});
|
|
1500
|
-
const
|
|
1735
|
+
const completed = await runTraceAnalysisLoop({
|
|
1501
1736
|
id: spec.id,
|
|
1502
1737
|
description: spec.description,
|
|
1503
1738
|
prompt: actorDescription,
|
|
@@ -1510,8 +1745,10 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1510
1745
|
maxParallelSubqueries: maxParallel,
|
|
1511
1746
|
maxTurns: spec.maxTurns ?? 12,
|
|
1512
1747
|
maxRuntimeChars: spec.maxRuntimeChars ?? 6e3,
|
|
1748
|
+
...preparedContext !== void 0 ? { context: preparedContext } : {},
|
|
1513
1749
|
...ctx.signal ? { signal: ctx.signal } : {}
|
|
1514
1750
|
});
|
|
1751
|
+
const { report, findings: submittedFindings } = completed;
|
|
1515
1752
|
const expectedSubjects = KIND_EXPECTED_SUBJECTS[spec.id];
|
|
1516
1753
|
const out = [];
|
|
1517
1754
|
const rawRows = submittedFindings;
|
|
@@ -1561,7 +1798,10 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1561
1798
|
if (!parsed) continue;
|
|
1562
1799
|
const postProcessed = processRow(parsed);
|
|
1563
1800
|
if (!postProcessed) continue;
|
|
1564
|
-
out.push(toAnalystFinding(spec, version, postProcessed
|
|
1801
|
+
out.push(toAnalystFinding(spec, version, postProcessed, {
|
|
1802
|
+
analysis_mode: analysisMode,
|
|
1803
|
+
analysis_turn_count: completed.turnCount
|
|
1804
|
+
}));
|
|
1565
1805
|
}
|
|
1566
1806
|
ctx.log?.(`analyst.kind ${spec.id} done`, {
|
|
1567
1807
|
emitted: rawRows.length,
|
|
@@ -1598,6 +1838,7 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1598
1838
|
});
|
|
1599
1839
|
}
|
|
1600
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)}`);
|
|
1601
1842
|
const fallback = processRow({
|
|
1602
1843
|
claim: "Analyst produced a diagnosis but no structured findings — see report.",
|
|
1603
1844
|
rationale: report.slice(0, 1500),
|
|
@@ -1608,7 +1849,11 @@ function createTraceAnalystKind(spec, opts) {
|
|
|
1608
1849
|
excerpt: report.slice(0, 2e3)
|
|
1609
1850
|
}]
|
|
1610
1851
|
});
|
|
1611
|
-
if (fallback) out.push(toAnalystFinding(spec, version, fallback, {
|
|
1852
|
+
if (fallback) out.push(toAnalystFinding(spec, version, fallback, {
|
|
1853
|
+
analysis_mode: analysisMode,
|
|
1854
|
+
analysis_turn_count: completed.turnCount,
|
|
1855
|
+
outcome: "extraction_failed"
|
|
1856
|
+
}));
|
|
1612
1857
|
else throw new Error(`Trace analyst '${spec.id}' produced a substantive report, but no finding satisfied its acceptance rules`);
|
|
1613
1858
|
}
|
|
1614
1859
|
}
|
|
@@ -1719,6 +1964,66 @@ function truncateForContext(s, max) {
|
|
|
1719
1964
|
return `${s.slice(0, max - 1).trimEnd()}…`;
|
|
1720
1965
|
}
|
|
1721
1966
|
//#endregion
|
|
1967
|
+
//#region src/analyst/kinds/control-integrity.ts
|
|
1968
|
+
const ANALYST_ID = "control-integrity";
|
|
1969
|
+
function shown(value) {
|
|
1970
|
+
if (value === void 0) return "<absent>";
|
|
1971
|
+
const encoded = JSON.stringify(value);
|
|
1972
|
+
return encoded === void 0 ? String(value) : encoded;
|
|
1973
|
+
}
|
|
1974
|
+
function evidenceRef(namespace, value) {
|
|
1975
|
+
return {
|
|
1976
|
+
kind: "metric",
|
|
1977
|
+
uri: `supervisor-run://${encodeURIComponent(namespace)}/${value.path}`,
|
|
1978
|
+
excerpt: shown(value.value)
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
/** Translate typed supervisor-run integrity issues into the shared analyst envelope. */
|
|
1982
|
+
function emitControlIntegrityFindings(input, producedAt) {
|
|
1983
|
+
const report = analyzeSupervisorRunIntegrity(input, { capturedAt: producedAt });
|
|
1984
|
+
return report.issues.map((issue) => makeFinding({
|
|
1985
|
+
analyst_id: ANALYST_ID,
|
|
1986
|
+
produced_at: producedAt,
|
|
1987
|
+
area: issue.area,
|
|
1988
|
+
severity: issue.severity,
|
|
1989
|
+
subject: `${report.runRef}/${issue.subject}`,
|
|
1990
|
+
claim: issue.claim,
|
|
1991
|
+
rationale: issue.detail,
|
|
1992
|
+
evidence_refs: issue.evidence.map((value) => evidenceRef(report.runRef, value)),
|
|
1993
|
+
recommended_action: issue.recommendedAction,
|
|
1994
|
+
validation_plan: "Re-run this deterministic analyst on the retained SupervisorRunSources or SupervisorRunTree after correcting the producer.",
|
|
1995
|
+
confidence: 1,
|
|
1996
|
+
metadata: {
|
|
1997
|
+
integrity_code: issue.code,
|
|
1998
|
+
integrity_input: report.input,
|
|
1999
|
+
integrity_run_ref: report.runRef,
|
|
2000
|
+
integrity_subject: issue.subject,
|
|
2001
|
+
...issue.metadata
|
|
2002
|
+
}
|
|
2003
|
+
}));
|
|
2004
|
+
}
|
|
2005
|
+
/** Deterministic Analyst adapter for `SupervisorRunSources | SupervisorRunTree`. */
|
|
2006
|
+
var ControlIntegrityAnalyst = class {
|
|
2007
|
+
id = ANALYST_ID;
|
|
2008
|
+
description = "Deterministic supervisor-run integrity checks with explicit unavailable evidence.";
|
|
2009
|
+
inputKind = "custom";
|
|
2010
|
+
cost = {
|
|
2011
|
+
kind: "deterministic",
|
|
2012
|
+
est_usd_per_run: 0
|
|
2013
|
+
};
|
|
2014
|
+
version = "2.0.0";
|
|
2015
|
+
executionConfig = {
|
|
2016
|
+
kind: "control-integrity",
|
|
2017
|
+
produced_at_source: "tags.producedAt-or-system-clock"
|
|
2018
|
+
};
|
|
2019
|
+
async analyze(input, ctx) {
|
|
2020
|
+
const findings = emitControlIntegrityFindings(input, ctx.tags?.producedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
2021
|
+
ctx.log?.(`control-integrity: ${findings.length} finding(s)`, { input: "nodes" in input ? "SupervisorRunTree" : "SupervisorRunSources" });
|
|
2022
|
+
return findings;
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
const CONTROL_INTEGRITY_ANALYST = new ControlIntegrityAnalyst();
|
|
2026
|
+
//#endregion
|
|
1722
2027
|
//#region src/analyst/tool-groups.ts
|
|
1723
2028
|
const TOOL_NAMES_BY_GROUP = {
|
|
1724
2029
|
all: /* @__PURE__ */ new Set(),
|
|
@@ -1746,6 +2051,13 @@ const TOOL_NAMES_BY_GROUP = {
|
|
|
1746
2051
|
"queryTraces",
|
|
1747
2052
|
"viewSpans",
|
|
1748
2053
|
"searchSpan"
|
|
2054
|
+
]),
|
|
2055
|
+
singleTrace: /* @__PURE__ */ new Set([
|
|
2056
|
+
"getDatasetOverview",
|
|
2057
|
+
"viewTrace",
|
|
2058
|
+
"viewSpans",
|
|
2059
|
+
"searchTrace",
|
|
2060
|
+
"searchSpan"
|
|
1749
2061
|
])
|
|
1750
2062
|
};
|
|
1751
2063
|
/**
|
|
@@ -1936,6 +2248,483 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
|
|
|
1936
2248
|
IMPROVEMENT_KIND_SPEC
|
|
1937
2249
|
];
|
|
1938
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
|
|
1939
2728
|
//#region src/analyst/registry.ts
|
|
1940
2729
|
/**
|
|
1941
2730
|
* AnalystRegistry — orchestrate N analysts against one run.
|
|
@@ -1954,6 +2743,17 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
|
|
|
1954
2743
|
* (equal split vs weighted vs custom) lives in `BudgetPolicy`. Both
|
|
1955
2744
|
* have sensible defaults; consumers override only what they need.
|
|
1956
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
|
+
};
|
|
1957
2757
|
var AnalystRegistry = class {
|
|
1958
2758
|
analysts = /* @__PURE__ */ new Map();
|
|
1959
2759
|
options;
|
|
@@ -1961,12 +2761,15 @@ var AnalystRegistry = class {
|
|
|
1961
2761
|
this.options = options;
|
|
1962
2762
|
}
|
|
1963
2763
|
register(analyst) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
if (
|
|
1968
|
-
if (
|
|
1969
|
-
|
|
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);
|
|
1970
2773
|
}
|
|
1971
2774
|
list() {
|
|
1972
2775
|
return Array.from(this.analysts.values()).map((a) => ({
|
|
@@ -1980,6 +2783,15 @@ var AnalystRegistry = class {
|
|
|
1980
2783
|
for await (const ev of this.runStream(runId, inputs, runOpts)) if (ev.type === "run-completed") return ev.result;
|
|
1981
2784
|
throw new Error("AnalystRegistry.run: stream completed without run-completed event");
|
|
1982
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
|
+
}
|
|
1983
2795
|
/**
|
|
1984
2796
|
* Streaming counterpart to `run()`. Emits `AnalystRunEvent` values
|
|
1985
2797
|
* in real time — `run-started`, then per-analyst `skipped` /
|
|
@@ -1992,50 +2804,124 @@ var AnalystRegistry = class {
|
|
|
1992
2804
|
* replacement.
|
|
1993
2805
|
*/
|
|
1994
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);
|
|
1995
2879
|
const correlationId = `ar_${randomUUID().slice(0, 12)}`;
|
|
1996
|
-
const log =
|
|
1997
|
-
const hooks = this.options.hooks ?? {};
|
|
2880
|
+
const log = plan.log;
|
|
1998
2881
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1999
2882
|
const started = Date.now();
|
|
2000
|
-
const
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
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({
|
|
2008
2893
|
type: "run-started",
|
|
2009
|
-
run_id: runId,
|
|
2894
|
+
run_id: plan.runId,
|
|
2010
2895
|
correlation_id: correlationId,
|
|
2011
2896
|
started_at: startedAt,
|
|
2012
|
-
analyst_ids:
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
const
|
|
2016
|
-
let
|
|
2017
|
-
|
|
2018
|
-
const runnableAnalysts = selected.filter((a) => this.routeInput(a, inputs).kind !== "missing");
|
|
2019
|
-
const runnableCount = runnableAnalysts.length;
|
|
2020
|
-
const weights = budget?.weights;
|
|
2021
|
-
const totalWeight = weights && budget?.totalUsd != null && !budget.allocate && runnableCount > 0 ? runnableAnalysts.reduce((sum, analyst) => sum + analystWeight(weights, analyst.id), 0) : void 0;
|
|
2022
|
-
if (totalWeight === 0) throw new Error("BudgetPolicy.weights must allocate positive weight to a runnable analyst");
|
|
2023
|
-
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) {
|
|
2024
2903
|
const t0 = Date.now();
|
|
2025
2904
|
if (runSignal?.aborted) {
|
|
2026
2905
|
const summary = abortedBeforeStartSummary(analyst, runSignal);
|
|
2027
|
-
|
|
2906
|
+
executions.push({
|
|
2907
|
+
summary,
|
|
2908
|
+
findings: [],
|
|
2909
|
+
budgetDebitUsd: 0
|
|
2910
|
+
});
|
|
2028
2911
|
log(`[analyst] skip ${analyst.id} — run aborted`, {
|
|
2029
|
-
runId,
|
|
2912
|
+
runId: plan.runId,
|
|
2030
2913
|
reason: summary.reason
|
|
2031
2914
|
});
|
|
2032
|
-
yield {
|
|
2915
|
+
yield snapshotExecutionEvent({
|
|
2033
2916
|
type: "analyst-skipped",
|
|
2034
2917
|
summary
|
|
2035
|
-
};
|
|
2918
|
+
}, exact);
|
|
2919
|
+
if (exact) {
|
|
2920
|
+
executionFailure = abortReason(runSignal);
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2036
2923
|
continue;
|
|
2037
2924
|
}
|
|
2038
|
-
const input = this.routeInput(analyst, inputs);
|
|
2039
2925
|
if (input.kind === "missing") {
|
|
2040
2926
|
const summary = {
|
|
2041
2927
|
analyst_id: analyst.id,
|
|
@@ -2045,189 +2931,295 @@ var AnalystRegistry = class {
|
|
|
2045
2931
|
latency_ms: 0,
|
|
2046
2932
|
usage: zeroUsage()
|
|
2047
2933
|
};
|
|
2048
|
-
|
|
2934
|
+
const execution = {
|
|
2935
|
+
summary,
|
|
2936
|
+
findings: [],
|
|
2937
|
+
budgetDebitUsd: 0
|
|
2938
|
+
};
|
|
2939
|
+
executions.push(execution);
|
|
2049
2940
|
log(`[analyst] skip ${analyst.id} — missing input`, {
|
|
2050
|
-
runId,
|
|
2941
|
+
runId: plan.runId,
|
|
2051
2942
|
kind: analyst.inputKind
|
|
2052
2943
|
});
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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({
|
|
2060
2957
|
type: "analyst-skipped",
|
|
2061
2958
|
summary
|
|
2062
|
-
};
|
|
2959
|
+
}, exact);
|
|
2960
|
+
if (executionFailure !== void 0) break;
|
|
2063
2961
|
continue;
|
|
2064
2962
|
}
|
|
2065
|
-
const
|
|
2963
|
+
const allocatedUsd = plan.executionSnapshot === void 0 ? allocateBudget(plan.budget.kind === "dynamic" ? plan.budget.policy : void 0, {
|
|
2066
2964
|
analyst,
|
|
2067
2965
|
remainingUsd,
|
|
2068
|
-
runningCount:
|
|
2966
|
+
runningCount: runnable.length,
|
|
2069
2967
|
totalWeight
|
|
2070
|
-
});
|
|
2968
|
+
}) : exactPlannedAllocation(plan.executionSnapshot, analyst.id);
|
|
2969
|
+
const budgetCeilingUsd = plan.executionSnapshot === void 0 ? remainingUsd : allocatedUsd;
|
|
2071
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;
|
|
2072
2974
|
const ctx = {
|
|
2073
|
-
runId,
|
|
2975
|
+
runId: plan.runId,
|
|
2074
2976
|
correlationId,
|
|
2075
2977
|
deadlineMs,
|
|
2076
|
-
budgetUsd:
|
|
2077
|
-
costLedger:
|
|
2078
|
-
costPhase:
|
|
2079
|
-
chat:
|
|
2080
|
-
tags:
|
|
2081
|
-
log: (
|
|
2082
|
-
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,
|
|
2083
2985
|
correlationId,
|
|
2084
2986
|
...fields
|
|
2085
2987
|
}),
|
|
2086
2988
|
signal: runSignal,
|
|
2087
|
-
priorFindings
|
|
2088
|
-
upstreamFindings:
|
|
2989
|
+
priorFindings,
|
|
2990
|
+
upstreamFindings: chainedFindings,
|
|
2089
2991
|
recordUsage: (receipt) => {
|
|
2090
|
-
|
|
2091
|
-
|
|
2992
|
+
if (!exact) {
|
|
2993
|
+
assertValidAnalystUsageReceipt(receipt);
|
|
2994
|
+
usageReceipts.push(receipt);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
usageReceipts.push(snapshotUsageReceiptOnce(receipt, `AnalystRegistry.runExact analyst "${analyst.id}" usage`));
|
|
2092
2998
|
}
|
|
2093
2999
|
};
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
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
|
+
}
|
|
2099
3017
|
if (runSignal?.aborted) {
|
|
2100
3018
|
const summary = abortedBeforeStartSummary(analyst, runSignal, Date.now() - t0);
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
3019
|
+
executions.push({
|
|
3020
|
+
summary,
|
|
3021
|
+
findings: [],
|
|
3022
|
+
budgetDebitUsd: 0
|
|
2105
3023
|
});
|
|
2106
|
-
yield {
|
|
3024
|
+
yield snapshotExecutionEvent({
|
|
2107
3025
|
type: "analyst-skipped",
|
|
2108
3026
|
summary
|
|
2109
|
-
};
|
|
3027
|
+
}, exact);
|
|
3028
|
+
if (exact) {
|
|
3029
|
+
executionFailure = abortReason(runSignal);
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
2110
3032
|
continue;
|
|
2111
3033
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
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({
|
|
2114
3045
|
type: "analyst-started",
|
|
2115
3046
|
analyst_id: analyst.id,
|
|
2116
3047
|
started_at: new Date(t0).toISOString()
|
|
2117
|
-
};
|
|
3048
|
+
}, exact);
|
|
2118
3049
|
let findings;
|
|
2119
3050
|
let summary;
|
|
3051
|
+
let lifecycleFailure;
|
|
3052
|
+
let analysisFailure;
|
|
2120
3053
|
try {
|
|
2121
3054
|
if (runSignal?.aborted) throw abortReason(runSignal);
|
|
2122
|
-
findings = await waitForOperation(analyst.analyze(input.value,
|
|
2123
|
-
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
if (
|
|
2128
|
-
|
|
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) {
|
|
2129
3080
|
summary = {
|
|
2130
3081
|
analyst_id: analyst.id,
|
|
2131
3082
|
status: "ok",
|
|
2132
3083
|
findings_count: findings.length,
|
|
2133
|
-
latency_ms:
|
|
2134
|
-
usage
|
|
3084
|
+
latency_ms: Date.now() - t0,
|
|
3085
|
+
usage,
|
|
3086
|
+
...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {}
|
|
2135
3087
|
};
|
|
2136
|
-
summaries.push(summary);
|
|
2137
3088
|
log(`[analyst] ok ${analyst.id}`, {
|
|
2138
|
-
runId,
|
|
3089
|
+
runId: plan.runId,
|
|
2139
3090
|
findings: findings.length,
|
|
2140
|
-
latency_ms:
|
|
2141
|
-
cost_usd:
|
|
3091
|
+
latency_ms: summary.latency_ms,
|
|
3092
|
+
cost_usd: knownCostUsd(usage),
|
|
2142
3093
|
cost_kind: usage.cost.kind,
|
|
2143
3094
|
input_tokens: usage.tokens?.input ?? null,
|
|
2144
3095
|
output_tokens: usage.tokens?.output ?? null
|
|
2145
3096
|
});
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
});
|
|
2151
|
-
} catch (err) {
|
|
2152
|
-
const latency = Date.now() - t0;
|
|
2153
|
-
const e = err instanceof Error ? err : new Error(String(err));
|
|
2154
|
-
const hookFindings = runSignal?.aborted ? [] : await hooks.onError?.({
|
|
2155
|
-
analyst,
|
|
2156
|
-
error: e,
|
|
2157
|
-
runId
|
|
2158
|
-
}) ?? [];
|
|
2159
|
-
if (hookFindings.length) allFindings.push(...hookFindings);
|
|
2160
|
-
const usage = resolveUsage(analyst, usageReceipts);
|
|
2161
|
-
const cost = knownCostUsd(usage);
|
|
2162
|
-
totalCost += cost;
|
|
2163
|
-
if (typeof remainingUsd === "number") remainingUsd = Math.max(0, remainingUsd - budgetDebit(usage, effectiveBudget));
|
|
2164
|
-
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 = {
|
|
2165
3101
|
analyst_id: analyst.id,
|
|
2166
3102
|
status: "failed",
|
|
2167
|
-
findings_count:
|
|
2168
|
-
latency_ms:
|
|
3103
|
+
findings_count: findings.length,
|
|
3104
|
+
latency_ms: Date.now() - t0,
|
|
2169
3105
|
usage,
|
|
3106
|
+
...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {},
|
|
2170
3107
|
error: {
|
|
2171
|
-
class:
|
|
2172
|
-
message:
|
|
3108
|
+
class: errorClass,
|
|
3109
|
+
message: errorMessage
|
|
2173
3110
|
}
|
|
2174
3111
|
};
|
|
2175
|
-
summaries.push(summary);
|
|
2176
3112
|
log(`[analyst] FAIL ${analyst.id}`, {
|
|
2177
|
-
runId,
|
|
2178
|
-
error_class:
|
|
2179
|
-
error:
|
|
2180
|
-
cost_usd:
|
|
3113
|
+
runId: plan.runId,
|
|
3114
|
+
error_class: errorClass,
|
|
3115
|
+
error: errorMessage,
|
|
3116
|
+
cost_usd: knownCostUsd(usage),
|
|
2181
3117
|
cost_kind: usage.cost.kind
|
|
2182
3118
|
});
|
|
2183
|
-
if (effectiveBudget !== void 0 && usage.cost.kind === "uncaptured") log(`[analyst] WARN ${analyst.id} — USD cost uncaptured; budget not reconciled`, {
|
|
2184
|
-
runId,
|
|
2185
|
-
budget_usd: effectiveBudget,
|
|
2186
|
-
cost_captured: false
|
|
2187
|
-
});
|
|
2188
|
-
await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
|
|
2189
|
-
analyst,
|
|
2190
|
-
summary,
|
|
2191
|
-
findings: hookFindings,
|
|
2192
|
-
runId
|
|
2193
|
-
}) : void 0, runSignal);
|
|
2194
|
-
yield {
|
|
2195
|
-
type: "analyst-completed",
|
|
2196
|
-
summary,
|
|
2197
|
-
findings: hookFindings
|
|
2198
|
-
};
|
|
2199
|
-
continue;
|
|
2200
3119
|
}
|
|
2201
|
-
|
|
3120
|
+
logUncapturedBudgetWarning({
|
|
2202
3121
|
analyst,
|
|
3122
|
+
runId: plan.runId,
|
|
3123
|
+
budgetUsd: effectiveBudget,
|
|
3124
|
+
usage,
|
|
3125
|
+
log
|
|
3126
|
+
});
|
|
3127
|
+
const execution = {
|
|
2203
3128
|
summary,
|
|
2204
3129
|
findings,
|
|
2205
|
-
|
|
2206
|
-
}
|
|
2207
|
-
|
|
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({
|
|
2208
3160
|
type: "analyst-completed",
|
|
2209
3161
|
summary,
|
|
2210
3162
|
findings
|
|
2211
|
-
};
|
|
3163
|
+
}, exact);
|
|
3164
|
+
if (exact && runSignal?.aborted) {
|
|
3165
|
+
executionFailure = abortReason(runSignal);
|
|
3166
|
+
break;
|
|
3167
|
+
}
|
|
2212
3168
|
}
|
|
2213
|
-
const
|
|
2214
|
-
|
|
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,
|
|
2215
3174
|
correlation_id: correlationId,
|
|
2216
3175
|
started_at: startedAt,
|
|
2217
3176
|
ended_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2218
|
-
findings
|
|
3177
|
+
findings,
|
|
2219
3178
|
per_analyst: summaries,
|
|
2220
|
-
total_cost_usd:
|
|
2221
|
-
total_cost_provenance:
|
|
2222
|
-
kind: "uncaptured",
|
|
2223
|
-
usd: null
|
|
2224
|
-
}))
|
|
2225
|
-
};
|
|
2226
|
-
await waitForHook(hooks.onComplete ? () => hooks.onComplete?.({ result }) : void 0, runSignal);
|
|
2227
|
-
yield {
|
|
2228
|
-
type: "run-completed",
|
|
2229
|
-
result
|
|
3179
|
+
total_cost_usd: cost.known,
|
|
3180
|
+
total_cost_provenance: cost.provenance
|
|
2230
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 });
|
|
2231
3223
|
}
|
|
2232
3224
|
selectAnalysts(opts) {
|
|
2233
3225
|
let candidates = Array.from(this.analysts.values());
|
|
@@ -2241,34 +3233,377 @@ var AnalystRegistry = class {
|
|
|
2241
3233
|
}
|
|
2242
3234
|
return candidates;
|
|
2243
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
|
+
}
|
|
2244
3246
|
routeInput(analyst, inputs) {
|
|
2245
3247
|
switch (analyst.inputKind) {
|
|
2246
|
-
case "trace-store":
|
|
2247
|
-
|
|
2248
|
-
value
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
kind: "
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
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
|
+
}
|
|
2262
3276
|
case "custom": {
|
|
2263
|
-
const
|
|
2264
|
-
return
|
|
3277
|
+
const value = inputs.custom?.[analyst.id];
|
|
3278
|
+
return value !== void 0 ? {
|
|
2265
3279
|
kind: "present",
|
|
2266
|
-
value
|
|
3280
|
+
value
|
|
2267
3281
|
} : { kind: "missing" };
|
|
2268
3282
|
}
|
|
2269
3283
|
}
|
|
2270
3284
|
}
|
|
2271
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
|
+
}
|
|
2272
3607
|
function validateTimeout(timeoutMs) {
|
|
2273
3608
|
if (timeoutMs === void 0) return void 0;
|
|
2274
3609
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new TypeError("RegistryRunOpts.timeoutMs must be a positive safe integer no greater than 2147483647");
|
|
@@ -2398,8 +3733,8 @@ function zeroUsage() {
|
|
|
2398
3733
|
}
|
|
2399
3734
|
};
|
|
2400
3735
|
}
|
|
2401
|
-
function resolveUsage(analyst, receipts) {
|
|
2402
|
-
if (receipts.length > 0) return mergeUsageReceipts(receipts);
|
|
3736
|
+
function resolveUsage(analyst, receipts, exact = false) {
|
|
3737
|
+
if (receipts.length > 0) return mergeUsageReceipts(receipts, exact);
|
|
2403
3738
|
if (analyst.cost.kind === "deterministic") return zeroUsage();
|
|
2404
3739
|
return {
|
|
2405
3740
|
calls: null,
|
|
@@ -2410,24 +3745,21 @@ function resolveUsage(analyst, receipts) {
|
|
|
2410
3745
|
}
|
|
2411
3746
|
};
|
|
2412
3747
|
}
|
|
2413
|
-
function mergeUsageReceipts(receipts) {
|
|
2414
|
-
const calls = receipts.every((receipt) => receipt.calls !== null) ? receipts.
|
|
2415
|
-
const tokens = receipts.every((receipt) => receipt.tokens !== null) ?
|
|
2416
|
-
input
|
|
2417
|
-
output
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
output: 0
|
|
2424
|
-
}) : null;
|
|
2425
|
-
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);
|
|
2426
3758
|
return {
|
|
2427
3759
|
calls,
|
|
2428
3760
|
tokens,
|
|
2429
3761
|
cost,
|
|
2430
|
-
...cost.kind === "uncaptured" ? { knownCostUsd: receipts.
|
|
3762
|
+
...cost.kind === "uncaptured" ? { knownCostUsd: usageSum(receipts.map(knownCostUsd), exact, "known cost") } : {}
|
|
2431
3763
|
};
|
|
2432
3764
|
}
|
|
2433
3765
|
function knownCostUsd(receipt) {
|
|
@@ -2437,12 +3769,12 @@ function budgetDebit(receipt, allocatedUsd) {
|
|
|
2437
3769
|
const known = knownCostUsd(receipt);
|
|
2438
3770
|
return receipt.cost.kind === "uncaptured" && allocatedUsd !== void 0 ? Math.max(known, allocatedUsd) : known;
|
|
2439
3771
|
}
|
|
2440
|
-
function aggregateCostProvenance(costs) {
|
|
3772
|
+
function aggregateCostProvenance(costs, exact = false) {
|
|
2441
3773
|
if (costs.some((cost) => cost.kind === "uncaptured")) return {
|
|
2442
3774
|
kind: "uncaptured",
|
|
2443
3775
|
usd: null
|
|
2444
3776
|
};
|
|
2445
|
-
const usd = costs.
|
|
3777
|
+
const usd = usageSum(costs.map((cost) => cost.usd ?? 0), exact, "captured cost");
|
|
2446
3778
|
return costs.some((cost) => cost.kind === "estimated") ? {
|
|
2447
3779
|
kind: "estimated",
|
|
2448
3780
|
usd
|
|
@@ -2451,24 +3783,17 @@ function aggregateCostProvenance(costs) {
|
|
|
2451
3783
|
usd
|
|
2452
3784
|
};
|
|
2453
3785
|
}
|
|
2454
|
-
function
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
assertNonNegativeFinite(receipt.tokens.reasoning, "tokens.reasoning");
|
|
2461
|
-
if (receipt.tokens.reasoning > receipt.tokens.output) throw new Error("AnalystContext.recordUsage: tokens.reasoning must not exceed tokens.output");
|
|
2462
|
-
}
|
|
2463
|
-
if (receipt.tokens.cached !== void 0) assertNonNegativeFinite(receipt.tokens.cached, "tokens.cached");
|
|
2464
|
-
if (receipt.tokens.cacheWrite !== void 0) assertNonNegativeFinite(receipt.tokens.cacheWrite, "tokens.cacheWrite");
|
|
2465
|
-
}
|
|
2466
|
-
if (receipt.cost.kind !== "uncaptured") assertNonNegativeFinite(receipt.cost.usd, "cost.usd");
|
|
2467
|
-
else if (receipt.cost.usd !== null) throw new Error("AnalystContext.recordUsage: uncaptured cost.usd must be null");
|
|
2468
|
-
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
|
+
};
|
|
2469
3792
|
}
|
|
2470
|
-
function
|
|
2471
|
-
|
|
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;
|
|
2472
3797
|
}
|
|
2473
3798
|
/**
|
|
2474
3799
|
* Resolve the `priorFindings` slice an analyst sees.
|
|
@@ -2497,17 +3822,18 @@ function selectPriorFindings(source, analystId) {
|
|
|
2497
3822
|
//#region src/analyst/default-registry.ts
|
|
2498
3823
|
function buildDefaultAnalystRegistry(opts = {}) {
|
|
2499
3824
|
const registry = new AnalystRegistry(opts.registry);
|
|
2500
|
-
if (opts.includeBehavioral !== false) registry.register(behavioralAnalyst());
|
|
3825
|
+
if (opts.includeBehavioral !== false) registry.register(behavioralAnalyst(opts.behavioral));
|
|
2501
3826
|
if (opts.ai) {
|
|
2502
3827
|
const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
|
|
2503
3828
|
for (const spec of kinds) registry.register(createTraceAnalystKind(spec, {
|
|
2504
3829
|
ai: opts.ai,
|
|
2505
|
-
model: opts.model
|
|
3830
|
+
model: opts.model,
|
|
3831
|
+
aiIdentity: opts.aiIdentity
|
|
2506
3832
|
}));
|
|
2507
3833
|
}
|
|
2508
3834
|
return registry;
|
|
2509
3835
|
}
|
|
2510
3836
|
//#endregion
|
|
2511
|
-
export {
|
|
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 };
|
|
2512
3838
|
|
|
2513
|
-
//# sourceMappingURL=default-registry-
|
|
3839
|
+
//# sourceMappingURL=default-registry-lp5R0lve.js.map
|