@tangle-network/agent-eval 0.190.0 → 0.191.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/adapters/http.d.ts +2 -2
  3. package/dist/{agent-profile-DNV_U6QR.d.ts → agent-profile-DDfIjdyS.d.ts} +3 -3
  4. package/dist/{agent-profile-DNV_U6QR.d.ts.map → agent-profile-DDfIjdyS.d.ts.map} +1 -1
  5. package/dist/analyst/index.d.ts +11 -11
  6. package/dist/analyst/index.d.ts.map +1 -1
  7. package/dist/analyst/index.js +6 -6
  8. package/dist/{insight-report-CvPkEIcC.d.ts → analyze-runs-CPiKLJFs.d.ts} +73 -3
  9. package/dist/analyze-runs-CPiKLJFs.d.ts.map +1 -0
  10. package/dist/{attribute-vocabulary-DLJ6303h.d.ts → attribute-vocabulary-CcXqHODT.d.ts} +23 -12
  11. package/dist/attribute-vocabulary-CcXqHODT.d.ts.map +1 -0
  12. package/dist/{benchmark-command-TZlsuAh9.js → benchmark-command-3tZJnD82.js} +12 -11
  13. package/dist/benchmark-command-3tZJnD82.js.map +1 -0
  14. package/dist/benchmarks/index.d.ts +3 -3
  15. package/dist/benchmarks/index.js +3 -3
  16. package/dist/campaign/index.d.ts +6 -6
  17. package/dist/campaign/index.js +9 -7
  18. package/dist/{campaign-_29Qpc9Z.js → campaign-C8iWEF0M.js} +11 -9
  19. package/dist/{campaign-_29Qpc9Z.js.map → campaign-C8iWEF0M.js.map} +1 -1
  20. package/dist/{campaign-evidence-CWF8pEIk.js → campaign-evidence-BqjlTM3P.js} +11 -112
  21. package/dist/campaign-evidence-BqjlTM3P.js.map +1 -0
  22. package/dist/{chat-client-DbH06Ek6.js → chat-client-B_ItsSBw.js} +2 -2
  23. package/dist/{chat-client-DbH06Ek6.js.map → chat-client-B_ItsSBw.js.map} +1 -1
  24. package/dist/{chat-json-call-B0xTEY0I.js → chat-json-call-CXHBvUz8.js} +2 -2
  25. package/dist/{chat-json-call-B0xTEY0I.js.map → chat-json-call-CXHBvUz8.js.map} +1 -1
  26. package/dist/claim-V9Zqbq0J.js +54 -0
  27. package/dist/claim-V9Zqbq0J.js.map +1 -0
  28. package/dist/cli.js +101 -4
  29. package/dist/cli.js.map +1 -1
  30. package/dist/client-wFyDisxi.d.ts +236 -0
  31. package/dist/client-wFyDisxi.d.ts.map +1 -0
  32. package/dist/contract/index.d.ts +10 -11
  33. package/dist/contract/index.js +11 -141
  34. package/dist/contract/index.js.map +1 -1
  35. package/dist/{default-registry-B5HrwrPM.d.ts → default-registry-DK_9aVJf.d.ts} +6 -6
  36. package/dist/{default-registry-B5HrwrPM.d.ts.map → default-registry-DK_9aVJf.d.ts.map} +1 -1
  37. package/dist/{default-registry-16I-Bxlf.js → default-registry-xMLX160E.js} +3 -3
  38. package/dist/{default-registry-16I-Bxlf.js.map → default-registry-xMLX160E.js.map} +1 -1
  39. package/dist/{define-agent-eval-DrGfrCTZ.js → define-agent-eval-BE43YTm4.js} +27 -109
  40. package/dist/define-agent-eval-BE43YTm4.js.map +1 -0
  41. package/dist/{define-agent-eval-BWeyIpny.d.ts → define-agent-eval-P28y7mI1.d.ts} +16 -20
  42. package/dist/define-agent-eval-P28y7mI1.d.ts.map +1 -0
  43. package/dist/diagnosis/index.d.ts +16 -4
  44. package/dist/diagnosis/index.d.ts.map +1 -1
  45. package/dist/diagnosis/index.js +10 -3
  46. package/dist/diagnosis/index.js.map +1 -1
  47. package/dist/{dspy-rlm-engine-CTIGomgr.js → dspy-rlm-engine-MfpwoEaj.js} +3 -3
  48. package/dist/{dspy-rlm-engine-CTIGomgr.js.map → dspy-rlm-engine-MfpwoEaj.js.map} +1 -1
  49. package/dist/{engine-BkLCnZQX.d.ts → engine-vNiM02Ub.d.ts} +4 -4
  50. package/dist/{engine-BkLCnZQX.d.ts.map → engine-vNiM02Ub.d.ts.map} +1 -1
  51. package/dist/{eval-campaign-CMKRjWl0.js → eval-campaign-CwsBn0Gm.js} +2 -2
  52. package/dist/{eval-campaign-CMKRjWl0.js.map → eval-campaign-CwsBn0Gm.js.map} +1 -1
  53. package/dist/evaluation.d.ts +2 -2
  54. package/dist/{exact-types-CfpP2A4Z.d.ts → exact-types-KGxBWH4b.d.ts} +2 -2
  55. package/dist/{exact-types-CfpP2A4Z.d.ts.map → exact-types-KGxBWH4b.d.ts.map} +1 -1
  56. package/dist/experiment/index.d.ts +1 -1
  57. package/dist/experiment/index.js +2 -1
  58. package/dist/experiment/index.js.map +1 -1
  59. package/dist/{external-optimizer-process-Mc83UoWl.js → external-optimizer-process-xFTliuiK.js} +3 -3
  60. package/dist/{external-optimizer-process-Mc83UoWl.js.map → external-optimizer-process-xFTliuiK.js.map} +1 -1
  61. package/dist/{external-optimizer-subprocess-C0dHXH8e.js → external-optimizer-subprocess-B234V9V0.js} +5 -28
  62. package/dist/external-optimizer-subprocess-B234V9V0.js.map +1 -0
  63. package/dist/{extract-usage-BrQ8mCLX.js → extract-usage-DFATEuWW.js} +2 -2
  64. package/dist/{extract-usage-BrQ8mCLX.js.map → extract-usage-DFATEuWW.js.map} +1 -1
  65. package/dist/{feedback-trajectory-BYjh7NEM.d.ts → feedback-trajectory-BXCGTRkz.d.ts} +3 -3
  66. package/dist/{feedback-trajectory-BYjh7NEM.d.ts.map → feedback-trajectory-BXCGTRkz.d.ts.map} +1 -1
  67. package/dist/hosted/index.d.ts +66 -6
  68. package/dist/hosted/index.d.ts.map +1 -1
  69. package/dist/hosted/index.js +2 -2
  70. package/dist/{index-Ci0-EYzc.d.ts → index-CzfnASj_.d.ts} +18 -11
  71. package/dist/index-CzfnASj_.d.ts.map +1 -0
  72. package/dist/{index-2e_MTznj.d.ts → index-D-TN7ZRM.d.ts} +6 -6
  73. package/dist/{index-2e_MTznj.d.ts.map → index-D-TN7ZRM.d.ts.map} +1 -1
  74. package/dist/{index-BR_R1eBE.d.ts → index-DH-MWH5f.d.ts} +10 -104
  75. package/dist/index-DH-MWH5f.d.ts.map +1 -0
  76. package/dist/{index-BqDJ6fUK.d.ts → index-I4UJGRno.d.ts} +74 -2
  77. package/dist/index-I4UJGRno.d.ts.map +1 -0
  78. package/dist/index.d.ts +17 -18
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +17 -17
  81. package/dist/jev.d.ts +2 -2
  82. package/dist/{journal-CDP0Vhx_.js → journal-c3qY0SDP.js} +246 -5
  83. package/dist/journal-c3qY0SDP.js.map +1 -0
  84. package/dist/{kind-factory-ByZeXDqF.js → kind-factory-CdPPHtGZ.js} +10 -26
  85. package/dist/kind-factory-CdPPHtGZ.js.map +1 -0
  86. package/dist/ledger-core/index.d.ts +2 -2
  87. package/dist/ledger-core/index.js +2 -2
  88. package/dist/{llm-client-BqmRnibA.js → llm-client-KYQBh9C8.js} +2 -2
  89. package/dist/{llm-client-BqmRnibA.js.map → llm-client-KYQBh9C8.js.map} +1 -1
  90. package/dist/{llm-judge-bwbgDMP0.js → llm-judge-NJOHM0iA.js} +11 -1428
  91. package/dist/llm-judge-NJOHM0iA.js.map +1 -0
  92. package/dist/{matrix-CNypzP-v.d.ts → matrix-CgqdFm5N.d.ts} +2 -2
  93. package/dist/{matrix-CNypzP-v.d.ts.map → matrix-CgqdFm5N.d.ts.map} +1 -1
  94. package/dist/multishot/golden/index.d.ts +1 -1
  95. package/dist/multishot/index.d.ts +2 -2
  96. package/dist/openapi.json +1 -1
  97. package/dist/{otel-spans-CQY1pNiN.js → otel-spans-DS7t1t1J.js} +8 -19
  98. package/dist/otel-spans-DS7t1t1J.js.map +1 -0
  99. package/dist/otlp-attributes-D9ptWaIJ.js +53 -0
  100. package/dist/otlp-attributes-D9ptWaIJ.js.map +1 -0
  101. package/dist/pipelines/index.d.ts +1 -1
  102. package/dist/pipelines/index.js +1 -1
  103. package/dist/pipelines/index.js.map +1 -1
  104. package/dist/{prime-protocol-Fwvctmhm.d.ts → prime-protocol-T0i6xcAs.d.ts} +2 -2
  105. package/dist/{prime-protocol-Fwvctmhm.d.ts.map → prime-protocol-T0i6xcAs.d.ts.map} +1 -1
  106. package/dist/{produced-state-zSaGDC_Q.js → produced-state-CY-L7CqS.js} +4 -4
  107. package/dist/{produced-state-zSaGDC_Q.js.map → produced-state-CY-L7CqS.js.map} +1 -1
  108. package/dist/{raw-provider-sink-DIRCZjP_.js → raw-provider-sink-DYh4Bszw.js} +2 -2
  109. package/dist/{raw-provider-sink-DIRCZjP_.js.map → raw-provider-sink-DYh4Bszw.js.map} +1 -1
  110. package/dist/{redact-8fX8-uKr.js → redact-B7rt9Zot.js} +117 -6
  111. package/dist/redact-B7rt9Zot.js.map +1 -0
  112. package/dist/{redact-Du3WKUOP.d.ts → redact-CopuMxll.d.ts} +2 -2
  113. package/dist/{redact-Du3WKUOP.d.ts.map → redact-CopuMxll.d.ts.map} +1 -1
  114. package/dist/{registry-mAQVtuLx.d.ts → registry-Bu3lNole.d.ts} +3 -3
  115. package/dist/{registry-mAQVtuLx.d.ts.map → registry-Bu3lNole.d.ts.map} +1 -1
  116. package/dist/rl.d.ts +1 -1
  117. package/dist/rl.js +2 -2
  118. package/dist/{run-record-DnMFyg_g.js → run-record-tNnlQIrD.js} +2 -10
  119. package/dist/{run-record-DnMFyg_g.js.map → run-record-tNnlQIrD.js.map} +1 -1
  120. package/dist/search-ledger-CQ8Uy4FK.js +1451 -0
  121. package/dist/search-ledger-CQ8Uy4FK.js.map +1 -0
  122. package/dist/search-ledger-file-EvdDZRhv.js +27 -0
  123. package/dist/search-ledger-file-EvdDZRhv.js.map +1 -0
  124. package/dist/search-shipper-C55V7HE3.js +781 -0
  125. package/dist/search-shipper-C55V7HE3.js.map +1 -0
  126. package/dist/{semantic-concept-judge-BY0me-Jq.js → semantic-concept-judge-Bt7bXAAo.js} +6 -6
  127. package/dist/{semantic-concept-judge-BY0me-Jq.js.map → semantic-concept-judge-Bt7bXAAo.js.map} +1 -1
  128. package/dist/{server-DnJCDLf8.js → server-DtetGGoA.js} +2 -2
  129. package/dist/{server-DnJCDLf8.js.map → server-DtetGGoA.js.map} +1 -1
  130. package/dist/{skillopt-optimization-method-CfnQEjgT.js → skillopt-optimization-method-DwO3TOAV.js} +7 -6
  131. package/dist/{skillopt-optimization-method-CfnQEjgT.js.map → skillopt-optimization-method-DwO3TOAV.js.map} +1 -1
  132. package/dist/{spans-D1RftP_w.d.ts → spans-BWtjk_9c.d.ts} +2 -2
  133. package/dist/{spans-D1RftP_w.d.ts.map → spans-BWtjk_9c.d.ts.map} +1 -1
  134. package/dist/{statistical-heldout-CLpjYffJ.d.ts → statistical-heldout-BED3wBtX.d.ts} +2 -2
  135. package/dist/{statistical-heldout-CLpjYffJ.d.ts.map → statistical-heldout-BED3wBtX.d.ts.map} +1 -1
  136. package/dist/{store-otlp-84DH0OEH.js → store-otlp-BPowgPm6.js} +2 -2
  137. package/dist/{store-otlp-84DH0OEH.js.map → store-otlp-BPowgPm6.js.map} +1 -1
  138. package/dist/{store-tool-spans-DSbYpl7f.js → store-tool-spans-BcC_5ef-.js} +6 -6
  139. package/dist/{store-tool-spans-DSbYpl7f.js.map → store-tool-spans-BcC_5ef-.js.map} +1 -1
  140. package/dist/{store-tool-spans-BBXs8hDP.d.ts → store-tool-spans-Cfh9XUCm.d.ts} +5 -9
  141. package/dist/store-tool-spans-Cfh9XUCm.d.ts.map +1 -0
  142. package/dist/{task-failure-attributes-B34TiMBe.js → task-failure-attributes-CEqEfuIY.js} +17 -9
  143. package/dist/task-failure-attributes-CEqEfuIY.js.map +1 -0
  144. package/dist/{tool-groups-W7MJobX_.d.ts → tool-groups-D-mkeN0N.d.ts} +3 -3
  145. package/dist/tool-groups-D-mkeN0N.d.ts.map +1 -0
  146. package/dist/trace-attributes.d.ts +1 -1
  147. package/dist/trace-attributes.js +23 -75
  148. package/dist/trace-attributes.js.map +1 -1
  149. package/dist/trace-repair/index.d.ts +1 -1
  150. package/dist/traces.d.ts +12 -19
  151. package/dist/traces.d.ts.map +1 -1
  152. package/dist/traces.js +18 -18
  153. package/dist/traces.js.map +1 -1
  154. package/dist/{types-BvmWNjjs.d.ts → types-BmQRs661.d.ts} +2 -2
  155. package/dist/{types-BvmWNjjs.d.ts.map → types-BmQRs661.d.ts.map} +1 -1
  156. package/dist/{types-Bay1dh2o.d.ts → types-CeDfCRXp.d.ts} +11 -16
  157. package/dist/types-CeDfCRXp.d.ts.map +1 -0
  158. package/dist/{types-Cvqy7asY.d.ts → types-ClK-sHs8.d.ts} +2 -2
  159. package/dist/{types-Cvqy7asY.d.ts.map → types-ClK-sHs8.d.ts.map} +1 -1
  160. package/dist/wire/index.d.ts +1 -1
  161. package/dist/wire/index.js +1 -1
  162. package/docs/hosted-ingest-spec.md +92 -133
  163. package/docs/public-api.md +58 -43
  164. package/docs/search-ledger.md +17 -0
  165. package/package.json +2 -2
  166. package/dist/analyze-runs-BvK4-wSs.d.ts +0 -75
  167. package/dist/analyze-runs-BvK4-wSs.d.ts.map +0 -1
  168. package/dist/attribute-vocabulary-DLJ6303h.d.ts.map +0 -1
  169. package/dist/benchmark-command-TZlsuAh9.js.map +0 -1
  170. package/dist/campaign-evidence-CWF8pEIk.js.map +0 -1
  171. package/dist/client-BhEyAdfe.d.ts +0 -202
  172. package/dist/client-BhEyAdfe.d.ts.map +0 -1
  173. package/dist/client-QwJpsNYU.js +0 -655
  174. package/dist/client-QwJpsNYU.js.map +0 -1
  175. package/dist/define-agent-eval-BWeyIpny.d.ts.map +0 -1
  176. package/dist/define-agent-eval-DrGfrCTZ.js.map +0 -1
  177. package/dist/external-optimizer-subprocess-C0dHXH8e.js.map +0 -1
  178. package/dist/index-BR_R1eBE.d.ts.map +0 -1
  179. package/dist/index-BqDJ6fUK.d.ts.map +0 -1
  180. package/dist/index-Ci0-EYzc.d.ts.map +0 -1
  181. package/dist/insight-report-CvPkEIcC.d.ts.map +0 -1
  182. package/dist/journal-CDP0Vhx_.js.map +0 -1
  183. package/dist/kind-factory-ByZeXDqF.js.map +0 -1
  184. package/dist/llm-judge-bwbgDMP0.js.map +0 -1
  185. package/dist/otel-spans-CQY1pNiN.js.map +0 -1
  186. package/dist/otlp-attributes-BpC_blLF.js +0 -83
  187. package/dist/otlp-attributes-BpC_blLF.js.map +0 -1
  188. package/dist/redact-8fX8-uKr.js.map +0 -1
  189. package/dist/store-tool-spans-BBXs8hDP.d.ts.map +0 -1
  190. package/dist/task-failure-attributes-B34TiMBe.js.map +0 -1
  191. package/dist/tool-groups-W7MJobX_.d.ts.map +0 -1
  192. package/dist/types-Bay1dh2o.d.ts.map +0 -1
@@ -0,0 +1,1451 @@
1
+ import { a as hashCanonical, i as compareCodeUnits, r as canonicalString } from "./canonical-BsZVgBhW.js";
2
+ import { i as modelHasSnapshot, l as searchCellRunId } from "./run-record-BRlM99rX.js";
3
+ import { n as replayLedgerText, t as FileLedgerJournal } from "./journal-c3qY0SDP.js";
4
+ import "./ledger-core/index.js";
5
+ import { a as SearchLedgerError, i as SearchLedgerConflictError, o as SearchLedgerIntegrityError, t as SEARCH_LEDGER_FILE_CONTEXT } from "./search-ledger-file-EvdDZRhv.js";
6
+ import { n as evaluationClaimSchema } from "./claim-V9Zqbq0J.js";
7
+ import { createHash } from "node:crypto";
8
+ import { z } from "zod";
9
+ //#region src/campaign/search-ledger-ordering.ts
10
+ function artifactKey(artifact) {
11
+ return canonicalString(artifact);
12
+ }
13
+ function compareStrings(a, b) {
14
+ return a < b ? -1 : a > b ? 1 : 0;
15
+ }
16
+ //#endregion
17
+ //#region src/campaign/search-ledger-types.ts
18
+ /**
19
+ * Schema tag every search-ledger entry carries. A ledger written under another
20
+ * tag, such as the retired `tangle.search-ledger.v1` candidate-slot format, is
21
+ * refused with its tag named; it is never translated.
22
+ */
23
+ const SEARCH_LEDGER_SCHEMA = "tangle.search-ledger.2026-09";
24
+ //#endregion
25
+ //#region src/campaign/search-state.ts
26
+ /**
27
+ * `SearchState`: the one projection of a search ledger.
28
+ *
29
+ * The same code checks every invariant for the producer's journal, the kernel,
30
+ * and the Intelligence verifier, and builds the read model they share. It keeps
31
+ * compact indexes (nodes, edges, cells, per-unit score sums, running audit
32
+ * totals), not the raw event list, and applies each entry in time proportional
33
+ * to that entry, so a search of any length appends at a constant cost.
34
+ *
35
+ * The journal asks for a snapshot after every append. A snapshot is a
36
+ * `SearchStateView` that reads the live indexes, so taking one costs nothing.
37
+ * The view is valid until the state applies its next entry; a read after that
38
+ * throws rather than returning numbers from a later ledger position. Read the
39
+ * ledger's state again to see newer entries. The audit, header, head and claim
40
+ * on a view are plain values and stay readable.
41
+ */
42
+ /** Deterministic node id: identical content in one search is one node. */
43
+ function searchNodeId(searchId, artifactDigest) {
44
+ return `node_${shortDigest([searchId, artifactDigest])}`;
45
+ }
46
+ /** Deterministic cell id: one node on one task in one split at one repeat. */
47
+ function searchCellId(searchId, nodeId, taskId, split, rep) {
48
+ return `cell_${shortDigest([
49
+ searchId,
50
+ nodeId,
51
+ taskId,
52
+ split,
53
+ rep
54
+ ])}`;
55
+ }
56
+ /** Digest a split's task list in taskId order. */
57
+ function searchTaskSetDigest(tasks) {
58
+ return hashCanonical([...tasks].sort((left, right) => compareCodeUnits(left.taskId, right.taskId)));
59
+ }
60
+ /** 128 bits of SHA-256 over the canonical encoding: collision-safe and short
61
+ * enough for directory names. */
62
+ function shortDigest(value) {
63
+ return createHash("sha256").update(canonicalString(value)).digest("hex").slice(0, 32);
64
+ }
65
+ const MAX_LABEL_CHARS = 200;
66
+ const USD_TOLERANCE = 1e-9;
67
+ const SPLITS = [
68
+ "train",
69
+ "selection",
70
+ "test"
71
+ ];
72
+ /**
73
+ * Per-unit means of scored cells, in unitId order. Each unit sums its cells in
74
+ * cellId order, so the result depends on the set of cells alone, never on the
75
+ * order they settled in: a verifier that reads the same cells from a database
76
+ * gets the same bits.
77
+ */
78
+ function searchUnitScores(cells) {
79
+ const byUnit = /* @__PURE__ */ new Map();
80
+ for (const cell of cells) {
81
+ const unit = byUnit.get(cell.unitId);
82
+ if (unit) unit.push(cell);
83
+ else byUnit.set(cell.unitId, [cell]);
84
+ }
85
+ return [...byUnit.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([unitId, unitCells]) => {
86
+ let sum = 0;
87
+ for (const cell of unitCells.sort((left, right) => compareCodeUnits(left.cellId, right.cellId))) sum += cell.score;
88
+ return {
89
+ unitId,
90
+ sum,
91
+ count: unitCells.length,
92
+ mean: sum / unitCells.length
93
+ };
94
+ });
95
+ }
96
+ /**
97
+ * The pure, incremental projection of one search ledger. `apply` refuses any
98
+ * entry that breaks an invariant; the journal then discards this instance and
99
+ * replays into a new one, so a refused entry leaves no partial state behind.
100
+ */
101
+ var SearchState = class {
102
+ searchId;
103
+ version = 0;
104
+ header = null;
105
+ closed = null;
106
+ head = null;
107
+ lastOccurredAt = Number.NEGATIVE_INFINITY;
108
+ lastOccurredAtText = null;
109
+ tasks = /* @__PURE__ */ new Map();
110
+ nodes = /* @__PURE__ */ new Map();
111
+ nodeOrder = [];
112
+ nodeByDigest = /* @__PURE__ */ new Map();
113
+ edges = /* @__PURE__ */ new Map();
114
+ edgeOrder = [];
115
+ cells = /* @__PURE__ */ new Map();
116
+ operations = /* @__PURE__ */ new Map();
117
+ selectedNodeId = null;
118
+ claimUsedUsd = 0;
119
+ audit;
120
+ constructor(searchId) {
121
+ this.searchId = searchId;
122
+ this.audit = {
123
+ searchId,
124
+ eventCount: 0,
125
+ headHash: null,
126
+ status: "open",
127
+ closeReason: null,
128
+ nodes: 0,
129
+ nodesWithoutEdge: 0,
130
+ undecidedNodes: 0,
131
+ selectedNodeId: null,
132
+ edges: {
133
+ explicit: 0,
134
+ correlated: 0,
135
+ unknown: 0,
136
+ reproposals: 0
137
+ },
138
+ cells: {
139
+ allocated: 0,
140
+ settled: 0,
141
+ cancelled: 0,
142
+ open: 0
143
+ },
144
+ attempts: 0,
145
+ outcomes: {
146
+ passed: 0,
147
+ failed: 0,
148
+ errored: 0
149
+ },
150
+ operations: {
151
+ started: 0,
152
+ recorded: 0,
153
+ open: 0
154
+ },
155
+ spend: {
156
+ knownUsd: 0,
157
+ floorUsd: 0,
158
+ unknownCostCells: 0,
159
+ unknownCostOperations: 0,
160
+ committedUsd: 0,
161
+ openReservationUsd: 0,
162
+ overspendUsd: 0,
163
+ unreservedCells: 0,
164
+ boxMinutes: 0
165
+ },
166
+ tokens: {
167
+ inputTokens: 0,
168
+ outputTokens: 0,
169
+ cachedTokens: 0,
170
+ unknownTokenAttempts: 0
171
+ }
172
+ };
173
+ }
174
+ apply(entry, index) {
175
+ const event = entry.event;
176
+ if (this.closed) throw integrity(`event ${event.eventId} follows search-closed ${this.closed.eventId}`);
177
+ const occurredAt = Date.parse(event.occurredAt);
178
+ if (occurredAt < this.lastOccurredAt) throw integrity(`event ${event.eventId} occurred before the preceding event`);
179
+ assertUnique(event.artifacts.map(artifactKey), "artifact receipt", event.eventId);
180
+ if (event.kind === "search-opened") {
181
+ if (index !== 0 || this.header) throw integrity("search-opened must be the first and only opening event");
182
+ this.open(event);
183
+ } else {
184
+ if (!this.header) throw integrity(`event ${event.eventId} appears before search-opened`);
185
+ this.applyEvent(event, index);
186
+ }
187
+ this.lastOccurredAt = occurredAt;
188
+ this.lastOccurredAtText = event.occurredAt;
189
+ this.head = {
190
+ sequence: index,
191
+ entryHash: entry.entryHash
192
+ };
193
+ this.audit.eventCount = index + 1;
194
+ this.audit.headHash = entry.entryHash;
195
+ this.version += 1;
196
+ }
197
+ snapshot() {
198
+ return new SearchStateView(this, this.version);
199
+ }
200
+ /** @internal Views check this before every read of the live indexes. */
201
+ isAt(version) {
202
+ return this.version === version;
203
+ }
204
+ applyEvent(event, index) {
205
+ switch (event.kind) {
206
+ case "operation-started":
207
+ this.startOperation(event);
208
+ return;
209
+ case "operation-recorded":
210
+ this.recordOperation(event);
211
+ return;
212
+ case "node-registered":
213
+ this.registerNode(event, index);
214
+ return;
215
+ case "edge-recorded":
216
+ this.recordEdge(event, index);
217
+ return;
218
+ case "cell-allocated":
219
+ this.allocateCell(event, index);
220
+ return;
221
+ case "cell-settled":
222
+ this.settleCell(event, index);
223
+ return;
224
+ case "cell-cancelled":
225
+ this.cancelCell(event, index);
226
+ return;
227
+ case "node-decided":
228
+ this.decideNode(event, index);
229
+ return;
230
+ case "search-closed":
231
+ this.close(event);
232
+ return;
233
+ }
234
+ }
235
+ open(event) {
236
+ if (event.containment?.searchId === this.searchId) throw integrity("a search cannot contain itself");
237
+ if (event.derivedFrom?.searchId === this.searchId) throw integrity("a search cannot derive from itself");
238
+ const units = {
239
+ train: /* @__PURE__ */ new Set(),
240
+ selection: /* @__PURE__ */ new Set(),
241
+ test: /* @__PURE__ */ new Set()
242
+ };
243
+ for (const split of SPLITS) {
244
+ const { tasks, taskSetDigest } = event.splits[split];
245
+ if (searchTaskSetDigest(tasks) !== taskSetDigest) throw integrity(`the ${split} split's taskSetDigest does not match its tasks`);
246
+ for (const task of tasks) {
247
+ const owner = this.tasks.get(task.taskId);
248
+ if (owner) throw integrity(`task ${task.taskId} is in both the ${owner.split} and ${split} splits`);
249
+ this.tasks.set(task.taskId, {
250
+ split,
251
+ unitId: task.unitId
252
+ });
253
+ units[split].add(task.unitId);
254
+ }
255
+ }
256
+ if (event.splits.heldOutUnits) {
257
+ const seen = [...units.test].filter((unit) => units.train.has(unit) || units.selection.has(unit));
258
+ if (seen.length > 0) throw integrity(`test units ${seen.join(", ")} also appear in train or selection, but the splits declare heldOutUnits`);
259
+ }
260
+ const { budget } = event;
261
+ if (budget.maxUsd !== null && budget.reservedClaimUsd > budget.maxUsd + USD_TOLERANCE) throw integrity(`the claim reserve $${budget.reservedClaimUsd} exceeds the search cap $${budget.maxUsd}`);
262
+ this.header = event;
263
+ }
264
+ startOperation(event) {
265
+ if (this.operations.has(event.operationId)) throw integrity(`operation ${event.operationId} was started twice`);
266
+ this.admit(event.reservation, false, `operation ${event.operationId}`);
267
+ this.operations.set(event.operationId, {
268
+ operationId: event.operationId,
269
+ operationKind: event.operationKind,
270
+ reservation: event.reservation,
271
+ recorded: false,
272
+ spentUsd: 0
273
+ });
274
+ this.audit.operations.started += 1;
275
+ this.audit.operations.open += 1;
276
+ this.audit.spend.openReservationUsd += event.reservation?.usd ?? 0;
277
+ }
278
+ recordOperation(event) {
279
+ const operation = this.operations.get(event.operationId);
280
+ if (!operation) throw integrity(`operation ${event.operationId} was recorded before it started`);
281
+ if (operation.recorded) throw integrity(`operation ${event.operationId} was recorded twice`);
282
+ if (operation.operationKind !== event.operationKind) throw integrity(`operation ${event.operationId} started as ${operation.operationKind} but recorded as ${event.operationKind}`);
283
+ const cost = this.book(event.accounting);
284
+ if (cost.unknown) this.audit.spend.unknownCostOperations += 1;
285
+ const reserved = operation.reservation?.usd ?? 0;
286
+ this.audit.spend.openReservationUsd -= reserved;
287
+ if (operation.reservation) this.audit.spend.overspendUsd += Math.max(0, cost.usd - reserved);
288
+ operation.recorded = true;
289
+ operation.spentUsd = cost.usd;
290
+ this.audit.operations.recorded += 1;
291
+ this.audit.operations.open -= 1;
292
+ }
293
+ registerNode(event, index) {
294
+ const expected = searchNodeId(this.searchId, event.artifactDigest);
295
+ if (event.nodeId !== expected) throw integrity(`node ${event.nodeId} does not match its artifact digest (expected ${expected})`);
296
+ if (this.nodes.has(event.nodeId)) throw integrity(`node ${event.nodeId} was registered twice; a re-proposal is a second edge into it`);
297
+ assertUnique(event.surfaces.map((surface) => surface.surfaceId), "surfaceId", event.eventId);
298
+ const maxNodes = this.header.budget.maxNodes;
299
+ if (maxNodes !== null && this.nodes.size + 1 > maxNodes) throw integrity(`node ${event.nodeId} exceeds the search's maxNodes ${maxNodes}`);
300
+ this.nodes.set(event.nodeId, {
301
+ event,
302
+ ordinal: this.nodeOrder.length,
303
+ registeredSequence: index,
304
+ updatedSequence: index,
305
+ edgeIds: [],
306
+ parents: [],
307
+ parentKeys: /* @__PURE__ */ new Set(),
308
+ primaryParentId: null,
309
+ depth: null,
310
+ children: [],
311
+ decisions: [],
312
+ finalist: false,
313
+ cellIds: [],
314
+ scoredCells: 0,
315
+ spend: {
316
+ knownUsd: 0,
317
+ floorUsd: 0,
318
+ unknownCostCells: 0,
319
+ boxMinutes: 0
320
+ }
321
+ });
322
+ this.nodeOrder.push(event.nodeId);
323
+ this.nodeByDigest.set(event.artifactDigest, event.nodeId);
324
+ this.audit.nodes += 1;
325
+ this.audit.nodesWithoutEdge += 1;
326
+ this.audit.undecidedNodes += 1;
327
+ }
328
+ recordEdge(event, index) {
329
+ if (this.edges.has(event.edgeId)) throw integrity(`edge ${event.edgeId} was recorded twice`);
330
+ const child = this.nodes.get(event.childNodeId);
331
+ if (!child) throw integrity(`edge ${event.edgeId} names unregistered child ${event.childNodeId}`);
332
+ if (event.label.length > MAX_LABEL_CHARS) throw integrity(`edge ${event.edgeId} label exceeds ${MAX_LABEL_CHARS} characters`);
333
+ if (event.diffs.length !== event.parents.length) throw integrity(`edge ${event.edgeId} needs one diff per parent`);
334
+ assertUnique(event.parents.map((parent) => canonicalString(parent)), "parent", event.eventId);
335
+ const { operator, attribution, parents } = event;
336
+ if (operator === "seed" !== (event.proposer === null)) throw integrity(`edge ${event.edgeId}: exactly the seed edge has no proposer`);
337
+ if (operator === "seed" && (parents.length > 0 || attribution === "unknown")) throw integrity(`seed edge ${event.edgeId} must have no parents and a known attribution`);
338
+ if (attribution === "unknown" && parents.length > 0) throw integrity(`edge ${event.edgeId} has unknown attribution but names parents`);
339
+ if (operator !== "seed" && attribution !== "unknown" && parents.length === 0) throw integrity(`${operator} edge ${event.edgeId} names no parent`);
340
+ if (operator === "merge" && attribution !== "unknown" && parents.length < 2) throw integrity(`merge edge ${event.edgeId} needs at least two parents`);
341
+ const operationId = event.proposer?.operationId ?? null;
342
+ if (operationId !== null && !this.operations.has(operationId)) throw integrity(`edge ${event.edgeId} names operation ${operationId}, which never started`);
343
+ const inSearch = [];
344
+ for (const parent of parents) {
345
+ if (parent.searchId !== this.searchId) {
346
+ const from = this.header.derivedFrom;
347
+ 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`);
348
+ continue;
349
+ }
350
+ if (operator === "derive") throw integrity(`derive edge ${event.edgeId} must name the parent in derivedFrom`);
351
+ const record = this.nodes.get(parent.nodeId);
352
+ if (!record) throw integrity(`edge ${event.edgeId} names unregistered parent ${parent.nodeId}`);
353
+ if (record.ordinal >= child.ordinal) throw integrity(`edge ${event.edgeId}: parent ${parent.nodeId} registered after child ${child.event.nodeId}`);
354
+ if (record.edgeIds.length === 0) throw integrity(`edge ${event.edgeId}: parent ${parent.nodeId} has no edge of its own yet`);
355
+ inSearch.push(record);
356
+ }
357
+ if (child.edgeIds.length === 0) {
358
+ const primary = inSearch[0];
359
+ child.primaryParentId = primary?.event.nodeId ?? null;
360
+ child.depth = operator === "seed" || operator === "derive" ? 0 : primary && primary.depth !== null ? primary.depth + 1 : null;
361
+ this.audit.nodesWithoutEdge -= 1;
362
+ } else this.audit.edges.reproposals += 1;
363
+ child.edgeIds.push(event.edgeId);
364
+ for (const parent of parents) {
365
+ const key = canonicalString(parent);
366
+ if (child.parentKeys.has(key)) continue;
367
+ child.parentKeys.add(key);
368
+ child.parents.push(parent);
369
+ }
370
+ for (const parent of inSearch) if (!parent.children.includes(child.event.nodeId)) parent.children.push(child.event.nodeId);
371
+ child.updatedSequence = index;
372
+ this.edges.set(event.edgeId, event);
373
+ this.edgeOrder.push(event.edgeId);
374
+ this.audit.edges[attribution] += 1;
375
+ }
376
+ allocateCell(event, index) {
377
+ const expected = searchCellId(this.searchId, event.nodeId, event.taskId, event.split, event.rep);
378
+ if (event.cellId !== expected) throw integrity(`cell ${event.cellId} does not match its coordinates (expected ${expected})`);
379
+ if (this.cells.has(event.cellId)) throw integrity(`cell ${event.cellId} was allocated twice`);
380
+ const node = this.nodes.get(event.nodeId);
381
+ if (!node) throw integrity(`cell ${event.cellId} names unregistered node ${event.nodeId}`);
382
+ if (node.edgeIds.length === 0) throw integrity(`cell ${event.cellId}: node ${event.nodeId} has no edge yet`);
383
+ const task = this.tasks.get(event.taskId);
384
+ if (!task || task.split !== event.split) throw integrity(`cell ${event.cellId}: task ${event.taskId} is not in the ${event.split} split`);
385
+ if (task.unitId !== event.unitId) throw integrity(`cell ${event.cellId}: task ${event.taskId} samples unit ${task.unitId}`);
386
+ assertStageSplit(event.cellId, event.stage, event.split, node.ordinal === 0);
387
+ 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`);
388
+ const maxCells = this.header.budget.maxCells;
389
+ if (maxCells !== null && this.audit.cells.allocated + 1 > maxCells) throw integrity(`cell ${event.cellId} exceeds the search's maxCells ${maxCells}`);
390
+ this.admit(event.reservation, event.stage === "claim", `cell ${event.cellId}`);
391
+ this.cells.set(event.cellId, {
392
+ cellId: event.cellId,
393
+ nodeId: event.nodeId,
394
+ taskId: event.taskId,
395
+ unitId: event.unitId,
396
+ split: event.split,
397
+ rep: event.rep,
398
+ stage: event.stage,
399
+ lane: event.lane,
400
+ reservation: event.reservation,
401
+ attempts: 0,
402
+ lastRunId: null,
403
+ outcome: null,
404
+ score: null,
405
+ final: false,
406
+ cancelled: null,
407
+ spentUsd: 0,
408
+ costKnown: true,
409
+ traceId: null,
410
+ allocatedSequence: index,
411
+ updatedSequence: index
412
+ });
413
+ node.cellIds.push(event.cellId);
414
+ node.updatedSequence = index;
415
+ this.audit.cells.allocated += 1;
416
+ this.audit.cells.open += 1;
417
+ this.audit.spend.openReservationUsd += event.reservation?.usd ?? 0;
418
+ if (event.stage === "claim") this.claimUsedUsd += event.reservation?.usd ?? 0;
419
+ }
420
+ settleCell(event, index) {
421
+ const cell = this.cells.get(event.cellId);
422
+ if (!cell) throw integrity(`attempt ${event.eventId} names unallocated cell ${event.cellId}`);
423
+ if (cell.cancelled) throw integrity(`cell ${event.cellId} was settled after it was cancelled`);
424
+ if (cell.final) throw integrity(`cell ${event.cellId} was attempted again after a final ${cell.outcome} outcome; only a retryable error admits another attempt`);
425
+ if (event.attempt !== cell.attempts + 1) throw integrity(`cell ${event.cellId} attempt ${event.attempt} is not contiguous (expected ${cell.attempts + 1})`);
426
+ const runId = searchCellRunId({
427
+ cellId: event.cellId,
428
+ attempt: event.attempt
429
+ });
430
+ if (event.runId !== runId) throw integrity(`cell ${event.cellId} attempt ${event.attempt} must have runId ${runId}`);
431
+ const node = this.nodes.get(cell.nodeId);
432
+ const declared = new Set(node.event.surfaces.map((surface) => surface.surfaceId));
433
+ assertUnique(event.surfaceEvidence.map((evidence) => evidence.surfaceId), "surface evidence", event.eventId);
434
+ 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`);
435
+ if (event.artifacts.filter((artifact) => artifact.role === "run-record").length > 1) throw integrity(`attempt ${event.eventId} binds more than one RunRecord`);
436
+ const before = this.reservationParts(cell);
437
+ const cost = this.book(event.accounting);
438
+ if (cell.attempts === 0) {
439
+ this.audit.cells.settled += 1;
440
+ this.audit.cells.open -= 1;
441
+ if (cell.reservation === null) this.audit.spend.unreservedCells += 1;
442
+ }
443
+ if (cost.unknown && cell.costKnown) {
444
+ cell.costKnown = false;
445
+ this.audit.spend.unknownCostCells += 1;
446
+ node.spend.unknownCostCells += 1;
447
+ }
448
+ cell.attempts = event.attempt;
449
+ cell.lastRunId = runId;
450
+ cell.outcome = event.outcome.status;
451
+ cell.spentUsd += cost.usd;
452
+ cell.updatedSequence = index;
453
+ if ("traceId" in event.traceRef) cell.traceId = event.traceRef.traceId;
454
+ if (event.outcome.status === "errored") cell.final = !event.outcome.error.retryable;
455
+ else {
456
+ cell.final = true;
457
+ cell.score = event.outcome.score;
458
+ node.scoredCells += 1;
459
+ }
460
+ const after = this.reservationParts(cell);
461
+ this.audit.spend.openReservationUsd += after.open - before.open;
462
+ this.audit.spend.overspendUsd += after.over - before.over;
463
+ if (cell.stage === "claim") this.claimUsedUsd += after.used - before.used;
464
+ if (cost.unknown) node.spend.floorUsd += cost.usd;
465
+ else node.spend.knownUsd += cost.usd;
466
+ const boxMinutes = event.boxMinutes ?? 0;
467
+ node.spend.boxMinutes += boxMinutes;
468
+ this.audit.spend.boxMinutes += boxMinutes;
469
+ node.updatedSequence = index;
470
+ this.audit.attempts += 1;
471
+ this.audit.outcomes[event.outcome.status] += 1;
472
+ }
473
+ cancelCell(event, index) {
474
+ const cell = this.cells.get(event.cellId);
475
+ if (!cell) throw integrity(`cancellation ${event.eventId} names unallocated cell ${event.cellId}`);
476
+ if (cell.cancelled) throw integrity(`cell ${event.cellId} was cancelled twice`);
477
+ if (cell.final) throw integrity(`cell ${event.cellId} was cancelled after a final outcome`);
478
+ const before = this.reservationParts(cell);
479
+ cell.cancelled = event.reason;
480
+ cell.updatedSequence = index;
481
+ const after = this.reservationParts(cell);
482
+ this.audit.spend.openReservationUsd += after.open - before.open;
483
+ this.audit.spend.overspendUsd += after.over - before.over;
484
+ if (cell.stage === "claim") this.claimUsedUsd += after.used - before.used;
485
+ if (cell.attempts === 0) {
486
+ this.audit.cells.cancelled += 1;
487
+ this.audit.cells.open -= 1;
488
+ }
489
+ this.nodes.get(cell.nodeId).updatedSequence = index;
490
+ }
491
+ decideNode(event, index) {
492
+ const node = this.nodes.get(event.nodeId);
493
+ if (!node) throw integrity(`decision ${event.eventId} names unregistered node ${event.nodeId}`);
494
+ if (node.edgeIds.length === 0) throw integrity(`decision ${event.eventId}: node ${event.nodeId} has no edge yet`);
495
+ const status = event.decision.status;
496
+ if (status === "selected") {
497
+ if (this.selectedNodeId !== null && this.selectedNodeId !== event.nodeId) throw integrity(`node ${event.nodeId} was selected while ${this.selectedNodeId} is still selected`);
498
+ if (node.scoredCells === 0) throw integrity(`node ${event.nodeId} cannot be selected without a scored cell`);
499
+ }
500
+ const previous = node.decisions.at(-1)?.decision.status ?? null;
501
+ const wasUndecided = previous === null || previous === "advanced";
502
+ const isUndecided = status === "advanced";
503
+ this.audit.undecidedNodes += (isUndecided ? 1 : 0) - (wasUndecided ? 1 : 0);
504
+ if (previous === "selected" && status !== "selected") this.selectedNodeId = null;
505
+ if (status === "selected") this.selectedNodeId = event.nodeId;
506
+ if (status === "finalist") node.finalist = true;
507
+ node.decisions.push({
508
+ sequence: index,
509
+ decision: event.decision,
510
+ basis: event.basis,
511
+ rule: event.rule,
512
+ reason: event.reason
513
+ });
514
+ node.updatedSequence = index;
515
+ this.audit.selectedNodeId = this.selectedNodeId;
516
+ }
517
+ close(event) {
518
+ const reasons = this.incompleteReasons();
519
+ if (reasons.length > 0) throw integrity(`search-closed while incomplete: ${reasons.join("; ")}`);
520
+ if (event.claim) this.checkClaim(event.claim);
521
+ this.closed = event;
522
+ this.audit.status = "closed";
523
+ this.audit.closeReason = event.reason;
524
+ this.audit.spend.openReservationUsd = 0;
525
+ }
526
+ checkClaim(claim) {
527
+ if (claim.finalists.length > 3) throw integrity(`the claim names ${claim.finalists.length} finalists; at most 3 are allowed`);
528
+ assertUnique(claim.finalists.map((finalist) => finalist.nodeId), "claim finalist", "search-closed");
529
+ for (const finalist of claim.finalists) if (!this.nodes.get(finalist.nodeId)?.finalist) throw integrity(`claim finalist ${finalist.nodeId} was never decided finalist`);
530
+ if (claim.selected !== this.selectedNodeId) throw integrity(`the claim selects ${claim.selected ?? "nothing"} but the selected node is ${this.selectedNodeId ?? "none"}`);
531
+ const root = this.nodeOrder[0] ?? null;
532
+ if (claim.decision === "ship") {
533
+ const shipped = claim.finalists.find((finalist) => finalist.nodeId === claim.selected);
534
+ if (claim.selected === null || claim.selected === root || !shipped?.promote) throw integrity("a ship claim must select a finalist that the paired test promoted");
535
+ if (!this.header.splits.heldOutUnits) throw integrity("a ship claim needs test units held out from train and selection");
536
+ } else if (claim.selected !== null && claim.selected !== root) throw integrity(`a ${claim.decision} claim keeps the root; it cannot select another node`);
537
+ if (claim.decision === "test-cannot-resolve") {
538
+ 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");
539
+ }
540
+ }
541
+ /** @internal */
542
+ incompleteReasons() {
543
+ const reasons = [];
544
+ if (!this.header) reasons.push("search-opened is missing");
545
+ if (this.audit.cells.open > 0) reasons.push(count(this.audit.cells.open, "cell", "has no outcome"));
546
+ if (this.audit.operations.open > 0) reasons.push(count(this.audit.operations.open, "operation", "is unrecorded"));
547
+ if (this.audit.nodesWithoutEdge > 0) reasons.push(count(this.audit.nodesWithoutEdge, "node", "has no edge"));
548
+ if (this.audit.undecidedNodes > 0) reasons.push(count(this.audit.undecidedNodes, "node", "has no terminal decision"));
549
+ return reasons;
550
+ }
551
+ /** Book an attempt's or operation's accounting into the running totals. An
552
+ * unknown cost counts as its proven floor. */
553
+ book(accounting) {
554
+ const { tokens, cost } = accounting;
555
+ if (tokens.status === "known") {
556
+ this.audit.tokens.inputTokens += tokens.inputTokens;
557
+ this.audit.tokens.outputTokens += tokens.outputTokens;
558
+ this.audit.tokens.cachedTokens += tokens.cachedTokens;
559
+ } else this.audit.tokens.unknownTokenAttempts += 1;
560
+ const usd = cost.status === "known" ? cost.usd : cost.knownLowerBoundUsd;
561
+ if (cost.status === "known") this.audit.spend.knownUsd += usd;
562
+ else this.audit.spend.floorUsd += usd;
563
+ this.audit.spend.committedUsd += usd;
564
+ return {
565
+ usd,
566
+ unknown: cost.status === "unknown"
567
+ };
568
+ }
569
+ /** A cell's share of the open reservations, its overspend, and what it draws
570
+ * from the claim reserve: the larger of its hold and its spend. */
571
+ reservationParts(cell) {
572
+ const reserved = cell.reservation?.usd ?? 0;
573
+ const holding = !cell.final && cell.cancelled === null;
574
+ return {
575
+ open: holding ? Math.max(0, reserved - cell.spentUsd) : 0,
576
+ over: cell.reservation ? Math.max(0, cell.spentUsd - reserved) : 0,
577
+ used: holding ? Math.max(reserved, cell.spentUsd) : cell.spentUsd
578
+ };
579
+ }
580
+ /** The admission rule: committed spend, open holds, the unspent claim
581
+ * reserve and the new hold stay within the cap. A claim cell draws on the
582
+ * claim reserve instead of adding to it. */
583
+ admit(reservation, claim, subject) {
584
+ const { maxUsd, reservedClaimUsd } = this.header.budget;
585
+ if (maxUsd === null) return;
586
+ const hold = reservation?.usd ?? 0;
587
+ const remainingClaim = Math.max(0, reservedClaimUsd - this.claimUsedUsd);
588
+ 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}`);
589
+ }
590
+ /** @internal */
591
+ readHeader() {
592
+ return this.header;
593
+ }
594
+ /** @internal */
595
+ readClosed() {
596
+ return this.closed;
597
+ }
598
+ /** @internal */
599
+ readHead() {
600
+ return this.head;
601
+ }
602
+ /** @internal */
603
+ readLastOccurredAt() {
604
+ return this.lastOccurredAtText;
605
+ }
606
+ /** @internal */
607
+ readAudit() {
608
+ const audit = this.audit;
609
+ return {
610
+ ...audit,
611
+ edges: { ...audit.edges },
612
+ cells: { ...audit.cells },
613
+ outcomes: { ...audit.outcomes },
614
+ operations: { ...audit.operations },
615
+ spend: { ...audit.spend },
616
+ tokens: { ...audit.tokens }
617
+ };
618
+ }
619
+ /** @internal */
620
+ readNode(nodeId) {
621
+ const record = this.nodes.get(nodeId);
622
+ return record ? nodeView(record) : void 0;
623
+ }
624
+ /** @internal */
625
+ readNodeIds() {
626
+ return this.nodeOrder;
627
+ }
628
+ /** @internal */
629
+ readHasNode(nodeId) {
630
+ return this.nodes.has(nodeId);
631
+ }
632
+ /** @internal */
633
+ readNodeIdForDigest(artifactDigest) {
634
+ return this.nodeByDigest.get(artifactDigest);
635
+ }
636
+ /** @internal */
637
+ readEdge(edgeId) {
638
+ return this.edges.get(edgeId);
639
+ }
640
+ /** @internal */
641
+ readEdgeIds() {
642
+ return this.edgeOrder;
643
+ }
644
+ /** @internal */
645
+ readCell(cellId) {
646
+ const cell = this.cells.get(cellId);
647
+ return cell ? { ...cell } : void 0;
648
+ }
649
+ /** @internal */
650
+ readCellIds(nodeId) {
651
+ if (nodeId === void 0) return [...this.cells.keys()];
652
+ return this.nodes.get(nodeId)?.cellIds ?? [];
653
+ }
654
+ /** @internal */
655
+ readOperation(operationId) {
656
+ const operation = this.operations.get(operationId);
657
+ return operation ? { ...operation } : void 0;
658
+ }
659
+ /** @internal */
660
+ readScoredCells(nodeId, split) {
661
+ const scored = [];
662
+ for (const cellId of this.nodes.get(nodeId)?.cellIds ?? []) {
663
+ const cell = this.cells.get(cellId);
664
+ if (cell.split === split && cell.score !== null) scored.push({
665
+ cellId,
666
+ unitId: cell.unitId,
667
+ attempt: cell.attempts,
668
+ score: cell.score
669
+ });
670
+ }
671
+ return scored;
672
+ }
673
+ };
674
+ /**
675
+ * A read of `SearchState` at one ledger position. Header, head, audit and
676
+ * claim are values. Node, edge, cell and score reads go to the live indexes and
677
+ * throw once the state has applied a later entry.
678
+ */
679
+ var SearchStateView = class {
680
+ searchId;
681
+ header;
682
+ closed;
683
+ head;
684
+ /** `occurredAt` of the head entry; a writer stamps later events at or after it. */
685
+ lastOccurredAt;
686
+ audit;
687
+ completion;
688
+ state;
689
+ version;
690
+ constructor(state, version) {
691
+ this.state = state;
692
+ this.version = version;
693
+ this.searchId = state.searchId;
694
+ this.header = state.readHeader();
695
+ this.closed = state.readClosed();
696
+ this.head = state.readHead();
697
+ this.lastOccurredAt = state.readLastOccurredAt();
698
+ this.audit = state.readAudit();
699
+ const reasons = state.incompleteReasons();
700
+ if (!this.closed) reasons.unshift("search is open");
701
+ this.completion = {
702
+ complete: this.closed !== null,
703
+ reasons
704
+ };
705
+ }
706
+ /** The node the search started from: the first registered. */
707
+ get rootNodeId() {
708
+ return this.live().readNodeIds()[0] ?? null;
709
+ }
710
+ node(nodeId) {
711
+ return this.live().readNode(nodeId);
712
+ }
713
+ hasNode(nodeId) {
714
+ return this.live().readHasNode(nodeId);
715
+ }
716
+ /** Node ids in registration order. */
717
+ nodeIds() {
718
+ return [...this.live().readNodeIds()];
719
+ }
720
+ /** Nodes in registration order. */
721
+ nodes() {
722
+ const state = this.live();
723
+ return state.readNodeIds().map((nodeId) => state.readNode(nodeId));
724
+ }
725
+ nodeIdForDigest(artifactDigest) {
726
+ return this.live().readNodeIdForDigest(artifactDigest);
727
+ }
728
+ edge(edgeId) {
729
+ return this.live().readEdge(edgeId);
730
+ }
731
+ /** Edges in ledger order. */
732
+ edges() {
733
+ const state = this.live();
734
+ return state.readEdgeIds().map((edgeId) => state.readEdge(edgeId));
735
+ }
736
+ cell(cellId) {
737
+ return this.live().readCell(cellId);
738
+ }
739
+ /** Cells in allocation order, optionally of one node. */
740
+ cells(filter = {}) {
741
+ const state = this.live();
742
+ return state.readCellIds(filter.nodeId).map((cellId) => state.readCell(cellId));
743
+ }
744
+ operation(operationId) {
745
+ return this.live().readOperation(operationId);
746
+ }
747
+ /** A node's scored cells on one split, in allocation order. Unscored cells
748
+ * (errored, cancelled or in flight) are absent, never zero. */
749
+ scoredCells(nodeId, split) {
750
+ return this.live().readScoredCells(nodeId, split);
751
+ }
752
+ /** Per-unit score means of a node on one split, in unitId order. */
753
+ unitScores(nodeId, split) {
754
+ return searchUnitScores(this.scoredCells(nodeId, split));
755
+ }
756
+ /** The lineage a RunRecord mint needs for one cell attempt. */
757
+ lineage(coordinates) {
758
+ const state = this.live();
759
+ if (coordinates.searchId !== this.searchId) throw new SearchLedgerError(`coordinates name search ${coordinates.searchId}, not ${this.searchId}`);
760
+ const cell = state.readCell(coordinates.cellId);
761
+ if (!cell || cell.nodeId !== coordinates.nodeId) throw new SearchLedgerError(`cell ${coordinates.cellId} of node ${coordinates.nodeId} is not in search ${this.searchId}`);
762
+ if (coordinates.attempt < 1 || coordinates.attempt > cell.attempts) throw new SearchLedgerError(`cell ${coordinates.cellId} has ${cell.attempts} settled attempt(s), not attempt ${coordinates.attempt}`);
763
+ const node = state.readNode(cell.nodeId);
764
+ if (node.depth === null) throw new SearchLedgerError(`node ${node.nodeId} has unknown lineage, so its depth is unknown`);
765
+ const containment = this.header?.containment ?? null;
766
+ return {
767
+ depth: node.depth,
768
+ ordinal: node.ordinal,
769
+ rep: cell.rep,
770
+ containingRunId: containment ? searchCellRunId({
771
+ cellId: containment.cellId,
772
+ attempt: containment.attempt
773
+ }) : null
774
+ };
775
+ }
776
+ live() {
777
+ 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`);
778
+ return this.state;
779
+ }
780
+ };
781
+ function nodeView(record) {
782
+ const latest = record.decisions.at(-1)?.decision ?? null;
783
+ return {
784
+ nodeId: record.event.nodeId,
785
+ ordinal: record.ordinal,
786
+ artifactDigest: record.event.artifactDigest,
787
+ artifact: record.event.artifact,
788
+ surfaces: record.event.surfaces,
789
+ primaryParentId: record.primaryParentId,
790
+ depth: record.depth,
791
+ parents: [...record.parents],
792
+ children: [...record.children],
793
+ edgeIds: [...record.edgeIds],
794
+ status: latest?.status ?? null,
795
+ rung: latest?.status === "advanced" ? latest.rung : null,
796
+ finalist: record.finalist,
797
+ decisions: [...record.decisions],
798
+ cellCount: record.cellIds.length,
799
+ spend: { ...record.spend },
800
+ registeredSequence: record.registeredSequence,
801
+ updatedSequence: record.updatedSequence
802
+ };
803
+ }
804
+ function assertStageSplit(cellId, stage, split, isRoot) {
805
+ if (!{
806
+ root: ["train", "selection"],
807
+ train: ["train"],
808
+ screen: ["selection"],
809
+ rung: ["selection"],
810
+ claim: ["test"],
811
+ external: ["train", "selection"]
812
+ }[stage].includes(split)) throw integrity(`cell ${cellId}: a ${stage} cell cannot run on the ${split} split`);
813
+ if (stage === "root" && !isRoot) throw integrity(`cell ${cellId}: only the root node runs root cells`);
814
+ }
815
+ function assertUnique(values, label, eventId) {
816
+ if (new Set(values).size !== values.length) throw integrity(`event ${eventId} contains duplicate ${label} values`);
817
+ }
818
+ function count(value, noun, predicate) {
819
+ const verb = predicate.replace(/^(has|is) /, (word) => value === 1 ? word : word === "has " ? "have " : "are ");
820
+ return `${value} ${value === 1 ? noun : `${noun}s`} ${verb}`;
821
+ }
822
+ function round(usd) {
823
+ return Math.round(usd * 1e6) / 1e6;
824
+ }
825
+ function integrity(message) {
826
+ return new SearchLedgerIntegrityError(message);
827
+ }
828
+ //#endregion
829
+ //#region src/campaign/search-ledger.ts
830
+ /**
831
+ * Durable, append-only event log of one search: nodes (content-addressed
832
+ * artifacts), edges (the proposals that derived them), cells (one run of one
833
+ * node on one task), operations, decisions, and the claim.
834
+ *
835
+ * The ledger is the search's only lineage record and its only resume
836
+ * checkpoint. A RunRecord owns one measured run and `CostLedger` owns per-call
837
+ * accounting; this ledger binds them by digest instead of copying them.
838
+ *
839
+ * The file format is canonical JSONL with a SHA-256 hash chain. Every append is
840
+ * serialized across processes, fsynced before acknowledgement, and idempotent
841
+ * by `eventId`. A malformed, non-canonical, truncated, reordered, or conflicting
842
+ * log fails loudly; a bad row is never skipped. A ledger written under another
843
+ * schema tag is refused with that tag named, never translated.
844
+ *
845
+ * The journal machinery is the generic `ledger-core` journal. This module is
846
+ * the search codec: event schemas and canonical ordering. `SearchState` is the
847
+ * state machine and read model.
848
+ */
849
+ const NON_EMPTY = z.string().min(1).refine((value) => value.trim() === value, "must not contain surrounding whitespace");
850
+ const HASH = z.string().regex(/^sha256:[a-f0-9]{64}$/);
851
+ const IMMUTABLE_REVISION = z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64}|sha256:[a-f0-9]{64}|sha512:[A-Za-z0-9+/=]+)$/);
852
+ 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");
853
+ const NON_NEGATIVE_INT = z.number().int().nonnegative().safe();
854
+ const POSITIVE_INT = z.number().int().positive().safe();
855
+ const FINITE_NUMBER = z.number().finite();
856
+ const USD = z.number().finite().nonnegative();
857
+ const NODE_ID = z.string().regex(/^node_[a-f0-9]{32}$/);
858
+ const CELL_ID = z.string().regex(/^cell_[a-f0-9]{32}$/);
859
+ const ArtifactRefSchema = z.object({
860
+ role: NON_EMPTY,
861
+ uri: NON_EMPTY,
862
+ sha256: HASH,
863
+ byteLength: NON_NEGATIVE_INT
864
+ }).strict();
865
+ const SourceRefSchema = z.object({
866
+ uri: NON_EMPTY,
867
+ revision: IMMUTABLE_REVISION
868
+ }).strict();
869
+ const UnknownRefSchema = z.object({ unknown: NON_EMPTY }).strict();
870
+ const FailureReasonSchema = z.object({
871
+ code: NON_EMPTY,
872
+ message: NON_EMPTY
873
+ }).strict();
874
+ const ModelIdentitySchema = z.union([z.object({
875
+ provider: NON_EMPTY,
876
+ snapshot: NON_EMPTY.refine(modelHasSnapshot, "model must include an immutable snapshot")
877
+ }).strict(), z.object({
878
+ provider: NON_EMPTY,
879
+ alias: NON_EMPTY,
880
+ unknown: NON_EMPTY
881
+ }).strict()]);
882
+ const ExecutionIdentitySchema = z.object({
883
+ model: ModelIdentitySchema,
884
+ agent: SourceRefSchema,
885
+ benchmark: SourceRefSchema
886
+ }).strict();
887
+ const SURFACE_KINDS = [
888
+ "prompt",
889
+ "tool-contract",
890
+ "runtime-config",
891
+ "memory",
892
+ "knowledge",
893
+ "agent-profile",
894
+ "code",
895
+ "deployment"
896
+ ];
897
+ const SPLIT = z.enum([
898
+ "train",
899
+ "selection",
900
+ "test"
901
+ ]);
902
+ const ReservationSchema = z.object({
903
+ kind: z.enum(["hard", "estimate"]),
904
+ usd: USD
905
+ }).strict();
906
+ const EventBaseShape = {
907
+ eventId: NON_EMPTY,
908
+ occurredAt: ISO_TIMESTAMP,
909
+ artifacts: z.array(ArtifactRefSchema)
910
+ };
911
+ const OperationKindSchema = z.enum([
912
+ "candidate-generation",
913
+ "analysis",
914
+ "selection",
915
+ "judge",
916
+ "other"
917
+ ]);
918
+ const KnownTokensSchema = z.object({
919
+ status: z.literal("known"),
920
+ inputTokens: NON_NEGATIVE_INT,
921
+ outputTokens: NON_NEGATIVE_INT,
922
+ cachedTokens: NON_NEGATIVE_INT
923
+ }).strict();
924
+ const UnknownTokensSchema = z.object({
925
+ status: z.literal("unknown"),
926
+ reason: NON_EMPTY
927
+ }).strict();
928
+ const KnownCostSchema = z.object({
929
+ status: z.literal("known"),
930
+ usd: USD,
931
+ source: z.enum([
932
+ "provider",
933
+ "pricing-table",
934
+ "free"
935
+ ])
936
+ }).strict().superRefine((cost, ctx) => {
937
+ if (cost.source === "free" && cost.usd !== 0) ctx.addIssue({
938
+ code: "custom",
939
+ message: "free cost source must have usd 0"
940
+ });
941
+ });
942
+ const UnknownCostSchema = z.object({
943
+ status: z.literal("unknown"),
944
+ knownLowerBoundUsd: USD,
945
+ reason: NON_EMPTY
946
+ }).strict();
947
+ const AccountingSchema = z.object({
948
+ tokens: z.discriminatedUnion("status", [KnownTokensSchema, UnknownTokensSchema]),
949
+ cost: z.discriminatedUnion("status", [KnownCostSchema, UnknownCostSchema])
950
+ }).strict();
951
+ const MetricsSchema = z.record(NON_EMPTY, FINITE_NUMBER).superRefine((metrics, ctx) => {
952
+ for (const key of Object.keys(metrics)) if (key === "__proto__" || key === "constructor" || key === "prototype") ctx.addIssue({
953
+ code: "custom",
954
+ message: `unsafe metric key ${key}`
955
+ });
956
+ });
957
+ const OutcomeSchema = z.discriminatedUnion("status", [
958
+ z.object({
959
+ status: z.literal("passed"),
960
+ score: FINITE_NUMBER,
961
+ metrics: MetricsSchema
962
+ }).strict(),
963
+ z.object({
964
+ status: z.literal("failed"),
965
+ score: FINITE_NUMBER,
966
+ metrics: MetricsSchema,
967
+ failure: FailureReasonSchema
968
+ }).strict(),
969
+ z.object({
970
+ status: z.literal("errored"),
971
+ metrics: MetricsSchema,
972
+ error: FailureReasonSchema.extend({ retryable: z.boolean() }).strict()
973
+ }).strict()
974
+ ]);
975
+ const EffectSchema = z.discriminatedUnion("status", [z.object({
976
+ status: z.literal("measured"),
977
+ metric: NON_EMPTY,
978
+ baselineValue: FINITE_NUMBER,
979
+ candidateValue: FINITE_NUMBER,
980
+ delta: FINITE_NUMBER
981
+ }).strict().superRefine((effect, ctx) => {
982
+ const expected = effect.candidateValue - effect.baselineValue;
983
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(expected), Math.abs(effect.delta)) * 8;
984
+ if (Math.abs(effect.delta - expected) > tolerance) ctx.addIssue({
985
+ code: "custom",
986
+ message: "delta must equal candidateValue - baselineValue"
987
+ });
988
+ }), z.object({
989
+ status: z.literal("not-measured"),
990
+ reason: NON_EMPTY
991
+ }).strict()]);
992
+ const SurfaceEvidenceSchema = z.object({
993
+ surfaceId: NON_EMPTY,
994
+ fired: z.boolean(),
995
+ firingCount: NON_NEGATIVE_INT,
996
+ effect: EffectSchema,
997
+ evidence: z.array(ArtifactRefSchema).min(1)
998
+ }).strict().superRefine((evidence, ctx) => {
999
+ if (evidence.fired !== evidence.firingCount > 0) ctx.addIssue({
1000
+ code: "custom",
1001
+ message: "fired must equal firingCount > 0"
1002
+ });
1003
+ if (!evidence.fired && evidence.effect.status === "measured" && evidence.effect.delta !== 0) ctx.addIssue({
1004
+ code: "custom",
1005
+ message: "a surface that did not fire has no effect"
1006
+ });
1007
+ });
1008
+ const TaskSchema = z.object({
1009
+ taskId: NON_EMPTY,
1010
+ unitId: NON_EMPTY,
1011
+ source: SourceRefSchema
1012
+ }).strict();
1013
+ const SplitTasksSchema = z.object({
1014
+ taskSetDigest: HASH,
1015
+ tasks: z.array(TaskSchema)
1016
+ }).strict();
1017
+ const SearchOpenedSchema = z.object({
1018
+ ...EventBaseShape,
1019
+ kind: z.literal("search-opened"),
1020
+ subject: NON_EMPTY,
1021
+ process: z.object({
1022
+ name: NON_EMPTY,
1023
+ executionRef: SourceRefSchema
1024
+ }).strict(),
1025
+ artifactKind: z.enum([...SURFACE_KINDS, "output"]),
1026
+ objective: z.object({
1027
+ metric: NON_EMPTY,
1028
+ direction: z.enum(["maximize", "minimize"]),
1029
+ judge: z.union([SourceRefSchema, UnknownRefSchema]),
1030
+ claim: evaluationClaimSchema
1031
+ }).strict(),
1032
+ splits: z.object({
1033
+ train: SplitTasksSchema,
1034
+ selection: SplitTasksSchema,
1035
+ test: SplitTasksSchema,
1036
+ heldOutUnits: z.boolean()
1037
+ }).strict(),
1038
+ policy: z.object({
1039
+ expansion: NON_EMPTY,
1040
+ allocation: NON_EMPTY,
1041
+ seed: z.number().int().safe()
1042
+ }).strict(),
1043
+ budget: z.object({
1044
+ maxUsd: USD.nullable(),
1045
+ maxCells: POSITIVE_INT.nullable(),
1046
+ maxNodes: POSITIVE_INT.nullable(),
1047
+ deadline: ISO_TIMESTAMP.nullable(),
1048
+ maxConcurrency: POSITIVE_INT.nullable(),
1049
+ reservedClaimUsd: USD
1050
+ }).strict(),
1051
+ containment: z.object({
1052
+ searchId: NON_EMPTY,
1053
+ cellId: CELL_ID,
1054
+ attempt: POSITIVE_INT
1055
+ }).strict().nullable(),
1056
+ derivedFrom: z.object({
1057
+ searchId: NON_EMPTY,
1058
+ nodeId: NODE_ID,
1059
+ headHash: HASH
1060
+ }).strict().nullable(),
1061
+ identity: ExecutionIdentitySchema
1062
+ }).strict();
1063
+ const OperationStartedSchema = z.object({
1064
+ ...EventBaseShape,
1065
+ kind: z.literal("operation-started"),
1066
+ operationId: NON_EMPTY,
1067
+ operationKind: OperationKindSchema,
1068
+ reservation: ReservationSchema.nullable()
1069
+ }).strict();
1070
+ const OperationRecordedSchema = z.object({
1071
+ ...EventBaseShape,
1072
+ kind: z.literal("operation-recorded"),
1073
+ operationId: NON_EMPTY,
1074
+ operationKind: OperationKindSchema,
1075
+ execution: z.discriminatedUnion("kind", [z.object({
1076
+ kind: z.literal("model"),
1077
+ model: ModelIdentitySchema,
1078
+ source: SourceRefSchema
1079
+ }).strict(), z.object({
1080
+ kind: z.literal("deterministic"),
1081
+ source: SourceRefSchema
1082
+ }).strict()]),
1083
+ outcome: z.discriminatedUnion("status", [
1084
+ z.object({ status: z.literal("completed") }).strict(),
1085
+ z.object({
1086
+ status: z.literal("partial"),
1087
+ failure: FailureReasonSchema
1088
+ }).strict(),
1089
+ z.object({
1090
+ status: z.literal("failed"),
1091
+ failure: FailureReasonSchema
1092
+ }).strict()
1093
+ ]),
1094
+ accounting: AccountingSchema
1095
+ }).strict();
1096
+ const NodeRegisteredSchema = z.object({
1097
+ ...EventBaseShape,
1098
+ kind: z.literal("node-registered"),
1099
+ nodeId: NODE_ID,
1100
+ artifactDigest: HASH,
1101
+ artifact: ArtifactRefSchema,
1102
+ surfaces: z.array(z.object({
1103
+ surfaceId: NON_EMPTY,
1104
+ kind: z.enum(SURFACE_KINDS),
1105
+ artifact: ArtifactRefSchema
1106
+ }).strict())
1107
+ }).strict();
1108
+ const NodeRefSchema = z.object({
1109
+ searchId: NON_EMPTY,
1110
+ nodeId: NODE_ID
1111
+ }).strict();
1112
+ const ArtifactOrUnknownSchema = z.union([ArtifactRefSchema, UnknownRefSchema]);
1113
+ const EdgeRecordedSchema = z.object({
1114
+ ...EventBaseShape,
1115
+ kind: z.literal("edge-recorded"),
1116
+ edgeId: z.string().regex(/^edge_[a-f0-9]{32}$/),
1117
+ childNodeId: NODE_ID,
1118
+ parents: z.array(NodeRefSchema),
1119
+ operator: z.enum([
1120
+ "seed",
1121
+ "draft",
1122
+ "improve",
1123
+ "debug",
1124
+ "merge",
1125
+ "derive"
1126
+ ]),
1127
+ attribution: z.enum([
1128
+ "explicit",
1129
+ "correlated",
1130
+ "unknown"
1131
+ ]),
1132
+ proposer: z.object({
1133
+ kind: z.enum([
1134
+ "trace",
1135
+ "frontier-author",
1136
+ "human",
1137
+ "optimizer",
1138
+ "compound"
1139
+ ]),
1140
+ name: NON_EMPTY,
1141
+ operationId: NON_EMPTY.nullable(),
1142
+ source: SourceRefSchema
1143
+ }).strict().nullable(),
1144
+ selection: z.object({
1145
+ rule: NON_EMPTY,
1146
+ evidence: z.record(NON_EMPTY, FINITE_NUMBER)
1147
+ }).strict().nullable(),
1148
+ rationale: ArtifactOrUnknownSchema,
1149
+ diffs: z.array(ArtifactOrUnknownSchema),
1150
+ label: z.string()
1151
+ }).strict();
1152
+ const CellAllocatedSchema = z.object({
1153
+ ...EventBaseShape,
1154
+ kind: z.literal("cell-allocated"),
1155
+ cellId: CELL_ID,
1156
+ nodeId: NODE_ID,
1157
+ taskId: NON_EMPTY,
1158
+ unitId: NON_EMPTY,
1159
+ split: SPLIT,
1160
+ rep: NON_NEGATIVE_INT,
1161
+ stage: z.enum([
1162
+ "root",
1163
+ "train",
1164
+ "screen",
1165
+ "rung",
1166
+ "claim",
1167
+ "external"
1168
+ ]),
1169
+ lane: NON_EMPTY.nullable(),
1170
+ reservation: ReservationSchema.nullable()
1171
+ }).strict();
1172
+ const TraceRefSchema = z.union([z.object({
1173
+ traceId: NON_EMPTY,
1174
+ execRunId: NON_EMPTY.nullable(),
1175
+ spansWritten: NON_NEGATIVE_INT.nullable(),
1176
+ spansDropped: NON_NEGATIVE_INT.nullable()
1177
+ }).strict(), UnknownRefSchema]);
1178
+ const NON_NEGATIVE = z.number().finite().nonnegative();
1179
+ const CellSettledSchema = z.object({
1180
+ ...EventBaseShape,
1181
+ kind: z.literal("cell-settled"),
1182
+ cellId: CELL_ID,
1183
+ attempt: POSITIVE_INT,
1184
+ runId: NON_EMPTY,
1185
+ outcome: OutcomeSchema,
1186
+ accounting: AccountingSchema,
1187
+ boxMinutes: NON_NEGATIVE.nullable(),
1188
+ wallMs: NON_NEGATIVE.nullable(),
1189
+ queueMs: NON_NEGATIVE.nullable(),
1190
+ placement: z.object({
1191
+ lane: NON_EMPTY,
1192
+ boxId: NON_EMPTY.nullable()
1193
+ }).strict().nullable(),
1194
+ identity: ExecutionIdentitySchema,
1195
+ surfaceEvidence: z.array(SurfaceEvidenceSchema),
1196
+ traceRef: TraceRefSchema
1197
+ }).strict();
1198
+ const CellCancelledSchema = z.object({
1199
+ ...EventBaseShape,
1200
+ kind: z.literal("cell-cancelled"),
1201
+ cellId: CELL_ID,
1202
+ reason: z.enum([
1203
+ "pruned",
1204
+ "budget",
1205
+ "deadline",
1206
+ "aborted"
1207
+ ])
1208
+ }).strict();
1209
+ const NodeEstimateSchema = z.object({
1210
+ against: NODE_ID,
1211
+ split: SPLIT,
1212
+ units: NON_NEGATIVE_INT,
1213
+ pairs: NON_NEGATIVE_INT,
1214
+ delta: FINITE_NUMBER.nullable(),
1215
+ interval: z.tuple([FINITE_NUMBER, FINITE_NUMBER]).nullable(),
1216
+ method: z.enum([
1217
+ "none",
1218
+ "insufficient",
1219
+ "descriptive",
1220
+ "bootstrap"
1221
+ ]),
1222
+ exactSignP: z.number().min(0).max(1).nullable(),
1223
+ indeterminate: z.boolean(),
1224
+ cellSetDigest: HASH,
1225
+ estimator: SourceRefSchema
1226
+ }).strict();
1227
+ const NodeDecidedSchema = z.object({
1228
+ ...EventBaseShape,
1229
+ kind: z.literal("node-decided"),
1230
+ nodeId: NODE_ID,
1231
+ decision: z.discriminatedUnion("status", [
1232
+ z.object({
1233
+ status: z.literal("advanced"),
1234
+ rung: NON_NEGATIVE_INT
1235
+ }).strict(),
1236
+ z.object({ status: z.literal("pruned") }).strict(),
1237
+ z.object({ status: z.literal("invalid") }).strict(),
1238
+ z.object({ status: z.literal("finalist") }).strict(),
1239
+ z.object({ status: z.literal("selected") }).strict(),
1240
+ z.object({ status: z.literal("rejected") }).strict()
1241
+ ]),
1242
+ basis: NodeEstimateSchema.nullable(),
1243
+ rule: NON_EMPTY,
1244
+ reason: NON_EMPTY
1245
+ }).strict();
1246
+ const SearchClosedSchema = z.object({
1247
+ ...EventBaseShape,
1248
+ kind: z.literal("search-closed"),
1249
+ reason: z.enum([
1250
+ "budget",
1251
+ "deadline",
1252
+ "max-nodes",
1253
+ "patience",
1254
+ "converged",
1255
+ "aborted"
1256
+ ]),
1257
+ claim: z.object({
1258
+ power: z.union([z.object({
1259
+ adequate: z.boolean(),
1260
+ minimumEffect: z.number().finite().positive(),
1261
+ powerAtMinimumEffect: z.number().min(0).max(1),
1262
+ units: NON_NEGATIVE_INT
1263
+ }).strict(), UnknownRefSchema]),
1264
+ finalists: z.array(z.object({
1265
+ nodeId: NODE_ID,
1266
+ estimate: NodeEstimateSchema.nullable(),
1267
+ promote: z.boolean()
1268
+ }).strict()),
1269
+ selected: NODE_ID.nullable(),
1270
+ decision: z.enum([
1271
+ "ship",
1272
+ "hold",
1273
+ "test-cannot-resolve"
1274
+ ])
1275
+ }).strict().nullable()
1276
+ }).strict();
1277
+ const EventSchema = z.discriminatedUnion("kind", [
1278
+ SearchOpenedSchema,
1279
+ OperationStartedSchema,
1280
+ OperationRecordedSchema,
1281
+ NodeRegisteredSchema,
1282
+ EdgeRecordedSchema,
1283
+ CellAllocatedSchema,
1284
+ CellSettledSchema,
1285
+ CellCancelledSchema,
1286
+ NodeDecidedSchema,
1287
+ SearchClosedSchema
1288
+ ]);
1289
+ const EntrySchema = z.object({
1290
+ schema: z.literal(SEARCH_LEDGER_SCHEMA),
1291
+ searchId: NON_EMPTY,
1292
+ sequence: NON_NEGATIVE_INT,
1293
+ previousHash: z.union([HASH, z.null()]),
1294
+ event: EventSchema,
1295
+ entryHash: HASH
1296
+ }).strict();
1297
+ /** Validate and return a canonical copy. Arrays whose order carries no meaning
1298
+ * are sorted, so retries from different processes produce identical bytes. */
1299
+ function validateSearchLedgerEvent(input) {
1300
+ const parsed = EventSchema.safeParse(input);
1301
+ if (!parsed.success) throw new SearchLedgerError(`invalid search ledger event: ${formatZodError(parsed.error)}`);
1302
+ return normalizeEvent(parsed.data);
1303
+ }
1304
+ /** Open a durable filesystem search ledger. Construction performs no I/O; the
1305
+ * first `append` or `state` verifies the whole existing file. */
1306
+ function openSearchLedger(options) {
1307
+ return new FileSearchLedger(options.path, options.searchId, options.trustedHead);
1308
+ }
1309
+ /** Verify and project immutable search-ledger JSONL through the same codec as
1310
+ * `FileSearchLedger`. */
1311
+ function replaySearchLedgerText(text, searchId, source) {
1312
+ return replayLedgerText(text, source, searchLedgerCodec(searchId));
1313
+ }
1314
+ /**
1315
+ * Verify one stored ledger line in isolation: the schema tag, the entry and
1316
+ * event schemas, canonical bytes, the entry's own hash, and its search. The
1317
+ * chain (sequence and previous hash) and the state machine are the caller's,
1318
+ * because they need the lines before this one. An ingest server runs this on
1319
+ * every received line, so it hashes exactly the bytes the producer hashed.
1320
+ */
1321
+ function parseSearchLedgerLine(line, searchId, context) {
1322
+ let raw;
1323
+ try {
1324
+ raw = JSON.parse(line);
1325
+ } catch (error) {
1326
+ throw new SearchLedgerIntegrityError(`search ledger ${context.path} has invalid JSON at line ${context.line}`, { cause: error });
1327
+ }
1328
+ const entry = parseSearchLedgerEntry(raw, context);
1329
+ if (entry.searchId !== searchId) throw new SearchLedgerIntegrityError(`search ledger ${context.path} line ${context.line} belongs to search ${entry.searchId}, expected ${searchId}`);
1330
+ if (canonicalString(entry) !== line) throw new SearchLedgerIntegrityError(`search ledger ${context.path} has non-canonical bytes at line ${context.line}`);
1331
+ const { entryHash, ...material } = entry;
1332
+ const expected = hashCanonical(material);
1333
+ if (entryHash !== expected) throw new SearchLedgerIntegrityError(`search ledger ${context.path} line ${context.line} hash mismatch: expected ${expected}, got ${entryHash}`);
1334
+ return entry;
1335
+ }
1336
+ function searchLedgerCodec(searchId) {
1337
+ return {
1338
+ ...SEARCH_LEDGER_FILE_CONTEXT,
1339
+ header: {
1340
+ schema: SEARCH_LEDGER_SCHEMA,
1341
+ searchId
1342
+ },
1343
+ conflictError: (message) => new SearchLedgerConflictError(message),
1344
+ parseEntry: parseSearchLedgerEntry,
1345
+ checkEntryHeader: (entry, index) => {
1346
+ if (entry.searchId !== searchId) throw new SearchLedgerIntegrityError(`entry ${index} belongs to search ${entry.searchId}, expected ${searchId}`);
1347
+ },
1348
+ createProjector: () => new SearchState(searchId)
1349
+ };
1350
+ }
1351
+ /** Append-only file-backed search ledger with idempotent writes. */
1352
+ var FileSearchLedger = class {
1353
+ path;
1354
+ searchId;
1355
+ trustedHeadPath;
1356
+ trustedHeadMode;
1357
+ journal;
1358
+ constructor(path, searchId, trustedHead = "pin") {
1359
+ if (path.trim().length === 0) throw new SearchLedgerError("ledger path is empty");
1360
+ if (searchId.length === 0 || searchId.trim() !== searchId) throw new SearchLedgerError("searchId must be non-empty without surrounding whitespace");
1361
+ this.searchId = searchId;
1362
+ this.trustedHeadMode = trustedHead;
1363
+ this.journal = new FileLedgerJournal(path, searchLedgerCodec(searchId), { requireTrustedHead: trustedHead === "require" });
1364
+ this.path = this.journal.path;
1365
+ this.trustedHeadPath = this.journal.trustedHeadPath;
1366
+ }
1367
+ async state() {
1368
+ return this.journal.replay();
1369
+ }
1370
+ async append(input) {
1371
+ const event = validateSearchLedgerEvent(input);
1372
+ const { entry, appended, projection } = await this.journal.append(event, { pinHead: this.trustedHeadMode !== "off" });
1373
+ return {
1374
+ entry,
1375
+ appended,
1376
+ state: projection
1377
+ };
1378
+ }
1379
+ async trustedHead() {
1380
+ return this.journal.trustedHead();
1381
+ }
1382
+ async pinTrustedHead() {
1383
+ return this.journal.pinTrustedHead();
1384
+ }
1385
+ async clearTrustedHead() {
1386
+ return this.journal.clearTrustedHead();
1387
+ }
1388
+ };
1389
+ function parseSearchLedgerEntry(raw, context) {
1390
+ const schema = raw?.schema;
1391
+ 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`);
1392
+ const parsed = EntrySchema.safeParse(raw);
1393
+ if (!parsed.success) throw new SearchLedgerIntegrityError(`search ledger ${context.path} has a malformed entry at line ${context.line}: ${formatZodError(parsed.error)}`);
1394
+ const entry = parsed.data;
1395
+ if (canonicalString(validateSearchLedgerEvent(entry.event)) !== canonicalString(entry.event)) throw new SearchLedgerIntegrityError(`search ledger ${context.path} has non-canonical event ordering at line ${context.line}`);
1396
+ return entry;
1397
+ }
1398
+ function normalizeEvent(event) {
1399
+ const artifacts = sortArtifacts(event.artifacts);
1400
+ switch (event.kind) {
1401
+ case "search-opened": return {
1402
+ ...event,
1403
+ artifacts,
1404
+ splits: {
1405
+ ...event.splits,
1406
+ train: {
1407
+ ...event.splits.train,
1408
+ tasks: sortTasks(event.splits.train.tasks)
1409
+ },
1410
+ selection: {
1411
+ ...event.splits.selection,
1412
+ tasks: sortTasks(event.splits.selection.tasks)
1413
+ },
1414
+ test: {
1415
+ ...event.splits.test,
1416
+ tasks: sortTasks(event.splits.test.tasks)
1417
+ }
1418
+ }
1419
+ };
1420
+ case "node-registered": return {
1421
+ ...event,
1422
+ artifacts,
1423
+ surfaces: [...event.surfaces].sort((a, b) => compareStrings(a.surfaceId, b.surfaceId))
1424
+ };
1425
+ case "cell-settled": return {
1426
+ ...event,
1427
+ artifacts,
1428
+ surfaceEvidence: [...event.surfaceEvidence].map((evidence) => ({
1429
+ ...evidence,
1430
+ evidence: sortArtifacts(evidence.evidence)
1431
+ })).sort((a, b) => compareStrings(a.surfaceId, b.surfaceId))
1432
+ };
1433
+ default: return {
1434
+ ...event,
1435
+ artifacts
1436
+ };
1437
+ }
1438
+ }
1439
+ function sortTasks(tasks) {
1440
+ return [...tasks].sort((a, b) => compareStrings(a.taskId, b.taskId));
1441
+ }
1442
+ function sortArtifacts(artifacts) {
1443
+ return [...artifacts].sort((a, b) => compareStrings(artifactKey(a), artifactKey(b)));
1444
+ }
1445
+ function formatZodError(error) {
1446
+ return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`).join("; ");
1447
+ }
1448
+ //#endregion
1449
+ export { validateSearchLedgerEvent as a, searchCellId as c, searchUnitScores as d, SEARCH_LEDGER_SCHEMA as f, replaySearchLedgerText as i, searchNodeId as l, openSearchLedger as n, SearchState as o, parseSearchLedgerLine as r, SearchStateView as s, FileSearchLedger as t, searchTaskSetDigest as u };
1450
+
1451
+ //# sourceMappingURL=search-ledger-CQ8Uy4FK.js.map