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