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