@tangle-network/agent-eval 0.188.0 → 0.190.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +3 -2
- package/dist/adapters/http.d.ts +11 -2
- 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/{agent-profile-CfbuZk8b.d.ts → agent-profile-DNV_U6QR.d.ts} +3 -3
- package/dist/{agent-profile-CfbuZk8b.d.ts.map → agent-profile-DNV_U6QR.d.ts.map} +1 -1
- package/dist/analyst/index.d.ts +75 -12
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +213 -8
- package/dist/analyst/index.js.map +1 -1
- package/dist/{analyze-runs-CLz5O01x.d.ts → analyze-runs-BvK4-wSs.d.ts} +5 -5
- package/dist/{analyze-runs-CLz5O01x.d.ts.map → analyze-runs-BvK4-wSs.d.ts.map} +1 -1
- package/dist/{analyze-runs-DCY7LMt4.js → analyze-runs-SCSWYpy0.js} +17 -9
- package/dist/analyze-runs-SCSWYpy0.js.map +1 -0
- package/dist/{benchmark-command-CdUGku3W.js → benchmark-command-TZlsuAh9.js} +12 -12
- package/dist/{benchmark-command-CdUGku3W.js.map → benchmark-command-TZlsuAh9.js.map} +1 -1
- package/dist/benchmarks/index.d.ts +4 -4
- package/dist/benchmarks/index.js +3 -3
- package/dist/campaign/index.d.ts +7 -7
- package/dist/campaign/index.js +8 -8
- package/dist/{campaign-DZuysBbS.js → campaign-_29Qpc9Z.js} +258 -13
- package/dist/campaign-_29Qpc9Z.js.map +1 -0
- package/dist/{campaign-evidence-IF1p0oDa.js → campaign-evidence-CWF8pEIk.js} +9 -8
- package/dist/campaign-evidence-CWF8pEIk.js.map +1 -0
- package/dist/{chat-client-C4dg7Ng_.js → chat-client-DbH06Ek6.js} +2 -2
- package/dist/{chat-client-C4dg7Ng_.js.map → chat-client-DbH06Ek6.js.map} +1 -1
- package/dist/{chat-json-call-JVGbfx95.js → chat-json-call-B0xTEY0I.js} +2 -2
- package/dist/{chat-json-call-JVGbfx95.js.map → chat-json-call-B0xTEY0I.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/{client-CFj9dfcn.d.ts → client-BhEyAdfe.d.ts} +4 -4
- package/dist/{client-CFj9dfcn.d.ts.map → client-BhEyAdfe.d.ts.map} +1 -1
- package/dist/{client-CXE-U1SA.js → client-QwJpsNYU.js} +5 -1
- package/dist/client-QwJpsNYU.js.map +1 -0
- package/dist/contract/index.d.ts +10 -10
- package/dist/contract/index.js +22 -13
- package/dist/contract/index.js.map +1 -1
- package/dist/deep-freeze-CurcOQg3.js +16 -0
- package/dist/deep-freeze-CurcOQg3.js.map +1 -0
- package/dist/{default-registry-BtZ_t-9b.js → default-registry-16I-Bxlf.js} +4 -3
- package/dist/default-registry-16I-Bxlf.js.map +1 -0
- package/dist/{default-registry-BLsGfnoO.d.ts → default-registry-B5HrwrPM.d.ts} +6 -6
- package/dist/{default-registry-BLsGfnoO.d.ts.map → default-registry-B5HrwrPM.d.ts.map} +1 -1
- package/dist/{define-agent-eval-CsicmDxt.d.ts → define-agent-eval-BWeyIpny.d.ts} +8 -7
- package/dist/define-agent-eval-BWeyIpny.d.ts.map +1 -0
- package/dist/{define-agent-eval-ChtddasD.js → define-agent-eval-DrGfrCTZ.js} +8 -8
- package/dist/{define-agent-eval-ChtddasD.js.map → define-agent-eval-DrGfrCTZ.js.map} +1 -1
- package/dist/diagnosis/index.d.ts +3 -3
- package/dist/diagnosis/index.d.ts.map +1 -1
- package/dist/diagnosis/index.js +4 -4
- package/dist/diagnosis/index.js.map +1 -1
- package/dist/{dspy-rlm-engine-sPX7B5ac.js → dspy-rlm-engine-CTIGomgr.js} +3 -3
- package/dist/{dspy-rlm-engine-sPX7B5ac.js.map → dspy-rlm-engine-CTIGomgr.js.map} +1 -1
- package/dist/{engine-Gw2n1irt.d.ts → engine-BkLCnZQX.d.ts} +4 -4
- package/dist/{engine-Gw2n1irt.d.ts.map → engine-BkLCnZQX.d.ts.map} +1 -1
- package/dist/{eval-campaign-CeotQ1ZR.js → eval-campaign-CMKRjWl0.js} +3 -3
- package/dist/{eval-campaign-CeotQ1ZR.js.map → eval-campaign-CMKRjWl0.js.map} +1 -1
- package/dist/evaluation.d.ts +2 -2
- package/dist/{exact-types-CrhPoDp8.d.ts → exact-types-CfpP2A4Z.d.ts} +2 -2
- package/dist/{exact-types-CrhPoDp8.d.ts.map → exact-types-CfpP2A4Z.d.ts.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-D3BLpZyR.js → external-optimizer-process-Mc83UoWl.js} +3 -3
- package/dist/{external-optimizer-process-D3BLpZyR.js.map → external-optimizer-process-Mc83UoWl.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-BrfsgL92.js → external-optimizer-subprocess-C0dHXH8e.js} +3 -3
- package/dist/{external-optimizer-subprocess-BrfsgL92.js.map → external-optimizer-subprocess-C0dHXH8e.js.map} +1 -1
- package/dist/{feedback-trajectory-BJi4t3Vf.d.ts → feedback-trajectory-BYjh7NEM.d.ts} +4 -4
- package/dist/{feedback-trajectory-BJi4t3Vf.d.ts.map → feedback-trajectory-BYjh7NEM.d.ts.map} +1 -1
- package/dist/hosted/index.d.ts +2 -2
- package/dist/hosted/index.d.ts.map +1 -1
- package/dist/hosted/index.js +1 -1
- package/dist/{index-Ca0_IjFu.d.ts → index-2e_MTznj.d.ts} +7 -7
- package/dist/{index-Ca0_IjFu.d.ts.map → index-2e_MTznj.d.ts.map} +1 -1
- package/dist/{index-BD1oSbHr.d.ts → index-BR_R1eBE.d.ts} +11 -11
- package/dist/{index-BD1oSbHr.d.ts.map → index-BR_R1eBE.d.ts.map} +1 -1
- package/dist/{index-u0d1Jp4F.d.ts → index-BqDJ6fUK.d.ts} +45 -24
- package/dist/index-BqDJ6fUK.d.ts.map +1 -0
- package/dist/{index-eTTnPe3r.d.ts → index-Ci0-EYzc.d.ts} +678 -249
- package/dist/index-Ci0-EYzc.d.ts.map +1 -0
- package/dist/{index-B-s8qDRJ.d.ts → index-D4pC7Tvd.d.ts} +4 -3
- package/dist/index-D4pC7Tvd.d.ts.map +1 -0
- package/dist/index.d.ts +81 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +385 -41
- package/dist/index.js.map +1 -1
- package/dist/{insight-report-DeE1solA.d.ts → insight-report-CvPkEIcC.d.ts} +13 -6
- package/dist/insight-report-CvPkEIcC.d.ts.map +1 -0
- package/dist/jev.d.ts +2 -2
- package/dist/jev.js +2 -1
- package/dist/jev.js.map +1 -1
- package/dist/{journal-BuRLm6ho.js → journal-CDP0Vhx_.js} +262 -115
- package/dist/journal-CDP0Vhx_.js.map +1 -0
- package/dist/{kind-factory-g1ROw67A.js → kind-factory-ByZeXDqF.js} +4 -3
- package/dist/{kind-factory-g1ROw67A.js.map → kind-factory-ByZeXDqF.js.map} +1 -1
- package/dist/ledger-core/index.d.ts +2 -2
- package/dist/ledger-core/index.js +1 -1
- package/dist/{llm-client-DF9bPNYA.js → llm-client-BqmRnibA.js} +2 -2
- package/dist/{llm-client-DF9bPNYA.js.map → llm-client-BqmRnibA.js.map} +1 -1
- package/dist/{llm-judge-DF18J3CR.js → llm-judge-bwbgDMP0.js} +1895 -1379
- package/dist/llm-judge-bwbgDMP0.js.map +1 -0
- package/dist/{matrix-BvA1WUpj.d.ts → matrix-CNypzP-v.d.ts} +2 -2
- package/dist/{matrix-BvA1WUpj.d.ts.map → matrix-CNypzP-v.d.ts.map} +1 -1
- package/dist/meta-eval/index.d.ts +1 -1
- package/dist/mint-C9Lojt1V.d.ts +147 -0
- package/dist/mint-C9Lojt1V.d.ts.map +1 -0
- package/dist/{mint-B4zgSAJW.js → mint-iCD-W43s.js} +30 -12
- package/dist/mint-iCD-W43s.js.map +1 -0
- package/dist/multishot/golden/index.d.ts +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.d.ts +1 -1
- package/dist/{prime-protocol-CF8Z43Fj.d.ts → prime-protocol-Fwvctmhm.d.ts} +2 -2
- package/dist/{prime-protocol-CF8Z43Fj.d.ts.map → prime-protocol-Fwvctmhm.d.ts.map} +1 -1
- package/dist/{produced-state-xsMMjYVc.js → produced-state-zSaGDC_Q.js} +4 -4
- package/dist/{produced-state-xsMMjYVc.js.map → produced-state-zSaGDC_Q.js.map} +1 -1
- package/dist/{raw-provider-sink-C96CcqeG.js → raw-provider-sink-DIRCZjP_.js} +2 -2
- package/dist/{raw-provider-sink-C96CcqeG.js.map → raw-provider-sink-DIRCZjP_.js.map} +1 -1
- package/dist/{redact-DxwlUAvh.js → redact-8fX8-uKr.js} +16 -7
- package/dist/redact-8fX8-uKr.js.map +1 -0
- package/dist/{redact-DPoH1-OW.d.ts → redact-Du3WKUOP.d.ts} +7 -5
- package/dist/redact-Du3WKUOP.d.ts.map +1 -0
- package/dist/{registry-BM2Kdk8T.d.ts → registry-mAQVtuLx.d.ts} +3 -3
- package/dist/{registry-BM2Kdk8T.d.ts.map → registry-mAQVtuLx.d.ts.map} +1 -1
- package/dist/{release-confidence-CG4Z9QLB.d.ts → release-confidence-CkoXFx9I.d.ts} +3 -3
- package/dist/{release-confidence-CG4Z9QLB.d.ts.map → release-confidence-CkoXFx9I.d.ts.map} +1 -1
- package/dist/{release-confidence-RanRUXRW.js → release-confidence-XQm_xjhR.js} +3 -3
- package/dist/release-confidence-XQm_xjhR.js.map +1 -0
- package/dist/reporting.d.ts +3 -3
- package/dist/reporting.js +1 -1
- package/dist/{researcher-Bjcd_8T_.d.ts → researcher-DojEMb7E.d.ts} +3 -3
- package/dist/{researcher-Bjcd_8T_.d.ts.map → researcher-DojEMb7E.d.ts.map} +1 -1
- package/dist/{reward-hacking-DrKXJtgH.d.ts → reward-hacking-Cyk-QHk9.d.ts} +2 -2
- package/dist/{reward-hacking-DrKXJtgH.d.ts.map → reward-hacking-Cyk-QHk9.d.ts.map} +1 -1
- package/dist/rl.d.ts +10 -8
- package/dist/rl.d.ts.map +1 -1
- package/dist/rl.js +7 -6
- package/dist/rl.js.map +1 -1
- package/dist/rollout/index.d.ts +5 -4
- package/dist/rollout/index.js +2 -2
- package/dist/{rollout-C0Ifk3vK.js → rollout-B-XBReTC.js} +2 -2
- package/dist/{rollout-C0Ifk3vK.js.map → rollout-B-XBReTC.js.map} +1 -1
- package/dist/{rubric-predictive-validity-B14yBAmM.d.ts → rubric-predictive-validity-DIUwJJ9Y.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-B14yBAmM.d.ts.map → rubric-predictive-validity-DIUwJJ9Y.d.ts.map} +1 -1
- package/dist/{run-record-tg_5H2IK.js → run-record-BRlM99rX.js} +41 -3
- package/dist/run-record-BRlM99rX.js.map +1 -0
- package/dist/{run-record-xEULpgML.d.ts → run-record-DF-fuu31.d.ts} +58 -6
- package/dist/run-record-DF-fuu31.d.ts.map +1 -0
- package/dist/{run-record-CNNzWGpB.js → run-record-DnMFyg_g.js} +26 -13
- package/dist/run-record-DnMFyg_g.js.map +1 -0
- package/dist/{schema-BzWDXhOR.d.ts → schema-B5LtYvn9.d.ts} +7 -5
- package/dist/{schema-BzWDXhOR.d.ts.map → schema-B5LtYvn9.d.ts.map} +1 -1
- package/dist/schema-C1aaAxTf.js.map +1 -1
- 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-70LxrCSL.js → semantic-concept-judge-BY0me-Jq.js} +6 -6
- package/dist/{semantic-concept-judge-70LxrCSL.js.map → semantic-concept-judge-BY0me-Jq.js.map} +1 -1
- package/dist/{server-C8XvqCg8.js → server-DnJCDLf8.js} +2 -2
- package/dist/{server-C8XvqCg8.js.map → server-DnJCDLf8.js.map} +1 -1
- package/dist/{skillopt-optimization-method-C60h0Got.js → skillopt-optimization-method-CfnQEjgT.js} +364 -19
- package/dist/skillopt-optimization-method-CfnQEjgT.js.map +1 -0
- package/dist/{spans-W0o8pbEg.d.ts → spans-D1RftP_w.d.ts} +2 -2
- package/dist/{spans-W0o8pbEg.d.ts.map → spans-D1RftP_w.d.ts.map} +1 -1
- package/dist/{statistical-heldout-CPv-y6sK.d.ts → statistical-heldout-CLpjYffJ.d.ts} +4 -38
- package/dist/statistical-heldout-CLpjYffJ.d.ts.map +1 -0
- package/dist/{store-otlp-DPgzSQKS.js → store-otlp-84DH0OEH.js} +2 -2
- package/dist/{store-otlp-DPgzSQKS.js.map → store-otlp-84DH0OEH.js.map} +1 -1
- package/dist/{store-tool-spans-C2lLwDzg.d.ts → store-tool-spans-BBXs8hDP.d.ts} +6 -6
- package/dist/{store-tool-spans-C2lLwDzg.d.ts.map → store-tool-spans-BBXs8hDP.d.ts.map} +1 -1
- package/dist/{store-tool-spans-ClPxcvh2.js → store-tool-spans-DSbYpl7f.js} +5 -5
- package/dist/{store-tool-spans-ClPxcvh2.js.map → store-tool-spans-DSbYpl7f.js.map} +1 -1
- package/dist/{summary-report-SNvJoAvD.d.ts → summary-report-DvvesgR_.d.ts} +5 -4
- package/dist/summary-report-DvvesgR_.d.ts.map +1 -0
- package/dist/supervisor-run/index.d.ts +2 -2
- package/dist/{tool-groups-CCQqAa16.d.ts → tool-groups-W7MJobX_.d.ts} +3 -3
- package/dist/tool-groups-W7MJobX_.d.ts.map +1 -0
- package/dist/trace-repair/index.d.ts +2 -2
- package/dist/traces.d.ts +6 -66
- package/dist/traces.d.ts.map +1 -1
- package/dist/traces.js +7 -196
- package/dist/traces.js.map +1 -1
- package/dist/{types-DDp2Mb8Y.d.ts → types-Bay1dh2o.d.ts} +5 -5
- package/dist/{types-DDp2Mb8Y.d.ts.map → types-Bay1dh2o.d.ts.map} +1 -1
- package/dist/{types-B1vDZ694.d.ts → types-BvmWNjjs.d.ts} +3 -3
- package/dist/{types-B1vDZ694.d.ts.map → types-BvmWNjjs.d.ts.map} +1 -1
- package/dist/{types-C8VBbiw6.js → types-CW5b90JH.js} +2 -15
- package/dist/types-CW5b90JH.js.map +1 -0
- package/dist/{types-5ak2PqnJ.d.ts → types-Cvqy7asY.d.ts} +3 -3
- package/dist/{types-5ak2PqnJ.d.ts.map → types-Cvqy7asY.d.ts.map} +1 -1
- package/dist/{types-Tp5WhYL9.d.ts → types-DRNlLbBR.d.ts} +2 -2
- package/dist/{types-Tp5WhYL9.d.ts.map → types-DRNlLbBR.d.ts.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/code-agent-intake.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/insight-report.md +3 -2
- package/docs/public-api.md +174 -68
- package/docs/redaction.md +7 -1
- package/docs/rollout.md +5 -5
- package/docs/search-ledger.md +174 -0
- package/docs/trace-contracts.md +48 -3
- package/package.json +1 -1
- package/dist/analyze-runs-DCY7LMt4.js.map +0 -1
- package/dist/campaign-DZuysBbS.js.map +0 -1
- package/dist/campaign-evidence-IF1p0oDa.js.map +0 -1
- package/dist/client-CXE-U1SA.js.map +0 -1
- package/dist/default-registry-BtZ_t-9b.js.map +0 -1
- package/dist/define-agent-eval-CsicmDxt.d.ts.map +0 -1
- package/dist/index-B-s8qDRJ.d.ts.map +0 -1
- package/dist/index-eTTnPe3r.d.ts.map +0 -1
- package/dist/index-u0d1Jp4F.d.ts.map +0 -1
- package/dist/insight-report-DeE1solA.d.ts.map +0 -1
- package/dist/journal-BuRLm6ho.js.map +0 -1
- package/dist/llm-judge-DF18J3CR.js.map +0 -1
- package/dist/mint-B4zgSAJW.js.map +0 -1
- package/dist/mint-CJFSYBIU.d.ts +0 -285
- package/dist/mint-CJFSYBIU.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/release-confidence-RanRUXRW.js.map +0 -1
- package/dist/run-record-CNNzWGpB.js.map +0 -1
- package/dist/run-record-tg_5H2IK.js.map +0 -1
- package/dist/run-record-xEULpgML.d.ts.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-C60h0Got.js.map +0 -1
- package/dist/statistical-heldout-CPv-y6sK.d.ts.map +0 -1
- package/dist/summary-report-SNvJoAvD.d.ts.map +0 -1
- package/dist/tool-groups-CCQqAa16.d.ts.map +0 -1
- package/dist/types-C8VBbiw6.js.map +0 -1
- package/docs/search-history-receipts.md +0 -232
|
@@ -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-
|
|
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
|
-
import { o as projectCampaignCellQuality, t as campaignCellCostProvenance } from "./run-record-
|
|
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 { d as mapConcurrent, n as replayLedgerText, t as FileLedgerJournal } from "./journal-
|
|
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-CWF8pEIk.js";
|
|
11
|
+
import { d as mapConcurrent, n as replayLedgerText, t as FileLedgerJournal } from "./journal-CDP0Vhx_.js";
|
|
12
|
+
import { t as deepFreezeCanonicalJson } from "./deep-freeze-CurcOQg3.js";
|
|
12
13
|
import "./ledger-core/index.js";
|
|
13
|
-
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-
|
|
14
|
-
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-C0dHXH8e.js";
|
|
15
|
+
import { f as redactText, n as REDACTION_VERSION, o as detectCredential, s as detectPersonalData } from "./redact-8fX8-uKr.js";
|
|
15
16
|
import { t as detectRewardHacking } from "./reward-hacking-D0XwhVWE.js";
|
|
16
17
|
import { n as assertProposalFindings, t as combineAbortSignals } from "./abort-signal-CtzAM_sJ.js";
|
|
17
|
-
import {
|
|
18
|
-
import { d as stripFencedJson, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-DF9bPNYA.js";
|
|
18
|
+
import { d as stripFencedJson, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-BqmRnibA.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,337 +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
|
-
|
|
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) {
|
|
2566
2716
|
const event = entry.event;
|
|
2567
|
-
if (
|
|
2717
|
+
if (this.closed) throw integrity(`event ${event.eventId} follows search-closed ${this.closed.eventId}`);
|
|
2568
2718
|
const occurredAt = Date.parse(event.occurredAt);
|
|
2569
|
-
if (occurredAt <
|
|
2570
|
-
previousOccurredAt = occurredAt;
|
|
2719
|
+
if (occurredAt < this.lastOccurredAt) throw integrity(`event ${event.eventId} occurred before the preceding event`);
|
|
2571
2720
|
assertUnique(event.artifacts.map(artifactKey), "artifact receipt", event.eventId);
|
|
2572
|
-
if (event.kind === "search-
|
|
2573
|
-
if (index !== 0 ||
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
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);
|
|
2581
2795
|
}
|
|
2582
|
-
planEvent = event;
|
|
2583
|
-
return;
|
|
2584
2796
|
}
|
|
2585
|
-
if (
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
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
|
|
2592
2861
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
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;
|
|
2597
2890
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
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);
|
|
2600
2897
|
}
|
|
2601
|
-
if (
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
if (
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
assertUnique(event.lineage.parentCandidateIds, "parentCandidateId", event.eventId);
|
|
2614
|
-
const parents = event.lineage.parentCandidateIds.map((id) => {
|
|
2615
|
-
const parent = candidates.get(id);
|
|
2616
|
-
if (!parent) throw new SearchLedgerIntegrityError(`candidate ${event.candidateId} references unknown parent ${id}`);
|
|
2617
|
-
return parent;
|
|
2618
|
-
});
|
|
2619
|
-
const expectedGeneration = parents.length === 0 ? 0 : Math.max(...parents.map((parent) => parent.registered.lineage.generation)) + 1;
|
|
2620
|
-
if (event.lineage.generation !== expectedGeneration) throw new SearchLedgerIntegrityError(`candidate ${event.candidateId} generation ${event.lineage.generation} does not follow its parents (expected ${expectedGeneration})`);
|
|
2621
|
-
assertUnique(event.surfaces.map((surface) => surface.surfaceId), "surfaceId", event.eventId);
|
|
2622
|
-
candidates.set(event.candidateId, {
|
|
2623
|
-
registered: event,
|
|
2624
|
-
attempts: [],
|
|
2625
|
-
decision: null
|
|
2626
|
-
});
|
|
2627
|
-
candidateBySlot.set(event.slotId, event.candidateId);
|
|
2628
|
-
lineageNodes.set(event.lineage.lineageNodeId, event.candidateId);
|
|
2629
|
-
candidateEvents.push(event);
|
|
2630
|
-
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);
|
|
2631
2910
|
}
|
|
2632
|
-
if (event.
|
|
2633
|
-
|
|
2634
|
-
|
|
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
|
-
|
|
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;
|
|
2668
2983
|
}
|
|
2669
|
-
if (
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
if (operationsById.has(event.operationId)) throw new SearchLedgerIntegrityError(`operation ${event.operationId} was recorded twice`);
|
|
2674
|
-
operationsById.set(event.operationId, event);
|
|
2675
|
-
operationEvents.push(event);
|
|
2676
|
-
return;
|
|
2984
|
+
if (cost.unknown && cell.costKnown) {
|
|
2985
|
+
cell.costKnown = false;
|
|
2986
|
+
this.audit.spend.unknownCostCells += 1;
|
|
2987
|
+
node.spend.unknownCostCells += 1;
|
|
2677
2988
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
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;
|
|
2690
3000
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
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;
|
|
2702
3029
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
if (
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
if (slots.length === 0) continue;
|
|
2714
|
-
const registeredCount = slots.filter((slot) => candidateBySlot.has(slot.slotId)).length;
|
|
2715
|
-
const closedCount = slots.filter((slot) => closedSlots.has(slot.slotId)).length;
|
|
2716
|
-
if (generationOutcome === "completed" && closedCount > 0) throw new SearchLedgerIntegrityError(`completed generation operation ${operation.operationId} contains ${closedCount} closed slot(s)`);
|
|
2717
|
-
if (generationOutcome === "failed" && registeredCount > 0) throw new SearchLedgerIntegrityError(`failed generation operation ${operation.operationId} contains ${registeredCount} registered candidate(s)`);
|
|
2718
|
-
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`);
|
|
2719
3040
|
}
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
const
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
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"
|
|
2735
3108
|
};
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
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
|
|
2740
3119
|
};
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
const
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
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
|
+
});
|
|
2760
3211
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
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
|
|
2775
3245
|
};
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
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;
|
|
2781
3307
|
return {
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
decisions,
|
|
2790
|
-
completion,
|
|
2791
|
-
audit: {
|
|
2792
|
-
campaignId,
|
|
2793
|
-
eventCount: entries.length,
|
|
2794
|
-
candidateCount: candidates.size,
|
|
2795
|
-
closedCandidateSlotCount: closedSlots.size,
|
|
2796
|
-
attemptCount: attempts.length,
|
|
2797
|
-
operationCount: operationEvents.length,
|
|
2798
|
-
outcomes: outcomeCounts,
|
|
2799
|
-
operationOutcomes: operationOutcomeCounts,
|
|
2800
|
-
decisions: {
|
|
2801
|
-
selected: selectedDecisions.length,
|
|
2802
|
-
rejected: rejectedDecisions.length,
|
|
2803
|
-
pending: candidates.size - decisions.length
|
|
2804
|
-
},
|
|
2805
|
-
expected: {
|
|
2806
|
-
candidateSlots: plannedSlots.size,
|
|
2807
|
-
taskOutcomes: candidates.size * (planEvent?.plan.tasks.length ?? 0),
|
|
2808
|
-
operations: plannedOperations.size,
|
|
2809
|
-
missingCandidateSlots,
|
|
2810
|
-
missingTaskOutcomes,
|
|
2811
|
-
missingOperations
|
|
2812
|
-
},
|
|
2813
|
-
status,
|
|
2814
|
-
selectedCandidateId,
|
|
2815
|
-
accounting,
|
|
2816
|
-
headHash: entries.at(-1)?.entryHash ?? null
|
|
2817
|
-
}
|
|
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
|
|
2818
3315
|
};
|
|
2819
|
-
}
|
|
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;
|
|
2820
3324
|
return {
|
|
2821
|
-
|
|
2822
|
-
|
|
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
|
|
2823
3343
|
};
|
|
2824
3344
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
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}`);
|
|
2836
|
-
}
|
|
2837
|
-
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`);
|
|
2838
3355
|
}
|
|
2839
3356
|
function assertUnique(values, label, eventId) {
|
|
2840
|
-
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);
|
|
2841
3368
|
}
|
|
2842
|
-
//#endregion
|
|
2843
|
-
//#region src/campaign/search-ledger-types.ts
|
|
2844
|
-
const SEARCH_LEDGER_SCHEMA = "tangle.search-ledger.v1";
|
|
2845
3369
|
//#endregion
|
|
2846
3370
|
//#region src/campaign/search-ledger.ts
|
|
2847
3371
|
/**
|
|
2848
|
-
* 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.
|
|
2849
3375
|
*
|
|
2850
|
-
*
|
|
2851
|
-
* measured run and `CostLedger` owns per-call
|
|
2852
|
-
*
|
|
2853
|
-
* search can answer, after a crash, exactly which candidates and task attempts
|
|
2854
|
-
* existed, which surfaces actually fired, what they cost, and why they were
|
|
2855
|
-
* 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.
|
|
2856
3379
|
*
|
|
2857
3380
|
* The file format is canonical JSONL with a SHA-256 hash chain. Every append is
|
|
2858
3381
|
* serialized across processes, fsynced before acknowledgement, and idempotent
|
|
2859
3382
|
* by `eventId`. A malformed, non-canonical, truncated, reordered, or conflicting
|
|
2860
|
-
* 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.
|
|
2861
3385
|
*
|
|
2862
|
-
* The journal machinery
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
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.
|
|
2865
3389
|
*/
|
|
2866
3390
|
const NON_EMPTY = z.string().min(1).refine((value) => value.trim() === value, "must not contain surrounding whitespace");
|
|
2867
3391
|
const HASH = z.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
2868
|
-
const LINEAGE_NODE_ID = z.string().regex(/^[a-f0-9]{16}$/);
|
|
2869
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+/=]+)$/);
|
|
2870
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");
|
|
2871
3394
|
const NON_NEGATIVE_INT = z.number().int().nonnegative().safe();
|
|
3395
|
+
const POSITIVE_INT = z.number().int().positive().safe();
|
|
2872
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}$/);
|
|
2873
3400
|
const ArtifactRefSchema = z.object({
|
|
2874
3401
|
role: NON_EMPTY,
|
|
2875
3402
|
uri: NON_EMPTY,
|
|
@@ -2880,14 +3407,47 @@ const SourceRefSchema = z.object({
|
|
|
2880
3407
|
uri: NON_EMPTY,
|
|
2881
3408
|
revision: IMMUTABLE_REVISION
|
|
2882
3409
|
}).strict();
|
|
3410
|
+
const UnknownRefSchema = z.object({ unknown: NON_EMPTY }).strict();
|
|
2883
3411
|
const FailureReasonSchema = z.object({
|
|
2884
3412
|
code: NON_EMPTY,
|
|
2885
3413
|
message: NON_EMPTY
|
|
2886
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();
|
|
2887
3447
|
const EventBaseShape = {
|
|
2888
3448
|
eventId: NON_EMPTY,
|
|
2889
3449
|
occurredAt: ISO_TIMESTAMP,
|
|
2890
|
-
artifacts: z.array(ArtifactRefSchema)
|
|
3450
|
+
artifacts: z.array(ArtifactRefSchema)
|
|
2891
3451
|
};
|
|
2892
3452
|
const OperationKindSchema = z.enum([
|
|
2893
3453
|
"candidate-generation",
|
|
@@ -2896,89 +3456,19 @@ const OperationKindSchema = z.enum([
|
|
|
2896
3456
|
"judge",
|
|
2897
3457
|
"other"
|
|
2898
3458
|
]);
|
|
2899
|
-
const CandidateSlotSchema = z.object({
|
|
2900
|
-
slotId: NON_EMPTY,
|
|
2901
|
-
generationOperationId: NON_EMPTY
|
|
2902
|
-
}).strict();
|
|
2903
|
-
const PlannedOperationSchema = z.object({
|
|
2904
|
-
operationId: NON_EMPTY,
|
|
2905
|
-
kind: OperationKindSchema
|
|
2906
|
-
}).strict();
|
|
2907
|
-
const SearchPlanExtendedSchema = z.object({
|
|
2908
|
-
...EventBaseShape,
|
|
2909
|
-
kind: z.literal("search-plan-extended"),
|
|
2910
|
-
extension: z.object({
|
|
2911
|
-
candidateSlots: z.array(CandidateSlotSchema),
|
|
2912
|
-
operations: z.array(PlannedOperationSchema)
|
|
2913
|
-
}).strict().superRefine((extension, ctx) => {
|
|
2914
|
-
if (extension.candidateSlots.length === 0 && extension.operations.length === 0) ctx.addIssue({
|
|
2915
|
-
code: "custom",
|
|
2916
|
-
message: "a plan extension must add slots or operations"
|
|
2917
|
-
});
|
|
2918
|
-
})
|
|
2919
|
-
}).strict();
|
|
2920
|
-
const SearchPlannedSchema = z.object({
|
|
2921
|
-
...EventBaseShape,
|
|
2922
|
-
kind: z.literal("search-planned"),
|
|
2923
|
-
plan: z.object({
|
|
2924
|
-
candidateSlots: z.array(CandidateSlotSchema).min(1),
|
|
2925
|
-
tasks: z.array(z.object({
|
|
2926
|
-
taskId: NON_EMPTY,
|
|
2927
|
-
source: SourceRefSchema,
|
|
2928
|
-
benchmark: SourceRefSchema,
|
|
2929
|
-
maxAttempts: z.number().int().positive().safe()
|
|
2930
|
-
}).strict()).min(1),
|
|
2931
|
-
operations: z.array(PlannedOperationSchema).min(1)
|
|
2932
|
-
}).strict()
|
|
2933
|
-
}).strict();
|
|
2934
|
-
const CandidateRegisteredSchema = z.object({
|
|
2935
|
-
...EventBaseShape,
|
|
2936
|
-
kind: z.literal("candidate-registered"),
|
|
2937
|
-
slotId: NON_EMPTY,
|
|
2938
|
-
generationOperationId: NON_EMPTY,
|
|
2939
|
-
candidateId: NON_EMPTY,
|
|
2940
|
-
lineage: z.object({
|
|
2941
|
-
lineageNodeId: LINEAGE_NODE_ID,
|
|
2942
|
-
parentCandidateIds: z.array(NON_EMPTY),
|
|
2943
|
-
generation: NON_NEGATIVE_INT,
|
|
2944
|
-
proposer: NON_EMPTY,
|
|
2945
|
-
proposerSource: SourceRefSchema
|
|
2946
|
-
}).strict(),
|
|
2947
|
-
surfaces: z.array(z.object({
|
|
2948
|
-
surfaceId: NON_EMPTY,
|
|
2949
|
-
kind: z.enum([
|
|
2950
|
-
"prompt",
|
|
2951
|
-
"tool-contract",
|
|
2952
|
-
"runtime-config",
|
|
2953
|
-
"memory",
|
|
2954
|
-
"knowledge",
|
|
2955
|
-
"agent-profile",
|
|
2956
|
-
"code",
|
|
2957
|
-
"deployment"
|
|
2958
|
-
]),
|
|
2959
|
-
artifact: ArtifactRefSchema
|
|
2960
|
-
}).strict()).min(1)
|
|
2961
|
-
}).strict();
|
|
2962
|
-
const CandidateSlotClosedSchema = z.object({
|
|
2963
|
-
...EventBaseShape,
|
|
2964
|
-
kind: z.literal("candidate-slot-closed"),
|
|
2965
|
-
slotId: NON_EMPTY,
|
|
2966
|
-
generationOperationId: NON_EMPTY,
|
|
2967
|
-
reason: FailureReasonSchema
|
|
2968
|
-
}).strict();
|
|
2969
3459
|
const KnownTokensSchema = z.object({
|
|
2970
3460
|
status: z.literal("known"),
|
|
2971
3461
|
inputTokens: NON_NEGATIVE_INT,
|
|
2972
3462
|
outputTokens: NON_NEGATIVE_INT,
|
|
2973
3463
|
cachedTokens: NON_NEGATIVE_INT
|
|
2974
3464
|
}).strict();
|
|
2975
|
-
const
|
|
3465
|
+
const UnknownTokensSchema = z.object({
|
|
2976
3466
|
status: z.literal("unknown"),
|
|
2977
3467
|
reason: NON_EMPTY
|
|
2978
3468
|
}).strict();
|
|
2979
3469
|
const KnownCostSchema = z.object({
|
|
2980
3470
|
status: z.literal("known"),
|
|
2981
|
-
usd:
|
|
3471
|
+
usd: USD,
|
|
2982
3472
|
source: z.enum([
|
|
2983
3473
|
"provider",
|
|
2984
3474
|
"pricing-table",
|
|
@@ -2992,11 +3482,11 @@ const KnownCostSchema = z.object({
|
|
|
2992
3482
|
});
|
|
2993
3483
|
const UnknownCostSchema = z.object({
|
|
2994
3484
|
status: z.literal("unknown"),
|
|
2995
|
-
knownLowerBoundUsd:
|
|
3485
|
+
knownLowerBoundUsd: USD,
|
|
2996
3486
|
reason: NON_EMPTY
|
|
2997
3487
|
}).strict();
|
|
2998
3488
|
const AccountingSchema = z.object({
|
|
2999
|
-
tokens: z.discriminatedUnion("status", [KnownTokensSchema,
|
|
3489
|
+
tokens: z.discriminatedUnion("status", [KnownTokensSchema, UnknownTokensSchema]),
|
|
3000
3490
|
cost: z.discriminatedUnion("status", [KnownCostSchema, UnknownCostSchema])
|
|
3001
3491
|
}).strict();
|
|
3002
3492
|
const MetricsSchema = z.record(NON_EMPTY, FINITE_NUMBER).superRefine((metrics, ctx) => {
|
|
@@ -3047,52 +3537,85 @@ const SurfaceEvidenceSchema = z.object({
|
|
|
3047
3537
|
effect: EffectSchema,
|
|
3048
3538
|
evidence: z.array(ArtifactRefSchema).min(1)
|
|
3049
3539
|
}).strict().superRefine((evidence, ctx) => {
|
|
3050
|
-
if (evidence.fired
|
|
3051
|
-
code: "custom",
|
|
3052
|
-
message: "a fired surface must have firingCount >= 1"
|
|
3053
|
-
});
|
|
3054
|
-
if (!evidence.fired && evidence.firingCount !== 0) ctx.addIssue({
|
|
3540
|
+
if (evidence.fired !== evidence.firingCount > 0) ctx.addIssue({
|
|
3055
3541
|
code: "custom",
|
|
3056
|
-
message: "
|
|
3542
|
+
message: "fired must equal firingCount > 0"
|
|
3057
3543
|
});
|
|
3058
3544
|
if (!evidence.fired && evidence.effect.status === "measured" && evidence.effect.delta !== 0) ctx.addIssue({
|
|
3059
3545
|
code: "custom",
|
|
3060
|
-
message: "a surface that did not fire
|
|
3546
|
+
message: "a surface that did not fire has no effect"
|
|
3061
3547
|
});
|
|
3062
3548
|
});
|
|
3063
|
-
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({
|
|
3064
3559
|
...EventBaseShape,
|
|
3065
|
-
kind: z.literal("
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
taskId: NON_EMPTY,
|
|
3071
|
-
source: SourceRefSchema
|
|
3560
|
+
kind: z.literal("search-opened"),
|
|
3561
|
+
subject: NON_EMPTY,
|
|
3562
|
+
process: z.object({
|
|
3563
|
+
name: NON_EMPTY,
|
|
3564
|
+
executionRef: SourceRefSchema
|
|
3072
3565
|
}).strict(),
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
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
|
|
3080
3572
|
}).strict(),
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
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
|
|
3084
3603
|
}).strict();
|
|
3085
|
-
const
|
|
3604
|
+
const OperationStartedSchema = z.object({
|
|
3086
3605
|
...EventBaseShape,
|
|
3087
|
-
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"),
|
|
3088
3614
|
operationId: NON_EMPTY,
|
|
3089
3615
|
operationKind: OperationKindSchema,
|
|
3090
3616
|
execution: z.discriminatedUnion("kind", [z.object({
|
|
3091
3617
|
kind: z.literal("model"),
|
|
3092
|
-
model:
|
|
3093
|
-
provider: NON_EMPTY,
|
|
3094
|
-
snapshot: NON_EMPTY.refine(modelHasSnapshot, "model must include an immutable snapshot")
|
|
3095
|
-
}).strict(),
|
|
3618
|
+
model: ModelIdentitySchema,
|
|
3096
3619
|
source: SourceRefSchema
|
|
3097
3620
|
}).strict(), z.object({
|
|
3098
3621
|
kind: z.literal("deterministic"),
|
|
@@ -3111,95 +3634,257 @@ const SearchOperationRecordedSchema = z.object({
|
|
|
3111
3634
|
]),
|
|
3112
3635
|
accounting: AccountingSchema
|
|
3113
3636
|
}).strict();
|
|
3114
|
-
const
|
|
3637
|
+
const NodeRegisteredSchema = z.object({
|
|
3115
3638
|
...EventBaseShape,
|
|
3116
|
-
kind: z.literal("
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
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
|
|
3122
3652
|
}).strict();
|
|
3123
|
-
const
|
|
3653
|
+
const ArtifactOrUnknownSchema = z.union([ArtifactRefSchema, UnknownRefSchema]);
|
|
3654
|
+
const EdgeRecordedSchema = z.object({
|
|
3124
3655
|
...EventBaseShape,
|
|
3125
|
-
kind: z.literal("
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
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()
|
|
3133
3817
|
}).strict();
|
|
3134
3818
|
const EventSchema = z.discriminatedUnion("kind", [
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3819
|
+
SearchOpenedSchema,
|
|
3820
|
+
OperationStartedSchema,
|
|
3821
|
+
OperationRecordedSchema,
|
|
3822
|
+
NodeRegisteredSchema,
|
|
3823
|
+
EdgeRecordedSchema,
|
|
3824
|
+
CellAllocatedSchema,
|
|
3825
|
+
CellSettledSchema,
|
|
3826
|
+
CellCancelledSchema,
|
|
3827
|
+
NodeDecidedSchema,
|
|
3828
|
+
SearchClosedSchema
|
|
3143
3829
|
]);
|
|
3144
3830
|
const EntrySchema = z.object({
|
|
3145
3831
|
schema: z.literal(SEARCH_LEDGER_SCHEMA),
|
|
3146
|
-
|
|
3832
|
+
searchId: NON_EMPTY,
|
|
3147
3833
|
sequence: NON_NEGATIVE_INT,
|
|
3148
3834
|
previousHash: z.union([HASH, z.null()]),
|
|
3149
3835
|
event: EventSchema,
|
|
3150
3836
|
entryHash: HASH
|
|
3151
3837
|
}).strict();
|
|
3152
|
-
/** Validate and return a canonical copy. Arrays whose order
|
|
3153
|
-
* 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. */
|
|
3154
3840
|
function validateSearchLedgerEvent(input) {
|
|
3155
3841
|
const parsed = EventSchema.safeParse(input);
|
|
3156
3842
|
if (!parsed.success) throw new SearchLedgerError(`invalid search ledger event: ${formatZodError(parsed.error)}`);
|
|
3157
3843
|
return normalizeEvent(parsed.data);
|
|
3158
3844
|
}
|
|
3159
3845
|
/** Open a durable filesystem search ledger. Construction performs no I/O; the
|
|
3160
|
-
* first `append` or `
|
|
3846
|
+
* first `append` or `state` verifies the whole existing file. */
|
|
3161
3847
|
function openSearchLedger(options) {
|
|
3162
|
-
|
|
3163
|
-
return new FileSearchLedger(options.path, options.campaignId, options.trustedHead);
|
|
3848
|
+
return new FileSearchLedger(options.path, options.searchId, options.trustedHead);
|
|
3164
3849
|
}
|
|
3165
|
-
/**
|
|
3166
|
-
|
|
3167
|
-
|
|
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));
|
|
3168
3854
|
}
|
|
3169
|
-
function searchLedgerCodec(
|
|
3855
|
+
function searchLedgerCodec(searchId) {
|
|
3170
3856
|
return {
|
|
3171
3857
|
...SEARCH_LEDGER_FILE_CONTEXT,
|
|
3172
3858
|
header: {
|
|
3173
3859
|
schema: SEARCH_LEDGER_SCHEMA,
|
|
3174
|
-
|
|
3860
|
+
searchId
|
|
3175
3861
|
},
|
|
3176
3862
|
conflictError: (message) => new SearchLedgerConflictError(message),
|
|
3177
3863
|
parseEntry: parseSearchLedgerEntry,
|
|
3178
3864
|
checkEntryHeader: (entry, index) => {
|
|
3179
|
-
if (entry.
|
|
3865
|
+
if (entry.searchId !== searchId) throw new SearchLedgerIntegrityError(`entry ${index} belongs to search ${entry.searchId}, expected ${searchId}`);
|
|
3180
3866
|
},
|
|
3181
|
-
createProjector: () =>
|
|
3867
|
+
createProjector: () => new SearchState(searchId)
|
|
3182
3868
|
};
|
|
3183
3869
|
}
|
|
3184
|
-
/** Append-only file-backed search ledger with idempotent writes
|
|
3870
|
+
/** Append-only file-backed search ledger with idempotent writes. */
|
|
3185
3871
|
var FileSearchLedger = class {
|
|
3186
3872
|
path;
|
|
3187
|
-
|
|
3873
|
+
searchId;
|
|
3188
3874
|
trustedHeadPath;
|
|
3189
3875
|
trustedHeadMode;
|
|
3190
3876
|
journal;
|
|
3191
|
-
constructor(path,
|
|
3877
|
+
constructor(path, searchId, trustedHead = "pin") {
|
|
3192
3878
|
if (path.trim().length === 0) throw new SearchLedgerError("ledger path is empty");
|
|
3193
|
-
if (
|
|
3194
|
-
|
|
3195
|
-
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;
|
|
3196
3881
|
this.trustedHeadMode = trustedHead;
|
|
3197
|
-
this.journal = new FileLedgerJournal(path, searchLedgerCodec(
|
|
3882
|
+
this.journal = new FileLedgerJournal(path, searchLedgerCodec(searchId), { requireTrustedHead: trustedHead === "require" });
|
|
3198
3883
|
this.path = this.journal.path;
|
|
3199
3884
|
this.trustedHeadPath = this.journal.trustedHeadPath;
|
|
3200
3885
|
}
|
|
3201
|
-
async
|
|
3202
|
-
return
|
|
3886
|
+
async state() {
|
|
3887
|
+
return this.journal.replay();
|
|
3203
3888
|
}
|
|
3204
3889
|
async append(input) {
|
|
3205
3890
|
const event = validateSearchLedgerEvent(input);
|
|
@@ -3207,7 +3892,7 @@ var FileSearchLedger = class {
|
|
|
3207
3892
|
return {
|
|
3208
3893
|
entry,
|
|
3209
3894
|
appended,
|
|
3210
|
-
|
|
3895
|
+
state: projection
|
|
3211
3896
|
};
|
|
3212
3897
|
}
|
|
3213
3898
|
async trustedHead() {
|
|
@@ -3221,6 +3906,8 @@ var FileSearchLedger = class {
|
|
|
3221
3906
|
}
|
|
3222
3907
|
};
|
|
3223
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`);
|
|
3224
3911
|
const parsed = EntrySchema.safeParse(raw);
|
|
3225
3912
|
if (!parsed.success) throw new SearchLedgerIntegrityError(`search ledger ${context.path} has a malformed entry at line ${context.line}: ${formatZodError(parsed.error)}`);
|
|
3226
3913
|
const entry = parsed.data;
|
|
@@ -3229,74 +3916,82 @@ function parseSearchLedgerEntry(raw, context) {
|
|
|
3229
3916
|
}
|
|
3230
3917
|
function normalizeEvent(event) {
|
|
3231
3918
|
const artifacts = sortArtifacts(event.artifacts);
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
...event
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
...event,
|
|
3271
|
-
artifacts
|
|
3272
|
-
};
|
|
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
|
+
}
|
|
3273
3957
|
}
|
|
3274
|
-
function
|
|
3275
|
-
return [...
|
|
3958
|
+
function sortTasks(tasks) {
|
|
3959
|
+
return [...tasks].sort((a, b) => compareStrings(a.taskId, b.taskId));
|
|
3276
3960
|
}
|
|
3277
|
-
function
|
|
3278
|
-
return [...
|
|
3961
|
+
function sortArtifacts(artifacts) {
|
|
3962
|
+
return [...artifacts].sort((a, b) => compareStrings(artifactKey(a), artifactKey(b)));
|
|
3279
3963
|
}
|
|
3280
3964
|
function formatZodError(error) {
|
|
3281
3965
|
return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`).join("; ");
|
|
3282
3966
|
}
|
|
3283
3967
|
//#endregion
|
|
3284
3968
|
//#region src/campaign/search-history-receipt.ts
|
|
3285
|
-
const SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION = "
|
|
3969
|
+
const SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION = "2026-09";
|
|
3286
3970
|
const SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM = "rfc8785-sha256";
|
|
3287
3971
|
function assertSearchHistoryAdmissionOptions(options) {
|
|
3288
3972
|
if (options.searchHistoryPolicy !== void 0 && !["allow-missing", "require-complete"].includes(options.searchHistoryPolicy)) throw new Error(`unknown searchHistoryPolicy '${String(options.searchHistoryPolicy)}'`);
|
|
3289
3973
|
if (options.searchHistoryVerification !== void 0 && !["receipt", "ledger"].includes(options.searchHistoryVerification)) throw new Error(`unknown searchHistoryVerification '${String(options.searchHistoryVerification)}'`);
|
|
3290
3974
|
}
|
|
3291
|
-
/**
|
|
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. */
|
|
3292
3986
|
function verifySearchHistoryArtifact(receipt, storage) {
|
|
3293
3987
|
verifySearchHistoryReceipt(receipt);
|
|
3294
3988
|
const path = receipt.ledger.uri.startsWith("file:") ? fileURLToPath(receipt.ledger.uri) : receipt.ledger.uri;
|
|
3295
3989
|
const text = storage.read(path);
|
|
3296
3990
|
if (text === void 0) throw new Error(`search history ledger is missing or unreadable at '${path}'`);
|
|
3297
|
-
|
|
3298
|
-
if (
|
|
3299
|
-
|
|
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));
|
|
3300
3995
|
}
|
|
3301
3996
|
var SearchHistoryRequiredError = class extends Error {
|
|
3302
3997
|
producerId;
|
|
@@ -3309,73 +4004,25 @@ var SearchHistoryRequiredError = class extends Error {
|
|
|
3309
4004
|
this.reasons = Object.freeze([...normalized]);
|
|
3310
4005
|
}
|
|
3311
4006
|
};
|
|
3312
|
-
/**
|
|
3313
|
-
function createSearchHistoryReceipt(input) {
|
|
3314
|
-
if (!input || typeof input !== "object") throw new TypeError("search history input is required");
|
|
3315
|
-
const producerId = nonEmpty(input.producerId, "search history producerId");
|
|
3316
|
-
const runId = nonEmpty(input.runId, "search history runId");
|
|
3317
|
-
const ledger = normalizeArtifactRef(input.ledger);
|
|
3318
|
-
const replay = validateReplayProjection(input.replay);
|
|
3319
|
-
const auditDigest = ledgerHash(hashCanonical(replay.audit), "search history auditDigest");
|
|
3320
|
-
const summary = summarizeReplay(replay);
|
|
3321
|
-
const incompleteReasons = Object.freeze(historyIncompleteReasons(summary));
|
|
3322
|
-
const material = Object.freeze({
|
|
3323
|
-
schemaVersion: SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION,
|
|
3324
|
-
kind: "search-history-receipt",
|
|
3325
|
-
digestAlgorithm: SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM,
|
|
3326
|
-
producerId,
|
|
3327
|
-
runId,
|
|
3328
|
-
ledger,
|
|
3329
|
-
auditDigest,
|
|
3330
|
-
summary,
|
|
3331
|
-
complete: incompleteReasons.length === 0,
|
|
3332
|
-
incompleteReasons
|
|
3333
|
-
});
|
|
3334
|
-
return Object.freeze({
|
|
3335
|
-
...material,
|
|
3336
|
-
receiptDigest: hashCanonical(material)
|
|
3337
|
-
});
|
|
3338
|
-
}
|
|
3339
|
-
/** Verify the bounded receipt. Full ledger bytes are verified by SearchLedger. */
|
|
4007
|
+
/** Verify a receipt's own consistency and digest. */
|
|
3340
4008
|
function verifySearchHistoryReceipt(receipt) {
|
|
3341
4009
|
if (!receipt || typeof receipt !== "object") throw new TypeError("search history receipt is required");
|
|
3342
4010
|
if (receipt.schemaVersion !== SEARCH_HISTORY_RECEIPT_SCHEMA_VERSION) throw new Error(`unsupported search history schemaVersion '${receipt.schemaVersion}'`);
|
|
3343
4011
|
if (receipt.kind !== "search-history-receipt") throw new Error(`search history kind must be 'search-history-receipt'`);
|
|
3344
4012
|
if (receipt.digestAlgorithm !== SEARCH_HISTORY_RECEIPT_DIGEST_ALGORITHM) throw new Error(`unsupported search history digestAlgorithm '${receipt.digestAlgorithm}'`);
|
|
3345
|
-
const producerId = nonEmpty(receipt.producerId, "search history producerId");
|
|
3346
|
-
const runId = nonEmpty(receipt.runId, "search history runId");
|
|
3347
|
-
const ledger = normalizeArtifactRef(receipt.ledger);
|
|
3348
|
-
const auditDigest = ledgerHash(receipt.auditDigest, "search history auditDigest");
|
|
3349
4013
|
const summary = normalizeSummary(receipt.summary);
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
if (
|
|
3354
|
-
if (hashCanonical({
|
|
3355
|
-
schemaVersion: receipt.schemaVersion,
|
|
3356
|
-
kind: receipt.kind,
|
|
3357
|
-
digestAlgorithm: receipt.digestAlgorithm,
|
|
3358
|
-
producerId,
|
|
3359
|
-
runId,
|
|
3360
|
-
ledger,
|
|
3361
|
-
auditDigest,
|
|
3362
|
-
summary,
|
|
3363
|
-
complete: receipt.complete,
|
|
3364
|
-
incompleteReasons
|
|
3365
|
-
}) !== 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");
|
|
3366
4018
|
return receipt;
|
|
3367
4019
|
}
|
|
3368
|
-
/** Prove that a receipt
|
|
3369
|
-
function
|
|
4020
|
+
/** Prove that a receipt describes exactly the supplied replayed state. */
|
|
4021
|
+
function assertSearchHistoryMatchesState(receipt, state) {
|
|
3370
4022
|
verifySearchHistoryReceipt(receipt);
|
|
3371
|
-
if (
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
ledger: receipt.ledger,
|
|
3375
|
-
replay
|
|
3376
|
-
}).receiptDigest !== receipt.receiptDigest) throw new Error("search history receipt does not match the supplied SearchLedger replay");
|
|
3377
|
-
}
|
|
3378
|
-
/** 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. */
|
|
3379
4026
|
function assertCompleteSearchHistory(producerId, receipt) {
|
|
3380
4027
|
if (receipt === void 0) throw new SearchHistoryRequiredError(producerId, []);
|
|
3381
4028
|
verifySearchHistoryReceipt(receipt);
|
|
@@ -3398,590 +4045,442 @@ function searchHistoryCoverageRow(producerId, receipt) {
|
|
|
3398
4045
|
receipt
|
|
3399
4046
|
});
|
|
3400
4047
|
}
|
|
3401
|
-
function
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
if (replay.plan !== null && (!replay.plan || typeof replay.plan !== "object")) throw new TypeError("search history replay.plan must be an event or null");
|
|
3406
|
-
if (replay.completion !== null && (!replay.completion || typeof replay.completion !== "object")) throw new TypeError("search history replay.completion must be an event or null");
|
|
3407
|
-
if (replay.audit.eventCount !== replay.entries.length) throw new Error(`search history audit eventCount ${replay.audit.eventCount} does not match ${replay.entries.length} entries`);
|
|
3408
|
-
const observedHead = replay.entries.at(-1)?.entryHash ?? null;
|
|
3409
|
-
if (replay.audit.headHash !== observedHead) throw new Error("search history audit headHash does not match the final ledger entry");
|
|
3410
|
-
return replay;
|
|
3411
|
-
}
|
|
3412
|
-
function summarizeReplay(replay) {
|
|
3413
|
-
const audit = replay.audit;
|
|
3414
|
-
return normalizeSummary({
|
|
3415
|
-
campaignId: audit.campaignId,
|
|
4048
|
+
function receiptFor(producerId, runId, ledger, state) {
|
|
4049
|
+
const { audit } = state;
|
|
4050
|
+
const summary = normalizeSummary({
|
|
4051
|
+
searchId: audit.searchId,
|
|
3416
4052
|
headHash: audit.headHash,
|
|
3417
4053
|
status: audit.status,
|
|
3418
|
-
|
|
4054
|
+
closeReason: audit.closeReason,
|
|
4055
|
+
claimDecision: state.closed?.claim?.decision ?? null,
|
|
4056
|
+
selectedNodeId: audit.selectedNodeId,
|
|
3419
4057
|
eventCount: audit.eventCount,
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
missingCandidateSlots: audit.expected.missingCandidateSlots.length,
|
|
3428
|
-
missingTaskOutcomes: audit.expected.missingTaskOutcomes.length,
|
|
3429
|
-
missingOperations: audit.expected.missingOperations.length,
|
|
3430
|
-
pendingDecisions: audit.decisions.pending,
|
|
3431
|
-
hasPlan: replay.plan !== null,
|
|
3432
|
-
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
|
|
3433
4065
|
});
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
const expectedCandidateSlots = nonNegativeSafeInteger(value.expectedCandidateSlots, "search history summary.expectedCandidateSlots");
|
|
3447
|
-
const expectedTaskOutcomes = nonNegativeSafeInteger(value.expectedTaskOutcomes, "search history summary.expectedTaskOutcomes");
|
|
3448
|
-
const expectedOperations = nonNegativeSafeInteger(value.expectedOperations, "search history summary.expectedOperations");
|
|
3449
|
-
const missingCandidateSlots = nonNegativeSafeInteger(value.missingCandidateSlots, "search history summary.missingCandidateSlots");
|
|
3450
|
-
const missingTaskOutcomes = nonNegativeSafeInteger(value.missingTaskOutcomes, "search history summary.missingTaskOutcomes");
|
|
3451
|
-
const missingOperations = nonNegativeSafeInteger(value.missingOperations, "search history summary.missingOperations");
|
|
3452
|
-
const pendingDecisions = nonNegativeSafeInteger(value.pendingDecisions, "search history summary.pendingDecisions");
|
|
3453
|
-
const hasPlan = boolean(value.hasPlan, "search history summary.hasPlan");
|
|
3454
|
-
const hasCompletion = boolean(value.hasCompletion, "search history summary.hasCompletion");
|
|
3455
|
-
if (missingCandidateSlots > expectedCandidateSlots) throw new Error("search history missingCandidateSlots exceeds expectedCandidateSlots");
|
|
3456
|
-
if (missingTaskOutcomes > expectedTaskOutcomes) throw new Error("search history missingTaskOutcomes exceeds expectedTaskOutcomes");
|
|
3457
|
-
if (missingOperations > expectedOperations) throw new Error("search history missingOperations exceeds expectedOperations");
|
|
3458
|
-
if (pendingDecisions > candidateCount) throw new Error("search history pendingDecisions exceeds candidateCount");
|
|
3459
|
-
if (eventCount === 0 !== (headHash === null)) throw new Error("search history eventCount and headHash disagree");
|
|
3460
|
-
if (status === "in-progress" === hasCompletion) throw new Error("search history status and hasCompletion disagree");
|
|
3461
|
-
if (status === "selected" && selectedCandidateId === null) throw new Error("selected search history requires selectedCandidateId");
|
|
3462
|
-
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
|
+
};
|
|
3463
4078
|
return Object.freeze({
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
status,
|
|
3467
|
-
selectedCandidateId,
|
|
3468
|
-
eventCount,
|
|
3469
|
-
candidateCount,
|
|
3470
|
-
closedCandidateSlotCount,
|
|
3471
|
-
attemptCount,
|
|
3472
|
-
operationCount,
|
|
3473
|
-
expectedCandidateSlots,
|
|
3474
|
-
expectedTaskOutcomes,
|
|
3475
|
-
expectedOperations,
|
|
3476
|
-
missingCandidateSlots,
|
|
3477
|
-
missingTaskOutcomes,
|
|
3478
|
-
missingOperations,
|
|
3479
|
-
pendingDecisions,
|
|
3480
|
-
hasPlan,
|
|
3481
|
-
hasCompletion
|
|
4079
|
+
...material,
|
|
4080
|
+
receiptDigest: hashCanonical(material)
|
|
3482
4081
|
});
|
|
3483
4082
|
}
|
|
3484
|
-
function
|
|
3485
|
-
const
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
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");
|
|
3500
4111
|
return Object.freeze({
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
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
|
|
3505
4126
|
});
|
|
3506
4127
|
}
|
|
3507
|
-
function normalizeReasons(value) {
|
|
3508
|
-
if (!Array.isArray(value)) throw new TypeError("search history incompleteReasons must be an array");
|
|
3509
|
-
return Object.freeze(value.map((reason, index) => nonEmpty(reason, `search history incompleteReasons[${index}]`)));
|
|
3510
|
-
}
|
|
3511
|
-
function sameStrings(left, right) {
|
|
3512
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3513
|
-
}
|
|
3514
|
-
function searchStatus(value) {
|
|
3515
|
-
if (value !== "in-progress" && value !== "selected" && value !== "all-rejected") throw new TypeError(`search history summary.status is invalid: ${String(value)}`);
|
|
3516
|
-
return value;
|
|
3517
|
-
}
|
|
3518
|
-
function nullableLedgerHash(value, label) {
|
|
3519
|
-
return value === null ? null : ledgerHash(value, label);
|
|
3520
|
-
}
|
|
3521
|
-
function optionalNullableText(value, label) {
|
|
3522
|
-
return value === null ? null : nonEmpty(value, label);
|
|
3523
|
-
}
|
|
3524
|
-
function ledgerHash(value, label) {
|
|
3525
|
-
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value)) throw new TypeError(`${label} must be a lowercase sha256 digest`);
|
|
3526
|
-
return value;
|
|
3527
|
-
}
|
|
3528
4128
|
function nonEmpty(value, label) {
|
|
3529
4129
|
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) throw new TypeError(`${label} must be a trimmed non-empty string`);
|
|
3530
4130
|
return value;
|
|
3531
4131
|
}
|
|
3532
|
-
function nonNegativeSafeInteger(value, label) {
|
|
3533
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative safe integer`);
|
|
3534
|
-
return value;
|
|
3535
|
-
}
|
|
3536
|
-
function boolean(value, label) {
|
|
3537
|
-
if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean`);
|
|
3538
|
-
return value;
|
|
3539
|
-
}
|
|
3540
4132
|
//#endregion
|
|
3541
4133
|
//#region src/campaign/search-ledger-recording.ts
|
|
3542
|
-
const SEARCH_LEDGER_DIR = "search-ledger";
|
|
3543
4134
|
/**
|
|
3544
|
-
*
|
|
3545
|
-
*
|
|
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.
|
|
3546
4141
|
*
|
|
3547
|
-
*
|
|
3548
|
-
*
|
|
3549
|
-
* 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.
|
|
3550
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. */
|
|
3551
4175
|
var SearchRecorder = class SearchRecorder {
|
|
3552
|
-
|
|
4176
|
+
ledger;
|
|
4177
|
+
storage;
|
|
4178
|
+
blobDir;
|
|
4179
|
+
now;
|
|
3553
4180
|
tasks;
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
return recorder;
|
|
3572
|
-
}
|
|
3573
|
-
/**
|
|
3574
|
-
* Record one generation's candidate-generation call and the candidates it
|
|
3575
|
-
* produced. A proposal larger than the planned population extends the plan
|
|
3576
|
-
* with the extra slots; a proposal that fills fewer closes the rest.
|
|
3577
|
-
*/
|
|
3578
|
-
async recordGeneration(input) {
|
|
3579
|
-
const { generation, candidates } = input;
|
|
3580
|
-
const planned = this.opts.populationSize;
|
|
3581
|
-
if (candidates.length > planned) {
|
|
3582
|
-
const extra = [];
|
|
3583
|
-
for (let index = planned; index < candidates.length; index++) {
|
|
3584
|
-
const slot = {
|
|
3585
|
-
slotId: slotId(generation, index),
|
|
3586
|
-
generationOperationId: generationOperationId(generation)
|
|
3587
|
-
};
|
|
3588
|
-
extra.push(slot);
|
|
3589
|
-
this.openSlots.add(slot.slotId);
|
|
3590
|
-
}
|
|
3591
|
-
await this.append({
|
|
3592
|
-
kind: "search-plan-extended",
|
|
3593
|
-
eventId: `search:plan-extended:gen-${generation}`,
|
|
3594
|
-
occurredAt: this.stamp(),
|
|
3595
|
-
artifacts: [this.proposalArtifact(generation, candidates)],
|
|
3596
|
-
extension: {
|
|
3597
|
-
candidateSlots: extra,
|
|
3598
|
-
operations: []
|
|
3599
|
-
}
|
|
3600
|
-
});
|
|
3601
|
-
}
|
|
3602
|
-
await this.recordGenerationOperation(generation, candidates, planned - candidates.length);
|
|
3603
|
-
const parent = this.registered.get(input.parentSurfaceHash);
|
|
3604
|
-
for (const [index, candidate] of candidates.entries()) {
|
|
3605
|
-
const candidateId = candidate.surfaceHash;
|
|
3606
|
-
const slot = slotId(generation, index);
|
|
3607
|
-
const surfaceArtifact = this.writeArtifact("candidate-surface", `candidate-${candidateId}.json`, {
|
|
3608
|
-
surfaceHash: candidateId,
|
|
3609
|
-
surface: candidate.surface,
|
|
3610
|
-
label: candidate.label ?? ""
|
|
3611
|
-
});
|
|
3612
|
-
const ledgerGeneration = parent ? parent.generation + 1 : 0;
|
|
3613
|
-
await this.append({
|
|
3614
|
-
kind: "candidate-registered",
|
|
3615
|
-
eventId: `candidate:${candidateId}`,
|
|
3616
|
-
occurredAt: this.stamp(),
|
|
3617
|
-
artifacts: [surfaceArtifact],
|
|
3618
|
-
slotId: slot,
|
|
3619
|
-
generationOperationId: generationOperationId(generation),
|
|
3620
|
-
candidateId,
|
|
3621
|
-
lineage: {
|
|
3622
|
-
lineageNodeId: candidateId,
|
|
3623
|
-
parentCandidateIds: parent ? [parent.candidateId] : [],
|
|
3624
|
-
generation: ledgerGeneration,
|
|
3625
|
-
proposer: this.opts.proposerLabel,
|
|
3626
|
-
proposerSource: this.opts.binding.identity.proposer.source
|
|
3627
|
-
},
|
|
3628
|
-
surfaces: candidateSurfaces(candidate.surface, surfaceArtifact)
|
|
3629
|
-
});
|
|
3630
|
-
this.remember({
|
|
3631
|
-
candidateId,
|
|
3632
|
-
slotId: slot,
|
|
3633
|
-
generation: ledgerGeneration
|
|
3634
|
-
});
|
|
3635
|
-
}
|
|
3636
|
-
for (let index = candidates.length; index < planned; index++) await this.closeSlot(slotId(generation, index), generationOperationId(generation), {
|
|
3637
|
-
code: "proposal-short",
|
|
3638
|
-
message: `generation ${generation} proposed ${candidates.length} of ${planned} planned candidates`
|
|
3639
|
-
});
|
|
3640
|
-
}
|
|
3641
|
-
/** Append one task attempt per designed cell of each candidate campaign. */
|
|
3642
|
-
async recordResults(candidates) {
|
|
3643
|
-
for (const candidate of candidates) {
|
|
3644
|
-
const registered = this.registered.get(candidate.surfaceHash);
|
|
3645
|
-
if (!registered) throw new Error(`search ledger: candidate ${candidate.surfaceHash} produced results without a registration`);
|
|
3646
|
-
this.coverage.set(registered.candidateId, candidate.coverageComplete);
|
|
3647
|
-
const evidence = this.writeArtifact("candidate-cells", `cells-${registered.candidateId}.json`, {
|
|
3648
|
-
surfaceHash: candidate.surfaceHash,
|
|
3649
|
-
runDir: candidate.runDir,
|
|
3650
|
-
cells: candidate.cells.map((cell) => ({
|
|
3651
|
-
cellId: cell.cellId,
|
|
3652
|
-
scenarioId: cell.scenarioId,
|
|
3653
|
-
rep: cell.rep,
|
|
3654
|
-
judgeScores: cell.judgeScores,
|
|
3655
|
-
costUsd: cell.costUsd,
|
|
3656
|
-
costProvenance: cell.costProvenance,
|
|
3657
|
-
tokenUsage: cell.tokenUsage,
|
|
3658
|
-
...cell.error === void 0 ? {} : {
|
|
3659
|
-
error: cell.error,
|
|
3660
|
-
errorStage: cell.errorStage
|
|
3661
|
-
}
|
|
3662
|
-
}))
|
|
3663
|
-
});
|
|
3664
|
-
const surfaceIds = candidateSurfaces(candidate.surface, evidence).map((surface) => surface.surfaceId);
|
|
3665
|
-
for (const cell of candidate.cells) {
|
|
3666
|
-
const taskId = taskIdFor(cell.scenarioId, cell.rep);
|
|
3667
|
-
const task = this.tasks.find((planned) => planned.taskId === taskId);
|
|
3668
|
-
if (!task) throw new Error(`search ledger: cell ${cell.cellId} is outside the planned task set`);
|
|
3669
|
-
await this.append({
|
|
3670
|
-
kind: "task-attempted",
|
|
3671
|
-
eventId: `attempt:${registered.candidateId}:${taskId}`,
|
|
3672
|
-
occurredAt: this.stamp(),
|
|
3673
|
-
artifacts: [evidence],
|
|
3674
|
-
candidateId: registered.candidateId,
|
|
3675
|
-
runId: `${registered.candidateId}:${cell.cellId}`,
|
|
3676
|
-
attemptIndex: 0,
|
|
3677
|
-
task: {
|
|
3678
|
-
taskId,
|
|
3679
|
-
source: task.source
|
|
3680
|
-
},
|
|
3681
|
-
identity: {
|
|
3682
|
-
model: this.cellModel(cell),
|
|
3683
|
-
agent: this.opts.binding.identity.agent,
|
|
3684
|
-
benchmark: task.benchmark
|
|
3685
|
-
},
|
|
3686
|
-
outcome: cellOutcome(cell),
|
|
3687
|
-
accounting: cellAccounting(cell),
|
|
3688
|
-
surfaceEvidence: surfaceIds.map((surfaceId) => surfaceEvidenceFor(surfaceId, evidence))
|
|
3689
|
-
});
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
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;
|
|
3692
4198
|
}
|
|
3693
4199
|
/**
|
|
3694
|
-
*
|
|
3695
|
-
*
|
|
3696
|
-
*
|
|
3697
|
-
* The terminal event is appended only when canonical replay accounts for the
|
|
3698
|
-
* whole planned denominator. An interrupted or partly unscored search stays
|
|
3699
|
-
* `in-progress` and its receipt reports the exact gap, instead of claiming a
|
|
3700
|
-
* 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.
|
|
3701
4202
|
*/
|
|
3702
|
-
async
|
|
3703
|
-
const
|
|
3704
|
-
|
|
3705
|
-
|
|
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
|
+
}
|
|
3706
4226
|
};
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
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);
|
|
3710
4230
|
}
|
|
3711
|
-
const
|
|
3712
|
-
|
|
3713
|
-
|
|
4231
|
+
const recorder = new SearchRecorder(options, event, state.lastOccurredAt);
|
|
4232
|
+
await recorder.append({
|
|
4233
|
+
...event,
|
|
4234
|
+
occurredAt: recorder.stamp()
|
|
3714
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);
|
|
3715
4270
|
await this.append({
|
|
3716
|
-
kind: "
|
|
3717
|
-
eventId:
|
|
4271
|
+
kind: "node-registered",
|
|
4272
|
+
eventId: `node:${nodeId}`,
|
|
3718
4273
|
occurredAt: this.stamp(),
|
|
3719
|
-
artifacts: [
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
source: this.opts.binding.identity.search
|
|
3725
|
-
},
|
|
3726
|
-
outcome: { status: "completed" },
|
|
3727
|
-
accounting: {
|
|
3728
|
-
tokens: {
|
|
3729
|
-
status: "known",
|
|
3730
|
-
inputTokens: 0,
|
|
3731
|
-
outputTokens: 0,
|
|
3732
|
-
cachedTokens: 0
|
|
3733
|
-
},
|
|
3734
|
-
cost: {
|
|
3735
|
-
status: "known",
|
|
3736
|
-
usd: 0,
|
|
3737
|
-
source: "free"
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
4274
|
+
artifacts: [],
|
|
4275
|
+
nodeId,
|
|
4276
|
+
artifactDigest: input.artifactDigest,
|
|
4277
|
+
artifact: input.artifact,
|
|
4278
|
+
surfaces: input.surfaces
|
|
3740
4279
|
});
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
}
|
|
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
|
|
3757
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 ?? "")
|
|
3758
4315
|
});
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
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}`,
|
|
3764
4323
|
occurredAt: this.stamp(),
|
|
3765
|
-
artifacts: [
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
} : {
|
|
3770
|
-
status: "all-rejected",
|
|
3771
|
-
reason: {
|
|
3772
|
-
code: "no-promoted-candidate",
|
|
3773
|
-
message: "no candidate beat the incumbent on the designed denominator"
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
4324
|
+
artifacts: [],
|
|
4325
|
+
operationId: input.operationId,
|
|
4326
|
+
operationKind: input.operationKind,
|
|
4327
|
+
reservation: input.reservation ?? null
|
|
3776
4328
|
});
|
|
3777
|
-
return this.receipt(input.runId);
|
|
3778
4329
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
ledger: {
|
|
3788
|
-
role: "search-ledger",
|
|
3789
|
-
uri: `file://${ledger.path}`,
|
|
3790
|
-
sha256: hashCanonical(bytes),
|
|
3791
|
-
byteLength: new TextEncoder().encode(bytes).byteLength
|
|
3792
|
-
},
|
|
3793
|
-
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 ?? []
|
|
3794
4338
|
});
|
|
3795
4339
|
}
|
|
3796
|
-
/**
|
|
3797
|
-
async
|
|
3798
|
-
const
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
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;
|
|
3815
4362
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
const
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
};
|
|
3832
|
-
candidateSlots.push(slot);
|
|
3833
|
-
if (!this.registeredSlot(slot.slotId)) this.openSlots.add(slot.slotId);
|
|
3834
|
-
}
|
|
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));
|
|
3835
4378
|
}
|
|
3836
4379
|
await this.append({
|
|
3837
|
-
kind: "
|
|
3838
|
-
eventId:
|
|
4380
|
+
kind: "cell-settled",
|
|
4381
|
+
eventId: `settled:${input.cellId}:${attempt}`,
|
|
3839
4382
|
occurredAt: this.stamp(),
|
|
3840
|
-
artifacts
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
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" }
|
|
3852
4396
|
});
|
|
3853
4397
|
}
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
if (this.registered.has(entry.candidateId)) return;
|
|
3859
|
-
this.registered.set(entry.candidateId, entry);
|
|
3860
|
-
this.order.push(entry);
|
|
3861
|
-
}
|
|
3862
|
-
async recordGenerationOperation(generation, candidates, unfilled, failure) {
|
|
3863
|
-
const shortfall = {
|
|
3864
|
-
code: "proposal-short",
|
|
3865
|
-
message: `generation ${generation} left ${unfilled} planned candidate slot(s) unfilled`
|
|
3866
|
-
};
|
|
3867
|
-
const outcome = failure ? {
|
|
3868
|
-
status: "failed",
|
|
3869
|
-
failure
|
|
3870
|
-
} : unfilled <= 0 ? { status: "completed" } : candidates.length === 0 ? {
|
|
3871
|
-
status: "failed",
|
|
3872
|
-
failure: shortfall
|
|
3873
|
-
} : {
|
|
3874
|
-
status: "partial",
|
|
3875
|
-
failure: shortfall
|
|
3876
|
-
};
|
|
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;
|
|
3877
4402
|
await this.append({
|
|
3878
|
-
kind: "
|
|
3879
|
-
eventId: `
|
|
4403
|
+
kind: "cell-cancelled",
|
|
4404
|
+
eventId: `cancelled:${input.cellId}`,
|
|
3880
4405
|
occurredAt: this.stamp(),
|
|
3881
|
-
artifacts: [
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
execution: this.opts.binding.identity.proposer,
|
|
3885
|
-
outcome,
|
|
3886
|
-
accounting: this.proposalAccounting()
|
|
4406
|
+
artifacts: [],
|
|
4407
|
+
cellId: input.cellId,
|
|
4408
|
+
reason: input.reason
|
|
3887
4409
|
});
|
|
3888
4410
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
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;
|
|
3892
4417
|
await this.append({
|
|
3893
|
-
kind: "
|
|
3894
|
-
eventId: `
|
|
4418
|
+
kind: "node-decided",
|
|
4419
|
+
eventId: `decided:${input.nodeId}:${node.decisions.length}`,
|
|
3895
4420
|
occurredAt: this.stamp(),
|
|
3896
|
-
artifacts: [
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
reason
|
|
4421
|
+
artifacts: [],
|
|
4422
|
+
nodeId: input.nodeId,
|
|
4423
|
+
decision: input.decision,
|
|
4424
|
+
basis: input.basis ?? null,
|
|
4425
|
+
rule: input.rule,
|
|
4426
|
+
reason: input.reason
|
|
3903
4427
|
});
|
|
3904
4428
|
}
|
|
3905
|
-
/**
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
this.
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
return {
|
|
3917
|
-
provider: this.opts.binding.identity.model.provider,
|
|
3918
|
-
snapshot: resolved
|
|
3919
|
-
};
|
|
3920
|
-
}
|
|
3921
|
-
proposalArtifact(generation, candidates) {
|
|
3922
|
-
return this.writeArtifact("candidate-proposal", `proposal-gen-${generation}.json`, {
|
|
3923
|
-
generation,
|
|
3924
|
-
candidates: candidates.map((candidate) => ({
|
|
3925
|
-
surfaceHash: candidate.surfaceHash,
|
|
3926
|
-
label: candidate.label ?? ""
|
|
3927
|
-
}))
|
|
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
|
|
3928
4440
|
});
|
|
3929
4441
|
}
|
|
3930
|
-
/**
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
return {
|
|
3938
|
-
role,
|
|
3939
|
-
uri: `file://${path}`,
|
|
3940
|
-
sha256: hashCanonical(contents),
|
|
3941
|
-
byteLength: new TextEncoder().encode(contents).byteLength
|
|
3942
|
-
};
|
|
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
|
+
});
|
|
3943
4449
|
}
|
|
3944
4450
|
async append(event) {
|
|
3945
|
-
|
|
3946
|
-
await this.opts.binding.ledger.append(event);
|
|
3947
|
-
this.durableEventIds.add(event.eventId);
|
|
4451
|
+
await this.ledger.append(event);
|
|
3948
4452
|
}
|
|
3949
4453
|
/** Non-decreasing ISO stamps; the ledger refuses an event that moves back. */
|
|
3950
4454
|
stamp() {
|
|
3951
|
-
const now =
|
|
4455
|
+
const now = this.now();
|
|
3952
4456
|
this.lastStampMs = now > this.lastStampMs ? now : this.lastStampMs + 1;
|
|
3953
4457
|
return new Date(this.lastStampMs).toISOString();
|
|
3954
4458
|
}
|
|
3955
4459
|
};
|
|
3956
|
-
/**
|
|
3957
|
-
function
|
|
3958
|
-
const
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
source: {
|
|
3962
|
-
uri: `scenario://${scenario.id}`,
|
|
3963
|
-
revision: hashCanonical(scenario)
|
|
3964
|
-
},
|
|
3965
|
-
benchmark: {
|
|
3966
|
-
uri: `campaign://${runDir}`,
|
|
3967
|
-
revision: splitDigest
|
|
3968
|
-
},
|
|
3969
|
-
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
|
|
3970
4465
|
});
|
|
3971
|
-
return
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
}
|
|
3976
|
-
function slotId(generation, index) {
|
|
3977
|
-
return `gen-${generation}-slot-${index}`;
|
|
4466
|
+
return {
|
|
4467
|
+
artifactDigest: surfaceContentHash(surface),
|
|
4468
|
+
artifact,
|
|
4469
|
+
surfaces: declaredSurfaces(surface, artifact)
|
|
4470
|
+
};
|
|
3978
4471
|
}
|
|
3979
|
-
|
|
3980
|
-
|
|
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
|
+
});
|
|
3981
4480
|
}
|
|
3982
|
-
/** Declared surfaces of one
|
|
3983
|
-
*
|
|
3984
|
-
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) {
|
|
3985
4484
|
if (typeof surface === "string") return [{
|
|
3986
4485
|
surfaceId: "prompt",
|
|
3987
4486
|
kind: "prompt",
|
|
@@ -3998,19 +4497,269 @@ function candidateSurfaces(surface, artifact) {
|
|
|
3998
4497
|
artifact
|
|
3999
4498
|
}));
|
|
4000
4499
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
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}`;
|
|
4014
4763
|
}
|
|
4015
4764
|
function cellOutcome(cell) {
|
|
4016
4765
|
const scores = Object.entries(cell.judgeScores).filter(([, score]) => score.failed !== true && Number.isFinite(score.composite));
|
|
@@ -4091,264 +4840,6 @@ function receiptAccounting(receipts) {
|
|
|
4091
4840
|
}
|
|
4092
4841
|
};
|
|
4093
4842
|
}
|
|
4094
|
-
/**
|
|
4095
|
-
* Record an optimizer's own candidate graph into the same ledger.
|
|
4096
|
-
*
|
|
4097
|
-
* A complete optimization method searches inside its own process and reports
|
|
4098
|
-
* one artifact when it finishes: the candidate population, with each
|
|
4099
|
-
* candidate's parents and its score per selection scenario. This turns that
|
|
4100
|
-
* artifact into the canonical event stream, so a first-party method returns
|
|
4101
|
-
* the same `SearchHistoryReceipt` the in-process loop returns, and
|
|
4102
|
-
* `compareOptimizationMethods({ searchHistoryPolicy: 'require-complete' })`
|
|
4103
|
-
* accepts it.
|
|
4104
|
-
*
|
|
4105
|
-
* A candidate the optimizer left unscored on a planned scenario leaves the
|
|
4106
|
-
* planned denominator open, so the receipt reports the gap instead of closing
|
|
4107
|
-
* the search.
|
|
4108
|
-
*/
|
|
4109
|
-
async function recordCandidatePopulationSearch(input) {
|
|
4110
|
-
const { population, ledger, identity } = input;
|
|
4111
|
-
const stamps = monotonicStamps();
|
|
4112
|
-
const writeArtifact = artifactWriter(input.storage, input.runDir);
|
|
4113
|
-
const populationArtifact = {
|
|
4114
|
-
role: "candidate-population",
|
|
4115
|
-
uri: `file://${population.summary.path}`,
|
|
4116
|
-
sha256: population.summary.sha256,
|
|
4117
|
-
byteLength: population.summary.bytes
|
|
4118
|
-
};
|
|
4119
|
-
const tasks = input.scenarios.filter((scenario) => population.summary.scenarioIds.includes(scenario.id)).map((scenario) => ({
|
|
4120
|
-
taskId: taskIdFor(scenario.id, 0),
|
|
4121
|
-
source: {
|
|
4122
|
-
uri: `scenario://${scenario.id}`,
|
|
4123
|
-
revision: hashCanonical(scenario)
|
|
4124
|
-
},
|
|
4125
|
-
benchmark: {
|
|
4126
|
-
uri: `optimizer://${input.producerId}`,
|
|
4127
|
-
revision: population.summary.sha256
|
|
4128
|
-
},
|
|
4129
|
-
maxAttempts: 1
|
|
4130
|
-
}));
|
|
4131
|
-
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`);
|
|
4132
|
-
const generationOperation = "candidate-generation:population";
|
|
4133
|
-
await ledger.append({
|
|
4134
|
-
kind: "search-planned",
|
|
4135
|
-
eventId: "search:plan",
|
|
4136
|
-
occurredAt: stamps(),
|
|
4137
|
-
artifacts: [populationArtifact],
|
|
4138
|
-
plan: {
|
|
4139
|
-
candidateSlots: population.candidates.map((candidate) => ({
|
|
4140
|
-
slotId: `candidate-${candidate.index}`,
|
|
4141
|
-
generationOperationId: generationOperation
|
|
4142
|
-
})),
|
|
4143
|
-
tasks,
|
|
4144
|
-
operations: [{
|
|
4145
|
-
operationId: generationOperation,
|
|
4146
|
-
kind: "candidate-generation"
|
|
4147
|
-
}, {
|
|
4148
|
-
operationId: "selection",
|
|
4149
|
-
kind: "selection"
|
|
4150
|
-
}]
|
|
4151
|
-
}
|
|
4152
|
-
});
|
|
4153
|
-
await ledger.append({
|
|
4154
|
-
kind: "search-operation-recorded",
|
|
4155
|
-
eventId: `operation:${generationOperation}`,
|
|
4156
|
-
occurredAt: stamps(),
|
|
4157
|
-
artifacts: [populationArtifact],
|
|
4158
|
-
operationId: generationOperation,
|
|
4159
|
-
operationKind: "candidate-generation",
|
|
4160
|
-
execution: identity.proposer,
|
|
4161
|
-
outcome: { status: "completed" },
|
|
4162
|
-
accounting: input.generationAccounting
|
|
4163
|
-
});
|
|
4164
|
-
const candidateIds = /* @__PURE__ */ new Map();
|
|
4165
|
-
const depths = /* @__PURE__ */ new Map();
|
|
4166
|
-
for (const candidate of population.candidates) {
|
|
4167
|
-
const parents = candidate.parentIndices.filter((index) => index !== null);
|
|
4168
|
-
const candidateId = candidate.candidateHash;
|
|
4169
|
-
candidateIds.set(candidate.index, candidateId);
|
|
4170
|
-
const depth = parents.length === 0 ? 0 : Math.max(...parents.map((index) => depths.get(index) ?? 0)) + 1;
|
|
4171
|
-
depths.set(candidate.index, depth);
|
|
4172
|
-
const surfaceArtifact = writeArtifact("candidate-surface", `candidate-${candidate.index}.json`, {
|
|
4173
|
-
index: candidate.index,
|
|
4174
|
-
candidateDigest: candidate.candidateDigest,
|
|
4175
|
-
candidate: candidate.candidate
|
|
4176
|
-
});
|
|
4177
|
-
await ledger.append({
|
|
4178
|
-
kind: "candidate-registered",
|
|
4179
|
-
eventId: `candidate:${candidate.index}`,
|
|
4180
|
-
occurredAt: stamps(),
|
|
4181
|
-
artifacts: [surfaceArtifact],
|
|
4182
|
-
slotId: `candidate-${candidate.index}`,
|
|
4183
|
-
generationOperationId: generationOperation,
|
|
4184
|
-
candidateId,
|
|
4185
|
-
lineage: {
|
|
4186
|
-
lineageNodeId: candidateId.slice(0, 16),
|
|
4187
|
-
parentCandidateIds: parents.map((index) => {
|
|
4188
|
-
const parentId = candidateIds.get(index);
|
|
4189
|
-
if (!parentId) throw new Error(`search ledger: candidate ${candidate.index} names parent ${index}, which precedes no registered candidate`);
|
|
4190
|
-
return parentId;
|
|
4191
|
-
}),
|
|
4192
|
-
generation: depth,
|
|
4193
|
-
proposer: input.producerId,
|
|
4194
|
-
proposerSource: identity.proposer.source
|
|
4195
|
-
},
|
|
4196
|
-
surfaces: candidateSurfaces(externalSurface(candidate.candidate), surfaceArtifact)
|
|
4197
|
-
});
|
|
4198
|
-
const surfaceIds = candidateSurfaces(externalSurface(candidate.candidate), surfaceArtifact).map((surface) => surface.surfaceId);
|
|
4199
|
-
for (const score of candidate.selectionScores) {
|
|
4200
|
-
const task = tasks.find((planned) => planned.taskId === taskIdFor(score.scenarioId, 0));
|
|
4201
|
-
if (!task) throw new Error(`search ledger: candidate ${candidate.index} scored unplanned scenario ${score.scenarioId}`);
|
|
4202
|
-
await ledger.append({
|
|
4203
|
-
kind: "task-attempted",
|
|
4204
|
-
eventId: `attempt:${candidate.index}:${task.taskId}`,
|
|
4205
|
-
occurredAt: stamps(),
|
|
4206
|
-
artifacts: [populationArtifact],
|
|
4207
|
-
candidateId,
|
|
4208
|
-
runId: `${input.runId}:${candidate.index}:${task.taskId}`,
|
|
4209
|
-
attemptIndex: 0,
|
|
4210
|
-
task: {
|
|
4211
|
-
taskId: task.taskId,
|
|
4212
|
-
source: task.source
|
|
4213
|
-
},
|
|
4214
|
-
identity: {
|
|
4215
|
-
model: identity.model,
|
|
4216
|
-
agent: identity.agent,
|
|
4217
|
-
benchmark: task.benchmark
|
|
4218
|
-
},
|
|
4219
|
-
outcome: {
|
|
4220
|
-
status: "passed",
|
|
4221
|
-
score: score.score,
|
|
4222
|
-
metrics: { composite: score.score }
|
|
4223
|
-
},
|
|
4224
|
-
accounting: {
|
|
4225
|
-
tokens: {
|
|
4226
|
-
status: "unknown",
|
|
4227
|
-
reason: "the optimizer reports no token usage per candidate evaluation"
|
|
4228
|
-
},
|
|
4229
|
-
cost: {
|
|
4230
|
-
status: "unknown",
|
|
4231
|
-
knownLowerBoundUsd: 0,
|
|
4232
|
-
reason: "the optimizer reports no cost per candidate evaluation"
|
|
4233
|
-
}
|
|
4234
|
-
},
|
|
4235
|
-
surfaceEvidence: surfaceIds.map((surfaceId) => surfaceEvidenceFor(surfaceId, populationArtifact))
|
|
4236
|
-
});
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
await ledger.append({
|
|
4240
|
-
kind: "search-operation-recorded",
|
|
4241
|
-
eventId: "operation:selection",
|
|
4242
|
-
occurredAt: stamps(),
|
|
4243
|
-
artifacts: [populationArtifact],
|
|
4244
|
-
operationId: "selection",
|
|
4245
|
-
operationKind: "selection",
|
|
4246
|
-
execution: {
|
|
4247
|
-
kind: "deterministic",
|
|
4248
|
-
source: identity.search
|
|
4249
|
-
},
|
|
4250
|
-
outcome: { status: "completed" },
|
|
4251
|
-
accounting: {
|
|
4252
|
-
tokens: {
|
|
4253
|
-
status: "known",
|
|
4254
|
-
inputTokens: 0,
|
|
4255
|
-
outputTokens: 0,
|
|
4256
|
-
cachedTokens: 0
|
|
4257
|
-
},
|
|
4258
|
-
cost: {
|
|
4259
|
-
status: "known",
|
|
4260
|
-
usd: 0,
|
|
4261
|
-
source: "free"
|
|
4262
|
-
}
|
|
4263
|
-
}
|
|
4264
|
-
});
|
|
4265
|
-
const winnerId = candidateIds.get(population.bestIndex);
|
|
4266
|
-
for (const candidate of population.candidates) await ledger.append({
|
|
4267
|
-
kind: "candidate-decided",
|
|
4268
|
-
eventId: `decision:${candidate.index}`,
|
|
4269
|
-
occurredAt: stamps(),
|
|
4270
|
-
artifacts: [populationArtifact],
|
|
4271
|
-
candidateId: candidateIds.get(candidate.index),
|
|
4272
|
-
decision: candidate.index === population.bestIndex ? { status: "selected" } : {
|
|
4273
|
-
status: "rejected",
|
|
4274
|
-
reason: {
|
|
4275
|
-
code: "not-selected",
|
|
4276
|
-
message: "the optimizer selected another candidate"
|
|
4277
|
-
}
|
|
4278
|
-
}
|
|
4279
|
-
});
|
|
4280
|
-
const replay = await ledger.replay();
|
|
4281
|
-
const { missingCandidateSlots, missingTaskOutcomes, missingOperations } = replay.audit.expected;
|
|
4282
|
-
if (winnerId && missingCandidateSlots.length === 0 && missingTaskOutcomes.length === 0 && missingOperations.length === 0 && replay.audit.decisions.pending === 0) await ledger.append({
|
|
4283
|
-
kind: "search-completed",
|
|
4284
|
-
eventId: "search:completed",
|
|
4285
|
-
occurredAt: stamps(),
|
|
4286
|
-
artifacts: [populationArtifact],
|
|
4287
|
-
result: {
|
|
4288
|
-
status: "selected",
|
|
4289
|
-
candidateId: winnerId
|
|
4290
|
-
}
|
|
4291
|
-
});
|
|
4292
|
-
const final = await ledger.replay();
|
|
4293
|
-
const bytes = final.entries.map((entry) => `${canonicalString(entry)}\n`).join("");
|
|
4294
|
-
return createSearchHistoryReceipt({
|
|
4295
|
-
producerId: input.producerId,
|
|
4296
|
-
runId: input.runId,
|
|
4297
|
-
ledger: {
|
|
4298
|
-
role: "search-ledger",
|
|
4299
|
-
uri: `file://${ledger.path}`,
|
|
4300
|
-
sha256: hashCanonical(bytes),
|
|
4301
|
-
byteLength: new TextEncoder().encode(bytes).byteLength
|
|
4302
|
-
},
|
|
4303
|
-
replay: final
|
|
4304
|
-
});
|
|
4305
|
-
}
|
|
4306
|
-
/** An optimizer candidate as the canonical mutable surface it represents. */
|
|
4307
|
-
function externalSurface(candidate) {
|
|
4308
|
-
return typeof candidate === "string" ? candidate : {
|
|
4309
|
-
kind: "components",
|
|
4310
|
-
components: candidate
|
|
4311
|
-
};
|
|
4312
|
-
}
|
|
4313
|
-
function artifactWriter(storage, runDir) {
|
|
4314
|
-
const directory = `${runDir}/${SEARCH_LEDGER_DIR}`;
|
|
4315
|
-
return (role, name, body) => {
|
|
4316
|
-
const contents = canonicalString(body);
|
|
4317
|
-
storage.ensureDir(directory);
|
|
4318
|
-
storage.write(`${directory}/${name}`, contents);
|
|
4319
|
-
return {
|
|
4320
|
-
role,
|
|
4321
|
-
uri: `file://${directory}/${name}`,
|
|
4322
|
-
sha256: hashCanonical(contents),
|
|
4323
|
-
byteLength: new TextEncoder().encode(contents).byteLength
|
|
4324
|
-
};
|
|
4325
|
-
};
|
|
4326
|
-
}
|
|
4327
|
-
/** Non-decreasing ISO stamps; the ledger refuses an event that moves back. */
|
|
4328
|
-
function monotonicStamps() {
|
|
4329
|
-
let last = 0;
|
|
4330
|
-
return () => {
|
|
4331
|
-
const now = Date.now();
|
|
4332
|
-
last = now > last ? now : last + 1;
|
|
4333
|
-
return new Date(last).toISOString();
|
|
4334
|
-
};
|
|
4335
|
-
}
|
|
4336
|
-
//#endregion
|
|
4337
|
-
//#region src/campaign/types.ts
|
|
4338
|
-
/** Type guard: a proposal carrying its rationale vs a bare
|
|
4339
|
-
* surface. The loop branches on this to populate `GenerationCandidate`. */
|
|
4340
|
-
function isProposedCandidate(value) {
|
|
4341
|
-
return typeof value === "object" && value !== null && "surface" in value && "label" in value && "rationale" in value;
|
|
4342
|
-
}
|
|
4343
|
-
const LABEL_TRUST_RANK = {
|
|
4344
|
-
unverified: 0,
|
|
4345
|
-
"verified-signal": 1,
|
|
4346
|
-
"human-rated": 2
|
|
4347
|
-
};
|
|
4348
|
-
/** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
|
|
4349
|
-
function labelTrustRank(trust) {
|
|
4350
|
-
return LABEL_TRUST_RANK[trust ?? "unverified"];
|
|
4351
|
-
}
|
|
4352
4843
|
//#endregion
|
|
4353
4844
|
//#region src/campaign/presets/run-optimization.ts
|
|
4354
4845
|
/**
|
|
@@ -4404,18 +4895,26 @@ async function runOptimization(opts) {
|
|
|
4404
4895
|
const label = opts.premeasuredBaseline ? "premeasured baseline" : "baseline";
|
|
4405
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.`);
|
|
4406
4897
|
}
|
|
4407
|
-
const
|
|
4898
|
+
const opened = opts.searchLedger ? await OptimizationSearch.open({
|
|
4408
4899
|
binding: opts.searchLedger,
|
|
4409
4900
|
storage,
|
|
4410
|
-
|
|
4901
|
+
costLedger,
|
|
4411
4902
|
scenarios: opts.scenarios,
|
|
4412
4903
|
reps,
|
|
4413
|
-
|
|
4414
|
-
populationSize: opts.populationSize,
|
|
4904
|
+
seed: opts.seed ?? 42,
|
|
4415
4905
|
splitDigest: baselineCampaign.splitDigest,
|
|
4416
|
-
|
|
4417
|
-
|
|
4906
|
+
proposerName: proposer.kind,
|
|
4907
|
+
expansion: opts.selectParent ? "select-parent" : "incumbent",
|
|
4908
|
+
maxUsd: opts.costCeiling ?? null,
|
|
4909
|
+
baselineSurface,
|
|
4910
|
+
baselineCells: baselineCampaign.cells
|
|
4418
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";
|
|
4419
4918
|
const generations = [];
|
|
4420
4919
|
const history = [];
|
|
4421
4920
|
let currentFindings = initialFindings;
|
|
@@ -4453,7 +4952,10 @@ async function runOptimization(opts) {
|
|
|
4453
4952
|
}
|
|
4454
4953
|
for (let gen = 0; gen < opts.maxGenerations; gen++) {
|
|
4455
4954
|
const proposalHistory = immutableProposalSnapshot(history, "history");
|
|
4456
|
-
if (proposer.decide?.({ history: proposalHistory }).stop)
|
|
4955
|
+
if (proposer.decide?.({ history: proposalHistory }).stop) {
|
|
4956
|
+
stopReason = "converged";
|
|
4957
|
+
break;
|
|
4958
|
+
}
|
|
4457
4959
|
const paretoParents = immutableProposalSnapshot(computeParetoFrontier(scored), "Pareto parents");
|
|
4458
4960
|
const incumbentOutcome = immutableProposalSnapshot(winnerOutcome, "incumbent outcome");
|
|
4459
4961
|
const selected = opts.selectParent ? resolveSelectedParent(opts.selectParent(Object.freeze({
|
|
@@ -4481,10 +4983,15 @@ async function runOptimization(opts) {
|
|
|
4481
4983
|
costLedger,
|
|
4482
4984
|
costPhase: "search.proposal"
|
|
4483
4985
|
});
|
|
4986
|
+
await search?.startGeneration(gen);
|
|
4484
4987
|
const proposed = await proposer.propose(proposalContext);
|
|
4485
4988
|
if (!Array.isArray(proposed)) throw new TypeError("runOptimization: proposer must return an array");
|
|
4486
4989
|
const proposalSnapshot = immutableProposalSnapshot(proposed, "candidate outputs");
|
|
4487
|
-
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
|
+
}
|
|
4488
4995
|
const candidates = proposalSnapshot.map((p) => isProposedCandidate(p) ? p : {
|
|
4489
4996
|
surface: p,
|
|
4490
4997
|
label: "",
|
|
@@ -4497,15 +5004,22 @@ async function runOptimization(opts) {
|
|
|
4497
5004
|
generationHashes.add(hash);
|
|
4498
5005
|
}
|
|
4499
5006
|
for (const hash of generationHashes) admittedCandidateHashes.add(hash);
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
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
|
+
}
|
|
4509
5023
|
const surfaceResults = await mapConcurrent(candidates, candidateConcurrency, async ({ surface, label, rationale, attribution }, i, signal) => {
|
|
4510
5024
|
const hash = surfaceHash(surface);
|
|
4511
5025
|
const campaign = await runCampaign({
|
|
@@ -4544,13 +5058,11 @@ async function runOptimization(opts) {
|
|
|
4544
5058
|
});
|
|
4545
5059
|
}
|
|
4546
5060
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
coverageComplete: result.coverage.complete
|
|
4553
|
-
})));
|
|
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
|
+
}
|
|
4554
5066
|
surfaceResults.sort((a, b) => {
|
|
4555
5067
|
if (a.coverage.complete !== b.coverage.complete) return a.coverage.complete ? -1 : 1;
|
|
4556
5068
|
if (a.rankKey && b.rankKey) return compareRankKeys(b.rankKey, a.rankKey);
|
|
@@ -4560,6 +5072,8 @@ async function runOptimization(opts) {
|
|
|
4560
5072
|
const promoted = top && compareRankKeys(top.rankKey, winnerRankKey) > 0 ? [top] : [];
|
|
4561
5073
|
if (promoted[0]) {
|
|
4562
5074
|
const top = promoted[0];
|
|
5075
|
+
const promotedNodeId = nodeIdByHash.get(top.surfaceHash);
|
|
5076
|
+
if (promotedNodeId) promotedNodeIds.add(promotedNodeId);
|
|
4563
5077
|
winnerSurface = top.surface;
|
|
4564
5078
|
winnerSurfaceHash = top.surfaceHash;
|
|
4565
5079
|
winnerComposite = top.composite;
|
|
@@ -4621,9 +5135,11 @@ async function runOptimization(opts) {
|
|
|
4621
5135
|
currentFindings = immutableProposalSnapshot(assertProposalFindings(fresh, "runOptimization generation analysis findings"), "generation analysis findings");
|
|
4622
5136
|
}
|
|
4623
5137
|
}
|
|
4624
|
-
const searchHistory = await
|
|
4625
|
-
winnerSurfaceHash,
|
|
4626
|
-
|
|
5138
|
+
const searchHistory = await search?.finish({
|
|
5139
|
+
winnerNodeId: nodeIdByHash.get(winnerSurfaceHash),
|
|
5140
|
+
promotedNodeIds,
|
|
5141
|
+
incompleteNodeIds,
|
|
5142
|
+
reason: stopReason,
|
|
4627
5143
|
runId: opts.runDir
|
|
4628
5144
|
});
|
|
4629
5145
|
return {
|
|
@@ -6154,6 +6670,6 @@ function firstString(value) {
|
|
|
6154
6670
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
6155
6671
|
}
|
|
6156
6672
|
//#endregion
|
|
6157
|
-
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 };
|
|
6158
6674
|
|
|
6159
|
-
//# sourceMappingURL=llm-judge-
|
|
6675
|
+
//# sourceMappingURL=llm-judge-bwbgDMP0.js.map
|