@tangle-network/agent-eval 0.189.0 → 0.190.1
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 +94 -0
- package/README.md +3 -2
- package/dist/adapters/http.d.ts +10 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +75 -7
- package/dist/adapters/http.js.map +1 -1
- package/dist/analyst/index.d.ts +66 -3
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +212 -7
- package/dist/analyst/index.js.map +1 -1
- package/dist/{benchmark-command-BhhAYstU.js → benchmark-command-BXIr_k8D.js} +12 -11
- package/dist/benchmark-command-BXIr_k8D.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +3 -3
- package/dist/campaign/index.d.ts +4 -4
- package/dist/campaign/index.js +7 -7
- package/dist/{campaign-BWoSQPSU.js → campaign-B7q74uIh.js} +256 -11
- package/dist/campaign-B7q74uIh.js.map +1 -0
- package/dist/{campaign-evidence-CBlGJ3kC.js → campaign-evidence-D40-LHIL.js} +6 -5
- package/dist/campaign-evidence-D40-LHIL.js.map +1 -0
- package/dist/{chat-client-C4dg7Ng_.js → chat-client-B_ItsSBw.js} +2 -2
- package/dist/{chat-client-C4dg7Ng_.js.map → chat-client-B_ItsSBw.js.map} +1 -1
- package/dist/{chat-json-call-JVGbfx95.js → chat-json-call-CXHBvUz8.js} +2 -2
- package/dist/{chat-json-call-JVGbfx95.js.map → chat-json-call-CXHBvUz8.js.map} +1 -1
- package/dist/claim-BGOUqoMq.d.ts +38 -0
- package/dist/claim-BGOUqoMq.d.ts.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/contract/index.d.ts +4 -4
- package/dist/contract/index.js +8 -8
- package/dist/{default-registry-m2jhbG6Q.js → default-registry-16I-Bxlf.js} +2 -2
- package/dist/{default-registry-m2jhbG6Q.js.map → default-registry-16I-Bxlf.js.map} +1 -1
- package/dist/{define-agent-eval-98sgUaC1.js → define-agent-eval-DPizhHfy.js} +4 -4
- package/dist/{define-agent-eval-98sgUaC1.js.map → define-agent-eval-DPizhHfy.js.map} +1 -1
- package/dist/{define-agent-eval-q6890HU4.d.ts → define-agent-eval-DblGedQk.d.ts} +4 -3
- package/dist/define-agent-eval-DblGedQk.d.ts.map +1 -0
- package/dist/diagnosis/index.d.ts +14 -2
- package/dist/diagnosis/index.d.ts.map +1 -1
- package/dist/diagnosis/index.js +11 -4
- package/dist/diagnosis/index.js.map +1 -1
- package/dist/{dspy-rlm-engine-DLYttKBm.js → dspy-rlm-engine-Cs5LzJ-f.js} +3 -3
- package/dist/{dspy-rlm-engine-DLYttKBm.js.map → dspy-rlm-engine-Cs5LzJ-f.js.map} +1 -1
- package/dist/{eval-campaign-C0Q1Eeo_.js → eval-campaign-CwsBn0Gm.js} +2 -2
- package/dist/{eval-campaign-C0Q1Eeo_.js.map → eval-campaign-CwsBn0Gm.js.map} +1 -1
- package/dist/experiment/index.d.ts +2 -1
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/index.js +1 -1
- package/dist/exporters-XMCYJlqT.d.ts +161 -0
- package/dist/exporters-XMCYJlqT.d.ts.map +1 -0
- package/dist/{external-optimizer-process-C3quNeKV.js → external-optimizer-process-BnSmrkgL.js} +3 -3
- package/dist/{external-optimizer-process-C3quNeKV.js.map → external-optimizer-process-BnSmrkgL.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-BDwgKLO1.js → external-optimizer-subprocess-kbcPu5po.js} +3 -3
- package/dist/{external-optimizer-subprocess-BDwgKLO1.js.map → external-optimizer-subprocess-kbcPu5po.js.map} +1 -1
- package/dist/{feedback-trajectory-B3ZSkfAq.d.ts → feedback-trajectory-BYjh7NEM.d.ts} +2 -2
- package/dist/{feedback-trajectory-B3ZSkfAq.d.ts.map → feedback-trajectory-BYjh7NEM.d.ts.map} +1 -1
- package/dist/{index-CnJOPR_E.d.ts → index-CJv56rhX.d.ts} +673 -244
- package/dist/index-CJv56rhX.d.ts.map +1 -0
- package/dist/{index-Dk1dOUfm.d.ts → index-CWoIJHmy.d.ts} +4 -4
- package/dist/{index-Dk1dOUfm.d.ts.map → index-CWoIJHmy.d.ts.map} +1 -1
- package/dist/{index-Bcc9PkOz.d.ts → index-D4pC7Tvd.d.ts} +3 -2
- package/dist/index-D4pC7Tvd.d.ts.map +1 -0
- package/dist/{index-BqDJ6fUK.d.ts → index-I4UJGRno.d.ts} +74 -2
- package/dist/index-I4UJGRno.d.ts.map +1 -0
- package/dist/index.d.ts +66 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +375 -31
- package/dist/index.js.map +1 -1
- package/dist/{journal-CDP0Vhx_.js → journal-c3qY0SDP.js} +246 -5
- package/dist/journal-c3qY0SDP.js.map +1 -0
- package/dist/{kind-factory-BGwN0nE6.js → kind-factory-ByZeXDqF.js} +2 -2
- package/dist/{kind-factory-BGwN0nE6.js.map → kind-factory-ByZeXDqF.js.map} +1 -1
- package/dist/ledger-core/index.d.ts +2 -2
- package/dist/ledger-core/index.js +2 -2
- package/dist/{llm-client-DF9bPNYA.js → llm-client-KYQBh9C8.js} +2 -2
- package/dist/{llm-client-DF9bPNYA.js.map → llm-client-KYQBh9C8.js.map} +1 -1
- package/dist/{llm-judge-M_l_WXw2.js → llm-judge-BnxAv7ed.js} +1892 -1378
- package/dist/llm-judge-BnxAv7ed.js.map +1 -0
- package/dist/mint-C9Lojt1V.d.ts +147 -0
- package/dist/mint-C9Lojt1V.d.ts.map +1 -0
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.d.ts +1 -1
- package/dist/pipelines/index.js +1 -1
- package/dist/pipelines/index.js.map +1 -1
- package/dist/{produced-state-BOY98IJO.js → produced-state-BXjXJHJT.js} +4 -4
- package/dist/{produced-state-BOY98IJO.js.map → produced-state-BXjXJHJT.js.map} +1 -1
- package/dist/{raw-provider-sink-C96CcqeG.js → raw-provider-sink-DYh4Bszw.js} +2 -2
- package/dist/{raw-provider-sink-C96CcqeG.js.map → raw-provider-sink-DYh4Bszw.js.map} +1 -1
- package/dist/{redact-DxwlUAvh.js → redact-B7rt9Zot.js} +130 -10
- package/dist/redact-B7rt9Zot.js.map +1 -0
- package/dist/{redact-DPoH1-OW.d.ts → redact-CopuMxll.d.ts} +7 -5
- package/dist/redact-CopuMxll.d.ts.map +1 -0
- package/dist/rl.d.ts +3 -2
- package/dist/rl.d.ts.map +1 -1
- package/dist/rl.js +1 -1
- package/dist/rollout/index.d.ts +3 -2
- package/dist/search-ledger-types-C9anU2DX.d.ts +489 -0
- package/dist/search-ledger-types-C9anU2DX.d.ts.map +1 -0
- package/dist/{semantic-concept-judge-Cm2K2wAo.js → semantic-concept-judge-ClB73cei.js} +6 -6
- package/dist/{semantic-concept-judge-Cm2K2wAo.js.map → semantic-concept-judge-ClB73cei.js.map} +1 -1
- package/dist/{server-C8XvqCg8.js → server-DtetGGoA.js} +2 -2
- package/dist/{server-C8XvqCg8.js.map → server-DtetGGoA.js.map} +1 -1
- package/dist/{skillopt-optimization-method-BVSB676o.js → skillopt-optimization-method-l2vDBtKp.js} +363 -18
- package/dist/skillopt-optimization-method-l2vDBtKp.js.map +1 -0
- package/dist/{spans-W0o8pbEg.d.ts → spans-BWtjk_9c.d.ts} +2 -2
- package/dist/{spans-W0o8pbEg.d.ts.map → spans-BWtjk_9c.d.ts.map} +1 -1
- package/dist/{statistical-heldout-plAHG82H.d.ts → statistical-heldout-CLpjYffJ.d.ts} +2 -36
- package/dist/statistical-heldout-CLpjYffJ.d.ts.map +1 -0
- package/dist/{store-otlp-D0AV4cvB.js → store-otlp-84DH0OEH.js} +2 -2
- package/dist/{store-otlp-D0AV4cvB.js.map → store-otlp-84DH0OEH.js.map} +1 -1
- package/dist/{store-tool-spans-8PAyhRqb.d.ts → store-tool-spans-BBXs8hDP.d.ts} +2 -2
- package/dist/{store-tool-spans-8PAyhRqb.d.ts.map → store-tool-spans-BBXs8hDP.d.ts.map} +1 -1
- package/dist/{store-tool-spans-B_0mXvYl.js → store-tool-spans-DUiqyHvR.js} +5 -5
- package/dist/{store-tool-spans-B_0mXvYl.js.map → store-tool-spans-DUiqyHvR.js.map} +1 -1
- package/dist/traces.d.ts +3 -63
- package/dist/traces.d.ts.map +1 -1
- package/dist/traces.js +6 -195
- package/dist/traces.js.map +1 -1
- package/dist/wire/index.d.ts +1 -1
- package/dist/wire/index.js +1 -1
- package/docs/adapters-observability.md +31 -27
- package/docs/charter.md +1 -1
- package/docs/design/mlbenchmarks-book-review.md +1 -1
- package/docs/distributed-driver.md +1 -5
- package/docs/experiment.md +15 -0
- package/docs/public-api.md +181 -71
- package/docs/redaction.md +7 -1
- package/docs/search-ledger.md +174 -0
- package/docs/trace-contracts.md +48 -3
- package/package.json +1 -1
- package/dist/benchmark-command-BhhAYstU.js.map +0 -1
- package/dist/campaign-BWoSQPSU.js.map +0 -1
- package/dist/campaign-evidence-CBlGJ3kC.js.map +0 -1
- package/dist/define-agent-eval-q6890HU4.d.ts.map +0 -1
- package/dist/index-Bcc9PkOz.d.ts.map +0 -1
- package/dist/index-BqDJ6fUK.d.ts.map +0 -1
- package/dist/index-CnJOPR_E.d.ts.map +0 -1
- package/dist/journal-CDP0Vhx_.js.map +0 -1
- package/dist/llm-judge-M_l_WXw2.js.map +0 -1
- package/dist/mint-DsS38v5c.d.ts +0 -305
- package/dist/mint-DsS38v5c.d.ts.map +0 -1
- package/dist/redact-DPoH1-OW.d.ts.map +0 -1
- package/dist/redact-DxwlUAvh.js.map +0 -1
- package/dist/search-ledger-types-D7KDVg-o.d.ts +0 -302
- package/dist/search-ledger-types-D7KDVg-o.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-BVSB676o.js.map +0 -1
- package/dist/statistical-heldout-plAHG82H.d.ts.map +0 -1
- package/docs/search-history-receipts.md +0 -233
|
@@ -3,26 +3,26 @@ import { a as hashCanonical, i as compareCodeUnits, r as canonicalString, t as L
|
|
|
3
3
|
import { o as summarizeNumberSeries, s as weightedComposite, t as confidenceInterval } from "./descriptive-1V17A-qa.js";
|
|
4
4
|
import { t as decidePairedPromotion } from "./paired-promotion-decision-6cn3bLc3.js";
|
|
5
5
|
import { a as heldoutSignificance, i as dimensionRegressions, o as pairHoldout } from "./statistical-heldout-GcLIPxk4.js";
|
|
6
|
-
import { i as modelHasSnapshot } from "./run-record-BRlM99rX.js";
|
|
6
|
+
import { i as modelHasSnapshot, l as searchCellRunId } from "./run-record-BRlM99rX.js";
|
|
7
7
|
import { n as contentHash } from "./verdict-cache-BQEsVy9n.js";
|
|
8
8
|
import { o as projectCampaignCellQuality, t as campaignCellCostProvenance } from "./run-record-DnMFyg_g.js";
|
|
9
9
|
import { i as CostLedger, t as CostAccountingIncompleteError } from "./cost-ledger-_IbkejCF.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { A as FinalEvidenceConflictError, B as surfaceContentHash, C as campaignMeanCompositeOrNull, F as summarizeEvaluationUnits, H as surfaceHash, J as assertCampaignSplitIdentity, N as defineEvaluationClaim, P as evaluationClaimSchema, Q as campaignSplitDigest, S as campaignMeanComposite, U as surfaceHashMatches, V as surfaceDispatchRef, W as BackendIntegrityError, X as campaignCoverage, Y as assertCompleteCampaign, Z as campaignScenarioIdentity, b as assertFiniteRankKey, et as formatCoverageFailures, j as FinalEvidenceError, q as assertCampaignDesign, t as createCampaignEvidenceReceipt, w as compareRankKeys, x as campaignBreakdown, z as renderSurfaceDiff } from "./campaign-evidence-D40-LHIL.js";
|
|
11
|
+
import { n as replayLedgerText, p as mapConcurrent, t as FileLedgerJournal } from "./journal-c3qY0SDP.js";
|
|
12
12
|
import { t as deepFreezeCanonicalJson } from "./deep-freeze-CurcOQg3.js";
|
|
13
13
|
import "./ledger-core/index.js";
|
|
14
|
-
import { D as SearchLedgerIntegrityError, E as SearchLedgerError, S as fsCampaignStorage, T as SearchLedgerConflictError, g as isRecord, h as isExternalTextCandidate, w as SEARCH_LEDGER_FILE_CONTEXT, x as createRunCostLedger } from "./external-optimizer-subprocess-
|
|
15
|
-
import { f as redactText, o as detectCredential, s as detectPersonalData } from "./redact-
|
|
14
|
+
import { D as SearchLedgerIntegrityError, E as SearchLedgerError, S as fsCampaignStorage, T as SearchLedgerConflictError, g as isRecord, h as isExternalTextCandidate, w as SEARCH_LEDGER_FILE_CONTEXT, x as createRunCostLedger } from "./external-optimizer-subprocess-kbcPu5po.js";
|
|
15
|
+
import { f as redactText, n as REDACTION_VERSION, o as detectCredential, s as detectPersonalData } from "./redact-B7rt9Zot.js";
|
|
16
16
|
import { t as detectRewardHacking } from "./reward-hacking-D0XwhVWE.js";
|
|
17
17
|
import { n as assertProposalFindings, t as combineAbortSignals } from "./abort-signal-CtzAM_sJ.js";
|
|
18
|
-
import { d as stripFencedJson, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-
|
|
18
|
+
import { d as stripFencedJson, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-KYQBh9C8.js";
|
|
19
19
|
import { createHash } from "node:crypto";
|
|
20
20
|
import { writeFileSync } from "node:fs";
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { basename, isAbsolute, join } from "node:path";
|
|
22
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
23
23
|
import { homedir, tmpdir } from "node:os";
|
|
24
24
|
import { execSync } from "node:child_process";
|
|
25
|
-
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
26
|
//#region src/campaign/campaign-manifest.ts
|
|
27
27
|
/**
|
|
28
28
|
* Campaign identity: the manifest hash over (scenarios, judges, dispatch,
|
|
@@ -2531,6 +2531,22 @@ function paretoFrontierWithCrowding(candidates, objectives) {
|
|
|
2531
2531
|
return crowdingDistance(frontier, objectives).sort((a, b) => b.distance - a.distance);
|
|
2532
2532
|
}
|
|
2533
2533
|
//#endregion
|
|
2534
|
+
//#region src/campaign/types.ts
|
|
2535
|
+
/** Type guard: a proposal carrying its rationale vs a bare
|
|
2536
|
+
* surface. The loop branches on this to populate `GenerationCandidate`. */
|
|
2537
|
+
function isProposedCandidate(value) {
|
|
2538
|
+
return typeof value === "object" && value !== null && "surface" in value && "label" in value && "rationale" in value;
|
|
2539
|
+
}
|
|
2540
|
+
const LABEL_TRUST_RANK = {
|
|
2541
|
+
unverified: 0,
|
|
2542
|
+
"verified-signal": 1,
|
|
2543
|
+
"human-rated": 2
|
|
2544
|
+
};
|
|
2545
|
+
/** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
|
|
2546
|
+
function labelTrustRank(trust) {
|
|
2547
|
+
return LABEL_TRUST_RANK[trust ?? "unverified"];
|
|
2548
|
+
}
|
|
2549
|
+
//#endregion
|
|
2534
2550
|
//#region src/campaign/search-ledger-ordering.ts
|
|
2535
2551
|
function artifactKey(artifact) {
|
|
2536
2552
|
return canonicalString(artifact);
|
|
@@ -2539,339 +2555,848 @@ function compareStrings(a, b) {
|
|
|
2539
2555
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
2540
2556
|
}
|
|
2541
2557
|
//#endregion
|
|
2542
|
-
//#region src/campaign/search-ledger-
|
|
2543
|
-
/**
|
|
2544
|
-
*
|
|
2545
|
-
*
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2558
|
+
//#region src/campaign/search-ledger-types.ts
|
|
2559
|
+
/**
|
|
2560
|
+
* Schema tag every search-ledger entry carries. A ledger written under another
|
|
2561
|
+
* tag, such as the retired `tangle.search-ledger.v1` candidate-slot format, is
|
|
2562
|
+
* refused with its tag named; it is never translated.
|
|
2563
|
+
*/
|
|
2564
|
+
const SEARCH_LEDGER_SCHEMA = "tangle.search-ledger.2026-09";
|
|
2565
|
+
//#endregion
|
|
2566
|
+
//#region src/campaign/search-state.ts
|
|
2567
|
+
/**
|
|
2568
|
+
* `SearchState`: the one projection of a search ledger.
|
|
2569
|
+
*
|
|
2570
|
+
* The same code checks every invariant for the producer's journal, the kernel,
|
|
2571
|
+
* and the Intelligence verifier, and builds the read model they share. It keeps
|
|
2572
|
+
* compact indexes (nodes, edges, cells, per-unit score sums, running audit
|
|
2573
|
+
* totals), not the raw event list, and applies each entry in time proportional
|
|
2574
|
+
* to that entry, so a search of any length appends at a constant cost.
|
|
2575
|
+
*
|
|
2576
|
+
* The journal asks for a snapshot after every append. A snapshot is a
|
|
2577
|
+
* `SearchStateView` that reads the live indexes, so taking one costs nothing.
|
|
2578
|
+
* The view is valid until the state applies its next entry; a read after that
|
|
2579
|
+
* throws rather than returning numbers from a later ledger position. Read the
|
|
2580
|
+
* ledger's state again to see newer entries. The audit, header, head and claim
|
|
2581
|
+
* on a view are plain values and stay readable.
|
|
2582
|
+
*/
|
|
2583
|
+
/** Deterministic node id: identical content in one search is one node. */
|
|
2584
|
+
function searchNodeId(searchId, artifactDigest) {
|
|
2585
|
+
return `node_${shortDigest([searchId, artifactDigest])}`;
|
|
2586
|
+
}
|
|
2587
|
+
/** Deterministic cell id: one node on one task in one split at one repeat. */
|
|
2588
|
+
function searchCellId(searchId, nodeId, taskId, split, rep) {
|
|
2589
|
+
return `cell_${shortDigest([
|
|
2590
|
+
searchId,
|
|
2591
|
+
nodeId,
|
|
2592
|
+
taskId,
|
|
2593
|
+
split,
|
|
2594
|
+
rep
|
|
2595
|
+
])}`;
|
|
2596
|
+
}
|
|
2597
|
+
/** Digest a split's task list in taskId order. */
|
|
2598
|
+
function searchTaskSetDigest(tasks) {
|
|
2599
|
+
return hashCanonical([...tasks].sort((left, right) => compareCodeUnits(left.taskId, right.taskId)));
|
|
2600
|
+
}
|
|
2601
|
+
/** 128 bits of SHA-256 over the canonical encoding: collision-safe and short
|
|
2602
|
+
* enough for directory names. */
|
|
2603
|
+
function shortDigest(value) {
|
|
2604
|
+
return createHash("sha256").update(canonicalString(value)).digest("hex").slice(0, 32);
|
|
2605
|
+
}
|
|
2606
|
+
const MAX_LABEL_CHARS$1 = 200;
|
|
2607
|
+
const USD_TOLERANCE = 1e-9;
|
|
2608
|
+
const SPLITS = [
|
|
2609
|
+
"train",
|
|
2610
|
+
"selection",
|
|
2611
|
+
"test"
|
|
2612
|
+
];
|
|
2613
|
+
/**
|
|
2614
|
+
* Per-unit means of scored cells, in unitId order. Each unit sums its cells in
|
|
2615
|
+
* cellId order, so the result depends on the set of cells alone, never on the
|
|
2616
|
+
* order they settled in: a verifier that reads the same cells from a database
|
|
2617
|
+
* gets the same bits.
|
|
2618
|
+
*/
|
|
2619
|
+
function searchUnitScores(cells) {
|
|
2620
|
+
const byUnit = /* @__PURE__ */ new Map();
|
|
2621
|
+
for (const cell of cells) {
|
|
2622
|
+
const unit = byUnit.get(cell.unitId);
|
|
2623
|
+
if (unit) unit.push(cell);
|
|
2624
|
+
else byUnit.set(cell.unitId, [cell]);
|
|
2625
|
+
}
|
|
2626
|
+
return [...byUnit.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([unitId, unitCells]) => {
|
|
2627
|
+
let sum = 0;
|
|
2628
|
+
for (const cell of unitCells.sort((left, right) => compareCodeUnits(left.cellId, right.cellId))) sum += cell.score;
|
|
2629
|
+
return {
|
|
2630
|
+
unitId,
|
|
2631
|
+
sum,
|
|
2632
|
+
count: unitCells.length,
|
|
2633
|
+
mean: sum / unitCells.length
|
|
2634
|
+
};
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* The pure, incremental projection of one search ledger. `apply` refuses any
|
|
2639
|
+
* entry that breaks an invariant; the journal then discards this instance and
|
|
2640
|
+
* replays into a new one, so a refused entry leaves no partial state behind.
|
|
2641
|
+
*/
|
|
2642
|
+
var SearchState = class {
|
|
2643
|
+
searchId;
|
|
2644
|
+
version = 0;
|
|
2645
|
+
header = null;
|
|
2646
|
+
closed = null;
|
|
2647
|
+
head = null;
|
|
2648
|
+
lastOccurredAt = Number.NEGATIVE_INFINITY;
|
|
2649
|
+
lastOccurredAtText = null;
|
|
2650
|
+
tasks = /* @__PURE__ */ new Map();
|
|
2651
|
+
nodes = /* @__PURE__ */ new Map();
|
|
2652
|
+
nodeOrder = [];
|
|
2653
|
+
nodeByDigest = /* @__PURE__ */ new Map();
|
|
2654
|
+
edges = /* @__PURE__ */ new Map();
|
|
2655
|
+
edgeOrder = [];
|
|
2656
|
+
cells = /* @__PURE__ */ new Map();
|
|
2657
|
+
operations = /* @__PURE__ */ new Map();
|
|
2658
|
+
selectedNodeId = null;
|
|
2659
|
+
claimUsedUsd = 0;
|
|
2660
|
+
audit;
|
|
2661
|
+
constructor(searchId) {
|
|
2662
|
+
this.searchId = searchId;
|
|
2663
|
+
this.audit = {
|
|
2664
|
+
searchId,
|
|
2665
|
+
eventCount: 0,
|
|
2666
|
+
headHash: null,
|
|
2667
|
+
status: "open",
|
|
2668
|
+
closeReason: null,
|
|
2669
|
+
nodes: 0,
|
|
2670
|
+
nodesWithoutEdge: 0,
|
|
2671
|
+
undecidedNodes: 0,
|
|
2672
|
+
selectedNodeId: null,
|
|
2673
|
+
edges: {
|
|
2674
|
+
explicit: 0,
|
|
2675
|
+
correlated: 0,
|
|
2676
|
+
unknown: 0,
|
|
2677
|
+
reproposals: 0
|
|
2678
|
+
},
|
|
2679
|
+
cells: {
|
|
2680
|
+
allocated: 0,
|
|
2681
|
+
settled: 0,
|
|
2682
|
+
cancelled: 0,
|
|
2683
|
+
open: 0
|
|
2684
|
+
},
|
|
2685
|
+
attempts: 0,
|
|
2686
|
+
outcomes: {
|
|
2687
|
+
passed: 0,
|
|
2688
|
+
failed: 0,
|
|
2689
|
+
errored: 0
|
|
2690
|
+
},
|
|
2691
|
+
operations: {
|
|
2692
|
+
started: 0,
|
|
2693
|
+
recorded: 0,
|
|
2694
|
+
open: 0
|
|
2695
|
+
},
|
|
2696
|
+
spend: {
|
|
2697
|
+
knownUsd: 0,
|
|
2698
|
+
floorUsd: 0,
|
|
2699
|
+
unknownCostCells: 0,
|
|
2700
|
+
unknownCostOperations: 0,
|
|
2701
|
+
committedUsd: 0,
|
|
2702
|
+
openReservationUsd: 0,
|
|
2703
|
+
overspendUsd: 0,
|
|
2704
|
+
unreservedCells: 0,
|
|
2705
|
+
boxMinutes: 0
|
|
2706
|
+
},
|
|
2707
|
+
tokens: {
|
|
2708
|
+
inputTokens: 0,
|
|
2709
|
+
outputTokens: 0,
|
|
2710
|
+
cachedTokens: 0,
|
|
2711
|
+
unknownTokenAttempts: 0
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
apply(entry, index) {
|
|
2568
2716
|
const event = entry.event;
|
|
2569
|
-
if (
|
|
2717
|
+
if (this.closed) throw integrity(`event ${event.eventId} follows search-closed ${this.closed.eventId}`);
|
|
2570
2718
|
const occurredAt = Date.parse(event.occurredAt);
|
|
2571
|
-
if (occurredAt <
|
|
2572
|
-
previousOccurredAt = occurredAt;
|
|
2719
|
+
if (occurredAt < this.lastOccurredAt) throw integrity(`event ${event.eventId} occurred before the preceding event`);
|
|
2573
2720
|
assertUnique(event.artifacts.map(artifactKey), "artifact receipt", event.eventId);
|
|
2574
|
-
if (event.kind === "search-
|
|
2575
|
-
if (index !== 0 ||
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2721
|
+
if (event.kind === "search-opened") {
|
|
2722
|
+
if (index !== 0 || this.header) throw integrity("search-opened must be the first and only opening event");
|
|
2723
|
+
this.open(event);
|
|
2724
|
+
} else {
|
|
2725
|
+
if (!this.header) throw integrity(`event ${event.eventId} appears before search-opened`);
|
|
2726
|
+
this.applyEvent(event, index);
|
|
2727
|
+
}
|
|
2728
|
+
this.lastOccurredAt = occurredAt;
|
|
2729
|
+
this.lastOccurredAtText = event.occurredAt;
|
|
2730
|
+
this.head = {
|
|
2731
|
+
sequence: index,
|
|
2732
|
+
entryHash: entry.entryHash
|
|
2733
|
+
};
|
|
2734
|
+
this.audit.eventCount = index + 1;
|
|
2735
|
+
this.audit.headHash = entry.entryHash;
|
|
2736
|
+
this.version += 1;
|
|
2737
|
+
}
|
|
2738
|
+
snapshot() {
|
|
2739
|
+
return new SearchStateView(this, this.version);
|
|
2740
|
+
}
|
|
2741
|
+
/** @internal Views check this before every read of the live indexes. */
|
|
2742
|
+
isAt(version) {
|
|
2743
|
+
return this.version === version;
|
|
2744
|
+
}
|
|
2745
|
+
applyEvent(event, index) {
|
|
2746
|
+
switch (event.kind) {
|
|
2747
|
+
case "operation-started":
|
|
2748
|
+
this.startOperation(event);
|
|
2749
|
+
return;
|
|
2750
|
+
case "operation-recorded":
|
|
2751
|
+
this.recordOperation(event);
|
|
2752
|
+
return;
|
|
2753
|
+
case "node-registered":
|
|
2754
|
+
this.registerNode(event, index);
|
|
2755
|
+
return;
|
|
2756
|
+
case "edge-recorded":
|
|
2757
|
+
this.recordEdge(event, index);
|
|
2758
|
+
return;
|
|
2759
|
+
case "cell-allocated":
|
|
2760
|
+
this.allocateCell(event, index);
|
|
2761
|
+
return;
|
|
2762
|
+
case "cell-settled":
|
|
2763
|
+
this.settleCell(event, index);
|
|
2764
|
+
return;
|
|
2765
|
+
case "cell-cancelled":
|
|
2766
|
+
this.cancelCell(event, index);
|
|
2767
|
+
return;
|
|
2768
|
+
case "node-decided":
|
|
2769
|
+
this.decideNode(event, index);
|
|
2770
|
+
return;
|
|
2771
|
+
case "search-closed":
|
|
2772
|
+
this.close(event);
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
open(event) {
|
|
2777
|
+
if (event.containment?.searchId === this.searchId) throw integrity("a search cannot contain itself");
|
|
2778
|
+
if (event.derivedFrom?.searchId === this.searchId) throw integrity("a search cannot derive from itself");
|
|
2779
|
+
const units = {
|
|
2780
|
+
train: /* @__PURE__ */ new Set(),
|
|
2781
|
+
selection: /* @__PURE__ */ new Set(),
|
|
2782
|
+
test: /* @__PURE__ */ new Set()
|
|
2783
|
+
};
|
|
2784
|
+
for (const split of SPLITS) {
|
|
2785
|
+
const { tasks, taskSetDigest } = event.splits[split];
|
|
2786
|
+
if (searchTaskSetDigest(tasks) !== taskSetDigest) throw integrity(`the ${split} split's taskSetDigest does not match its tasks`);
|
|
2787
|
+
for (const task of tasks) {
|
|
2788
|
+
const owner = this.tasks.get(task.taskId);
|
|
2789
|
+
if (owner) throw integrity(`task ${task.taskId} is in both the ${owner.split} and ${split} splits`);
|
|
2790
|
+
this.tasks.set(task.taskId, {
|
|
2791
|
+
split,
|
|
2792
|
+
unitId: task.unitId
|
|
2793
|
+
});
|
|
2794
|
+
units[split].add(task.unitId);
|
|
2583
2795
|
}
|
|
2584
|
-
planEvent = event;
|
|
2585
|
-
return;
|
|
2586
2796
|
}
|
|
2587
|
-
if (
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2797
|
+
if (event.splits.heldOutUnits) {
|
|
2798
|
+
const seen = [...units.test].filter((unit) => units.train.has(unit) || units.selection.has(unit));
|
|
2799
|
+
if (seen.length > 0) throw integrity(`test units ${seen.join(", ")} also appear in train or selection, but the splits declare heldOutUnits`);
|
|
2800
|
+
}
|
|
2801
|
+
const { budget } = event;
|
|
2802
|
+
if (budget.maxUsd !== null && budget.reservedClaimUsd > budget.maxUsd + USD_TOLERANCE) throw integrity(`the claim reserve $${budget.reservedClaimUsd} exceeds the search cap $${budget.maxUsd}`);
|
|
2803
|
+
this.header = event;
|
|
2804
|
+
}
|
|
2805
|
+
startOperation(event) {
|
|
2806
|
+
if (this.operations.has(event.operationId)) throw integrity(`operation ${event.operationId} was started twice`);
|
|
2807
|
+
this.admit(event.reservation, false, `operation ${event.operationId}`);
|
|
2808
|
+
this.operations.set(event.operationId, {
|
|
2809
|
+
operationId: event.operationId,
|
|
2810
|
+
operationKind: event.operationKind,
|
|
2811
|
+
reservation: event.reservation,
|
|
2812
|
+
recorded: false,
|
|
2813
|
+
spentUsd: 0
|
|
2814
|
+
});
|
|
2815
|
+
this.audit.operations.started += 1;
|
|
2816
|
+
this.audit.operations.open += 1;
|
|
2817
|
+
this.audit.spend.openReservationUsd += event.reservation?.usd ?? 0;
|
|
2818
|
+
}
|
|
2819
|
+
recordOperation(event) {
|
|
2820
|
+
const operation = this.operations.get(event.operationId);
|
|
2821
|
+
if (!operation) throw integrity(`operation ${event.operationId} was recorded before it started`);
|
|
2822
|
+
if (operation.recorded) throw integrity(`operation ${event.operationId} was recorded twice`);
|
|
2823
|
+
if (operation.operationKind !== event.operationKind) throw integrity(`operation ${event.operationId} started as ${operation.operationKind} but recorded as ${event.operationKind}`);
|
|
2824
|
+
const cost = this.book(event.accounting);
|
|
2825
|
+
if (cost.unknown) this.audit.spend.unknownCostOperations += 1;
|
|
2826
|
+
const reserved = operation.reservation?.usd ?? 0;
|
|
2827
|
+
this.audit.spend.openReservationUsd -= reserved;
|
|
2828
|
+
if (operation.reservation) this.audit.spend.overspendUsd += Math.max(0, cost.usd - reserved);
|
|
2829
|
+
operation.recorded = true;
|
|
2830
|
+
operation.spentUsd = cost.usd;
|
|
2831
|
+
this.audit.operations.recorded += 1;
|
|
2832
|
+
this.audit.operations.open -= 1;
|
|
2833
|
+
}
|
|
2834
|
+
registerNode(event, index) {
|
|
2835
|
+
const expected = searchNodeId(this.searchId, event.artifactDigest);
|
|
2836
|
+
if (event.nodeId !== expected) throw integrity(`node ${event.nodeId} does not match its artifact digest (expected ${expected})`);
|
|
2837
|
+
if (this.nodes.has(event.nodeId)) throw integrity(`node ${event.nodeId} was registered twice; a re-proposal is a second edge into it`);
|
|
2838
|
+
assertUnique(event.surfaces.map((surface) => surface.surfaceId), "surfaceId", event.eventId);
|
|
2839
|
+
const maxNodes = this.header.budget.maxNodes;
|
|
2840
|
+
if (maxNodes !== null && this.nodes.size + 1 > maxNodes) throw integrity(`node ${event.nodeId} exceeds the search's maxNodes ${maxNodes}`);
|
|
2841
|
+
this.nodes.set(event.nodeId, {
|
|
2842
|
+
event,
|
|
2843
|
+
ordinal: this.nodeOrder.length,
|
|
2844
|
+
registeredSequence: index,
|
|
2845
|
+
updatedSequence: index,
|
|
2846
|
+
edgeIds: [],
|
|
2847
|
+
parents: [],
|
|
2848
|
+
parentKeys: /* @__PURE__ */ new Set(),
|
|
2849
|
+
primaryParentId: null,
|
|
2850
|
+
depth: null,
|
|
2851
|
+
children: [],
|
|
2852
|
+
decisions: [],
|
|
2853
|
+
finalist: false,
|
|
2854
|
+
cellIds: [],
|
|
2855
|
+
scoredCells: 0,
|
|
2856
|
+
spend: {
|
|
2857
|
+
knownUsd: 0,
|
|
2858
|
+
floorUsd: 0,
|
|
2859
|
+
unknownCostCells: 0,
|
|
2860
|
+
boxMinutes: 0
|
|
2594
2861
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2862
|
+
});
|
|
2863
|
+
this.nodeOrder.push(event.nodeId);
|
|
2864
|
+
this.nodeByDigest.set(event.artifactDigest, event.nodeId);
|
|
2865
|
+
this.audit.nodes += 1;
|
|
2866
|
+
this.audit.nodesWithoutEdge += 1;
|
|
2867
|
+
this.audit.undecidedNodes += 1;
|
|
2868
|
+
}
|
|
2869
|
+
recordEdge(event, index) {
|
|
2870
|
+
if (this.edges.has(event.edgeId)) throw integrity(`edge ${event.edgeId} was recorded twice`);
|
|
2871
|
+
const child = this.nodes.get(event.childNodeId);
|
|
2872
|
+
if (!child) throw integrity(`edge ${event.edgeId} names unregistered child ${event.childNodeId}`);
|
|
2873
|
+
if (event.label.length > MAX_LABEL_CHARS$1) throw integrity(`edge ${event.edgeId} label exceeds ${MAX_LABEL_CHARS$1} characters`);
|
|
2874
|
+
if (event.diffs.length !== event.parents.length) throw integrity(`edge ${event.edgeId} needs one diff per parent`);
|
|
2875
|
+
assertUnique(event.parents.map((parent) => canonicalString(parent)), "parent", event.eventId);
|
|
2876
|
+
const { operator, attribution, parents } = event;
|
|
2877
|
+
if (operator === "seed" !== (event.proposer === null)) throw integrity(`edge ${event.edgeId}: exactly the seed edge has no proposer`);
|
|
2878
|
+
if (operator === "seed" && (parents.length > 0 || attribution === "unknown")) throw integrity(`seed edge ${event.edgeId} must have no parents and a known attribution`);
|
|
2879
|
+
if (attribution === "unknown" && parents.length > 0) throw integrity(`edge ${event.edgeId} has unknown attribution but names parents`);
|
|
2880
|
+
if (operator !== "seed" && attribution !== "unknown" && parents.length === 0) throw integrity(`${operator} edge ${event.edgeId} names no parent`);
|
|
2881
|
+
if (operator === "merge" && attribution !== "unknown" && parents.length < 2) throw integrity(`merge edge ${event.edgeId} needs at least two parents`);
|
|
2882
|
+
const operationId = event.proposer?.operationId ?? null;
|
|
2883
|
+
if (operationId !== null && !this.operations.has(operationId)) throw integrity(`edge ${event.edgeId} names operation ${operationId}, which never started`);
|
|
2884
|
+
const inSearch = [];
|
|
2885
|
+
for (const parent of parents) {
|
|
2886
|
+
if (parent.searchId !== this.searchId) {
|
|
2887
|
+
const from = this.header.derivedFrom;
|
|
2888
|
+
if (operator !== "derive" || parents.length !== 1 || !from || from.searchId !== parent.searchId || from.nodeId !== parent.nodeId) throw integrity(`edge ${event.edgeId} names a parent in search ${parent.searchId}; only a derive edge matching derivedFrom may`);
|
|
2889
|
+
continue;
|
|
2599
2890
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2891
|
+
if (operator === "derive") throw integrity(`derive edge ${event.edgeId} must name the parent in derivedFrom`);
|
|
2892
|
+
const record = this.nodes.get(parent.nodeId);
|
|
2893
|
+
if (!record) throw integrity(`edge ${event.edgeId} names unregistered parent ${parent.nodeId}`);
|
|
2894
|
+
if (record.ordinal >= child.ordinal) throw integrity(`edge ${event.edgeId}: parent ${parent.nodeId} registered after child ${child.event.nodeId}`);
|
|
2895
|
+
if (record.edgeIds.length === 0) throw integrity(`edge ${event.edgeId}: parent ${parent.nodeId} has no edge of its own yet`);
|
|
2896
|
+
inSearch.push(record);
|
|
2602
2897
|
}
|
|
2603
|
-
if (
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
if (
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
assertUnique(event.lineage.parentCandidateIds, "parentCandidateId", event.eventId);
|
|
2616
|
-
const parents = event.lineage.parentCandidateIds.map((id) => {
|
|
2617
|
-
const parent = candidates.get(id);
|
|
2618
|
-
if (!parent) throw new SearchLedgerIntegrityError(`candidate ${event.candidateId} references unknown parent ${id}`);
|
|
2619
|
-
return parent;
|
|
2620
|
-
});
|
|
2621
|
-
const expectedGeneration = parents.length === 0 ? 0 : Math.max(...parents.map((parent) => parent.registered.lineage.generation)) + 1;
|
|
2622
|
-
if (event.lineage.generation !== expectedGeneration) throw new SearchLedgerIntegrityError(`candidate ${event.candidateId} generation ${event.lineage.generation} does not follow its parents (expected ${expectedGeneration})`);
|
|
2623
|
-
assertUnique(event.surfaces.map((surface) => surface.surfaceId), "surfaceId", event.eventId);
|
|
2624
|
-
candidates.set(event.candidateId, {
|
|
2625
|
-
registered: event,
|
|
2626
|
-
attempts: [],
|
|
2627
|
-
decision: null
|
|
2628
|
-
});
|
|
2629
|
-
candidateBySlot.set(event.slotId, event.candidateId);
|
|
2630
|
-
lineageNodes.set(event.lineage.lineageNodeId, event.candidateId);
|
|
2631
|
-
candidateEvents.push(event);
|
|
2632
|
-
return;
|
|
2898
|
+
if (child.edgeIds.length === 0) {
|
|
2899
|
+
const primary = inSearch[0];
|
|
2900
|
+
child.primaryParentId = primary?.event.nodeId ?? null;
|
|
2901
|
+
child.depth = operator === "seed" || operator === "derive" ? 0 : primary && primary.depth !== null ? primary.depth + 1 : null;
|
|
2902
|
+
this.audit.nodesWithoutEdge -= 1;
|
|
2903
|
+
} else this.audit.edges.reproposals += 1;
|
|
2904
|
+
child.edgeIds.push(event.edgeId);
|
|
2905
|
+
for (const parent of parents) {
|
|
2906
|
+
const key = canonicalString(parent);
|
|
2907
|
+
if (child.parentKeys.has(key)) continue;
|
|
2908
|
+
child.parentKeys.add(key);
|
|
2909
|
+
child.parents.push(parent);
|
|
2633
2910
|
}
|
|
2634
|
-
if (event.
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2911
|
+
for (const parent of inSearch) if (!parent.children.includes(child.event.nodeId)) parent.children.push(child.event.nodeId);
|
|
2912
|
+
child.updatedSequence = index;
|
|
2913
|
+
this.edges.set(event.edgeId, event);
|
|
2914
|
+
this.edgeOrder.push(event.edgeId);
|
|
2915
|
+
this.audit.edges[attribution] += 1;
|
|
2916
|
+
}
|
|
2917
|
+
allocateCell(event, index) {
|
|
2918
|
+
const expected = searchCellId(this.searchId, event.nodeId, event.taskId, event.split, event.rep);
|
|
2919
|
+
if (event.cellId !== expected) throw integrity(`cell ${event.cellId} does not match its coordinates (expected ${expected})`);
|
|
2920
|
+
if (this.cells.has(event.cellId)) throw integrity(`cell ${event.cellId} was allocated twice`);
|
|
2921
|
+
const node = this.nodes.get(event.nodeId);
|
|
2922
|
+
if (!node) throw integrity(`cell ${event.cellId} names unregistered node ${event.nodeId}`);
|
|
2923
|
+
if (node.edgeIds.length === 0) throw integrity(`cell ${event.cellId}: node ${event.nodeId} has no edge yet`);
|
|
2924
|
+
const task = this.tasks.get(event.taskId);
|
|
2925
|
+
if (!task || task.split !== event.split) throw integrity(`cell ${event.cellId}: task ${event.taskId} is not in the ${event.split} split`);
|
|
2926
|
+
if (task.unitId !== event.unitId) throw integrity(`cell ${event.cellId}: task ${event.taskId} samples unit ${task.unitId}`);
|
|
2927
|
+
assertStageSplit(event.cellId, event.stage, event.split, node.ordinal === 0);
|
|
2928
|
+
if (event.split === "test" && node.ordinal !== 0 && !node.finalist) throw integrity(`cell ${event.cellId}: the test split is sealed; only the root and finalists run on it`);
|
|
2929
|
+
const maxCells = this.header.budget.maxCells;
|
|
2930
|
+
if (maxCells !== null && this.audit.cells.allocated + 1 > maxCells) throw integrity(`cell ${event.cellId} exceeds the search's maxCells ${maxCells}`);
|
|
2931
|
+
this.admit(event.reservation, event.stage === "claim", `cell ${event.cellId}`);
|
|
2932
|
+
this.cells.set(event.cellId, {
|
|
2933
|
+
cellId: event.cellId,
|
|
2934
|
+
nodeId: event.nodeId,
|
|
2935
|
+
taskId: event.taskId,
|
|
2936
|
+
unitId: event.unitId,
|
|
2937
|
+
split: event.split,
|
|
2938
|
+
rep: event.rep,
|
|
2939
|
+
stage: event.stage,
|
|
2940
|
+
lane: event.lane,
|
|
2941
|
+
reservation: event.reservation,
|
|
2942
|
+
attempts: 0,
|
|
2943
|
+
lastRunId: null,
|
|
2944
|
+
outcome: null,
|
|
2945
|
+
score: null,
|
|
2946
|
+
final: false,
|
|
2947
|
+
cancelled: null,
|
|
2948
|
+
spentUsd: 0,
|
|
2949
|
+
costKnown: true,
|
|
2950
|
+
traceId: null,
|
|
2951
|
+
allocatedSequence: index,
|
|
2952
|
+
updatedSequence: index
|
|
2953
|
+
});
|
|
2954
|
+
node.cellIds.push(event.cellId);
|
|
2955
|
+
node.updatedSequence = index;
|
|
2956
|
+
this.audit.cells.allocated += 1;
|
|
2957
|
+
this.audit.cells.open += 1;
|
|
2958
|
+
this.audit.spend.openReservationUsd += event.reservation?.usd ?? 0;
|
|
2959
|
+
if (event.stage === "claim") this.claimUsedUsd += event.reservation?.usd ?? 0;
|
|
2960
|
+
}
|
|
2961
|
+
settleCell(event, index) {
|
|
2962
|
+
const cell = this.cells.get(event.cellId);
|
|
2963
|
+
if (!cell) throw integrity(`attempt ${event.eventId} names unallocated cell ${event.cellId}`);
|
|
2964
|
+
if (cell.cancelled) throw integrity(`cell ${event.cellId} was settled after it was cancelled`);
|
|
2965
|
+
if (cell.final) throw integrity(`cell ${event.cellId} was attempted again after a final ${cell.outcome} outcome; only a retryable error admits another attempt`);
|
|
2966
|
+
if (event.attempt !== cell.attempts + 1) throw integrity(`cell ${event.cellId} attempt ${event.attempt} is not contiguous (expected ${cell.attempts + 1})`);
|
|
2967
|
+
const runId = searchCellRunId({
|
|
2968
|
+
cellId: event.cellId,
|
|
2969
|
+
attempt: event.attempt
|
|
2970
|
+
});
|
|
2971
|
+
if (event.runId !== runId) throw integrity(`cell ${event.cellId} attempt ${event.attempt} must have runId ${runId}`);
|
|
2972
|
+
const node = this.nodes.get(cell.nodeId);
|
|
2973
|
+
const declared = new Set(node.event.surfaces.map((surface) => surface.surfaceId));
|
|
2974
|
+
assertUnique(event.surfaceEvidence.map((evidence) => evidence.surfaceId), "surface evidence", event.eventId);
|
|
2975
|
+
for (const evidence of event.surfaceEvidence) if (!declared.has(evidence.surfaceId)) throw integrity(`attempt ${event.eventId} reports surface ${evidence.surfaceId}, which node ${cell.nodeId} does not declare`);
|
|
2976
|
+
if (event.artifacts.filter((artifact) => artifact.role === "run-record").length > 1) throw integrity(`attempt ${event.eventId} binds more than one RunRecord`);
|
|
2977
|
+
const before = this.reservationParts(cell);
|
|
2978
|
+
const cost = this.book(event.accounting);
|
|
2979
|
+
if (cell.attempts === 0) {
|
|
2980
|
+
this.audit.cells.settled += 1;
|
|
2981
|
+
this.audit.cells.open -= 1;
|
|
2982
|
+
if (cell.reservation === null) this.audit.spend.unreservedCells += 1;
|
|
2670
2983
|
}
|
|
2671
|
-
if (
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
if (operationsById.has(event.operationId)) throw new SearchLedgerIntegrityError(`operation ${event.operationId} was recorded twice`);
|
|
2676
|
-
operationsById.set(event.operationId, event);
|
|
2677
|
-
operationEvents.push(event);
|
|
2678
|
-
return;
|
|
2984
|
+
if (cost.unknown && cell.costKnown) {
|
|
2985
|
+
cell.costKnown = false;
|
|
2986
|
+
this.audit.spend.unknownCostCells += 1;
|
|
2987
|
+
node.spend.unknownCostCells += 1;
|
|
2679
2988
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
return;
|
|
2989
|
+
cell.attempts = event.attempt;
|
|
2990
|
+
cell.lastRunId = runId;
|
|
2991
|
+
cell.outcome = event.outcome.status;
|
|
2992
|
+
cell.spentUsd += cost.usd;
|
|
2993
|
+
cell.updatedSequence = index;
|
|
2994
|
+
if ("traceId" in event.traceRef) cell.traceId = event.traceRef.traceId;
|
|
2995
|
+
if (event.outcome.status === "errored") cell.final = !event.outcome.error.retryable;
|
|
2996
|
+
else {
|
|
2997
|
+
cell.final = true;
|
|
2998
|
+
cell.score = event.outcome.score;
|
|
2999
|
+
node.scoredCells += 1;
|
|
2692
3000
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3001
|
+
const after = this.reservationParts(cell);
|
|
3002
|
+
this.audit.spend.openReservationUsd += after.open - before.open;
|
|
3003
|
+
this.audit.spend.overspendUsd += after.over - before.over;
|
|
3004
|
+
if (cell.stage === "claim") this.claimUsedUsd += after.used - before.used;
|
|
3005
|
+
if (cost.unknown) node.spend.floorUsd += cost.usd;
|
|
3006
|
+
else node.spend.knownUsd += cost.usd;
|
|
3007
|
+
const boxMinutes = event.boxMinutes ?? 0;
|
|
3008
|
+
node.spend.boxMinutes += boxMinutes;
|
|
3009
|
+
this.audit.spend.boxMinutes += boxMinutes;
|
|
3010
|
+
node.updatedSequence = index;
|
|
3011
|
+
this.audit.attempts += 1;
|
|
3012
|
+
this.audit.outcomes[event.outcome.status] += 1;
|
|
3013
|
+
}
|
|
3014
|
+
cancelCell(event, index) {
|
|
3015
|
+
const cell = this.cells.get(event.cellId);
|
|
3016
|
+
if (!cell) throw integrity(`cancellation ${event.eventId} names unallocated cell ${event.cellId}`);
|
|
3017
|
+
if (cell.cancelled) throw integrity(`cell ${event.cellId} was cancelled twice`);
|
|
3018
|
+
if (cell.final) throw integrity(`cell ${event.cellId} was cancelled after a final outcome`);
|
|
3019
|
+
const before = this.reservationParts(cell);
|
|
3020
|
+
cell.cancelled = event.reason;
|
|
3021
|
+
cell.updatedSequence = index;
|
|
3022
|
+
const after = this.reservationParts(cell);
|
|
3023
|
+
this.audit.spend.openReservationUsd += after.open - before.open;
|
|
3024
|
+
this.audit.spend.overspendUsd += after.over - before.over;
|
|
3025
|
+
if (cell.stage === "claim") this.claimUsedUsd += after.used - before.used;
|
|
3026
|
+
if (cell.attempts === 0) {
|
|
3027
|
+
this.audit.cells.cancelled += 1;
|
|
3028
|
+
this.audit.cells.open -= 1;
|
|
2704
3029
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if (
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
if (slots.length === 0) continue;
|
|
2716
|
-
const registeredCount = slots.filter((slot) => candidateBySlot.has(slot.slotId)).length;
|
|
2717
|
-
const closedCount = slots.filter((slot) => closedSlots.has(slot.slotId)).length;
|
|
2718
|
-
if (generationOutcome === "completed" && closedCount > 0) throw new SearchLedgerIntegrityError(`completed generation operation ${operation.operationId} contains ${closedCount} closed slot(s)`);
|
|
2719
|
-
if (generationOutcome === "failed" && registeredCount > 0) throw new SearchLedgerIntegrityError(`failed generation operation ${operation.operationId} contains ${registeredCount} registered candidate(s)`);
|
|
2720
|
-
if (generationOutcome === "partial" && (registeredCount === 0 || closedCount === 0)) throw new SearchLedgerIntegrityError(`partial generation operation ${operation.operationId} must contain both a registered candidate and a closed slot`);
|
|
3030
|
+
this.nodes.get(cell.nodeId).updatedSequence = index;
|
|
3031
|
+
}
|
|
3032
|
+
decideNode(event, index) {
|
|
3033
|
+
const node = this.nodes.get(event.nodeId);
|
|
3034
|
+
if (!node) throw integrity(`decision ${event.eventId} names unregistered node ${event.nodeId}`);
|
|
3035
|
+
if (node.edgeIds.length === 0) throw integrity(`decision ${event.eventId}: node ${event.nodeId} has no edge yet`);
|
|
3036
|
+
const status = event.decision.status;
|
|
3037
|
+
if (status === "selected") {
|
|
3038
|
+
if (this.selectedNodeId !== null && this.selectedNodeId !== event.nodeId) throw integrity(`node ${event.nodeId} was selected while ${this.selectedNodeId} is still selected`);
|
|
3039
|
+
if (node.scoredCells === 0) throw integrity(`node ${event.nodeId} cannot be selected without a scored cell`);
|
|
2721
3040
|
}
|
|
2722
|
-
const
|
|
2723
|
-
|
|
2724
|
-
const
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3041
|
+
const previous = node.decisions.at(-1)?.decision.status ?? null;
|
|
3042
|
+
const wasUndecided = previous === null || previous === "advanced";
|
|
3043
|
+
const isUndecided = status === "advanced";
|
|
3044
|
+
this.audit.undecidedNodes += (isUndecided ? 1 : 0) - (wasUndecided ? 1 : 0);
|
|
3045
|
+
if (previous === "selected" && status !== "selected") this.selectedNodeId = null;
|
|
3046
|
+
if (status === "selected") this.selectedNodeId = event.nodeId;
|
|
3047
|
+
if (status === "finalist") node.finalist = true;
|
|
3048
|
+
node.decisions.push({
|
|
3049
|
+
sequence: index,
|
|
3050
|
+
decision: event.decision,
|
|
3051
|
+
basis: event.basis,
|
|
3052
|
+
rule: event.rule,
|
|
3053
|
+
reason: event.reason
|
|
3054
|
+
});
|
|
3055
|
+
node.updatedSequence = index;
|
|
3056
|
+
this.audit.selectedNodeId = this.selectedNodeId;
|
|
3057
|
+
}
|
|
3058
|
+
close(event) {
|
|
3059
|
+
const reasons = this.incompleteReasons();
|
|
3060
|
+
if (reasons.length > 0) throw integrity(`search-closed while incomplete: ${reasons.join("; ")}`);
|
|
3061
|
+
if (event.claim) this.checkClaim(event.claim);
|
|
3062
|
+
this.closed = event;
|
|
3063
|
+
this.audit.status = "closed";
|
|
3064
|
+
this.audit.closeReason = event.reason;
|
|
3065
|
+
this.audit.spend.openReservationUsd = 0;
|
|
3066
|
+
}
|
|
3067
|
+
checkClaim(claim) {
|
|
3068
|
+
if (claim.finalists.length > 3) throw integrity(`the claim names ${claim.finalists.length} finalists; at most 3 are allowed`);
|
|
3069
|
+
assertUnique(claim.finalists.map((finalist) => finalist.nodeId), "claim finalist", "search-closed");
|
|
3070
|
+
for (const finalist of claim.finalists) if (!this.nodes.get(finalist.nodeId)?.finalist) throw integrity(`claim finalist ${finalist.nodeId} was never decided finalist`);
|
|
3071
|
+
if (claim.selected !== this.selectedNodeId) throw integrity(`the claim selects ${claim.selected ?? "nothing"} but the selected node is ${this.selectedNodeId ?? "none"}`);
|
|
3072
|
+
const root = this.nodeOrder[0] ?? null;
|
|
3073
|
+
if (claim.decision === "ship") {
|
|
3074
|
+
const shipped = claim.finalists.find((finalist) => finalist.nodeId === claim.selected);
|
|
3075
|
+
if (claim.selected === null || claim.selected === root || !shipped?.promote) throw integrity("a ship claim must select a finalist that the paired test promoted");
|
|
3076
|
+
if (!this.header.splits.heldOutUnits) throw integrity("a ship claim needs test units held out from train and selection");
|
|
3077
|
+
} else if (claim.selected !== null && claim.selected !== root) throw integrity(`a ${claim.decision} claim keeps the root; it cannot select another node`);
|
|
3078
|
+
if (claim.decision === "test-cannot-resolve") {
|
|
3079
|
+
for (const cell of this.cells.values()) if (cell.split === "test" && cell.attempts > 0) throw integrity("a test-cannot-resolve claim spends nothing on test cells");
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
/** @internal */
|
|
3083
|
+
incompleteReasons() {
|
|
3084
|
+
const reasons = [];
|
|
3085
|
+
if (!this.header) reasons.push("search-opened is missing");
|
|
3086
|
+
if (this.audit.cells.open > 0) reasons.push(count(this.audit.cells.open, "cell", "has no outcome"));
|
|
3087
|
+
if (this.audit.operations.open > 0) reasons.push(count(this.audit.operations.open, "operation", "is unrecorded"));
|
|
3088
|
+
if (this.audit.nodesWithoutEdge > 0) reasons.push(count(this.audit.nodesWithoutEdge, "node", "has no edge"));
|
|
3089
|
+
if (this.audit.undecidedNodes > 0) reasons.push(count(this.audit.undecidedNodes, "node", "has no terminal decision"));
|
|
3090
|
+
return reasons;
|
|
3091
|
+
}
|
|
3092
|
+
/** Book an attempt's or operation's accounting into the running totals. An
|
|
3093
|
+
* unknown cost counts as its proven floor. */
|
|
3094
|
+
book(accounting) {
|
|
3095
|
+
const { tokens, cost } = accounting;
|
|
3096
|
+
if (tokens.status === "known") {
|
|
3097
|
+
this.audit.tokens.inputTokens += tokens.inputTokens;
|
|
3098
|
+
this.audit.tokens.outputTokens += tokens.outputTokens;
|
|
3099
|
+
this.audit.tokens.cachedTokens += tokens.cachedTokens;
|
|
3100
|
+
} else this.audit.tokens.unknownTokenAttempts += 1;
|
|
3101
|
+
const usd = cost.status === "known" ? cost.usd : cost.knownLowerBoundUsd;
|
|
3102
|
+
if (cost.status === "known") this.audit.spend.knownUsd += usd;
|
|
3103
|
+
else this.audit.spend.floorUsd += usd;
|
|
3104
|
+
this.audit.spend.committedUsd += usd;
|
|
3105
|
+
return {
|
|
3106
|
+
usd,
|
|
3107
|
+
unknown: cost.status === "unknown"
|
|
2737
3108
|
};
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3109
|
+
}
|
|
3110
|
+
/** A cell's share of the open reservations, its overspend, and what it draws
|
|
3111
|
+
* from the claim reserve: the larger of its hold and its spend. */
|
|
3112
|
+
reservationParts(cell) {
|
|
3113
|
+
const reserved = cell.reservation?.usd ?? 0;
|
|
3114
|
+
const holding = !cell.final && cell.cancelled === null;
|
|
3115
|
+
return {
|
|
3116
|
+
open: holding ? Math.max(0, reserved - cell.spentUsd) : 0,
|
|
3117
|
+
over: cell.reservation ? Math.max(0, cell.spentUsd - reserved) : 0,
|
|
3118
|
+
used: holding ? Math.max(reserved, cell.spentUsd) : cell.spentUsd
|
|
2742
3119
|
};
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
const
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
3120
|
+
}
|
|
3121
|
+
/** The admission rule: committed spend, open holds, the unspent claim
|
|
3122
|
+
* reserve and the new hold stay within the cap. A claim cell draws on the
|
|
3123
|
+
* claim reserve instead of adding to it. */
|
|
3124
|
+
admit(reservation, claim, subject) {
|
|
3125
|
+
const { maxUsd, reservedClaimUsd } = this.header.budget;
|
|
3126
|
+
if (maxUsd === null) return;
|
|
3127
|
+
const hold = reservation?.usd ?? 0;
|
|
3128
|
+
const remainingClaim = Math.max(0, reservedClaimUsd - this.claimUsedUsd);
|
|
3129
|
+
if (this.audit.spend.committedUsd + this.audit.spend.openReservationUsd + (claim ? Math.max(0, remainingClaim - hold) : remainingClaim) + hold > maxUsd + USD_TOLERANCE) throw integrity(`${subject} is not admissible: committed $${round(this.audit.spend.committedUsd)} + open $${round(this.audit.spend.openReservationUsd)} + claim reserve $${round(remainingClaim)} + hold $${round(hold)} exceeds the cap $${maxUsd}`);
|
|
3130
|
+
}
|
|
3131
|
+
/** @internal */
|
|
3132
|
+
readHeader() {
|
|
3133
|
+
return this.header;
|
|
3134
|
+
}
|
|
3135
|
+
/** @internal */
|
|
3136
|
+
readClosed() {
|
|
3137
|
+
return this.closed;
|
|
3138
|
+
}
|
|
3139
|
+
/** @internal */
|
|
3140
|
+
readHead() {
|
|
3141
|
+
return this.head;
|
|
3142
|
+
}
|
|
3143
|
+
/** @internal */
|
|
3144
|
+
readLastOccurredAt() {
|
|
3145
|
+
return this.lastOccurredAtText;
|
|
3146
|
+
}
|
|
3147
|
+
/** @internal */
|
|
3148
|
+
readAudit() {
|
|
3149
|
+
const audit = this.audit;
|
|
3150
|
+
return {
|
|
3151
|
+
...audit,
|
|
3152
|
+
edges: { ...audit.edges },
|
|
3153
|
+
cells: { ...audit.cells },
|
|
3154
|
+
outcomes: { ...audit.outcomes },
|
|
3155
|
+
operations: { ...audit.operations },
|
|
3156
|
+
spend: { ...audit.spend },
|
|
3157
|
+
tokens: { ...audit.tokens }
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
/** @internal */
|
|
3161
|
+
readNode(nodeId) {
|
|
3162
|
+
const record = this.nodes.get(nodeId);
|
|
3163
|
+
return record ? nodeView(record) : void 0;
|
|
3164
|
+
}
|
|
3165
|
+
/** @internal */
|
|
3166
|
+
readNodeIds() {
|
|
3167
|
+
return this.nodeOrder;
|
|
3168
|
+
}
|
|
3169
|
+
/** @internal */
|
|
3170
|
+
readHasNode(nodeId) {
|
|
3171
|
+
return this.nodes.has(nodeId);
|
|
3172
|
+
}
|
|
3173
|
+
/** @internal */
|
|
3174
|
+
readNodeIdForDigest(artifactDigest) {
|
|
3175
|
+
return this.nodeByDigest.get(artifactDigest);
|
|
3176
|
+
}
|
|
3177
|
+
/** @internal */
|
|
3178
|
+
readEdge(edgeId) {
|
|
3179
|
+
return this.edges.get(edgeId);
|
|
3180
|
+
}
|
|
3181
|
+
/** @internal */
|
|
3182
|
+
readEdgeIds() {
|
|
3183
|
+
return this.edgeOrder;
|
|
3184
|
+
}
|
|
3185
|
+
/** @internal */
|
|
3186
|
+
readCell(cellId) {
|
|
3187
|
+
const cell = this.cells.get(cellId);
|
|
3188
|
+
return cell ? { ...cell } : void 0;
|
|
3189
|
+
}
|
|
3190
|
+
/** @internal */
|
|
3191
|
+
readCellIds(nodeId) {
|
|
3192
|
+
if (nodeId === void 0) return [...this.cells.keys()];
|
|
3193
|
+
return this.nodes.get(nodeId)?.cellIds ?? [];
|
|
3194
|
+
}
|
|
3195
|
+
/** @internal */
|
|
3196
|
+
readOperation(operationId) {
|
|
3197
|
+
const operation = this.operations.get(operationId);
|
|
3198
|
+
return operation ? { ...operation } : void 0;
|
|
3199
|
+
}
|
|
3200
|
+
/** @internal */
|
|
3201
|
+
readScoredCells(nodeId, split) {
|
|
3202
|
+
const scored = [];
|
|
3203
|
+
for (const cellId of this.nodes.get(nodeId)?.cellIds ?? []) {
|
|
3204
|
+
const cell = this.cells.get(cellId);
|
|
3205
|
+
if (cell.split === split && cell.score !== null) scored.push({
|
|
3206
|
+
cellId,
|
|
3207
|
+
unitId: cell.unitId,
|
|
3208
|
+
attempt: cell.attempts,
|
|
3209
|
+
score: cell.score
|
|
3210
|
+
});
|
|
2762
3211
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
3212
|
+
return scored;
|
|
3213
|
+
}
|
|
3214
|
+
};
|
|
3215
|
+
/**
|
|
3216
|
+
* A read of `SearchState` at one ledger position. Header, head, audit and
|
|
3217
|
+
* claim are values. Node, edge, cell and score reads go to the live indexes and
|
|
3218
|
+
* throw once the state has applied a later entry.
|
|
3219
|
+
*/
|
|
3220
|
+
var SearchStateView = class {
|
|
3221
|
+
searchId;
|
|
3222
|
+
header;
|
|
3223
|
+
closed;
|
|
3224
|
+
head;
|
|
3225
|
+
/** `occurredAt` of the head entry; a writer stamps later events at or after it. */
|
|
3226
|
+
lastOccurredAt;
|
|
3227
|
+
audit;
|
|
3228
|
+
completion;
|
|
3229
|
+
state;
|
|
3230
|
+
version;
|
|
3231
|
+
constructor(state, version) {
|
|
3232
|
+
this.state = state;
|
|
3233
|
+
this.version = version;
|
|
3234
|
+
this.searchId = state.searchId;
|
|
3235
|
+
this.header = state.readHeader();
|
|
3236
|
+
this.closed = state.readClosed();
|
|
3237
|
+
this.head = state.readHead();
|
|
3238
|
+
this.lastOccurredAt = state.readLastOccurredAt();
|
|
3239
|
+
this.audit = state.readAudit();
|
|
3240
|
+
const reasons = state.incompleteReasons();
|
|
3241
|
+
if (!this.closed) reasons.unshift("search is open");
|
|
3242
|
+
this.completion = {
|
|
3243
|
+
complete: this.closed !== null,
|
|
3244
|
+
reasons
|
|
2777
3245
|
};
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3246
|
+
}
|
|
3247
|
+
/** The node the search started from: the first registered. */
|
|
3248
|
+
get rootNodeId() {
|
|
3249
|
+
return this.live().readNodeIds()[0] ?? null;
|
|
3250
|
+
}
|
|
3251
|
+
node(nodeId) {
|
|
3252
|
+
return this.live().readNode(nodeId);
|
|
3253
|
+
}
|
|
3254
|
+
hasNode(nodeId) {
|
|
3255
|
+
return this.live().readHasNode(nodeId);
|
|
3256
|
+
}
|
|
3257
|
+
/** Node ids in registration order. */
|
|
3258
|
+
nodeIds() {
|
|
3259
|
+
return [...this.live().readNodeIds()];
|
|
3260
|
+
}
|
|
3261
|
+
/** Nodes in registration order. */
|
|
3262
|
+
nodes() {
|
|
3263
|
+
const state = this.live();
|
|
3264
|
+
return state.readNodeIds().map((nodeId) => state.readNode(nodeId));
|
|
3265
|
+
}
|
|
3266
|
+
nodeIdForDigest(artifactDigest) {
|
|
3267
|
+
return this.live().readNodeIdForDigest(artifactDigest);
|
|
3268
|
+
}
|
|
3269
|
+
edge(edgeId) {
|
|
3270
|
+
return this.live().readEdge(edgeId);
|
|
3271
|
+
}
|
|
3272
|
+
/** Edges in ledger order. */
|
|
3273
|
+
edges() {
|
|
3274
|
+
const state = this.live();
|
|
3275
|
+
return state.readEdgeIds().map((edgeId) => state.readEdge(edgeId));
|
|
3276
|
+
}
|
|
3277
|
+
cell(cellId) {
|
|
3278
|
+
return this.live().readCell(cellId);
|
|
3279
|
+
}
|
|
3280
|
+
/** Cells in allocation order, optionally of one node. */
|
|
3281
|
+
cells(filter = {}) {
|
|
3282
|
+
const state = this.live();
|
|
3283
|
+
return state.readCellIds(filter.nodeId).map((cellId) => state.readCell(cellId));
|
|
3284
|
+
}
|
|
3285
|
+
operation(operationId) {
|
|
3286
|
+
return this.live().readOperation(operationId);
|
|
3287
|
+
}
|
|
3288
|
+
/** A node's scored cells on one split, in allocation order. Unscored cells
|
|
3289
|
+
* (errored, cancelled or in flight) are absent, never zero. */
|
|
3290
|
+
scoredCells(nodeId, split) {
|
|
3291
|
+
return this.live().readScoredCells(nodeId, split);
|
|
3292
|
+
}
|
|
3293
|
+
/** Per-unit score means of a node on one split, in unitId order. */
|
|
3294
|
+
unitScores(nodeId, split) {
|
|
3295
|
+
return searchUnitScores(this.scoredCells(nodeId, split));
|
|
3296
|
+
}
|
|
3297
|
+
/** The lineage a RunRecord mint needs for one cell attempt. */
|
|
3298
|
+
lineage(coordinates) {
|
|
3299
|
+
const state = this.live();
|
|
3300
|
+
if (coordinates.searchId !== this.searchId) throw new SearchLedgerError(`coordinates name search ${coordinates.searchId}, not ${this.searchId}`);
|
|
3301
|
+
const cell = state.readCell(coordinates.cellId);
|
|
3302
|
+
if (!cell || cell.nodeId !== coordinates.nodeId) throw new SearchLedgerError(`cell ${coordinates.cellId} of node ${coordinates.nodeId} is not in search ${this.searchId}`);
|
|
3303
|
+
if (coordinates.attempt < 1 || coordinates.attempt > cell.attempts) throw new SearchLedgerError(`cell ${coordinates.cellId} has ${cell.attempts} settled attempt(s), not attempt ${coordinates.attempt}`);
|
|
3304
|
+
const node = state.readNode(cell.nodeId);
|
|
3305
|
+
if (node.depth === null) throw new SearchLedgerError(`node ${node.nodeId} has unknown lineage, so its depth is unknown`);
|
|
3306
|
+
const containment = this.header?.containment ?? null;
|
|
2783
3307
|
return {
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
decisions: [...decisions],
|
|
2792
|
-
completion,
|
|
2793
|
-
audit: {
|
|
2794
|
-
campaignId,
|
|
2795
|
-
eventCount: entries.length,
|
|
2796
|
-
candidateCount: candidates.size,
|
|
2797
|
-
closedCandidateSlotCount: closedSlots.size,
|
|
2798
|
-
attemptCount: attempts.length,
|
|
2799
|
-
operationCount: operationEvents.length,
|
|
2800
|
-
outcomes: outcomeCounts,
|
|
2801
|
-
operationOutcomes: operationOutcomeCounts,
|
|
2802
|
-
decisions: {
|
|
2803
|
-
selected: selectedDecisions.length,
|
|
2804
|
-
rejected: rejectedDecisions.length,
|
|
2805
|
-
pending: candidates.size - decisions.length
|
|
2806
|
-
},
|
|
2807
|
-
expected: {
|
|
2808
|
-
candidateSlots: plannedSlots.size,
|
|
2809
|
-
taskOutcomes: candidates.size * (planEvent?.plan.tasks.length ?? 0),
|
|
2810
|
-
operations: plannedOperations.size,
|
|
2811
|
-
missingCandidateSlots,
|
|
2812
|
-
missingTaskOutcomes,
|
|
2813
|
-
missingOperations
|
|
2814
|
-
},
|
|
2815
|
-
status,
|
|
2816
|
-
selectedCandidateId,
|
|
2817
|
-
accounting,
|
|
2818
|
-
headHash: entries.at(-1)?.entryHash ?? null
|
|
2819
|
-
}
|
|
3308
|
+
depth: node.depth,
|
|
3309
|
+
ordinal: node.ordinal,
|
|
3310
|
+
rep: cell.rep,
|
|
3311
|
+
containingRunId: containment ? searchCellRunId({
|
|
3312
|
+
cellId: containment.cellId,
|
|
3313
|
+
attempt: containment.attempt
|
|
3314
|
+
}) : null
|
|
2820
3315
|
};
|
|
2821
|
-
}
|
|
3316
|
+
}
|
|
3317
|
+
live() {
|
|
3318
|
+
if (!this.state.isAt(this.version)) throw new SearchLedgerError(`this SearchStateView is from sequence ${this.head?.sequence ?? -1}, and the ledger has moved on; read the ledger's state again`);
|
|
3319
|
+
return this.state;
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
function nodeView(record) {
|
|
3323
|
+
const latest = record.decisions.at(-1)?.decision ?? null;
|
|
2822
3324
|
return {
|
|
2823
|
-
|
|
2824
|
-
|
|
3325
|
+
nodeId: record.event.nodeId,
|
|
3326
|
+
ordinal: record.ordinal,
|
|
3327
|
+
artifactDigest: record.event.artifactDigest,
|
|
3328
|
+
artifact: record.event.artifact,
|
|
3329
|
+
surfaces: record.event.surfaces,
|
|
3330
|
+
primaryParentId: record.primaryParentId,
|
|
3331
|
+
depth: record.depth,
|
|
3332
|
+
parents: [...record.parents],
|
|
3333
|
+
children: [...record.children],
|
|
3334
|
+
edgeIds: [...record.edgeIds],
|
|
3335
|
+
status: latest?.status ?? null,
|
|
3336
|
+
rung: latest?.status === "advanced" ? latest.rung : null,
|
|
3337
|
+
finalist: record.finalist,
|
|
3338
|
+
decisions: [...record.decisions],
|
|
3339
|
+
cellCount: record.cellIds.length,
|
|
3340
|
+
spend: { ...record.spend },
|
|
3341
|
+
registeredSequence: record.registeredSequence,
|
|
3342
|
+
updatedSequence: record.updatedSequence
|
|
2825
3343
|
};
|
|
2826
3344
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
for (const task of planEvent.plan.tasks) if (!candidate.attempts.some((attempt) => attempt.task.taskId === task.taskId && attempt.outcome.status !== "errored")) missing.push(`${slotId}/${task.taskId}`);
|
|
2838
|
-
}
|
|
2839
|
-
return missing;
|
|
3345
|
+
function assertStageSplit(cellId, stage, split, isRoot) {
|
|
3346
|
+
if (!{
|
|
3347
|
+
root: ["train", "selection"],
|
|
3348
|
+
train: ["train"],
|
|
3349
|
+
screen: ["selection"],
|
|
3350
|
+
rung: ["selection"],
|
|
3351
|
+
claim: ["test"],
|
|
3352
|
+
external: ["train", "selection"]
|
|
3353
|
+
}[stage].includes(split)) throw integrity(`cell ${cellId}: a ${stage} cell cannot run on the ${split} split`);
|
|
3354
|
+
if (stage === "root" && !isRoot) throw integrity(`cell ${cellId}: only the root node runs root cells`);
|
|
2840
3355
|
}
|
|
2841
3356
|
function assertUnique(values, label, eventId) {
|
|
2842
|
-
if (new Set(values).size !== values.length) throw
|
|
3357
|
+
if (new Set(values).size !== values.length) throw integrity(`event ${eventId} contains duplicate ${label} values`);
|
|
3358
|
+
}
|
|
3359
|
+
function count(value, noun, predicate) {
|
|
3360
|
+
const verb = predicate.replace(/^(has|is) /, (word) => value === 1 ? word : word === "has " ? "have " : "are ");
|
|
3361
|
+
return `${value} ${value === 1 ? noun : `${noun}s`} ${verb}`;
|
|
3362
|
+
}
|
|
3363
|
+
function round(usd) {
|
|
3364
|
+
return Math.round(usd * 1e6) / 1e6;
|
|
3365
|
+
}
|
|
3366
|
+
function integrity(message) {
|
|
3367
|
+
return new SearchLedgerIntegrityError(message);
|
|
2843
3368
|
}
|
|
2844
|
-
//#endregion
|
|
2845
|
-
//#region src/campaign/search-ledger-types.ts
|
|
2846
|
-
const SEARCH_LEDGER_SCHEMA = "tangle.search-ledger.v1";
|
|
2847
3369
|
//#endregion
|
|
2848
3370
|
//#region src/campaign/search-ledger.ts
|
|
2849
3371
|
/**
|
|
2850
|
-
* Durable append-only
|
|
3372
|
+
* Durable, append-only event log of one search: nodes (content-addressed
|
|
3373
|
+
* artifacts), edges (the proposals that derived them), cells (one run of one
|
|
3374
|
+
* node on one task), operations, decisions, and the claim.
|
|
2851
3375
|
*
|
|
2852
|
-
*
|
|
2853
|
-
* measured run and `CostLedger` owns per-call
|
|
2854
|
-
*
|
|
2855
|
-
* search can answer, after a crash, exactly which candidates and task attempts
|
|
2856
|
-
* existed, which surfaces actually fired, what they cost, and why they were
|
|
2857
|
-
* selected or rejected.
|
|
3376
|
+
* The ledger is the search's only lineage record and its only resume
|
|
3377
|
+
* checkpoint. A RunRecord owns one measured run and `CostLedger` owns per-call
|
|
3378
|
+
* accounting; this ledger binds them by digest instead of copying them.
|
|
2858
3379
|
*
|
|
2859
3380
|
* The file format is canonical JSONL with a SHA-256 hash chain. Every append is
|
|
2860
3381
|
* serialized across processes, fsynced before acknowledgement, and idempotent
|
|
2861
3382
|
* by `eventId`. A malformed, non-canonical, truncated, reordered, or conflicting
|
|
2862
|
-
* log fails loudly;
|
|
3383
|
+
* log fails loudly; a bad row is never skipped. A ledger written under another
|
|
3384
|
+
* schema tag is refused with that tag named, never translated.
|
|
2863
3385
|
*
|
|
2864
|
-
* The journal machinery
|
|
2865
|
-
*
|
|
2866
|
-
*
|
|
3386
|
+
* The journal machinery is the generic `ledger-core` journal. This module is
|
|
3387
|
+
* the search codec: event schemas and canonical ordering. `SearchState` is the
|
|
3388
|
+
* state machine and read model.
|
|
2867
3389
|
*/
|
|
2868
3390
|
const NON_EMPTY = z.string().min(1).refine((value) => value.trim() === value, "must not contain surrounding whitespace");
|
|
2869
3391
|
const HASH = z.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
2870
|
-
const LINEAGE_NODE_ID = z.string().regex(/^[a-f0-9]{16}$/);
|
|
2871
3392
|
const IMMUTABLE_REVISION = z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64}|sha256:[a-f0-9]{64}|sha512:[A-Za-z0-9+/=]+)$/);
|
|
2872
3393
|
const ISO_TIMESTAMP = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/).refine((value) => Number.isFinite(Date.parse(value)), "invalid timestamp");
|
|
2873
3394
|
const NON_NEGATIVE_INT = z.number().int().nonnegative().safe();
|
|
3395
|
+
const POSITIVE_INT = z.number().int().positive().safe();
|
|
2874
3396
|
const FINITE_NUMBER = z.number().finite();
|
|
3397
|
+
const USD = z.number().finite().nonnegative();
|
|
3398
|
+
const NODE_ID = z.string().regex(/^node_[a-f0-9]{32}$/);
|
|
3399
|
+
const CELL_ID = z.string().regex(/^cell_[a-f0-9]{32}$/);
|
|
2875
3400
|
const ArtifactRefSchema = z.object({
|
|
2876
3401
|
role: NON_EMPTY,
|
|
2877
3402
|
uri: NON_EMPTY,
|
|
@@ -2882,14 +3407,47 @@ const SourceRefSchema = z.object({
|
|
|
2882
3407
|
uri: NON_EMPTY,
|
|
2883
3408
|
revision: IMMUTABLE_REVISION
|
|
2884
3409
|
}).strict();
|
|
3410
|
+
const UnknownRefSchema = z.object({ unknown: NON_EMPTY }).strict();
|
|
2885
3411
|
const FailureReasonSchema = z.object({
|
|
2886
3412
|
code: NON_EMPTY,
|
|
2887
3413
|
message: NON_EMPTY
|
|
2888
3414
|
}).strict();
|
|
3415
|
+
const ModelIdentitySchema = z.union([z.object({
|
|
3416
|
+
provider: NON_EMPTY,
|
|
3417
|
+
snapshot: NON_EMPTY.refine(modelHasSnapshot, "model must include an immutable snapshot")
|
|
3418
|
+
}).strict(), z.object({
|
|
3419
|
+
provider: NON_EMPTY,
|
|
3420
|
+
alias: NON_EMPTY,
|
|
3421
|
+
unknown: NON_EMPTY
|
|
3422
|
+
}).strict()]);
|
|
3423
|
+
const ExecutionIdentitySchema = z.object({
|
|
3424
|
+
model: ModelIdentitySchema,
|
|
3425
|
+
agent: SourceRefSchema,
|
|
3426
|
+
benchmark: SourceRefSchema
|
|
3427
|
+
}).strict();
|
|
3428
|
+
const SURFACE_KINDS = [
|
|
3429
|
+
"prompt",
|
|
3430
|
+
"tool-contract",
|
|
3431
|
+
"runtime-config",
|
|
3432
|
+
"memory",
|
|
3433
|
+
"knowledge",
|
|
3434
|
+
"agent-profile",
|
|
3435
|
+
"code",
|
|
3436
|
+
"deployment"
|
|
3437
|
+
];
|
|
3438
|
+
const SPLIT = z.enum([
|
|
3439
|
+
"train",
|
|
3440
|
+
"selection",
|
|
3441
|
+
"test"
|
|
3442
|
+
]);
|
|
3443
|
+
const ReservationSchema = z.object({
|
|
3444
|
+
kind: z.enum(["hard", "estimate"]),
|
|
3445
|
+
usd: USD
|
|
3446
|
+
}).strict();
|
|
2889
3447
|
const EventBaseShape = {
|
|
2890
3448
|
eventId: NON_EMPTY,
|
|
2891
3449
|
occurredAt: ISO_TIMESTAMP,
|
|
2892
|
-
artifacts: z.array(ArtifactRefSchema)
|
|
3450
|
+
artifacts: z.array(ArtifactRefSchema)
|
|
2893
3451
|
};
|
|
2894
3452
|
const OperationKindSchema = z.enum([
|
|
2895
3453
|
"candidate-generation",
|
|
@@ -2898,89 +3456,19 @@ const OperationKindSchema = z.enum([
|
|
|
2898
3456
|
"judge",
|
|
2899
3457
|
"other"
|
|
2900
3458
|
]);
|
|
2901
|
-
const CandidateSlotSchema = z.object({
|
|
2902
|
-
slotId: NON_EMPTY,
|
|
2903
|
-
generationOperationId: NON_EMPTY
|
|
2904
|
-
}).strict();
|
|
2905
|
-
const PlannedOperationSchema = z.object({
|
|
2906
|
-
operationId: NON_EMPTY,
|
|
2907
|
-
kind: OperationKindSchema
|
|
2908
|
-
}).strict();
|
|
2909
|
-
const SearchPlanExtendedSchema = z.object({
|
|
2910
|
-
...EventBaseShape,
|
|
2911
|
-
kind: z.literal("search-plan-extended"),
|
|
2912
|
-
extension: z.object({
|
|
2913
|
-
candidateSlots: z.array(CandidateSlotSchema),
|
|
2914
|
-
operations: z.array(PlannedOperationSchema)
|
|
2915
|
-
}).strict().superRefine((extension, ctx) => {
|
|
2916
|
-
if (extension.candidateSlots.length === 0 && extension.operations.length === 0) ctx.addIssue({
|
|
2917
|
-
code: "custom",
|
|
2918
|
-
message: "a plan extension must add slots or operations"
|
|
2919
|
-
});
|
|
2920
|
-
})
|
|
2921
|
-
}).strict();
|
|
2922
|
-
const SearchPlannedSchema = z.object({
|
|
2923
|
-
...EventBaseShape,
|
|
2924
|
-
kind: z.literal("search-planned"),
|
|
2925
|
-
plan: z.object({
|
|
2926
|
-
candidateSlots: z.array(CandidateSlotSchema).min(1),
|
|
2927
|
-
tasks: z.array(z.object({
|
|
2928
|
-
taskId: NON_EMPTY,
|
|
2929
|
-
source: SourceRefSchema,
|
|
2930
|
-
benchmark: SourceRefSchema,
|
|
2931
|
-
maxAttempts: z.number().int().positive().safe()
|
|
2932
|
-
}).strict()).min(1),
|
|
2933
|
-
operations: z.array(PlannedOperationSchema).min(1)
|
|
2934
|
-
}).strict()
|
|
2935
|
-
}).strict();
|
|
2936
|
-
const CandidateRegisteredSchema = z.object({
|
|
2937
|
-
...EventBaseShape,
|
|
2938
|
-
kind: z.literal("candidate-registered"),
|
|
2939
|
-
slotId: NON_EMPTY,
|
|
2940
|
-
generationOperationId: NON_EMPTY,
|
|
2941
|
-
candidateId: NON_EMPTY,
|
|
2942
|
-
lineage: z.object({
|
|
2943
|
-
lineageNodeId: LINEAGE_NODE_ID,
|
|
2944
|
-
parentCandidateIds: z.array(NON_EMPTY),
|
|
2945
|
-
generation: NON_NEGATIVE_INT,
|
|
2946
|
-
proposer: NON_EMPTY,
|
|
2947
|
-
proposerSource: SourceRefSchema
|
|
2948
|
-
}).strict(),
|
|
2949
|
-
surfaces: z.array(z.object({
|
|
2950
|
-
surfaceId: NON_EMPTY,
|
|
2951
|
-
kind: z.enum([
|
|
2952
|
-
"prompt",
|
|
2953
|
-
"tool-contract",
|
|
2954
|
-
"runtime-config",
|
|
2955
|
-
"memory",
|
|
2956
|
-
"knowledge",
|
|
2957
|
-
"agent-profile",
|
|
2958
|
-
"code",
|
|
2959
|
-
"deployment"
|
|
2960
|
-
]),
|
|
2961
|
-
artifact: ArtifactRefSchema
|
|
2962
|
-
}).strict()).min(1)
|
|
2963
|
-
}).strict();
|
|
2964
|
-
const CandidateSlotClosedSchema = z.object({
|
|
2965
|
-
...EventBaseShape,
|
|
2966
|
-
kind: z.literal("candidate-slot-closed"),
|
|
2967
|
-
slotId: NON_EMPTY,
|
|
2968
|
-
generationOperationId: NON_EMPTY,
|
|
2969
|
-
reason: FailureReasonSchema
|
|
2970
|
-
}).strict();
|
|
2971
3459
|
const KnownTokensSchema = z.object({
|
|
2972
3460
|
status: z.literal("known"),
|
|
2973
3461
|
inputTokens: NON_NEGATIVE_INT,
|
|
2974
3462
|
outputTokens: NON_NEGATIVE_INT,
|
|
2975
3463
|
cachedTokens: NON_NEGATIVE_INT
|
|
2976
3464
|
}).strict();
|
|
2977
|
-
const
|
|
3465
|
+
const UnknownTokensSchema = z.object({
|
|
2978
3466
|
status: z.literal("unknown"),
|
|
2979
3467
|
reason: NON_EMPTY
|
|
2980
3468
|
}).strict();
|
|
2981
3469
|
const KnownCostSchema = z.object({
|
|
2982
3470
|
status: z.literal("known"),
|
|
2983
|
-
usd:
|
|
3471
|
+
usd: USD,
|
|
2984
3472
|
source: z.enum([
|
|
2985
3473
|
"provider",
|
|
2986
3474
|
"pricing-table",
|
|
@@ -2994,11 +3482,11 @@ const KnownCostSchema = z.object({
|
|
|
2994
3482
|
});
|
|
2995
3483
|
const UnknownCostSchema = z.object({
|
|
2996
3484
|
status: z.literal("unknown"),
|
|
2997
|
-
knownLowerBoundUsd:
|
|
3485
|
+
knownLowerBoundUsd: USD,
|
|
2998
3486
|
reason: NON_EMPTY
|
|
2999
3487
|
}).strict();
|
|
3000
3488
|
const AccountingSchema = z.object({
|
|
3001
|
-
tokens: z.discriminatedUnion("status", [KnownTokensSchema,
|
|
3489
|
+
tokens: z.discriminatedUnion("status", [KnownTokensSchema, UnknownTokensSchema]),
|
|
3002
3490
|
cost: z.discriminatedUnion("status", [KnownCostSchema, UnknownCostSchema])
|
|
3003
3491
|
}).strict();
|
|
3004
3492
|
const MetricsSchema = z.record(NON_EMPTY, FINITE_NUMBER).superRefine((metrics, ctx) => {
|
|
@@ -3049,52 +3537,85 @@ const SurfaceEvidenceSchema = z.object({
|
|
|
3049
3537
|
effect: EffectSchema,
|
|
3050
3538
|
evidence: z.array(ArtifactRefSchema).min(1)
|
|
3051
3539
|
}).strict().superRefine((evidence, ctx) => {
|
|
3052
|
-
if (evidence.fired
|
|
3053
|
-
code: "custom",
|
|
3054
|
-
message: "a fired surface must have firingCount >= 1"
|
|
3055
|
-
});
|
|
3056
|
-
if (!evidence.fired && evidence.firingCount !== 0) ctx.addIssue({
|
|
3540
|
+
if (evidence.fired !== evidence.firingCount > 0) ctx.addIssue({
|
|
3057
3541
|
code: "custom",
|
|
3058
|
-
message: "
|
|
3542
|
+
message: "fired must equal firingCount > 0"
|
|
3059
3543
|
});
|
|
3060
3544
|
if (!evidence.fired && evidence.effect.status === "measured" && evidence.effect.delta !== 0) ctx.addIssue({
|
|
3061
3545
|
code: "custom",
|
|
3062
|
-
message: "a surface that did not fire
|
|
3546
|
+
message: "a surface that did not fire has no effect"
|
|
3063
3547
|
});
|
|
3064
3548
|
});
|
|
3065
|
-
const
|
|
3549
|
+
const TaskSchema = z.object({
|
|
3550
|
+
taskId: NON_EMPTY,
|
|
3551
|
+
unitId: NON_EMPTY,
|
|
3552
|
+
source: SourceRefSchema
|
|
3553
|
+
}).strict();
|
|
3554
|
+
const SplitTasksSchema = z.object({
|
|
3555
|
+
taskSetDigest: HASH,
|
|
3556
|
+
tasks: z.array(TaskSchema)
|
|
3557
|
+
}).strict();
|
|
3558
|
+
const SearchOpenedSchema = z.object({
|
|
3066
3559
|
...EventBaseShape,
|
|
3067
|
-
kind: z.literal("
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
taskId: NON_EMPTY,
|
|
3073
|
-
source: SourceRefSchema
|
|
3560
|
+
kind: z.literal("search-opened"),
|
|
3561
|
+
subject: NON_EMPTY,
|
|
3562
|
+
process: z.object({
|
|
3563
|
+
name: NON_EMPTY,
|
|
3564
|
+
executionRef: SourceRefSchema
|
|
3074
3565
|
}).strict(),
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
benchmark: SourceRefSchema
|
|
3566
|
+
artifactKind: z.enum([...SURFACE_KINDS, "output"]),
|
|
3567
|
+
objective: z.object({
|
|
3568
|
+
metric: NON_EMPTY,
|
|
3569
|
+
direction: z.enum(["maximize", "minimize"]),
|
|
3570
|
+
judge: z.union([SourceRefSchema, UnknownRefSchema]),
|
|
3571
|
+
claim: evaluationClaimSchema
|
|
3082
3572
|
}).strict(),
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3573
|
+
splits: z.object({
|
|
3574
|
+
train: SplitTasksSchema,
|
|
3575
|
+
selection: SplitTasksSchema,
|
|
3576
|
+
test: SplitTasksSchema,
|
|
3577
|
+
heldOutUnits: z.boolean()
|
|
3578
|
+
}).strict(),
|
|
3579
|
+
policy: z.object({
|
|
3580
|
+
expansion: NON_EMPTY,
|
|
3581
|
+
allocation: NON_EMPTY,
|
|
3582
|
+
seed: z.number().int().safe()
|
|
3583
|
+
}).strict(),
|
|
3584
|
+
budget: z.object({
|
|
3585
|
+
maxUsd: USD.nullable(),
|
|
3586
|
+
maxCells: POSITIVE_INT.nullable(),
|
|
3587
|
+
maxNodes: POSITIVE_INT.nullable(),
|
|
3588
|
+
deadline: ISO_TIMESTAMP.nullable(),
|
|
3589
|
+
maxConcurrency: POSITIVE_INT.nullable(),
|
|
3590
|
+
reservedClaimUsd: USD
|
|
3591
|
+
}).strict(),
|
|
3592
|
+
containment: z.object({
|
|
3593
|
+
searchId: NON_EMPTY,
|
|
3594
|
+
cellId: CELL_ID,
|
|
3595
|
+
attempt: POSITIVE_INT
|
|
3596
|
+
}).strict().nullable(),
|
|
3597
|
+
derivedFrom: z.object({
|
|
3598
|
+
searchId: NON_EMPTY,
|
|
3599
|
+
nodeId: NODE_ID,
|
|
3600
|
+
headHash: HASH
|
|
3601
|
+
}).strict().nullable(),
|
|
3602
|
+
identity: ExecutionIdentitySchema
|
|
3086
3603
|
}).strict();
|
|
3087
|
-
const
|
|
3604
|
+
const OperationStartedSchema = z.object({
|
|
3088
3605
|
...EventBaseShape,
|
|
3089
|
-
kind: z.literal("
|
|
3606
|
+
kind: z.literal("operation-started"),
|
|
3607
|
+
operationId: NON_EMPTY,
|
|
3608
|
+
operationKind: OperationKindSchema,
|
|
3609
|
+
reservation: ReservationSchema.nullable()
|
|
3610
|
+
}).strict();
|
|
3611
|
+
const OperationRecordedSchema = z.object({
|
|
3612
|
+
...EventBaseShape,
|
|
3613
|
+
kind: z.literal("operation-recorded"),
|
|
3090
3614
|
operationId: NON_EMPTY,
|
|
3091
3615
|
operationKind: OperationKindSchema,
|
|
3092
3616
|
execution: z.discriminatedUnion("kind", [z.object({
|
|
3093
3617
|
kind: z.literal("model"),
|
|
3094
|
-
model:
|
|
3095
|
-
provider: NON_EMPTY,
|
|
3096
|
-
snapshot: NON_EMPTY.refine(modelHasSnapshot, "model must include an immutable snapshot")
|
|
3097
|
-
}).strict(),
|
|
3618
|
+
model: ModelIdentitySchema,
|
|
3098
3619
|
source: SourceRefSchema
|
|
3099
3620
|
}).strict(), z.object({
|
|
3100
3621
|
kind: z.literal("deterministic"),
|
|
@@ -3113,94 +3634,256 @@ const SearchOperationRecordedSchema = z.object({
|
|
|
3113
3634
|
]),
|
|
3114
3635
|
accounting: AccountingSchema
|
|
3115
3636
|
}).strict();
|
|
3116
|
-
const
|
|
3637
|
+
const NodeRegisteredSchema = z.object({
|
|
3117
3638
|
...EventBaseShape,
|
|
3118
|
-
kind: z.literal("
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3639
|
+
kind: z.literal("node-registered"),
|
|
3640
|
+
nodeId: NODE_ID,
|
|
3641
|
+
artifactDigest: HASH,
|
|
3642
|
+
artifact: ArtifactRefSchema,
|
|
3643
|
+
surfaces: z.array(z.object({
|
|
3644
|
+
surfaceId: NON_EMPTY,
|
|
3645
|
+
kind: z.enum(SURFACE_KINDS),
|
|
3646
|
+
artifact: ArtifactRefSchema
|
|
3647
|
+
}).strict())
|
|
3648
|
+
}).strict();
|
|
3649
|
+
const NodeRefSchema = z.object({
|
|
3650
|
+
searchId: NON_EMPTY,
|
|
3651
|
+
nodeId: NODE_ID
|
|
3124
3652
|
}).strict();
|
|
3125
|
-
const
|
|
3653
|
+
const ArtifactOrUnknownSchema = z.union([ArtifactRefSchema, UnknownRefSchema]);
|
|
3654
|
+
const EdgeRecordedSchema = z.object({
|
|
3126
3655
|
...EventBaseShape,
|
|
3127
|
-
kind: z.literal("
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3656
|
+
kind: z.literal("edge-recorded"),
|
|
3657
|
+
edgeId: z.string().regex(/^edge_[a-f0-9]{32}$/),
|
|
3658
|
+
childNodeId: NODE_ID,
|
|
3659
|
+
parents: z.array(NodeRefSchema),
|
|
3660
|
+
operator: z.enum([
|
|
3661
|
+
"seed",
|
|
3662
|
+
"draft",
|
|
3663
|
+
"improve",
|
|
3664
|
+
"debug",
|
|
3665
|
+
"merge",
|
|
3666
|
+
"derive"
|
|
3667
|
+
]),
|
|
3668
|
+
attribution: z.enum([
|
|
3669
|
+
"explicit",
|
|
3670
|
+
"correlated",
|
|
3671
|
+
"unknown"
|
|
3672
|
+
]),
|
|
3673
|
+
proposer: z.object({
|
|
3674
|
+
kind: z.enum([
|
|
3675
|
+
"trace",
|
|
3676
|
+
"frontier-author",
|
|
3677
|
+
"human",
|
|
3678
|
+
"optimizer",
|
|
3679
|
+
"compound"
|
|
3680
|
+
]),
|
|
3681
|
+
name: NON_EMPTY,
|
|
3682
|
+
operationId: NON_EMPTY.nullable(),
|
|
3683
|
+
source: SourceRefSchema
|
|
3684
|
+
}).strict().nullable(),
|
|
3685
|
+
selection: z.object({
|
|
3686
|
+
rule: NON_EMPTY,
|
|
3687
|
+
evidence: z.record(NON_EMPTY, FINITE_NUMBER)
|
|
3688
|
+
}).strict().nullable(),
|
|
3689
|
+
rationale: ArtifactOrUnknownSchema,
|
|
3690
|
+
diffs: z.array(ArtifactOrUnknownSchema),
|
|
3691
|
+
label: z.string()
|
|
3692
|
+
}).strict();
|
|
3693
|
+
const CellAllocatedSchema = z.object({
|
|
3694
|
+
...EventBaseShape,
|
|
3695
|
+
kind: z.literal("cell-allocated"),
|
|
3696
|
+
cellId: CELL_ID,
|
|
3697
|
+
nodeId: NODE_ID,
|
|
3698
|
+
taskId: NON_EMPTY,
|
|
3699
|
+
unitId: NON_EMPTY,
|
|
3700
|
+
split: SPLIT,
|
|
3701
|
+
rep: NON_NEGATIVE_INT,
|
|
3702
|
+
stage: z.enum([
|
|
3703
|
+
"root",
|
|
3704
|
+
"train",
|
|
3705
|
+
"screen",
|
|
3706
|
+
"rung",
|
|
3707
|
+
"claim",
|
|
3708
|
+
"external"
|
|
3709
|
+
]),
|
|
3710
|
+
lane: NON_EMPTY.nullable(),
|
|
3711
|
+
reservation: ReservationSchema.nullable()
|
|
3712
|
+
}).strict();
|
|
3713
|
+
const TraceRefSchema = z.union([z.object({
|
|
3714
|
+
traceId: NON_EMPTY,
|
|
3715
|
+
execRunId: NON_EMPTY.nullable(),
|
|
3716
|
+
spansWritten: NON_NEGATIVE_INT.nullable(),
|
|
3717
|
+
spansDropped: NON_NEGATIVE_INT.nullable()
|
|
3718
|
+
}).strict(), UnknownRefSchema]);
|
|
3719
|
+
const NON_NEGATIVE = z.number().finite().nonnegative();
|
|
3720
|
+
const CellSettledSchema = z.object({
|
|
3721
|
+
...EventBaseShape,
|
|
3722
|
+
kind: z.literal("cell-settled"),
|
|
3723
|
+
cellId: CELL_ID,
|
|
3724
|
+
attempt: POSITIVE_INT,
|
|
3725
|
+
runId: NON_EMPTY,
|
|
3726
|
+
outcome: OutcomeSchema,
|
|
3727
|
+
accounting: AccountingSchema,
|
|
3728
|
+
boxMinutes: NON_NEGATIVE.nullable(),
|
|
3729
|
+
wallMs: NON_NEGATIVE.nullable(),
|
|
3730
|
+
queueMs: NON_NEGATIVE.nullable(),
|
|
3731
|
+
placement: z.object({
|
|
3732
|
+
lane: NON_EMPTY,
|
|
3733
|
+
boxId: NON_EMPTY.nullable()
|
|
3734
|
+
}).strict().nullable(),
|
|
3735
|
+
identity: ExecutionIdentitySchema,
|
|
3736
|
+
surfaceEvidence: z.array(SurfaceEvidenceSchema),
|
|
3737
|
+
traceRef: TraceRefSchema
|
|
3738
|
+
}).strict();
|
|
3739
|
+
const CellCancelledSchema = z.object({
|
|
3740
|
+
...EventBaseShape,
|
|
3741
|
+
kind: z.literal("cell-cancelled"),
|
|
3742
|
+
cellId: CELL_ID,
|
|
3743
|
+
reason: z.enum([
|
|
3744
|
+
"pruned",
|
|
3745
|
+
"budget",
|
|
3746
|
+
"deadline",
|
|
3747
|
+
"aborted"
|
|
3748
|
+
])
|
|
3749
|
+
}).strict();
|
|
3750
|
+
const NodeEstimateSchema = z.object({
|
|
3751
|
+
against: NODE_ID,
|
|
3752
|
+
split: SPLIT,
|
|
3753
|
+
units: NON_NEGATIVE_INT,
|
|
3754
|
+
pairs: NON_NEGATIVE_INT,
|
|
3755
|
+
delta: FINITE_NUMBER.nullable(),
|
|
3756
|
+
interval: z.tuple([FINITE_NUMBER, FINITE_NUMBER]).nullable(),
|
|
3757
|
+
method: z.enum([
|
|
3758
|
+
"none",
|
|
3759
|
+
"insufficient",
|
|
3760
|
+
"descriptive",
|
|
3761
|
+
"bootstrap"
|
|
3762
|
+
]),
|
|
3763
|
+
exactSignP: z.number().min(0).max(1).nullable(),
|
|
3764
|
+
indeterminate: z.boolean(),
|
|
3765
|
+
cellSetDigest: HASH,
|
|
3766
|
+
estimator: SourceRefSchema
|
|
3767
|
+
}).strict();
|
|
3768
|
+
const NodeDecidedSchema = z.object({
|
|
3769
|
+
...EventBaseShape,
|
|
3770
|
+
kind: z.literal("node-decided"),
|
|
3771
|
+
nodeId: NODE_ID,
|
|
3772
|
+
decision: z.discriminatedUnion("status", [
|
|
3773
|
+
z.object({
|
|
3774
|
+
status: z.literal("advanced"),
|
|
3775
|
+
rung: NON_NEGATIVE_INT
|
|
3776
|
+
}).strict(),
|
|
3777
|
+
z.object({ status: z.literal("pruned") }).strict(),
|
|
3778
|
+
z.object({ status: z.literal("invalid") }).strict(),
|
|
3779
|
+
z.object({ status: z.literal("finalist") }).strict(),
|
|
3780
|
+
z.object({ status: z.literal("selected") }).strict(),
|
|
3781
|
+
z.object({ status: z.literal("rejected") }).strict()
|
|
3782
|
+
]),
|
|
3783
|
+
basis: NodeEstimateSchema.nullable(),
|
|
3784
|
+
rule: NON_EMPTY,
|
|
3785
|
+
reason: NON_EMPTY
|
|
3786
|
+
}).strict();
|
|
3787
|
+
const SearchClosedSchema = z.object({
|
|
3788
|
+
...EventBaseShape,
|
|
3789
|
+
kind: z.literal("search-closed"),
|
|
3790
|
+
reason: z.enum([
|
|
3791
|
+
"budget",
|
|
3792
|
+
"deadline",
|
|
3793
|
+
"max-nodes",
|
|
3794
|
+
"patience",
|
|
3795
|
+
"converged",
|
|
3796
|
+
"aborted"
|
|
3797
|
+
]),
|
|
3798
|
+
claim: z.object({
|
|
3799
|
+
power: z.union([z.object({
|
|
3800
|
+
adequate: z.boolean(),
|
|
3801
|
+
minimumEffect: z.number().finite().positive(),
|
|
3802
|
+
powerAtMinimumEffect: z.number().min(0).max(1),
|
|
3803
|
+
units: NON_NEGATIVE_INT
|
|
3804
|
+
}).strict(), UnknownRefSchema]),
|
|
3805
|
+
finalists: z.array(z.object({
|
|
3806
|
+
nodeId: NODE_ID,
|
|
3807
|
+
estimate: NodeEstimateSchema.nullable(),
|
|
3808
|
+
promote: z.boolean()
|
|
3809
|
+
}).strict()),
|
|
3810
|
+
selected: NODE_ID.nullable(),
|
|
3811
|
+
decision: z.enum([
|
|
3812
|
+
"ship",
|
|
3813
|
+
"hold",
|
|
3814
|
+
"test-cannot-resolve"
|
|
3815
|
+
])
|
|
3816
|
+
}).strict().nullable()
|
|
3135
3817
|
}).strict();
|
|
3136
3818
|
const EventSchema = z.discriminatedUnion("kind", [
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3819
|
+
SearchOpenedSchema,
|
|
3820
|
+
OperationStartedSchema,
|
|
3821
|
+
OperationRecordedSchema,
|
|
3822
|
+
NodeRegisteredSchema,
|
|
3823
|
+
EdgeRecordedSchema,
|
|
3824
|
+
CellAllocatedSchema,
|
|
3825
|
+
CellSettledSchema,
|
|
3826
|
+
CellCancelledSchema,
|
|
3827
|
+
NodeDecidedSchema,
|
|
3828
|
+
SearchClosedSchema
|
|
3145
3829
|
]);
|
|
3146
3830
|
const EntrySchema = z.object({
|
|
3147
3831
|
schema: z.literal(SEARCH_LEDGER_SCHEMA),
|
|
3148
|
-
|
|
3832
|
+
searchId: NON_EMPTY,
|
|
3149
3833
|
sequence: NON_NEGATIVE_INT,
|
|
3150
3834
|
previousHash: z.union([HASH, z.null()]),
|
|
3151
3835
|
event: EventSchema,
|
|
3152
3836
|
entryHash: HASH
|
|
3153
3837
|
}).strict();
|
|
3154
|
-
/** Validate and return a canonical copy. Arrays whose order
|
|
3155
|
-
* sorted so retries from different processes produce
|
|
3838
|
+
/** Validate and return a canonical copy. Arrays whose order carries no meaning
|
|
3839
|
+
* are sorted, so retries from different processes produce identical bytes. */
|
|
3156
3840
|
function validateSearchLedgerEvent(input) {
|
|
3157
3841
|
const parsed = EventSchema.safeParse(input);
|
|
3158
3842
|
if (!parsed.success) throw new SearchLedgerError(`invalid search ledger event: ${formatZodError(parsed.error)}`);
|
|
3159
3843
|
return normalizeEvent(parsed.data);
|
|
3160
3844
|
}
|
|
3161
3845
|
/** Open a durable filesystem search ledger. Construction performs no I/O; the
|
|
3162
|
-
* first `append` or `
|
|
3846
|
+
* first `append` or `state` verifies the whole existing file. */
|
|
3163
3847
|
function openSearchLedger(options) {
|
|
3164
|
-
|
|
3165
|
-
return new FileSearchLedger(options.path, options.campaignId, options.trustedHead);
|
|
3848
|
+
return new FileSearchLedger(options.path, options.searchId, options.trustedHead);
|
|
3166
3849
|
}
|
|
3167
|
-
/**
|
|
3168
|
-
|
|
3169
|
-
|
|
3850
|
+
/** Verify and project immutable search-ledger JSONL through the same codec as
|
|
3851
|
+
* `FileSearchLedger`. */
|
|
3852
|
+
function replaySearchLedgerText(text, searchId, source) {
|
|
3853
|
+
return replayLedgerText(text, source, searchLedgerCodec(searchId));
|
|
3170
3854
|
}
|
|
3171
|
-
function searchLedgerCodec(
|
|
3855
|
+
function searchLedgerCodec(searchId) {
|
|
3172
3856
|
return {
|
|
3173
3857
|
...SEARCH_LEDGER_FILE_CONTEXT,
|
|
3174
3858
|
header: {
|
|
3175
3859
|
schema: SEARCH_LEDGER_SCHEMA,
|
|
3176
|
-
|
|
3860
|
+
searchId
|
|
3177
3861
|
},
|
|
3178
3862
|
conflictError: (message) => new SearchLedgerConflictError(message),
|
|
3179
3863
|
parseEntry: parseSearchLedgerEntry,
|
|
3180
3864
|
checkEntryHeader: (entry, index) => {
|
|
3181
|
-
if (entry.
|
|
3865
|
+
if (entry.searchId !== searchId) throw new SearchLedgerIntegrityError(`entry ${index} belongs to search ${entry.searchId}, expected ${searchId}`);
|
|
3182
3866
|
},
|
|
3183
|
-
createProjector: () =>
|
|
3867
|
+
createProjector: () => new SearchState(searchId)
|
|
3184
3868
|
};
|
|
3185
3869
|
}
|
|
3186
|
-
/** Append-only file-backed search ledger with idempotent writes
|
|
3870
|
+
/** Append-only file-backed search ledger with idempotent writes. */
|
|
3187
3871
|
var FileSearchLedger = class {
|
|
3188
3872
|
path;
|
|
3189
|
-
|
|
3873
|
+
searchId;
|
|
3190
3874
|
trustedHeadPath;
|
|
3191
3875
|
trustedHeadMode;
|
|
3192
3876
|
journal;
|
|
3193
|
-
constructor(path,
|
|
3877
|
+
constructor(path, searchId, trustedHead = "pin") {
|
|
3194
3878
|
if (path.trim().length === 0) throw new SearchLedgerError("ledger path is empty");
|
|
3195
|
-
if (
|
|
3196
|
-
|
|
3197
|
-
this.campaignId = campaignId;
|
|
3879
|
+
if (searchId.length === 0 || searchId.trim() !== searchId) throw new SearchLedgerError("searchId must be non-empty without surrounding whitespace");
|
|
3880
|
+
this.searchId = searchId;
|
|
3198
3881
|
this.trustedHeadMode = trustedHead;
|
|
3199
|
-
this.journal = new FileLedgerJournal(path, searchLedgerCodec(
|
|
3882
|
+
this.journal = new FileLedgerJournal(path, searchLedgerCodec(searchId), { requireTrustedHead: trustedHead === "require" });
|
|
3200
3883
|
this.path = this.journal.path;
|
|
3201
3884
|
this.trustedHeadPath = this.journal.trustedHeadPath;
|
|
3202
3885
|
}
|
|
3203
|
-
async
|
|
3886
|
+
async state() {
|
|
3204
3887
|
return this.journal.replay();
|
|
3205
3888
|
}
|
|
3206
3889
|
async append(input) {
|
|
@@ -3209,7 +3892,7 @@ var FileSearchLedger = class {
|
|
|
3209
3892
|
return {
|
|
3210
3893
|
entry,
|
|
3211
3894
|
appended,
|
|
3212
|
-
|
|
3895
|
+
state: projection
|
|
3213
3896
|
};
|
|
3214
3897
|
}
|
|
3215
3898
|
async trustedHead() {
|
|
@@ -3223,6 +3906,8 @@ var FileSearchLedger = class {
|
|
|
3223
3906
|
}
|
|
3224
3907
|
};
|
|
3225
3908
|
function parseSearchLedgerEntry(raw, context) {
|
|
3909
|
+
const schema = raw?.schema;
|
|
3910
|
+
if (schema !== "tangle.search-ledger.2026-09") throw new SearchLedgerIntegrityError(`search ledger ${context.path} line ${context.line} was written under schema ${JSON.stringify(schema)}; this version reads only ${SEARCH_LEDGER_SCHEMA} and does not translate other formats`);
|
|
3226
3911
|
const parsed = EntrySchema.safeParse(raw);
|
|
3227
3912
|
if (!parsed.success) throw new SearchLedgerIntegrityError(`search ledger ${context.path} has a malformed entry at line ${context.line}: ${formatZodError(parsed.error)}`);
|
|
3228
3913
|
const entry = parsed.data;
|
|
@@ -3231,74 +3916,82 @@ function parseSearchLedgerEntry(raw, context) {
|
|
|
3231
3916
|
}
|
|
3232
3917
|
function normalizeEvent(event) {
|
|
3233
3918
|
const artifacts = sortArtifacts(event.artifacts);
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
...event
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
...event,
|
|
3273
|
-
artifacts
|
|
3274
|
-
};
|
|
3919
|
+
switch (event.kind) {
|
|
3920
|
+
case "search-opened": return {
|
|
3921
|
+
...event,
|
|
3922
|
+
artifacts,
|
|
3923
|
+
splits: {
|
|
3924
|
+
...event.splits,
|
|
3925
|
+
train: {
|
|
3926
|
+
...event.splits.train,
|
|
3927
|
+
tasks: sortTasks(event.splits.train.tasks)
|
|
3928
|
+
},
|
|
3929
|
+
selection: {
|
|
3930
|
+
...event.splits.selection,
|
|
3931
|
+
tasks: sortTasks(event.splits.selection.tasks)
|
|
3932
|
+
},
|
|
3933
|
+
test: {
|
|
3934
|
+
...event.splits.test,
|
|
3935
|
+
tasks: sortTasks(event.splits.test.tasks)
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
};
|
|
3939
|
+
case "node-registered": return {
|
|
3940
|
+
...event,
|
|
3941
|
+
artifacts,
|
|
3942
|
+
surfaces: [...event.surfaces].sort((a, b) => compareStrings(a.surfaceId, b.surfaceId))
|
|
3943
|
+
};
|
|
3944
|
+
case "cell-settled": return {
|
|
3945
|
+
...event,
|
|
3946
|
+
artifacts,
|
|
3947
|
+
surfaceEvidence: [...event.surfaceEvidence].map((evidence) => ({
|
|
3948
|
+
...evidence,
|
|
3949
|
+
evidence: sortArtifacts(evidence.evidence)
|
|
3950
|
+
})).sort((a, b) => compareStrings(a.surfaceId, b.surfaceId))
|
|
3951
|
+
};
|
|
3952
|
+
default: return {
|
|
3953
|
+
...event,
|
|
3954
|
+
artifacts
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3275
3957
|
}
|
|
3276
|
-
function
|
|
3277
|
-
return [...
|
|
3958
|
+
function sortTasks(tasks) {
|
|
3959
|
+
return [...tasks].sort((a, b) => compareStrings(a.taskId, b.taskId));
|
|
3278
3960
|
}
|
|
3279
|
-
function
|
|
3280
|
-
return [...
|
|
3961
|
+
function sortArtifacts(artifacts) {
|
|
3962
|
+
return [...artifacts].sort((a, b) => compareStrings(artifactKey(a), artifactKey(b)));
|
|
3281
3963
|
}
|
|
3282
3964
|
function formatZodError(error) {
|
|
3283
3965
|
return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`).join("; ");
|
|
3284
3966
|
}
|
|
3285
3967
|
//#endregion
|
|
3286
3968
|
//#region src/campaign/search-history-receipt.ts
|
|
3287
|
-
const SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION = "
|
|
3969
|
+
const SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION = "2026-09";
|
|
3288
3970
|
const SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM = "rfc8785-sha256";
|
|
3289
3971
|
function assertSearchHistoryAdmissionOptions(options) {
|
|
3290
3972
|
if (options.searchHistoryPolicy !== void 0 && !["allow-missing", "require-complete"].includes(options.searchHistoryPolicy)) throw new Error(`unknown searchHistoryPolicy '${String(options.searchHistoryPolicy)}'`);
|
|
3291
3973
|
if (options.searchHistoryVerification !== void 0 && !["receipt", "ledger"].includes(options.searchHistoryVerification)) throw new Error(`unknown searchHistoryVerification '${String(options.searchHistoryVerification)}'`);
|
|
3292
3974
|
}
|
|
3293
|
-
/**
|
|
3975
|
+
/** Read the ledger's bytes, replay them through the search codec, and build
|
|
3976
|
+
* the receipt from that replay. */
|
|
3977
|
+
function createSearchHistoryReceipt(input) {
|
|
3978
|
+
const producerId = nonEmpty(input.producerId, "search history producerId");
|
|
3979
|
+
const runId = nonEmpty(input.runId, "search history runId");
|
|
3980
|
+
const text = (input.storage ?? fsCampaignStorage()).read(input.ledger.path) ?? "";
|
|
3981
|
+
const state = replaySearchLedgerText(text, input.ledger.searchId, input.ledger.path);
|
|
3982
|
+
return receiptFor(producerId, runId, ledgerArtifact(input.ledger.path, text), state);
|
|
3983
|
+
}
|
|
3984
|
+
/** Verify the receipt, then resolve its ledger bytes, check them, and replay
|
|
3985
|
+
* them with the canonical codec. Never trusts a caller-supplied projection. */
|
|
3294
3986
|
function verifySearchHistoryArtifact(receipt, storage) {
|
|
3295
3987
|
verifySearchHistoryReceipt(receipt);
|
|
3296
3988
|
const path = receipt.ledger.uri.startsWith("file:") ? fileURLToPath(receipt.ledger.uri) : receipt.ledger.uri;
|
|
3297
3989
|
const text = storage.read(path);
|
|
3298
3990
|
if (text === void 0) throw new Error(`search history ledger is missing or unreadable at '${path}'`);
|
|
3299
|
-
|
|
3300
|
-
if (
|
|
3301
|
-
|
|
3991
|
+
const artifact = ledgerArtifact(path, text);
|
|
3992
|
+
if (artifact.byteLength !== receipt.ledger.byteLength) throw new Error("search history ledger byte length mismatch");
|
|
3993
|
+
if (artifact.sha256 !== receipt.ledger.sha256) throw new Error("search history ledger digest mismatch");
|
|
3994
|
+
assertSearchHistoryMatchesState(receipt, replaySearchLedgerText(text, receipt.summary.searchId, path));
|
|
3302
3995
|
}
|
|
3303
3996
|
var SearchHistoryRequiredError = class extends Error {
|
|
3304
3997
|
producerId;
|
|
@@ -3311,73 +4004,25 @@ var SearchHistoryRequiredError = class extends Error {
|
|
|
3311
4004
|
this.reasons = Object.freeze([...normalized]);
|
|
3312
4005
|
}
|
|
3313
4006
|
};
|
|
3314
|
-
/**
|
|
3315
|
-
function createSearchHistoryReceipt(input) {
|
|
3316
|
-
if (!input || typeof input !== "object") throw new TypeError("search history input is required");
|
|
3317
|
-
const producerId = nonEmpty(input.producerId, "search history producerId");
|
|
3318
|
-
const runId = nonEmpty(input.runId, "search history runId");
|
|
3319
|
-
const ledger = normalizeArtifactRef(input.ledger);
|
|
3320
|
-
const replay = validateReplayProjection(input.replay);
|
|
3321
|
-
const auditDigest = ledgerHash(hashCanonical(replay.audit), "search history auditDigest");
|
|
3322
|
-
const summary = summarizeReplay(replay);
|
|
3323
|
-
const incompleteReasons = Object.freeze(historyIncompleteReasons(summary));
|
|
3324
|
-
const material = Object.freeze({
|
|
3325
|
-
schemaVersion: SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION,
|
|
3326
|
-
kind: "search-history-receipt",
|
|
3327
|
-
digestAlgorithm: SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM,
|
|
3328
|
-
producerId,
|
|
3329
|
-
runId,
|
|
3330
|
-
ledger,
|
|
3331
|
-
auditDigest,
|
|
3332
|
-
summary,
|
|
3333
|
-
complete: incompleteReasons.length === 0,
|
|
3334
|
-
incompleteReasons
|
|
3335
|
-
});
|
|
3336
|
-
return Object.freeze({
|
|
3337
|
-
...material,
|
|
3338
|
-
receiptDigest: hashCanonical(material)
|
|
3339
|
-
});
|
|
3340
|
-
}
|
|
3341
|
-
/** Verify the bounded receipt. Full ledger bytes are verified by SearchLedger. */
|
|
4007
|
+
/** Verify a receipt's own consistency and digest. */
|
|
3342
4008
|
function verifySearchHistoryReceipt(receipt) {
|
|
3343
4009
|
if (!receipt || typeof receipt !== "object") throw new TypeError("search history receipt is required");
|
|
3344
4010
|
if (receipt.schemaVersion !== SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION) throw new Error(`unsupported search history schemaVersion '${receipt.schemaVersion}'`);
|
|
3345
4011
|
if (receipt.kind !== "search-history-receipt") throw new Error(`search history kind must be 'search-history-receipt'`);
|
|
3346
4012
|
if (receipt.digestAlgorithm !== SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM) throw new Error(`unsupported search history digestAlgorithm '${receipt.digestAlgorithm}'`);
|
|
3347
|
-
const producerId = nonEmpty(receipt.producerId, "search history producerId");
|
|
3348
|
-
const runId = nonEmpty(receipt.runId, "search history runId");
|
|
3349
|
-
const ledger = normalizeArtifactRef(receipt.ledger);
|
|
3350
|
-
const auditDigest = ledgerHash(receipt.auditDigest, "search history auditDigest");
|
|
3351
4013
|
const summary = normalizeSummary(receipt.summary);
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
if (
|
|
3356
|
-
if (hashCanonical({
|
|
3357
|
-
schemaVersion: receipt.schemaVersion,
|
|
3358
|
-
kind: receipt.kind,
|
|
3359
|
-
digestAlgorithm: receipt.digestAlgorithm,
|
|
3360
|
-
producerId,
|
|
3361
|
-
runId,
|
|
3362
|
-
ledger,
|
|
3363
|
-
auditDigest,
|
|
3364
|
-
summary,
|
|
3365
|
-
complete: receipt.complete,
|
|
3366
|
-
incompleteReasons
|
|
3367
|
-
}) !== receipt.receiptDigest) throw new Error("search history receipt digest mismatch");
|
|
4014
|
+
if (receipt.complete !== (summary.status === "closed")) throw new Error("search history complete disagrees with its summary");
|
|
4015
|
+
if (!Array.isArray(receipt.incompleteReasons) || receipt.complete !== (receipt.incompleteReasons.length === 0)) throw new Error("search history incompleteReasons disagree with complete");
|
|
4016
|
+
const { receiptDigest, ...material } = receipt;
|
|
4017
|
+
if (hashCanonical(material) !== receiptDigest) throw new Error("search history receipt digest mismatch");
|
|
3368
4018
|
return receipt;
|
|
3369
4019
|
}
|
|
3370
|
-
/** Prove that a receipt
|
|
3371
|
-
function
|
|
4020
|
+
/** Prove that a receipt describes exactly the supplied replayed state. */
|
|
4021
|
+
function assertSearchHistoryMatchesState(receipt, state) {
|
|
3372
4022
|
verifySearchHistoryReceipt(receipt);
|
|
3373
|
-
if (
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
ledger: receipt.ledger,
|
|
3377
|
-
replay
|
|
3378
|
-
}).receiptDigest !== receipt.receiptDigest) throw new Error("search history receipt does not match the supplied SearchLedger replay");
|
|
3379
|
-
}
|
|
3380
|
-
/** Require a receipt owned by this producer and a terminal, denominator-complete history. */
|
|
4023
|
+
if (receiptFor(receipt.producerId, receipt.runId, receipt.ledger, state).receiptDigest !== receipt.receiptDigest) throw new Error("search history receipt does not match the supplied search ledger state");
|
|
4024
|
+
}
|
|
4025
|
+
/** Require a receipt owned by this producer over a closed search. */
|
|
3381
4026
|
function assertCompleteSearchHistory(producerId, receipt) {
|
|
3382
4027
|
if (receipt === void 0) throw new SearchHistoryRequiredError(producerId, []);
|
|
3383
4028
|
verifySearchHistoryReceipt(receipt);
|
|
@@ -3400,590 +4045,442 @@ function searchHistoryCoverageRow(producerId, receipt) {
|
|
|
3400
4045
|
receipt
|
|
3401
4046
|
});
|
|
3402
4047
|
}
|
|
3403
|
-
function
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
if (replay.plan !== null && (!replay.plan || typeof replay.plan !== "object")) throw new TypeError("search history replay.plan must be an event or null");
|
|
3408
|
-
if (replay.completion !== null && (!replay.completion || typeof replay.completion !== "object")) throw new TypeError("search history replay.completion must be an event or null");
|
|
3409
|
-
if (replay.audit.eventCount !== replay.entries.length) throw new Error(`search history audit eventCount ${replay.audit.eventCount} does not match ${replay.entries.length} entries`);
|
|
3410
|
-
const observedHead = replay.entries.at(-1)?.entryHash ?? null;
|
|
3411
|
-
if (replay.audit.headHash !== observedHead) throw new Error("search history audit headHash does not match the final ledger entry");
|
|
3412
|
-
return replay;
|
|
3413
|
-
}
|
|
3414
|
-
function summarizeReplay(replay) {
|
|
3415
|
-
const audit = replay.audit;
|
|
3416
|
-
return normalizeSummary({
|
|
3417
|
-
campaignId: audit.campaignId,
|
|
4048
|
+
function receiptFor(producerId, runId, ledger, state) {
|
|
4049
|
+
const { audit } = state;
|
|
4050
|
+
const summary = normalizeSummary({
|
|
4051
|
+
searchId: audit.searchId,
|
|
3418
4052
|
headHash: audit.headHash,
|
|
3419
4053
|
status: audit.status,
|
|
3420
|
-
|
|
4054
|
+
closeReason: audit.closeReason,
|
|
4055
|
+
claimDecision: state.closed?.claim?.decision ?? null,
|
|
4056
|
+
selectedNodeId: audit.selectedNodeId,
|
|
3421
4057
|
eventCount: audit.eventCount,
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
missingCandidateSlots: audit.expected.missingCandidateSlots.length,
|
|
3430
|
-
missingTaskOutcomes: audit.expected.missingTaskOutcomes.length,
|
|
3431
|
-
missingOperations: audit.expected.missingOperations.length,
|
|
3432
|
-
pendingDecisions: audit.decisions.pending,
|
|
3433
|
-
hasPlan: replay.plan !== null,
|
|
3434
|
-
hasCompletion: replay.completion !== null
|
|
4058
|
+
nodes: audit.nodes,
|
|
4059
|
+
edges: audit.edges.explicit + audit.edges.correlated + audit.edges.unknown,
|
|
4060
|
+
cells: audit.cells.allocated,
|
|
4061
|
+
openCells: audit.cells.open,
|
|
4062
|
+
openOperations: audit.operations.open,
|
|
4063
|
+
nodesWithoutEdge: audit.nodesWithoutEdge,
|
|
4064
|
+
undecidedNodes: audit.undecidedNodes
|
|
3435
4065
|
});
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
const expectedCandidateSlots = nonNegativeSafeInteger(value.expectedCandidateSlots, "search history summary.expectedCandidateSlots");
|
|
3449
|
-
const expectedTaskOutcomes = nonNegativeSafeInteger(value.expectedTaskOutcomes, "search history summary.expectedTaskOutcomes");
|
|
3450
|
-
const expectedOperations = nonNegativeSafeInteger(value.expectedOperations, "search history summary.expectedOperations");
|
|
3451
|
-
const missingCandidateSlots = nonNegativeSafeInteger(value.missingCandidateSlots, "search history summary.missingCandidateSlots");
|
|
3452
|
-
const missingTaskOutcomes = nonNegativeSafeInteger(value.missingTaskOutcomes, "search history summary.missingTaskOutcomes");
|
|
3453
|
-
const missingOperations = nonNegativeSafeInteger(value.missingOperations, "search history summary.missingOperations");
|
|
3454
|
-
const pendingDecisions = nonNegativeSafeInteger(value.pendingDecisions, "search history summary.pendingDecisions");
|
|
3455
|
-
const hasPlan = boolean(value.hasPlan, "search history summary.hasPlan");
|
|
3456
|
-
const hasCompletion = boolean(value.hasCompletion, "search history summary.hasCompletion");
|
|
3457
|
-
if (missingCandidateSlots > expectedCandidateSlots) throw new Error("search history missingCandidateSlots exceeds expectedCandidateSlots");
|
|
3458
|
-
if (missingTaskOutcomes > expectedTaskOutcomes) throw new Error("search history missingTaskOutcomes exceeds expectedTaskOutcomes");
|
|
3459
|
-
if (missingOperations > expectedOperations) throw new Error("search history missingOperations exceeds expectedOperations");
|
|
3460
|
-
if (pendingDecisions > candidateCount) throw new Error("search history pendingDecisions exceeds candidateCount");
|
|
3461
|
-
if (eventCount === 0 !== (headHash === null)) throw new Error("search history eventCount and headHash disagree");
|
|
3462
|
-
if (status === "in-progress" === hasCompletion) throw new Error("search history status and hasCompletion disagree");
|
|
3463
|
-
if (status === "selected" && selectedCandidateId === null) throw new Error("selected search history requires selectedCandidateId");
|
|
3464
|
-
if (status !== "selected" && selectedCandidateId !== null) throw new Error("non-selected search history cannot name selectedCandidateId");
|
|
4066
|
+
const material = {
|
|
4067
|
+
schemaVersion: SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION,
|
|
4068
|
+
kind: "search-history-receipt",
|
|
4069
|
+
digestAlgorithm: SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM,
|
|
4070
|
+
producerId,
|
|
4071
|
+
runId,
|
|
4072
|
+
ledger: Object.freeze({ ...ledger }),
|
|
4073
|
+
auditDigest: hashCanonical(audit),
|
|
4074
|
+
summary,
|
|
4075
|
+
complete: state.completion.complete,
|
|
4076
|
+
incompleteReasons: Object.freeze([...state.completion.reasons])
|
|
4077
|
+
};
|
|
3465
4078
|
return Object.freeze({
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
status,
|
|
3469
|
-
selectedCandidateId,
|
|
3470
|
-
eventCount,
|
|
3471
|
-
candidateCount,
|
|
3472
|
-
closedCandidateSlotCount,
|
|
3473
|
-
attemptCount,
|
|
3474
|
-
operationCount,
|
|
3475
|
-
expectedCandidateSlots,
|
|
3476
|
-
expectedTaskOutcomes,
|
|
3477
|
-
expectedOperations,
|
|
3478
|
-
missingCandidateSlots,
|
|
3479
|
-
missingTaskOutcomes,
|
|
3480
|
-
missingOperations,
|
|
3481
|
-
pendingDecisions,
|
|
3482
|
-
hasPlan,
|
|
3483
|
-
hasCompletion
|
|
4079
|
+
...material,
|
|
4080
|
+
receiptDigest: hashCanonical(material)
|
|
3484
4081
|
});
|
|
3485
4082
|
}
|
|
3486
|
-
function
|
|
3487
|
-
const
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
4083
|
+
function ledgerArtifact(path, text) {
|
|
4084
|
+
const bytes = Buffer.from(text, "utf8");
|
|
4085
|
+
return {
|
|
4086
|
+
role: "search-ledger",
|
|
4087
|
+
uri: pathToFileURL(path).href,
|
|
4088
|
+
sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
|
|
4089
|
+
byteLength: bytes.byteLength
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
function normalizeSummary(value) {
|
|
4093
|
+
if (!value || typeof value !== "object") throw new TypeError("search history summary is required");
|
|
4094
|
+
for (const key of [
|
|
4095
|
+
"eventCount",
|
|
4096
|
+
"nodes",
|
|
4097
|
+
"edges",
|
|
4098
|
+
"cells",
|
|
4099
|
+
"openCells",
|
|
4100
|
+
"openOperations",
|
|
4101
|
+
"nodesWithoutEdge",
|
|
4102
|
+
"undecidedNodes"
|
|
4103
|
+
]) {
|
|
4104
|
+
const count = value[key];
|
|
4105
|
+
if (!Number.isSafeInteger(count) || count < 0) throw new TypeError(`search history summary.${key} must be a non-negative safe integer`);
|
|
4106
|
+
}
|
|
4107
|
+
if (value.status !== "open" && value.status !== "closed") throw new TypeError(`search history summary.status is invalid: ${String(value.status)}`);
|
|
4108
|
+
if (value.eventCount === 0 !== (value.headHash === null)) throw new Error("search history eventCount and headHash disagree");
|
|
4109
|
+
if (value.status === "closed" !== (value.closeReason !== null)) throw new Error("search history status and closeReason disagree");
|
|
4110
|
+
if (value.status === "closed" && value.openCells + value.openOperations + value.nodesWithoutEdge + value.undecidedNodes > 0) throw new Error("a closed search history cannot have open work");
|
|
3502
4111
|
return Object.freeze({
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
4112
|
+
searchId: nonEmpty(value.searchId, "search history summary.searchId"),
|
|
4113
|
+
headHash: value.headHash,
|
|
4114
|
+
status: value.status,
|
|
4115
|
+
closeReason: value.closeReason,
|
|
4116
|
+
claimDecision: value.claimDecision,
|
|
4117
|
+
selectedNodeId: value.selectedNodeId,
|
|
4118
|
+
eventCount: value.eventCount,
|
|
4119
|
+
nodes: value.nodes,
|
|
4120
|
+
edges: value.edges,
|
|
4121
|
+
cells: value.cells,
|
|
4122
|
+
openCells: value.openCells,
|
|
4123
|
+
openOperations: value.openOperations,
|
|
4124
|
+
nodesWithoutEdge: value.nodesWithoutEdge,
|
|
4125
|
+
undecidedNodes: value.undecidedNodes
|
|
3507
4126
|
});
|
|
3508
4127
|
}
|
|
3509
|
-
function normalizeReasons(value) {
|
|
3510
|
-
if (!Array.isArray(value)) throw new TypeError("search history incompleteReasons must be an array");
|
|
3511
|
-
return Object.freeze(value.map((reason, index) => nonEmpty(reason, `search history incompleteReasons[${index}]`)));
|
|
3512
|
-
}
|
|
3513
|
-
function sameStrings(left, right) {
|
|
3514
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3515
|
-
}
|
|
3516
|
-
function searchStatus(value) {
|
|
3517
|
-
if (value !== "in-progress" && value !== "selected" && value !== "all-rejected") throw new TypeError(`search history summary.status is invalid: ${String(value)}`);
|
|
3518
|
-
return value;
|
|
3519
|
-
}
|
|
3520
|
-
function nullableLedgerHash(value, label) {
|
|
3521
|
-
return value === null ? null : ledgerHash(value, label);
|
|
3522
|
-
}
|
|
3523
|
-
function optionalNullableText(value, label) {
|
|
3524
|
-
return value === null ? null : nonEmpty(value, label);
|
|
3525
|
-
}
|
|
3526
|
-
function ledgerHash(value, label) {
|
|
3527
|
-
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value)) throw new TypeError(`${label} must be a lowercase sha256 digest`);
|
|
3528
|
-
return value;
|
|
3529
|
-
}
|
|
3530
4128
|
function nonEmpty(value, label) {
|
|
3531
4129
|
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) throw new TypeError(`${label} must be a trimmed non-empty string`);
|
|
3532
4130
|
return value;
|
|
3533
4131
|
}
|
|
3534
|
-
function nonNegativeSafeInteger(value, label) {
|
|
3535
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative safe integer`);
|
|
3536
|
-
return value;
|
|
3537
|
-
}
|
|
3538
|
-
function boolean(value, label) {
|
|
3539
|
-
if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean`);
|
|
3540
|
-
return value;
|
|
3541
|
-
}
|
|
3542
4132
|
//#endregion
|
|
3543
4133
|
//#region src/campaign/search-ledger-recording.ts
|
|
3544
|
-
const SEARCH_LEDGER_DIR = "search-ledger";
|
|
3545
4134
|
/**
|
|
3546
|
-
*
|
|
3547
|
-
*
|
|
4135
|
+
* `SearchRecorder`: writes a search into its ledger as it happens.
|
|
4136
|
+
*
|
|
4137
|
+
* Every loop that scores artifacts against an objective and keeps the better
|
|
4138
|
+
* ones records through this one writer: the kernel, `runOptimization`, and the
|
|
4139
|
+
* GEPA importer. Each call appends one event the moment the fact exists, so an
|
|
4140
|
+
* interrupted search leaves an exact, replayable account of what ran.
|
|
3548
4141
|
*
|
|
3549
|
-
*
|
|
3550
|
-
*
|
|
3551
|
-
* with
|
|
4142
|
+
* Content (surfaces, rationales, diffs, RunRecords) goes to content-addressed
|
|
4143
|
+
* blobs beside the ledger; the ledger holds their digests. Free text a proposer
|
|
4144
|
+
* wrote (rationales, labels) is redacted with the `share` profile before it is
|
|
4145
|
+
* hashed, so the chain never covers a secret. Surfaces are stored as they ran:
|
|
4146
|
+
* a redacted surface is not what ran.
|
|
4147
|
+
*
|
|
4148
|
+
* Every write is idempotent against the ledger's state. Ids are deterministic,
|
|
4149
|
+
* and a fact already in the ledger is not appended again, so a resumed search
|
|
4150
|
+
* continues its ledger instead of conflicting with its own history.
|
|
3552
4151
|
*/
|
|
4152
|
+
const MAX_LABEL_CHARS = 200;
|
|
4153
|
+
/** The default claim: development numbers over exactly the supplied tasks. */
|
|
4154
|
+
function developmentClaim(populationId) {
|
|
4155
|
+
return {
|
|
4156
|
+
use: "development",
|
|
4157
|
+
population: {
|
|
4158
|
+
id: populationId,
|
|
4159
|
+
description: "the tasks this search was given"
|
|
4160
|
+
},
|
|
4161
|
+
samplingFrame: "the caller-supplied task list",
|
|
4162
|
+
independentUnit: "taskId",
|
|
4163
|
+
generalization: "fixed-roster"
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
/** Deterministic edge id: one proposal of one child within one search. */
|
|
4167
|
+
function searchEdgeId(searchId, childNodeId, proposalKey) {
|
|
4168
|
+
return `edge_${createHash("sha256").update(canonicalString([
|
|
4169
|
+
searchId,
|
|
4170
|
+
childNodeId,
|
|
4171
|
+
proposalKey
|
|
4172
|
+
])).digest("hex").slice(0, 32)}`;
|
|
4173
|
+
}
|
|
4174
|
+
/** Writes one search's events into its ledger as they happen. */
|
|
3553
4175
|
var SearchRecorder = class SearchRecorder {
|
|
3554
|
-
|
|
4176
|
+
ledger;
|
|
4177
|
+
storage;
|
|
4178
|
+
blobDir;
|
|
4179
|
+
now;
|
|
3555
4180
|
tasks;
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
return recorder;
|
|
3574
|
-
}
|
|
3575
|
-
/**
|
|
3576
|
-
* Record one generation's candidate-generation call and the candidates it
|
|
3577
|
-
* produced. A proposal larger than the planned population extends the plan
|
|
3578
|
-
* with the extra slots; a proposal that fills fewer closes the rest.
|
|
3579
|
-
*/
|
|
3580
|
-
async recordGeneration(input) {
|
|
3581
|
-
const { generation, candidates } = input;
|
|
3582
|
-
const planned = this.opts.populationSize;
|
|
3583
|
-
if (candidates.length > planned) {
|
|
3584
|
-
const extra = [];
|
|
3585
|
-
for (let index = planned; index < candidates.length; index++) {
|
|
3586
|
-
const slot = {
|
|
3587
|
-
slotId: slotId(generation, index),
|
|
3588
|
-
generationOperationId: generationOperationId(generation)
|
|
3589
|
-
};
|
|
3590
|
-
extra.push(slot);
|
|
3591
|
-
this.openSlots.add(slot.slotId);
|
|
3592
|
-
}
|
|
3593
|
-
await this.append({
|
|
3594
|
-
kind: "search-plan-extended",
|
|
3595
|
-
eventId: `search:plan-extended:gen-${generation}`,
|
|
3596
|
-
occurredAt: this.stamp(),
|
|
3597
|
-
artifacts: [this.proposalArtifact(generation, candidates)],
|
|
3598
|
-
extension: {
|
|
3599
|
-
candidateSlots: extra,
|
|
3600
|
-
operations: []
|
|
3601
|
-
}
|
|
3602
|
-
});
|
|
3603
|
-
}
|
|
3604
|
-
await this.recordGenerationOperation(generation, candidates, planned - candidates.length);
|
|
3605
|
-
const parent = this.registered.get(input.parentSurfaceHash);
|
|
3606
|
-
for (const [index, candidate] of candidates.entries()) {
|
|
3607
|
-
const candidateId = candidate.surfaceHash;
|
|
3608
|
-
const slot = slotId(generation, index);
|
|
3609
|
-
const surfaceArtifact = this.writeArtifact("candidate-surface", `candidate-${candidateId}.json`, {
|
|
3610
|
-
surfaceHash: candidateId,
|
|
3611
|
-
surface: candidate.surface,
|
|
3612
|
-
label: candidate.label ?? ""
|
|
3613
|
-
});
|
|
3614
|
-
const ledgerGeneration = parent ? parent.generation + 1 : 0;
|
|
3615
|
-
await this.append({
|
|
3616
|
-
kind: "candidate-registered",
|
|
3617
|
-
eventId: `candidate:${candidateId}`,
|
|
3618
|
-
occurredAt: this.stamp(),
|
|
3619
|
-
artifacts: [surfaceArtifact],
|
|
3620
|
-
slotId: slot,
|
|
3621
|
-
generationOperationId: generationOperationId(generation),
|
|
3622
|
-
candidateId,
|
|
3623
|
-
lineage: {
|
|
3624
|
-
lineageNodeId: candidateId,
|
|
3625
|
-
parentCandidateIds: parent ? [parent.candidateId] : [],
|
|
3626
|
-
generation: ledgerGeneration,
|
|
3627
|
-
proposer: this.opts.proposerLabel,
|
|
3628
|
-
proposerSource: this.opts.binding.identity.proposer.source
|
|
3629
|
-
},
|
|
3630
|
-
surfaces: candidateSurfaces(candidate.surface, surfaceArtifact)
|
|
3631
|
-
});
|
|
3632
|
-
this.remember({
|
|
3633
|
-
candidateId,
|
|
3634
|
-
slotId: slot,
|
|
3635
|
-
generation: ledgerGeneration
|
|
3636
|
-
});
|
|
3637
|
-
}
|
|
3638
|
-
for (let index = candidates.length; index < planned; index++) await this.closeSlot(slotId(generation, index), generationOperationId(generation), {
|
|
3639
|
-
code: "proposal-short",
|
|
3640
|
-
message: `generation ${generation} proposed ${candidates.length} of ${planned} planned candidates`
|
|
3641
|
-
});
|
|
3642
|
-
}
|
|
3643
|
-
/** Append one task attempt per designed cell of each candidate campaign. */
|
|
3644
|
-
async recordResults(candidates) {
|
|
3645
|
-
for (const candidate of candidates) {
|
|
3646
|
-
const registered = this.registered.get(candidate.surfaceHash);
|
|
3647
|
-
if (!registered) throw new Error(`search ledger: candidate ${candidate.surfaceHash} produced results without a registration`);
|
|
3648
|
-
this.coverage.set(registered.candidateId, candidate.coverageComplete);
|
|
3649
|
-
const evidence = this.writeArtifact("candidate-cells", `cells-${registered.candidateId}.json`, {
|
|
3650
|
-
surfaceHash: candidate.surfaceHash,
|
|
3651
|
-
runDir: candidate.runDir,
|
|
3652
|
-
cells: candidate.cells.map((cell) => ({
|
|
3653
|
-
cellId: cell.cellId,
|
|
3654
|
-
scenarioId: cell.scenarioId,
|
|
3655
|
-
rep: cell.rep,
|
|
3656
|
-
judgeScores: cell.judgeScores,
|
|
3657
|
-
costUsd: cell.costUsd,
|
|
3658
|
-
costProvenance: cell.costProvenance,
|
|
3659
|
-
tokenUsage: cell.tokenUsage,
|
|
3660
|
-
...cell.error === void 0 ? {} : {
|
|
3661
|
-
error: cell.error,
|
|
3662
|
-
errorStage: cell.errorStage
|
|
3663
|
-
}
|
|
3664
|
-
}))
|
|
3665
|
-
});
|
|
3666
|
-
const surfaceIds = candidateSurfaces(candidate.surface, evidence).map((surface) => surface.surfaceId);
|
|
3667
|
-
for (const cell of candidate.cells) {
|
|
3668
|
-
const taskId = taskIdFor(cell.scenarioId, cell.rep);
|
|
3669
|
-
const task = this.tasks.find((planned) => planned.taskId === taskId);
|
|
3670
|
-
if (!task) throw new Error(`search ledger: cell ${cell.cellId} is outside the planned task set`);
|
|
3671
|
-
await this.append({
|
|
3672
|
-
kind: "task-attempted",
|
|
3673
|
-
eventId: `attempt:${registered.candidateId}:${taskId}`,
|
|
3674
|
-
occurredAt: this.stamp(),
|
|
3675
|
-
artifacts: [evidence],
|
|
3676
|
-
candidateId: registered.candidateId,
|
|
3677
|
-
runId: `${registered.candidateId}:${cell.cellId}`,
|
|
3678
|
-
attemptIndex: 0,
|
|
3679
|
-
task: {
|
|
3680
|
-
taskId,
|
|
3681
|
-
source: task.source
|
|
3682
|
-
},
|
|
3683
|
-
identity: {
|
|
3684
|
-
model: this.cellModel(cell),
|
|
3685
|
-
agent: this.opts.binding.identity.agent,
|
|
3686
|
-
benchmark: task.benchmark
|
|
3687
|
-
},
|
|
3688
|
-
outcome: cellOutcome(cell),
|
|
3689
|
-
accounting: cellAccounting(cell),
|
|
3690
|
-
surfaceEvidence: surfaceIds.map((surfaceId) => surfaceEvidenceFor(surfaceId, evidence))
|
|
3691
|
-
});
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
4181
|
+
lastStampMs;
|
|
4182
|
+
constructor(options, header, lastOccurredAt) {
|
|
4183
|
+
this.ledger = options.ledger;
|
|
4184
|
+
this.storage = options.storage ?? fsCampaignStorage();
|
|
4185
|
+
this.blobDir = options.blobDir ?? join(dirname(options.ledger.path), "blobs");
|
|
4186
|
+
this.now = options.now ?? Date.now;
|
|
4187
|
+
this.lastStampMs = lastOccurredAt === null ? 0 : Date.parse(lastOccurredAt);
|
|
4188
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
4189
|
+
for (const split of [
|
|
4190
|
+
"train",
|
|
4191
|
+
"selection",
|
|
4192
|
+
"test"
|
|
4193
|
+
]) for (const task of header.splits[split].tasks) tasks.set(task.taskId, {
|
|
4194
|
+
split,
|
|
4195
|
+
unitId: task.unitId
|
|
4196
|
+
});
|
|
4197
|
+
this.tasks = tasks;
|
|
3694
4198
|
}
|
|
3695
4199
|
/**
|
|
3696
|
-
*
|
|
3697
|
-
*
|
|
3698
|
-
*
|
|
3699
|
-
* The terminal event is appended only when canonical replay accounts for the
|
|
3700
|
-
* whole planned denominator. An interrupted or partly unscored search stays
|
|
3701
|
-
* `in-progress` and its receipt reports the exact gap, instead of claiming a
|
|
3702
|
-
* closed search.
|
|
4200
|
+
* Open the search: append `search-opened`, or, when the ledger already holds
|
|
4201
|
+
* one, check that it is this search and continue it.
|
|
3703
4202
|
*/
|
|
3704
|
-
async
|
|
3705
|
-
const
|
|
3706
|
-
|
|
3707
|
-
|
|
4203
|
+
static async open(options, opening, artifacts = []) {
|
|
4204
|
+
const state = await options.ledger.state();
|
|
4205
|
+
const event = {
|
|
4206
|
+
...opening,
|
|
4207
|
+
kind: "search-opened",
|
|
4208
|
+
eventId: "search-opened",
|
|
4209
|
+
occurredAt: "",
|
|
4210
|
+
artifacts,
|
|
4211
|
+
splits: {
|
|
4212
|
+
train: {
|
|
4213
|
+
taskSetDigest: searchTaskSetDigest(opening.splits.train),
|
|
4214
|
+
tasks: opening.splits.train
|
|
4215
|
+
},
|
|
4216
|
+
selection: {
|
|
4217
|
+
taskSetDigest: searchTaskSetDigest(opening.splits.selection),
|
|
4218
|
+
tasks: opening.splits.selection
|
|
4219
|
+
},
|
|
4220
|
+
test: {
|
|
4221
|
+
taskSetDigest: searchTaskSetDigest(opening.splits.test),
|
|
4222
|
+
tasks: opening.splits.test
|
|
4223
|
+
},
|
|
4224
|
+
heldOutUnits: opening.splits.heldOutUnits
|
|
4225
|
+
}
|
|
3708
4226
|
};
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
4227
|
+
if (state.header) {
|
|
4228
|
+
if (headerMaterial(state.header) !== headerMaterial(canonicalHeader(event))) throw new Error(`search ledger ${options.ledger.path} already holds a different search-opened header for ${state.searchId}; a changed search is a derived search`);
|
|
4229
|
+
return new SearchRecorder(options, state.header, state.lastOccurredAt);
|
|
3712
4230
|
}
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3715
|
-
|
|
4231
|
+
const recorder = new SearchRecorder(options, event, state.lastOccurredAt);
|
|
4232
|
+
await recorder.append({
|
|
4233
|
+
...event,
|
|
4234
|
+
occurredAt: recorder.stamp()
|
|
3716
4235
|
});
|
|
4236
|
+
return recorder;
|
|
4237
|
+
}
|
|
4238
|
+
get searchId() {
|
|
4239
|
+
return this.ledger.searchId;
|
|
4240
|
+
}
|
|
4241
|
+
/** The ledger's verified state now. */
|
|
4242
|
+
state() {
|
|
4243
|
+
return this.ledger.state();
|
|
4244
|
+
}
|
|
4245
|
+
/** Store `body` as a canonical JSON blob and return its content address. */
|
|
4246
|
+
blob(role, body) {
|
|
4247
|
+
const text = canonicalString(body);
|
|
4248
|
+
const bytes = Buffer.from(text, "utf8");
|
|
4249
|
+
const hex = createHash("sha256").update(bytes).digest("hex");
|
|
4250
|
+
const path = join(this.blobDir, `${hex}.json`);
|
|
4251
|
+
if (!this.storage.exists(path)) {
|
|
4252
|
+
this.storage.ensureDir(this.blobDir);
|
|
4253
|
+
this.storage.write(path, text);
|
|
4254
|
+
}
|
|
4255
|
+
return {
|
|
4256
|
+
role,
|
|
4257
|
+
uri: `file://${path}`,
|
|
4258
|
+
sha256: `sha256:${hex}`,
|
|
4259
|
+
byteLength: bytes.byteLength
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
/** Register a node, or return the existing node with the same artifact digest. */
|
|
4263
|
+
async registerNode(input) {
|
|
4264
|
+
const existing = (await this.ledger.state()).nodeIdForDigest(input.artifactDigest);
|
|
4265
|
+
if (existing !== void 0) return {
|
|
4266
|
+
nodeId: existing,
|
|
4267
|
+
existed: true
|
|
4268
|
+
};
|
|
4269
|
+
const nodeId = searchNodeId(this.searchId, input.artifactDigest);
|
|
3717
4270
|
await this.append({
|
|
3718
|
-
kind: "
|
|
3719
|
-
eventId:
|
|
4271
|
+
kind: "node-registered",
|
|
4272
|
+
eventId: `node:${nodeId}`,
|
|
3720
4273
|
occurredAt: this.stamp(),
|
|
3721
|
-
artifacts: [
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
source: this.opts.binding.identity.search
|
|
3727
|
-
},
|
|
3728
|
-
outcome: { status: "completed" },
|
|
3729
|
-
accounting: {
|
|
3730
|
-
tokens: {
|
|
3731
|
-
status: "known",
|
|
3732
|
-
inputTokens: 0,
|
|
3733
|
-
outputTokens: 0,
|
|
3734
|
-
cachedTokens: 0
|
|
3735
|
-
},
|
|
3736
|
-
cost: {
|
|
3737
|
-
status: "known",
|
|
3738
|
-
usd: 0,
|
|
3739
|
-
source: "free"
|
|
3740
|
-
}
|
|
3741
|
-
}
|
|
4274
|
+
artifacts: [],
|
|
4275
|
+
nodeId,
|
|
4276
|
+
artifactDigest: input.artifactDigest,
|
|
4277
|
+
artifact: input.artifact,
|
|
4278
|
+
surfaces: input.surfaces
|
|
3742
4279
|
});
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
}
|
|
4280
|
+
return {
|
|
4281
|
+
nodeId,
|
|
4282
|
+
existed: false
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
/** Record the proposal that derived a node. Returns the edge id. */
|
|
4286
|
+
async recordEdge(input) {
|
|
4287
|
+
const edgeId = searchEdgeId(this.searchId, input.childNodeId, input.proposalKey);
|
|
4288
|
+
if ((await this.ledger.state()).edge(edgeId)) return edgeId;
|
|
4289
|
+
const rationale = typeof input.rationale === "string" ? input.rationale.trim().length === 0 ? { unknown: "the proposer returned no rationale" } : this.blob("rationale", {
|
|
4290
|
+
kind: "rationale",
|
|
4291
|
+
text: redactText(input.rationale, { profile: "share" }),
|
|
4292
|
+
redaction: {
|
|
4293
|
+
profile: "share",
|
|
4294
|
+
version: REDACTION_VERSION
|
|
3759
4295
|
}
|
|
4296
|
+
}) : input.rationale;
|
|
4297
|
+
await this.append({
|
|
4298
|
+
kind: "edge-recorded",
|
|
4299
|
+
eventId: `edge:${edgeId}`,
|
|
4300
|
+
occurredAt: this.stamp(),
|
|
4301
|
+
artifacts: [],
|
|
4302
|
+
edgeId,
|
|
4303
|
+
childNodeId: input.childNodeId,
|
|
4304
|
+
parents: input.parents.map((parent) => typeof parent === "string" ? {
|
|
4305
|
+
searchId: this.searchId,
|
|
4306
|
+
nodeId: parent
|
|
4307
|
+
} : parent),
|
|
4308
|
+
operator: input.operator,
|
|
4309
|
+
attribution: input.attribution,
|
|
4310
|
+
proposer: input.proposer,
|
|
4311
|
+
selection: input.selection ?? null,
|
|
4312
|
+
rationale,
|
|
4313
|
+
diffs: input.diffs,
|
|
4314
|
+
label: boundedLabel(input.label ?? "")
|
|
3760
4315
|
});
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
4316
|
+
return edgeId;
|
|
4317
|
+
}
|
|
4318
|
+
async startOperation(input) {
|
|
4319
|
+
if ((await this.ledger.state()).operation(input.operationId)) return;
|
|
4320
|
+
await this.append({
|
|
4321
|
+
kind: "operation-started",
|
|
4322
|
+
eventId: `operation-started:${input.operationId}`,
|
|
3766
4323
|
occurredAt: this.stamp(),
|
|
3767
|
-
artifacts: [
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
} : {
|
|
3772
|
-
status: "all-rejected",
|
|
3773
|
-
reason: {
|
|
3774
|
-
code: "no-promoted-candidate",
|
|
3775
|
-
message: "no candidate beat the incumbent on the designed denominator"
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
4324
|
+
artifacts: [],
|
|
4325
|
+
operationId: input.operationId,
|
|
4326
|
+
operationKind: input.operationKind,
|
|
4327
|
+
reservation: input.reservation ?? null
|
|
3778
4328
|
});
|
|
3779
|
-
return this.receipt(input.runId);
|
|
3780
4329
|
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
ledger: {
|
|
3790
|
-
role: "search-ledger",
|
|
3791
|
-
uri: `file://${ledger.path}`,
|
|
3792
|
-
sha256: hashCanonical(bytes),
|
|
3793
|
-
byteLength: new TextEncoder().encode(bytes).byteLength
|
|
3794
|
-
},
|
|
3795
|
-
replay
|
|
4330
|
+
async recordOperation(input) {
|
|
4331
|
+
if ((await this.ledger.state()).operation(input.operationId)?.recorded) return;
|
|
4332
|
+
await this.append({
|
|
4333
|
+
...input,
|
|
4334
|
+
kind: "operation-recorded",
|
|
4335
|
+
eventId: `operation-recorded:${input.operationId}`,
|
|
4336
|
+
occurredAt: this.stamp(),
|
|
4337
|
+
artifacts: input.artifacts ?? []
|
|
3796
4338
|
});
|
|
3797
4339
|
}
|
|
3798
|
-
/**
|
|
3799
|
-
async
|
|
3800
|
-
const
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
4340
|
+
/** Plan one cell. Returns the cell id; an allocated cell is not allocated again. */
|
|
4341
|
+
async allocateCell(input) {
|
|
4342
|
+
const task = this.tasks.get(input.taskId);
|
|
4343
|
+
if (!task) throw new Error(`search ${this.searchId} declares no task ${input.taskId}`);
|
|
4344
|
+
const cellId = searchCellId(this.searchId, input.nodeId, input.taskId, input.split, input.rep);
|
|
4345
|
+
if ((await this.ledger.state()).cell(cellId)) return cellId;
|
|
4346
|
+
await this.append({
|
|
4347
|
+
kind: "cell-allocated",
|
|
4348
|
+
eventId: `cell:${cellId}`,
|
|
4349
|
+
occurredAt: this.stamp(),
|
|
4350
|
+
artifacts: [],
|
|
4351
|
+
cellId,
|
|
4352
|
+
nodeId: input.nodeId,
|
|
4353
|
+
taskId: input.taskId,
|
|
4354
|
+
unitId: task.unitId,
|
|
4355
|
+
split: input.split,
|
|
4356
|
+
rep: input.rep,
|
|
4357
|
+
stage: input.stage,
|
|
4358
|
+
lane: input.lane ?? null,
|
|
4359
|
+
reservation: input.reservation ?? null
|
|
4360
|
+
});
|
|
4361
|
+
return cellId;
|
|
3817
4362
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
};
|
|
3834
|
-
candidateSlots.push(slot);
|
|
3835
|
-
if (!this.registeredSlot(slot.slotId)) this.openSlots.add(slot.slotId);
|
|
3836
|
-
}
|
|
4363
|
+
/** Record one finished attempt at a cell. */
|
|
4364
|
+
async settleCell(input) {
|
|
4365
|
+
const cell = (await this.ledger.state()).cell(input.cellId);
|
|
4366
|
+
if (!cell) throw new Error(`cell ${input.cellId} was never allocated in ${this.searchId}`);
|
|
4367
|
+
const attempt = input.attempt ?? cell.attempts + 1;
|
|
4368
|
+
if (attempt <= cell.attempts) return;
|
|
4369
|
+
const runId = searchCellRunId({
|
|
4370
|
+
cellId: input.cellId,
|
|
4371
|
+
attempt
|
|
4372
|
+
});
|
|
4373
|
+
const artifacts = [...input.artifacts ?? []];
|
|
4374
|
+
if (input.runRecord) {
|
|
4375
|
+
const { search } = input.runRecord;
|
|
4376
|
+
if (input.runRecord.runId !== runId || search?.searchId !== this.searchId || search.nodeId !== cell.nodeId || search.cellId !== input.cellId || search.attempt !== attempt) throw new Error(`RunRecord ${input.runRecord.runId} does not carry the coordinates of ${runId}`);
|
|
4377
|
+
artifacts.push(this.blob("run-record", input.runRecord));
|
|
3837
4378
|
}
|
|
3838
4379
|
await this.append({
|
|
3839
|
-
kind: "
|
|
3840
|
-
eventId:
|
|
4380
|
+
kind: "cell-settled",
|
|
4381
|
+
eventId: `settled:${input.cellId}:${attempt}`,
|
|
3841
4382
|
occurredAt: this.stamp(),
|
|
3842
|
-
artifacts
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4383
|
+
artifacts,
|
|
4384
|
+
cellId: input.cellId,
|
|
4385
|
+
attempt,
|
|
4386
|
+
runId,
|
|
4387
|
+
outcome: input.outcome,
|
|
4388
|
+
accounting: input.accounting,
|
|
4389
|
+
boxMinutes: input.boxMinutes ?? null,
|
|
4390
|
+
wallMs: input.wallMs ?? null,
|
|
4391
|
+
queueMs: input.queueMs ?? null,
|
|
4392
|
+
placement: input.placement ?? null,
|
|
4393
|
+
identity: input.identity,
|
|
4394
|
+
surfaceEvidence: input.surfaceEvidence ?? [],
|
|
4395
|
+
traceRef: input.traceRef ?? { unknown: "the producer recorded no trace for this attempt" }
|
|
3854
4396
|
});
|
|
3855
4397
|
}
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
if (this.registered.has(entry.candidateId)) return;
|
|
3861
|
-
this.registered.set(entry.candidateId, entry);
|
|
3862
|
-
this.order.push(entry);
|
|
3863
|
-
}
|
|
3864
|
-
async recordGenerationOperation(generation, candidates, unfilled, failure) {
|
|
3865
|
-
const shortfall = {
|
|
3866
|
-
code: "proposal-short",
|
|
3867
|
-
message: `generation ${generation} left ${unfilled} planned candidate slot(s) unfilled`
|
|
3868
|
-
};
|
|
3869
|
-
const outcome = failure ? {
|
|
3870
|
-
status: "failed",
|
|
3871
|
-
failure
|
|
3872
|
-
} : unfilled <= 0 ? { status: "completed" } : candidates.length === 0 ? {
|
|
3873
|
-
status: "failed",
|
|
3874
|
-
failure: shortfall
|
|
3875
|
-
} : {
|
|
3876
|
-
status: "partial",
|
|
3877
|
-
failure: shortfall
|
|
3878
|
-
};
|
|
4398
|
+
async cancelCell(input) {
|
|
4399
|
+
const cell = (await this.ledger.state()).cell(input.cellId);
|
|
4400
|
+
if (!cell) throw new Error(`cell ${input.cellId} was never allocated in ${this.searchId}`);
|
|
4401
|
+
if (cell.cancelled || cell.final) return;
|
|
3879
4402
|
await this.append({
|
|
3880
|
-
kind: "
|
|
3881
|
-
eventId: `
|
|
4403
|
+
kind: "cell-cancelled",
|
|
4404
|
+
eventId: `cancelled:${input.cellId}`,
|
|
3882
4405
|
occurredAt: this.stamp(),
|
|
3883
|
-
artifacts: [
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
execution: this.opts.binding.identity.proposer,
|
|
3887
|
-
outcome,
|
|
3888
|
-
accounting: this.proposalAccounting()
|
|
4406
|
+
artifacts: [],
|
|
4407
|
+
cellId: input.cellId,
|
|
4408
|
+
reason: input.reason
|
|
3889
4409
|
});
|
|
3890
4410
|
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
this.
|
|
4411
|
+
/** Record a decision about a node. Repeating the node's latest decision appends nothing. */
|
|
4412
|
+
async decideNode(input) {
|
|
4413
|
+
const node = (await this.ledger.state()).node(input.nodeId);
|
|
4414
|
+
if (!node) throw new Error(`node ${input.nodeId} is not in ${this.searchId}`);
|
|
4415
|
+
const latest = node.decisions.at(-1);
|
|
4416
|
+
if (latest && canonicalString(latest.decision) === canonicalString(input.decision)) return;
|
|
3894
4417
|
await this.append({
|
|
3895
|
-
kind: "
|
|
3896
|
-
eventId: `
|
|
4418
|
+
kind: "node-decided",
|
|
4419
|
+
eventId: `decided:${input.nodeId}:${node.decisions.length}`,
|
|
3897
4420
|
occurredAt: this.stamp(),
|
|
3898
|
-
artifacts: [
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
reason
|
|
4421
|
+
artifacts: [],
|
|
4422
|
+
nodeId: input.nodeId,
|
|
4423
|
+
decision: input.decision,
|
|
4424
|
+
basis: input.basis ?? null,
|
|
4425
|
+
rule: input.rule,
|
|
4426
|
+
reason: input.reason
|
|
3905
4427
|
});
|
|
3906
4428
|
}
|
|
3907
|
-
/**
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
this.
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
return {
|
|
3919
|
-
provider: this.opts.binding.identity.model.provider,
|
|
3920
|
-
snapshot: resolved
|
|
3921
|
-
};
|
|
3922
|
-
}
|
|
3923
|
-
proposalArtifact(generation, candidates) {
|
|
3924
|
-
return this.writeArtifact("candidate-proposal", `proposal-gen-${generation}.json`, {
|
|
3925
|
-
generation,
|
|
3926
|
-
candidates: candidates.map((candidate) => ({
|
|
3927
|
-
surfaceHash: candidate.surfaceHash,
|
|
3928
|
-
label: candidate.label ?? ""
|
|
3929
|
-
}))
|
|
4429
|
+
/** Close the search. The ledger refuses a close while any cell, operation or
|
|
4430
|
+
* node decision is outstanding. */
|
|
4431
|
+
async close(input) {
|
|
4432
|
+
if ((await this.ledger.state()).closed) return;
|
|
4433
|
+
await this.append({
|
|
4434
|
+
kind: "search-closed",
|
|
4435
|
+
eventId: "search-closed",
|
|
4436
|
+
occurredAt: this.stamp(),
|
|
4437
|
+
artifacts: [],
|
|
4438
|
+
reason: input.reason,
|
|
4439
|
+
claim: input.claim
|
|
3930
4440
|
});
|
|
3931
4441
|
}
|
|
3932
|
-
/**
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
return {
|
|
3940
|
-
role,
|
|
3941
|
-
uri: `file://${path}`,
|
|
3942
|
-
sha256: hashCanonical(contents),
|
|
3943
|
-
byteLength: new TextEncoder().encode(contents).byteLength
|
|
3944
|
-
};
|
|
4442
|
+
/** A bounded receipt over the exact ledger bytes. */
|
|
4443
|
+
receipt(input) {
|
|
4444
|
+
return createSearchHistoryReceipt({
|
|
4445
|
+
...input,
|
|
4446
|
+
ledger: this.ledger,
|
|
4447
|
+
storage: this.storage
|
|
4448
|
+
});
|
|
3945
4449
|
}
|
|
3946
4450
|
async append(event) {
|
|
3947
|
-
|
|
3948
|
-
await this.opts.binding.ledger.append(event);
|
|
3949
|
-
this.durableEventIds.add(event.eventId);
|
|
4451
|
+
await this.ledger.append(event);
|
|
3950
4452
|
}
|
|
3951
4453
|
/** Non-decreasing ISO stamps; the ledger refuses an event that moves back. */
|
|
3952
4454
|
stamp() {
|
|
3953
|
-
const now =
|
|
4455
|
+
const now = this.now();
|
|
3954
4456
|
this.lastStampMs = now > this.lastStampMs ? now : this.lastStampMs + 1;
|
|
3955
4457
|
return new Date(this.lastStampMs).toISOString();
|
|
3956
4458
|
}
|
|
3957
4459
|
};
|
|
3958
|
-
/**
|
|
3959
|
-
function
|
|
3960
|
-
const
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
source: {
|
|
3964
|
-
uri: `scenario://${scenario.id}`,
|
|
3965
|
-
revision: hashCanonical(scenario)
|
|
3966
|
-
},
|
|
3967
|
-
benchmark: {
|
|
3968
|
-
uri: `campaign://${runDir}`,
|
|
3969
|
-
revision: splitDigest
|
|
3970
|
-
},
|
|
3971
|
-
maxAttempts: 1
|
|
4460
|
+
/** A mutable surface as a node: its content digest, stored bytes and declared surfaces. */
|
|
4461
|
+
function surfaceNode(recorder, surface) {
|
|
4462
|
+
const artifact = recorder.blob("surface", {
|
|
4463
|
+
kind: "mutable-surface",
|
|
4464
|
+
surface
|
|
3972
4465
|
});
|
|
3973
|
-
return
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
}
|
|
3978
|
-
function slotId(generation, index) {
|
|
3979
|
-
return `gen-${generation}-slot-${index}`;
|
|
4466
|
+
return {
|
|
4467
|
+
artifactDigest: surfaceContentHash(surface),
|
|
4468
|
+
artifact,
|
|
4469
|
+
surfaces: declaredSurfaces(surface, artifact)
|
|
4470
|
+
};
|
|
3980
4471
|
}
|
|
3981
|
-
|
|
3982
|
-
|
|
4472
|
+
/** A parent-to-child diff of two mutable surfaces, stored as a blob. */
|
|
4473
|
+
function surfaceDiff(recorder, parent, child) {
|
|
4474
|
+
return recorder.blob("diff", {
|
|
4475
|
+
kind: "surface-diff",
|
|
4476
|
+
from: surfaceContentHash(parent),
|
|
4477
|
+
to: surfaceContentHash(child),
|
|
4478
|
+
text: renderSurfaceDiff(child, parent)
|
|
4479
|
+
});
|
|
3983
4480
|
}
|
|
3984
|
-
/** Declared surfaces of one
|
|
3985
|
-
*
|
|
3986
|
-
function
|
|
4481
|
+
/** Declared surfaces of one node. A component surface declares one surface per
|
|
4482
|
+
* named component, so per-component evidence stays addressable. */
|
|
4483
|
+
function declaredSurfaces(surface, artifact) {
|
|
3987
4484
|
if (typeof surface === "string") return [{
|
|
3988
4485
|
surfaceId: "prompt",
|
|
3989
4486
|
kind: "prompt",
|
|
@@ -4000,19 +4497,269 @@ function candidateSurfaces(surface, artifact) {
|
|
|
4000
4497
|
artifact
|
|
4001
4498
|
}));
|
|
4002
4499
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4500
|
+
function boundedLabel(label) {
|
|
4501
|
+
const redacted = redactText(label.trim(), { profile: "share" });
|
|
4502
|
+
return redacted.length <= MAX_LABEL_CHARS ? redacted : `${redacted.slice(0, MAX_LABEL_CHARS - 1)}…`;
|
|
4503
|
+
}
|
|
4504
|
+
/** The header without its stamp, for comparing a resumed opening to the stored one. */
|
|
4505
|
+
function headerMaterial(header) {
|
|
4506
|
+
const { occurredAt: _occurredAt, ...material } = header;
|
|
4507
|
+
return canonicalString(material);
|
|
4508
|
+
}
|
|
4509
|
+
/** The header as the ledger would store it, so the comparison ignores ordering. */
|
|
4510
|
+
function canonicalHeader(header) {
|
|
4511
|
+
return validateSearchLedgerEvent({
|
|
4512
|
+
...header,
|
|
4513
|
+
occurredAt: "2000-01-01T00:00:00.000Z"
|
|
4514
|
+
});
|
|
4515
|
+
}
|
|
4516
|
+
//#endregion
|
|
4517
|
+
//#region src/campaign/presets/run-optimization-recording.ts
|
|
4518
|
+
var OptimizationSearch = class OptimizationSearch {
|
|
4519
|
+
recorder;
|
|
4520
|
+
binding;
|
|
4521
|
+
costLedger;
|
|
4522
|
+
scenarioIds;
|
|
4523
|
+
reps;
|
|
4524
|
+
identity;
|
|
4525
|
+
proposerName;
|
|
4526
|
+
proposalReceiptCount = 0;
|
|
4527
|
+
constructor(args) {
|
|
4528
|
+
this.recorder = args.recorder;
|
|
4529
|
+
this.binding = args.binding;
|
|
4530
|
+
this.costLedger = args.costLedger;
|
|
4531
|
+
this.scenarioIds = args.scenarioIds;
|
|
4532
|
+
this.reps = args.reps;
|
|
4533
|
+
this.identity = args.identity;
|
|
4534
|
+
this.proposerName = args.proposerName;
|
|
4535
|
+
}
|
|
4536
|
+
/** Open the search and record the measured baseline as its seeded root. */
|
|
4537
|
+
static async open(args) {
|
|
4538
|
+
const { binding } = args;
|
|
4539
|
+
const identity = {
|
|
4540
|
+
model: binding.identity.model,
|
|
4541
|
+
agent: binding.identity.agent,
|
|
4542
|
+
benchmark: {
|
|
4543
|
+
uri: "campaign://scenarios",
|
|
4544
|
+
revision: args.splitDigest
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
const train = args.scenarios.map((scenario) => ({
|
|
4548
|
+
taskId: scenario.id,
|
|
4549
|
+
unitId: scenario.id,
|
|
4550
|
+
source: {
|
|
4551
|
+
uri: `scenario://${scenario.id}`,
|
|
4552
|
+
revision: hashCanonical(scenario)
|
|
4553
|
+
}
|
|
4554
|
+
}));
|
|
4555
|
+
const recorder = await SearchRecorder.open({
|
|
4556
|
+
ledger: binding.ledger,
|
|
4557
|
+
storage: args.storage
|
|
4558
|
+
}, {
|
|
4559
|
+
subject: binding.identity.subject ?? args.proposerName,
|
|
4560
|
+
process: {
|
|
4561
|
+
name: "runOptimization",
|
|
4562
|
+
executionRef: binding.identity.search
|
|
4563
|
+
},
|
|
4564
|
+
artifactKind: "prompt",
|
|
4565
|
+
objective: {
|
|
4566
|
+
metric: "composite",
|
|
4567
|
+
direction: "maximize",
|
|
4568
|
+
judge: binding.identity.judge ?? { unknown: "runOptimization receives its judges as functions without a pinned source" },
|
|
4569
|
+
claim: binding.identity.claim ?? developmentClaim(args.splitDigest)
|
|
4570
|
+
},
|
|
4571
|
+
splits: {
|
|
4572
|
+
train,
|
|
4573
|
+
selection: [],
|
|
4574
|
+
test: [],
|
|
4575
|
+
heldOutUnits: true
|
|
4576
|
+
},
|
|
4577
|
+
policy: {
|
|
4578
|
+
expansion: args.expansion,
|
|
4579
|
+
allocation: "uniform",
|
|
4580
|
+
seed: args.seed
|
|
4581
|
+
},
|
|
4582
|
+
budget: {
|
|
4583
|
+
maxUsd: args.maxUsd,
|
|
4584
|
+
maxCells: null,
|
|
4585
|
+
maxNodes: null,
|
|
4586
|
+
deadline: null,
|
|
4587
|
+
maxConcurrency: null,
|
|
4588
|
+
reservedClaimUsd: 0
|
|
4589
|
+
},
|
|
4590
|
+
containment: null,
|
|
4591
|
+
derivedFrom: null,
|
|
4592
|
+
identity
|
|
4593
|
+
});
|
|
4594
|
+
const search = new OptimizationSearch({
|
|
4595
|
+
recorder,
|
|
4596
|
+
binding,
|
|
4597
|
+
costLedger: args.costLedger,
|
|
4598
|
+
scenarioIds: args.scenarios.map((scenario) => scenario.id),
|
|
4599
|
+
reps: args.reps,
|
|
4600
|
+
identity,
|
|
4601
|
+
proposerName: args.proposerName
|
|
4602
|
+
});
|
|
4603
|
+
const root = await recorder.registerNode(surfaceNode(recorder, args.baselineSurface));
|
|
4604
|
+
await recorder.recordEdge({
|
|
4605
|
+
childNodeId: root.nodeId,
|
|
4606
|
+
parents: [],
|
|
4607
|
+
operator: "seed",
|
|
4608
|
+
attribution: "explicit",
|
|
4609
|
+
proposer: null,
|
|
4610
|
+
proposalKey: "baseline",
|
|
4611
|
+
rationale: { unknown: "the baseline is the caller-supplied starting surface" },
|
|
4612
|
+
diffs: [],
|
|
4613
|
+
label: "baseline"
|
|
4614
|
+
});
|
|
4615
|
+
await search.recordCells(root.nodeId, "root", args.baselineCells);
|
|
4616
|
+
return {
|
|
4617
|
+
search,
|
|
4618
|
+
rootNodeId: root.nodeId
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
/** Start one generation's candidate-generation operation, before `propose()`. */
|
|
4622
|
+
async startGeneration(generation) {
|
|
4623
|
+
await this.recorder.startOperation({
|
|
4624
|
+
operationId: generationOperationId(generation),
|
|
4625
|
+
operationKind: "candidate-generation"
|
|
4626
|
+
});
|
|
4627
|
+
}
|
|
4628
|
+
/** Record the generation's proposal: its operation, then one node and edge per candidate. */
|
|
4629
|
+
async recordProposal(input) {
|
|
4630
|
+
const operationId = generationOperationId(input.generation);
|
|
4631
|
+
await this.recorder.recordOperation({
|
|
4632
|
+
operationId,
|
|
4633
|
+
operationKind: "candidate-generation",
|
|
4634
|
+
execution: this.binding.identity.proposer,
|
|
4635
|
+
outcome: { status: "completed" },
|
|
4636
|
+
accounting: this.proposalAccounting()
|
|
4637
|
+
});
|
|
4638
|
+
const nodeIds = [];
|
|
4639
|
+
for (const [index, candidate] of input.candidates.entries()) {
|
|
4640
|
+
const { nodeId } = await this.recorder.registerNode(surfaceNode(this.recorder, candidate.surface));
|
|
4641
|
+
await this.recorder.recordEdge({
|
|
4642
|
+
childNodeId: nodeId,
|
|
4643
|
+
parents: [input.parent.nodeId],
|
|
4644
|
+
operator: "improve",
|
|
4645
|
+
attribution: "explicit",
|
|
4646
|
+
proposer: {
|
|
4647
|
+
kind: "optimizer",
|
|
4648
|
+
name: this.proposerName,
|
|
4649
|
+
operationId,
|
|
4650
|
+
source: this.binding.identity.proposer.source
|
|
4651
|
+
},
|
|
4652
|
+
proposalKey: `gen-${input.generation}:candidate-${index}`,
|
|
4653
|
+
selection: {
|
|
4654
|
+
rule: input.selectionRule,
|
|
4655
|
+
evidence: input.parent.composite === null ? {} : { parentComposite: input.parent.composite }
|
|
4656
|
+
},
|
|
4657
|
+
rationale: candidate.rationale,
|
|
4658
|
+
diffs: [surfaceDiff(this.recorder, input.parent.surface, candidate.surface)],
|
|
4659
|
+
label: candidate.label
|
|
4660
|
+
});
|
|
4661
|
+
nodeIds.push(nodeId);
|
|
4662
|
+
}
|
|
4663
|
+
return nodeIds;
|
|
4664
|
+
}
|
|
4665
|
+
/** Close a generation whose proposer produced nothing or failed. */
|
|
4666
|
+
async failGeneration(generation, message) {
|
|
4667
|
+
await this.recorder.recordOperation({
|
|
4668
|
+
operationId: generationOperationId(generation),
|
|
4669
|
+
operationKind: "candidate-generation",
|
|
4670
|
+
execution: this.binding.identity.proposer,
|
|
4671
|
+
outcome: {
|
|
4672
|
+
status: "failed",
|
|
4673
|
+
failure: {
|
|
4674
|
+
code: "no-candidates",
|
|
4675
|
+
message
|
|
4676
|
+
}
|
|
4677
|
+
},
|
|
4678
|
+
accounting: this.proposalAccounting()
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4681
|
+
/** Allocate every designed cell of a node before its campaign runs. */
|
|
4682
|
+
async allocate(nodeId) {
|
|
4683
|
+
for (const taskId of this.scenarioIds) for (let rep = 0; rep < this.reps; rep++) await this.recorder.allocateCell({
|
|
4684
|
+
nodeId,
|
|
4685
|
+
taskId,
|
|
4686
|
+
split: "train",
|
|
4687
|
+
rep,
|
|
4688
|
+
stage: "train"
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
/** Settle a node's campaign cells; allocates any the campaign ran beyond the design. */
|
|
4692
|
+
async recordCells(nodeId, stage, cells) {
|
|
4693
|
+
for (const cell of cells) {
|
|
4694
|
+
const cellId = await this.recorder.allocateCell({
|
|
4695
|
+
nodeId,
|
|
4696
|
+
taskId: cell.scenarioId,
|
|
4697
|
+
split: "train",
|
|
4698
|
+
rep: cell.rep,
|
|
4699
|
+
stage
|
|
4700
|
+
});
|
|
4701
|
+
await this.recorder.settleCell({
|
|
4702
|
+
cellId,
|
|
4703
|
+
attempt: 1,
|
|
4704
|
+
outcome: cellOutcome(cell),
|
|
4705
|
+
accounting: cellAccounting(cell),
|
|
4706
|
+
identity: {
|
|
4707
|
+
...this.identity,
|
|
4708
|
+
model: this.cellModel(cell)
|
|
4709
|
+
},
|
|
4710
|
+
wallMs: cell.durationMs,
|
|
4711
|
+
traceRef: { unknown: "runCampaign reports no trace id per cell" }
|
|
4712
|
+
});
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
/**
|
|
4716
|
+
* Decide every node and close. The winner is selected; every other node is
|
|
4717
|
+
* rejected. Allocated cells a campaign never returned are cancelled.
|
|
4718
|
+
*/
|
|
4719
|
+
async finish(input) {
|
|
4720
|
+
const openCells = (await this.recorder.state()).cells().filter((cell) => cell.attempts === 0 && !cell.cancelled);
|
|
4721
|
+
for (const cell of openCells) await this.recorder.cancelCell({
|
|
4722
|
+
cellId: cell.cellId,
|
|
4723
|
+
reason: "aborted"
|
|
4724
|
+
});
|
|
4725
|
+
for (const node of (await this.recorder.state()).nodes()) await this.recorder.decideNode({
|
|
4726
|
+
nodeId: node.nodeId,
|
|
4727
|
+
decision: node.nodeId === input.winnerNodeId ? { status: "selected" } : { status: "rejected" },
|
|
4728
|
+
rule: "incumbent",
|
|
4729
|
+
reason: node.nodeId === input.winnerNodeId ? "the incumbent when the loop stopped" : node.ordinal === 0 || input.promotedNodeIds.has(node.nodeId) ? "a later candidate replaced it as the incumbent" : input.incompleteNodeIds.has(node.nodeId) ? "the candidate missed a designed cell and could not be ranked" : "the candidate did not beat the incumbent"
|
|
4730
|
+
});
|
|
4731
|
+
await this.recorder.close({
|
|
4732
|
+
reason: input.reason,
|
|
4733
|
+
claim: null
|
|
4734
|
+
});
|
|
4735
|
+
return this.recorder.receipt({
|
|
4736
|
+
producerId: this.proposerName,
|
|
4737
|
+
runId: input.runId
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4740
|
+
/** Spend booked to candidate generation since the previous generation. */
|
|
4741
|
+
proposalAccounting() {
|
|
4742
|
+
const receipts = this.costLedger.list({ phase: "search.proposal" });
|
|
4743
|
+
const fresh = receipts.slice(this.proposalReceiptCount);
|
|
4744
|
+
this.proposalReceiptCount = receipts.length;
|
|
4745
|
+
return receiptAccounting(fresh);
|
|
4746
|
+
}
|
|
4747
|
+
cellModel(cell) {
|
|
4748
|
+
const resolved = cell.resolvedModel;
|
|
4749
|
+
if (resolved === void 0) return this.identity.model;
|
|
4750
|
+
const provider = this.identity.model.provider;
|
|
4751
|
+
return modelHasSnapshot(resolved) ? {
|
|
4752
|
+
provider,
|
|
4753
|
+
snapshot: resolved
|
|
4754
|
+
} : {
|
|
4755
|
+
provider,
|
|
4756
|
+
alias: resolved,
|
|
4757
|
+
unknown: "the provider reported a moving alias, not a snapshot"
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
};
|
|
4761
|
+
function generationOperationId(generation) {
|
|
4762
|
+
return `candidate-generation:gen-${generation}`;
|
|
4016
4763
|
}
|
|
4017
4764
|
function cellOutcome(cell) {
|
|
4018
4765
|
const scores = Object.entries(cell.judgeScores).filter(([, score]) => score.failed !== true && Number.isFinite(score.composite));
|
|
@@ -4093,264 +4840,6 @@ function receiptAccounting(receipts) {
|
|
|
4093
4840
|
}
|
|
4094
4841
|
};
|
|
4095
4842
|
}
|
|
4096
|
-
/**
|
|
4097
|
-
* Record an optimizer's own candidate graph into the same ledger.
|
|
4098
|
-
*
|
|
4099
|
-
* A complete optimization method searches inside its own process and reports
|
|
4100
|
-
* one artifact when it finishes: the candidate population, with each
|
|
4101
|
-
* candidate's parents and its score per selection scenario. This turns that
|
|
4102
|
-
* artifact into the canonical event stream, so a first-party method returns
|
|
4103
|
-
* the same `SearchHistoryReceipt` the in-process loop returns, and
|
|
4104
|
-
* `compareOptimizationMethods({ searchHistoryPolicy: 'require-complete' })`
|
|
4105
|
-
* accepts it.
|
|
4106
|
-
*
|
|
4107
|
-
* A candidate the optimizer left unscored on a planned scenario leaves the
|
|
4108
|
-
* planned denominator open, so the receipt reports the gap instead of closing
|
|
4109
|
-
* the search.
|
|
4110
|
-
*/
|
|
4111
|
-
async function recordCandidatePopulationSearch(input) {
|
|
4112
|
-
const { population, ledger, identity } = input;
|
|
4113
|
-
const stamps = monotonicStamps();
|
|
4114
|
-
const writeArtifact = artifactWriter(input.storage, input.runDir);
|
|
4115
|
-
const populationArtifact = {
|
|
4116
|
-
role: "candidate-population",
|
|
4117
|
-
uri: `file://${population.summary.path}`,
|
|
4118
|
-
sha256: population.summary.sha256,
|
|
4119
|
-
byteLength: population.summary.bytes
|
|
4120
|
-
};
|
|
4121
|
-
const tasks = input.scenarios.filter((scenario) => population.summary.scenarioIds.includes(scenario.id)).map((scenario) => ({
|
|
4122
|
-
taskId: taskIdFor(scenario.id, 0),
|
|
4123
|
-
source: {
|
|
4124
|
-
uri: `scenario://${scenario.id}`,
|
|
4125
|
-
revision: hashCanonical(scenario)
|
|
4126
|
-
},
|
|
4127
|
-
benchmark: {
|
|
4128
|
-
uri: `optimizer://${input.producerId}`,
|
|
4129
|
-
revision: population.summary.sha256
|
|
4130
|
-
},
|
|
4131
|
-
maxAttempts: 1
|
|
4132
|
-
}));
|
|
4133
|
-
if (tasks.length !== population.summary.scenarioIds.length) throw new Error(`search ledger: the candidate population scored ${population.summary.scenarioIds.length} scenarios, but ${tasks.length} were supplied`);
|
|
4134
|
-
const generationOperation = "candidate-generation:population";
|
|
4135
|
-
await ledger.append({
|
|
4136
|
-
kind: "search-planned",
|
|
4137
|
-
eventId: "search:plan",
|
|
4138
|
-
occurredAt: stamps(),
|
|
4139
|
-
artifacts: [populationArtifact],
|
|
4140
|
-
plan: {
|
|
4141
|
-
candidateSlots: population.candidates.map((candidate) => ({
|
|
4142
|
-
slotId: `candidate-${candidate.index}`,
|
|
4143
|
-
generationOperationId: generationOperation
|
|
4144
|
-
})),
|
|
4145
|
-
tasks,
|
|
4146
|
-
operations: [{
|
|
4147
|
-
operationId: generationOperation,
|
|
4148
|
-
kind: "candidate-generation"
|
|
4149
|
-
}, {
|
|
4150
|
-
operationId: "selection",
|
|
4151
|
-
kind: "selection"
|
|
4152
|
-
}]
|
|
4153
|
-
}
|
|
4154
|
-
});
|
|
4155
|
-
await ledger.append({
|
|
4156
|
-
kind: "search-operation-recorded",
|
|
4157
|
-
eventId: `operation:${generationOperation}`,
|
|
4158
|
-
occurredAt: stamps(),
|
|
4159
|
-
artifacts: [populationArtifact],
|
|
4160
|
-
operationId: generationOperation,
|
|
4161
|
-
operationKind: "candidate-generation",
|
|
4162
|
-
execution: identity.proposer,
|
|
4163
|
-
outcome: { status: "completed" },
|
|
4164
|
-
accounting: input.generationAccounting
|
|
4165
|
-
});
|
|
4166
|
-
const candidateIds = /* @__PURE__ */ new Map();
|
|
4167
|
-
const depths = /* @__PURE__ */ new Map();
|
|
4168
|
-
for (const candidate of population.candidates) {
|
|
4169
|
-
const parents = candidate.parentIndices.filter((index) => index !== null);
|
|
4170
|
-
const candidateId = candidate.candidateHash;
|
|
4171
|
-
candidateIds.set(candidate.index, candidateId);
|
|
4172
|
-
const depth = parents.length === 0 ? 0 : Math.max(...parents.map((index) => depths.get(index) ?? 0)) + 1;
|
|
4173
|
-
depths.set(candidate.index, depth);
|
|
4174
|
-
const surfaceArtifact = writeArtifact("candidate-surface", `candidate-${candidate.index}.json`, {
|
|
4175
|
-
index: candidate.index,
|
|
4176
|
-
candidateDigest: candidate.candidateDigest,
|
|
4177
|
-
candidate: candidate.candidate
|
|
4178
|
-
});
|
|
4179
|
-
await ledger.append({
|
|
4180
|
-
kind: "candidate-registered",
|
|
4181
|
-
eventId: `candidate:${candidate.index}`,
|
|
4182
|
-
occurredAt: stamps(),
|
|
4183
|
-
artifacts: [surfaceArtifact],
|
|
4184
|
-
slotId: `candidate-${candidate.index}`,
|
|
4185
|
-
generationOperationId: generationOperation,
|
|
4186
|
-
candidateId,
|
|
4187
|
-
lineage: {
|
|
4188
|
-
lineageNodeId: candidateId.slice(0, 16),
|
|
4189
|
-
parentCandidateIds: parents.map((index) => {
|
|
4190
|
-
const parentId = candidateIds.get(index);
|
|
4191
|
-
if (!parentId) throw new Error(`search ledger: candidate ${candidate.index} names parent ${index}, which precedes no registered candidate`);
|
|
4192
|
-
return parentId;
|
|
4193
|
-
}),
|
|
4194
|
-
generation: depth,
|
|
4195
|
-
proposer: input.producerId,
|
|
4196
|
-
proposerSource: identity.proposer.source
|
|
4197
|
-
},
|
|
4198
|
-
surfaces: candidateSurfaces(externalSurface(candidate.candidate), surfaceArtifact)
|
|
4199
|
-
});
|
|
4200
|
-
const surfaceIds = candidateSurfaces(externalSurface(candidate.candidate), surfaceArtifact).map((surface) => surface.surfaceId);
|
|
4201
|
-
for (const score of candidate.selectionScores) {
|
|
4202
|
-
const task = tasks.find((planned) => planned.taskId === taskIdFor(score.scenarioId, 0));
|
|
4203
|
-
if (!task) throw new Error(`search ledger: candidate ${candidate.index} scored unplanned scenario ${score.scenarioId}`);
|
|
4204
|
-
await ledger.append({
|
|
4205
|
-
kind: "task-attempted",
|
|
4206
|
-
eventId: `attempt:${candidate.index}:${task.taskId}`,
|
|
4207
|
-
occurredAt: stamps(),
|
|
4208
|
-
artifacts: [populationArtifact],
|
|
4209
|
-
candidateId,
|
|
4210
|
-
runId: `${input.runId}:${candidate.index}:${task.taskId}`,
|
|
4211
|
-
attemptIndex: 0,
|
|
4212
|
-
task: {
|
|
4213
|
-
taskId: task.taskId,
|
|
4214
|
-
source: task.source
|
|
4215
|
-
},
|
|
4216
|
-
identity: {
|
|
4217
|
-
model: identity.model,
|
|
4218
|
-
agent: identity.agent,
|
|
4219
|
-
benchmark: task.benchmark
|
|
4220
|
-
},
|
|
4221
|
-
outcome: {
|
|
4222
|
-
status: "passed",
|
|
4223
|
-
score: score.score,
|
|
4224
|
-
metrics: { composite: score.score }
|
|
4225
|
-
},
|
|
4226
|
-
accounting: {
|
|
4227
|
-
tokens: {
|
|
4228
|
-
status: "unknown",
|
|
4229
|
-
reason: "the optimizer reports no token usage per candidate evaluation"
|
|
4230
|
-
},
|
|
4231
|
-
cost: {
|
|
4232
|
-
status: "unknown",
|
|
4233
|
-
knownLowerBoundUsd: 0,
|
|
4234
|
-
reason: "the optimizer reports no cost per candidate evaluation"
|
|
4235
|
-
}
|
|
4236
|
-
},
|
|
4237
|
-
surfaceEvidence: surfaceIds.map((surfaceId) => surfaceEvidenceFor(surfaceId, populationArtifact))
|
|
4238
|
-
});
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
await ledger.append({
|
|
4242
|
-
kind: "search-operation-recorded",
|
|
4243
|
-
eventId: "operation:selection",
|
|
4244
|
-
occurredAt: stamps(),
|
|
4245
|
-
artifacts: [populationArtifact],
|
|
4246
|
-
operationId: "selection",
|
|
4247
|
-
operationKind: "selection",
|
|
4248
|
-
execution: {
|
|
4249
|
-
kind: "deterministic",
|
|
4250
|
-
source: identity.search
|
|
4251
|
-
},
|
|
4252
|
-
outcome: { status: "completed" },
|
|
4253
|
-
accounting: {
|
|
4254
|
-
tokens: {
|
|
4255
|
-
status: "known",
|
|
4256
|
-
inputTokens: 0,
|
|
4257
|
-
outputTokens: 0,
|
|
4258
|
-
cachedTokens: 0
|
|
4259
|
-
},
|
|
4260
|
-
cost: {
|
|
4261
|
-
status: "known",
|
|
4262
|
-
usd: 0,
|
|
4263
|
-
source: "free"
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
});
|
|
4267
|
-
const winnerId = candidateIds.get(population.bestIndex);
|
|
4268
|
-
for (const candidate of population.candidates) await ledger.append({
|
|
4269
|
-
kind: "candidate-decided",
|
|
4270
|
-
eventId: `decision:${candidate.index}`,
|
|
4271
|
-
occurredAt: stamps(),
|
|
4272
|
-
artifacts: [populationArtifact],
|
|
4273
|
-
candidateId: candidateIds.get(candidate.index),
|
|
4274
|
-
decision: candidate.index === population.bestIndex ? { status: "selected" } : {
|
|
4275
|
-
status: "rejected",
|
|
4276
|
-
reason: {
|
|
4277
|
-
code: "not-selected",
|
|
4278
|
-
message: "the optimizer selected another candidate"
|
|
4279
|
-
}
|
|
4280
|
-
}
|
|
4281
|
-
});
|
|
4282
|
-
const replay = await ledger.replay();
|
|
4283
|
-
const { missingCandidateSlots, missingTaskOutcomes, missingOperations } = replay.audit.expected;
|
|
4284
|
-
if (winnerId && missingCandidateSlots.length === 0 && missingTaskOutcomes.length === 0 && missingOperations.length === 0 && replay.audit.decisions.pending === 0) await ledger.append({
|
|
4285
|
-
kind: "search-completed",
|
|
4286
|
-
eventId: "search:completed",
|
|
4287
|
-
occurredAt: stamps(),
|
|
4288
|
-
artifacts: [populationArtifact],
|
|
4289
|
-
result: {
|
|
4290
|
-
status: "selected",
|
|
4291
|
-
candidateId: winnerId
|
|
4292
|
-
}
|
|
4293
|
-
});
|
|
4294
|
-
const final = await ledger.replay();
|
|
4295
|
-
const bytes = final.entries.map((entry) => `${canonicalString(entry)}\n`).join("");
|
|
4296
|
-
return createSearchHistoryReceipt({
|
|
4297
|
-
producerId: input.producerId,
|
|
4298
|
-
runId: input.runId,
|
|
4299
|
-
ledger: {
|
|
4300
|
-
role: "search-ledger",
|
|
4301
|
-
uri: `file://${ledger.path}`,
|
|
4302
|
-
sha256: hashCanonical(bytes),
|
|
4303
|
-
byteLength: new TextEncoder().encode(bytes).byteLength
|
|
4304
|
-
},
|
|
4305
|
-
replay: final
|
|
4306
|
-
});
|
|
4307
|
-
}
|
|
4308
|
-
/** An optimizer candidate as the canonical mutable surface it represents. */
|
|
4309
|
-
function externalSurface(candidate) {
|
|
4310
|
-
return typeof candidate === "string" ? candidate : {
|
|
4311
|
-
kind: "components",
|
|
4312
|
-
components: candidate
|
|
4313
|
-
};
|
|
4314
|
-
}
|
|
4315
|
-
function artifactWriter(storage, runDir) {
|
|
4316
|
-
const directory = `${runDir}/${SEARCH_LEDGER_DIR}`;
|
|
4317
|
-
return (role, name, body) => {
|
|
4318
|
-
const contents = canonicalString(body);
|
|
4319
|
-
storage.ensureDir(directory);
|
|
4320
|
-
storage.write(`${directory}/${name}`, contents);
|
|
4321
|
-
return {
|
|
4322
|
-
role,
|
|
4323
|
-
uri: `file://${directory}/${name}`,
|
|
4324
|
-
sha256: hashCanonical(contents),
|
|
4325
|
-
byteLength: new TextEncoder().encode(contents).byteLength
|
|
4326
|
-
};
|
|
4327
|
-
};
|
|
4328
|
-
}
|
|
4329
|
-
/** Non-decreasing ISO stamps; the ledger refuses an event that moves back. */
|
|
4330
|
-
function monotonicStamps() {
|
|
4331
|
-
let last = 0;
|
|
4332
|
-
return () => {
|
|
4333
|
-
const now = Date.now();
|
|
4334
|
-
last = now > last ? now : last + 1;
|
|
4335
|
-
return new Date(last).toISOString();
|
|
4336
|
-
};
|
|
4337
|
-
}
|
|
4338
|
-
//#endregion
|
|
4339
|
-
//#region src/campaign/types.ts
|
|
4340
|
-
/** Type guard: a proposal carrying its rationale vs a bare
|
|
4341
|
-
* surface. The loop branches on this to populate `GenerationCandidate`. */
|
|
4342
|
-
function isProposedCandidate(value) {
|
|
4343
|
-
return typeof value === "object" && value !== null && "surface" in value && "label" in value && "rationale" in value;
|
|
4344
|
-
}
|
|
4345
|
-
const LABEL_TRUST_RANK = {
|
|
4346
|
-
unverified: 0,
|
|
4347
|
-
"verified-signal": 1,
|
|
4348
|
-
"human-rated": 2
|
|
4349
|
-
};
|
|
4350
|
-
/** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
|
|
4351
|
-
function labelTrustRank(trust) {
|
|
4352
|
-
return LABEL_TRUST_RANK[trust ?? "unverified"];
|
|
4353
|
-
}
|
|
4354
4843
|
//#endregion
|
|
4355
4844
|
//#region src/campaign/presets/run-optimization.ts
|
|
4356
4845
|
/**
|
|
@@ -4406,18 +4895,26 @@ async function runOptimization(opts) {
|
|
|
4406
4895
|
const label = opts.premeasuredBaseline ? "premeasured baseline" : "baseline";
|
|
4407
4896
|
throw new Error(`runOptimization: ${label} is incomplete (${baselineCoverage.scorableCellIds.length}/${baselineCoverage.expectedCellIds.length} designed cells scorable) — ${formatCoverageFailures(baselineCoverage)}. Refusing to optimize against an incomplete incumbent.`);
|
|
4408
4897
|
}
|
|
4409
|
-
const
|
|
4898
|
+
const opened = opts.searchLedger ? await OptimizationSearch.open({
|
|
4410
4899
|
binding: opts.searchLedger,
|
|
4411
4900
|
storage,
|
|
4412
|
-
|
|
4901
|
+
costLedger,
|
|
4413
4902
|
scenarios: opts.scenarios,
|
|
4414
4903
|
reps,
|
|
4415
|
-
|
|
4416
|
-
populationSize: opts.populationSize,
|
|
4904
|
+
seed: opts.seed ?? 42,
|
|
4417
4905
|
splitDigest: baselineCampaign.splitDigest,
|
|
4418
|
-
|
|
4419
|
-
|
|
4906
|
+
proposerName: proposer.kind,
|
|
4907
|
+
expansion: opts.selectParent ? "select-parent" : "incumbent",
|
|
4908
|
+
maxUsd: opts.costCeiling ?? null,
|
|
4909
|
+
baselineSurface,
|
|
4910
|
+
baselineCells: baselineCampaign.cells
|
|
4420
4911
|
}) : void 0;
|
|
4912
|
+
const search = opened?.search;
|
|
4913
|
+
const nodeIdByHash = /* @__PURE__ */ new Map();
|
|
4914
|
+
if (opened) nodeIdByHash.set(surfaceHash(baselineSurface), opened.rootNodeId);
|
|
4915
|
+
const incompleteNodeIds = /* @__PURE__ */ new Set();
|
|
4916
|
+
const promotedNodeIds = /* @__PURE__ */ new Set();
|
|
4917
|
+
let stopReason = "max-nodes";
|
|
4421
4918
|
const generations = [];
|
|
4422
4919
|
const history = [];
|
|
4423
4920
|
let currentFindings = initialFindings;
|
|
@@ -4455,7 +4952,10 @@ async function runOptimization(opts) {
|
|
|
4455
4952
|
}
|
|
4456
4953
|
for (let gen = 0; gen < opts.maxGenerations; gen++) {
|
|
4457
4954
|
const proposalHistory = immutableProposalSnapshot(history, "history");
|
|
4458
|
-
if (proposer.decide?.({ history: proposalHistory }).stop)
|
|
4955
|
+
if (proposer.decide?.({ history: proposalHistory }).stop) {
|
|
4956
|
+
stopReason = "converged";
|
|
4957
|
+
break;
|
|
4958
|
+
}
|
|
4459
4959
|
const paretoParents = immutableProposalSnapshot(computeParetoFrontier(scored), "Pareto parents");
|
|
4460
4960
|
const incumbentOutcome = immutableProposalSnapshot(winnerOutcome, "incumbent outcome");
|
|
4461
4961
|
const selected = opts.selectParent ? resolveSelectedParent(opts.selectParent(Object.freeze({
|
|
@@ -4483,10 +4983,15 @@ async function runOptimization(opts) {
|
|
|
4483
4983
|
costLedger,
|
|
4484
4984
|
costPhase: "search.proposal"
|
|
4485
4985
|
});
|
|
4986
|
+
await search?.startGeneration(gen);
|
|
4486
4987
|
const proposed = await proposer.propose(proposalContext);
|
|
4487
4988
|
if (!Array.isArray(proposed)) throw new TypeError("runOptimization: proposer must return an array");
|
|
4488
4989
|
const proposalSnapshot = immutableProposalSnapshot(proposed, "candidate outputs");
|
|
4489
|
-
if (proposalSnapshot.length === 0)
|
|
4990
|
+
if (proposalSnapshot.length === 0) {
|
|
4991
|
+
await search?.failGeneration(gen, "the proposer returned no candidates");
|
|
4992
|
+
stopReason = "converged";
|
|
4993
|
+
break;
|
|
4994
|
+
}
|
|
4490
4995
|
const candidates = proposalSnapshot.map((p) => isProposedCandidate(p) ? p : {
|
|
4491
4996
|
surface: p,
|
|
4492
4997
|
label: "",
|
|
@@ -4499,15 +5004,22 @@ async function runOptimization(opts) {
|
|
|
4499
5004
|
generationHashes.add(hash);
|
|
4500
5005
|
}
|
|
4501
5006
|
for (const hash of generationHashes) admittedCandidateHashes.add(hash);
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
5007
|
+
if (search) {
|
|
5008
|
+
const nodeIds = await search.recordProposal({
|
|
5009
|
+
generation: gen,
|
|
5010
|
+
parent: {
|
|
5011
|
+
nodeId: nodeIdByHash.get(parentSurfaceHash),
|
|
5012
|
+
surface: parentSurface,
|
|
5013
|
+
composite: parentComposite
|
|
5014
|
+
},
|
|
5015
|
+
selectionRule: opts.selectParent ? "select-parent" : "incumbent",
|
|
5016
|
+
candidates
|
|
5017
|
+
});
|
|
5018
|
+
for (const [index, nodeId] of nodeIds.entries()) {
|
|
5019
|
+
nodeIdByHash.set(surfaceHash(candidates[index].surface), nodeId);
|
|
5020
|
+
await search.allocate(nodeId);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
4511
5023
|
const surfaceResults = await mapConcurrent(candidates, candidateConcurrency, async ({ surface, label, rationale, attribution }, i, signal) => {
|
|
4512
5024
|
const hash = surfaceHash(surface);
|
|
4513
5025
|
const campaign = await runCampaign({
|
|
@@ -4546,13 +5058,11 @@ async function runOptimization(opts) {
|
|
|
4546
5058
|
});
|
|
4547
5059
|
}
|
|
4548
5060
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
coverageComplete: result.coverage.complete
|
|
4555
|
-
})));
|
|
5061
|
+
if (search) for (const result of surfaceResults) {
|
|
5062
|
+
const nodeId = nodeIdByHash.get(result.surfaceHash);
|
|
5063
|
+
await search.recordCells(nodeId, "train", result.campaign.cells);
|
|
5064
|
+
if (!result.coverage.complete) incompleteNodeIds.add(nodeId);
|
|
5065
|
+
}
|
|
4556
5066
|
surfaceResults.sort((a, b) => {
|
|
4557
5067
|
if (a.coverage.complete !== b.coverage.complete) return a.coverage.complete ? -1 : 1;
|
|
4558
5068
|
if (a.rankKey && b.rankKey) return compareRankKeys(b.rankKey, a.rankKey);
|
|
@@ -4562,6 +5072,8 @@ async function runOptimization(opts) {
|
|
|
4562
5072
|
const promoted = top && compareRankKeys(top.rankKey, winnerRankKey) > 0 ? [top] : [];
|
|
4563
5073
|
if (promoted[0]) {
|
|
4564
5074
|
const top = promoted[0];
|
|
5075
|
+
const promotedNodeId = nodeIdByHash.get(top.surfaceHash);
|
|
5076
|
+
if (promotedNodeId) promotedNodeIds.add(promotedNodeId);
|
|
4565
5077
|
winnerSurface = top.surface;
|
|
4566
5078
|
winnerSurfaceHash = top.surfaceHash;
|
|
4567
5079
|
winnerComposite = top.composite;
|
|
@@ -4623,9 +5135,11 @@ async function runOptimization(opts) {
|
|
|
4623
5135
|
currentFindings = immutableProposalSnapshot(assertProposalFindings(fresh, "runOptimization generation analysis findings"), "generation analysis findings");
|
|
4624
5136
|
}
|
|
4625
5137
|
}
|
|
4626
|
-
const searchHistory = await
|
|
4627
|
-
winnerSurfaceHash,
|
|
4628
|
-
|
|
5138
|
+
const searchHistory = await search?.finish({
|
|
5139
|
+
winnerNodeId: nodeIdByHash.get(winnerSurfaceHash),
|
|
5140
|
+
promotedNodeIds,
|
|
5141
|
+
incompleteNodeIds,
|
|
5142
|
+
reason: stopReason,
|
|
4629
5143
|
runId: opts.runDir
|
|
4630
5144
|
});
|
|
4631
5145
|
return {
|
|
@@ -6156,6 +6670,6 @@ function firstString(value) {
|
|
|
6156
6670
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
6157
6671
|
}
|
|
6158
6672
|
//#endregion
|
|
6159
|
-
export {
|
|
6673
|
+
export { scoreRedTeamOutput as $, FileSearchLedger as A, SEARCH_LEDGER_SCHEMA as B, assertCompleteSearchHistory as C, searchHistoryCoverageRow as D, createSearchHistoryReceipt as E, SearchStateView as F, paretoFrontierWithCrowding as G, labelTrustRank as H, searchCellId as I, captureJudge as J, runFinalComparison as K, searchNodeId as L, replaySearchLedgerText as M, validateSearchLedgerEvent as N, verifySearchHistoryArtifact as O, SearchState as P, redTeamReport as Q, searchTaskSetDigest as R, SearchHistoryRequiredError as S, assertSearchHistoryMatchesState as T, dominates as U, isProposedCandidate as V, paretoFrontier as W, DEFAULT_RED_TEAM_CORPUS as X, defaultProductionGate as Y, redTeamDataset as Z, SearchRecorder as _, transientDispatchFailure as a, runEval as at, surfaceDiff as b, compareOptimizationMethods as c, resolveRunDir as ct, combineComparisonCosts as d, buildCellSchedule as dt, runCanaries as et, costFromLedgerSummary as f, cellCachePath as ft, runOptimization as g, runImprovementLoop as h, quotaExhaustedUntil as i, reserveFinalEvidence as it, openSearchLedger as j, verifySearchHistoryReceipt as k, optimizationTokenUsageFromSummary as l, tangleTracesRoot as lt, readGepaCandidatePopulationArtifact as m, JudgeParseError as n, captureFinalEvidencePolicy as nt, aggregateRunScore as o, runCampaign as ot, assertGepaCandidatePopulationSummary as p, computeManifestHash as pt, openAutoPr as q, isTransientTransportFailure as r, evaluationUnitMap as rt, clamp01 as s, planCampaignRun as st, llmJudge as t, assertIndependentEvaluationSplit as tt, executeOptimizationMethod as u, readCachedCell as ut, developmentClaim as v, assertSearchHistoryAdmissionOptions as w, surfaceNode as x, searchEdgeId as y, searchUnitScores as z };
|
|
6160
6674
|
|
|
6161
|
-
//# sourceMappingURL=llm-judge-
|
|
6675
|
+
//# sourceMappingURL=llm-judge-BnxAv7ed.js.map
|