@harperfast/harper 5.3.0 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/bin/copyDb.ts +5 -1
  2. package/bin/deployRenderer.ts +3 -0
  3. package/components/Application.ts +66 -22
  4. package/components/DESIGN.md +89 -36
  5. package/components/componentLoader.ts +2 -0
  6. package/components/deploymentRecorder.ts +18 -1
  7. package/components/installFingerprint.ts +123 -0
  8. package/components/mcp/DESIGN.md +4 -4
  9. package/components/operations.js +29 -0
  10. package/config/DESIGN.md +5 -5
  11. package/dataLayer/DESIGN.md +37 -22
  12. package/dataLayer/backupRepository.ts +248 -0
  13. package/dataLayer/restoreMarker.ts +30 -75
  14. package/dataLayer/rocksdbBackup.ts +253 -110
  15. package/dist/bin/copyDb.js +3 -0
  16. package/dist/bin/copyDb.js.map +1 -1
  17. package/dist/bin/deployRenderer.js +3 -0
  18. package/dist/bin/deployRenderer.js.map +1 -1
  19. package/dist/components/Application.d.ts +5 -0
  20. package/dist/components/Application.js +51 -19
  21. package/dist/components/Application.js.map +1 -1
  22. package/dist/components/componentLoader.js +2 -0
  23. package/dist/components/componentLoader.js.map +1 -1
  24. package/dist/components/deploymentRecorder.js +18 -1
  25. package/dist/components/deploymentRecorder.js.map +1 -1
  26. package/dist/components/installFingerprint.d.ts +39 -0
  27. package/dist/components/installFingerprint.js +113 -0
  28. package/dist/components/installFingerprint.js.map +1 -0
  29. package/dist/components/operations.d.ts +2 -0
  30. package/dist/components/operations.js +31 -0
  31. package/dist/components/operations.js.map +1 -1
  32. package/dist/dataLayer/backupRepository.d.ts +33 -0
  33. package/dist/dataLayer/backupRepository.js +232 -0
  34. package/dist/dataLayer/backupRepository.js.map +1 -0
  35. package/dist/dataLayer/restoreMarker.d.ts +4 -1
  36. package/dist/dataLayer/restoreMarker.js +29 -68
  37. package/dist/dataLayer/restoreMarker.js.map +1 -1
  38. package/dist/dataLayer/rocksdbBackup.js +221 -112
  39. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/json/systemSchema.json +14 -0
  43. package/dist/resources/DatabaseTransaction.d.ts +140 -11
  44. package/dist/resources/DatabaseTransaction.js +1055 -464
  45. package/dist/resources/DatabaseTransaction.js.map +1 -1
  46. package/dist/resources/IterableEventQueue.d.ts +2 -1
  47. package/dist/resources/IterableEventQueue.js +16 -8
  48. package/dist/resources/IterableEventQueue.js.map +1 -1
  49. package/dist/resources/LMDBTransaction.d.ts +13 -2
  50. package/dist/resources/LMDBTransaction.js +219 -93
  51. package/dist/resources/LMDBTransaction.js.map +1 -1
  52. package/dist/resources/RecordEncoder.js +2 -3
  53. package/dist/resources/RecordEncoder.js.map +1 -1
  54. package/dist/resources/Resource.js +5 -6
  55. package/dist/resources/Resource.js.map +1 -1
  56. package/dist/resources/ResourceInterface.d.ts +3 -1
  57. package/dist/resources/ResourceInterface.js.map +1 -1
  58. package/dist/resources/RocksIndexStore.d.ts +2 -1
  59. package/dist/resources/RocksIndexStore.js +3 -0
  60. package/dist/resources/RocksIndexStore.js.map +1 -1
  61. package/dist/resources/Table.js +116 -12
  62. package/dist/resources/Table.js.map +1 -1
  63. package/dist/resources/analytics/write.js +6 -2
  64. package/dist/resources/analytics/write.js.map +1 -1
  65. package/dist/resources/auditStore.d.ts +71 -17
  66. package/dist/resources/auditStore.js +248 -64
  67. package/dist/resources/auditStore.js.map +1 -1
  68. package/dist/resources/branchDatabase.js +2 -0
  69. package/dist/resources/branchDatabase.js.map +1 -1
  70. package/dist/resources/databases.js +8 -2
  71. package/dist/resources/databases.js.map +1 -1
  72. package/dist/resources/indexes/fullTextNativeBinding.d.ts +6 -2
  73. package/dist/resources/indexes/fullTextNativeBinding.js +12 -2
  74. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
  75. package/dist/resources/indexes/fullTextQueryIndex.d.ts +4 -0
  76. package/dist/resources/indexes/fullTextQueryIndex.js +207 -10
  77. package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -1
  78. package/dist/resources/models/Models.d.ts +11 -0
  79. package/dist/resources/models/Models.js +95 -4
  80. package/dist/resources/models/Models.js.map +1 -1
  81. package/dist/resources/models/backendRegistry.d.ts +8 -0
  82. package/dist/resources/models/backendRegistry.js +15 -1
  83. package/dist/resources/models/backendRegistry.js.map +1 -1
  84. package/dist/resources/models/bootstrap.d.ts +2 -0
  85. package/dist/resources/models/bootstrap.js +6 -0
  86. package/dist/resources/models/bootstrap.js.map +1 -1
  87. package/dist/resources/models/calibration.d.ts +88 -0
  88. package/dist/resources/models/calibration.js +244 -0
  89. package/dist/resources/models/calibration.js.map +1 -0
  90. package/dist/resources/models/calibrationStore.d.ts +163 -0
  91. package/dist/resources/models/calibrationStore.js +986 -0
  92. package/dist/resources/models/calibrationStore.js.map +1 -0
  93. package/dist/resources/models/decisionStore.d.ts +24 -0
  94. package/dist/resources/models/decisionStore.js +45 -0
  95. package/dist/resources/models/decisionStore.js.map +1 -1
  96. package/dist/resources/models/generativeDecision.js +44 -15
  97. package/dist/resources/models/generativeDecision.js.map +1 -1
  98. package/dist/resources/models/types.d.ts +7 -0
  99. package/dist/resources/recordLock.d.ts +8 -5
  100. package/dist/resources/recordLock.js +6 -4
  101. package/dist/resources/recordLock.js.map +1 -1
  102. package/dist/resources/replayLogs.js +8 -1
  103. package/dist/resources/replayLogs.js.map +1 -1
  104. package/dist/resources/scheduler/engine.d.ts +4 -0
  105. package/dist/resources/scheduler/engine.js +30 -2
  106. package/dist/resources/scheduler/engine.js.map +1 -1
  107. package/dist/resources/search.d.ts +16 -4
  108. package/dist/resources/search.js +117 -13
  109. package/dist/resources/search.js.map +1 -1
  110. package/dist/resources/transaction.js +85 -5
  111. package/dist/resources/transaction.js.map +1 -1
  112. package/dist/resources/transactionBroadcast.d.ts +8 -0
  113. package/dist/resources/transactionBroadcast.js +65 -0
  114. package/dist/resources/transactionBroadcast.js.map +1 -1
  115. package/dist/security/keys.d.ts +1 -1
  116. package/dist/security/keys.js +170 -120
  117. package/dist/security/keys.js.map +1 -1
  118. package/dist/server/http.js +10 -0
  119. package/dist/server/http.js.map +1 -1
  120. package/dist/server/serverHelpers/Request.js +11 -2
  121. package/dist/server/serverHelpers/Request.js.map +1 -1
  122. package/dist/server/serverHelpers/contentTypes.js +8 -3
  123. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  124. package/dist/server/serverHelpers/uwsServer.js +38 -7
  125. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  126. package/dist/server/threads/manageThreads.d.ts +3 -3
  127. package/dist/server/threads/manageThreads.js +15 -13
  128. package/dist/server/threads/manageThreads.js.map +1 -1
  129. package/dist/server/threads/threadServer.d.ts +3 -0
  130. package/dist/server/threads/threadServer.js +24 -6
  131. package/dist/server/threads/threadServer.js.map +1 -1
  132. package/dist/upgrade/directives/5-3-0.js +9 -3
  133. package/dist/upgrade/directives/5-3-0.js.map +1 -1
  134. package/dist/utility/durableFile.d.ts +36 -0
  135. package/dist/utility/durableFile.js +106 -0
  136. package/dist/utility/durableFile.js.map +1 -0
  137. package/dist/utility/errors/hdbError.d.ts +4 -0
  138. package/dist/utility/errors/hdbError.js +10 -1
  139. package/dist/utility/errors/hdbError.js.map +1 -1
  140. package/dist/utility/globalSchema.d.ts +9 -0
  141. package/dist/utility/hdbTerms.d.ts +1 -0
  142. package/dist/utility/hdbTerms.js +1 -0
  143. package/dist/utility/hdbTerms.js.map +1 -1
  144. package/dist/validation/configValidator.js +29 -0
  145. package/dist/validation/configValidator.js.map +1 -1
  146. package/index.ts +7 -0
  147. package/json/systemSchema.json +14 -0
  148. package/npm-shrinkwrap.json +22 -22
  149. package/package.json +2 -2
  150. package/resources/DESIGN.md +57 -272
  151. package/resources/DatabaseTransaction.ts +708 -87
  152. package/resources/FULL_TEXT.md +6 -2
  153. package/resources/IterableEventQueue.ts +14 -7
  154. package/resources/LMDBTransaction.ts +212 -84
  155. package/resources/RecordEncoder.ts +2 -3
  156. package/resources/Resource.ts +5 -6
  157. package/resources/ResourceInterface.ts +3 -1
  158. package/resources/RocksIndexStore.ts +5 -0
  159. package/resources/Table.ts +118 -15
  160. package/resources/analytics/write.ts +6 -2
  161. package/resources/audit-retention.md +203 -0
  162. package/resources/auditStore.ts +306 -84
  163. package/resources/branchDatabase.ts +2 -0
  164. package/resources/databases.ts +6 -2
  165. package/resources/indexes/DESIGN.md +120 -177
  166. package/resources/indexes/fullTextNativeBinding.ts +22 -3
  167. package/resources/indexes/fullTextQueryIndex.ts +274 -11
  168. package/resources/models/DESIGN.md +11 -3
  169. package/resources/models/Models.ts +127 -4
  170. package/resources/models/backendRegistry.ts +17 -0
  171. package/resources/models/bootstrap.ts +9 -1
  172. package/resources/models/calibration.ts +298 -0
  173. package/resources/models/calibrationStore.ts +1167 -0
  174. package/resources/models/decisionStore.ts +57 -0
  175. package/resources/models/generativeDecision.ts +54 -16
  176. package/resources/models/types.ts +6 -0
  177. package/resources/record-locks.md +155 -141
  178. package/resources/recordLock.ts +12 -6
  179. package/resources/replayLogs.ts +9 -2
  180. package/resources/scheduler/DESIGN.md +4 -3
  181. package/resources/scheduler/engine.ts +35 -2
  182. package/resources/search.ts +154 -16
  183. package/resources/transaction.ts +50 -5
  184. package/resources/transactionBroadcast.ts +69 -0
  185. package/security/DESIGN.md +9 -30
  186. package/security/keys.ts +175 -127
  187. package/server/DESIGN.md +70 -36
  188. package/server/http.ts +10 -0
  189. package/server/serverHelpers/Request.ts +10 -2
  190. package/server/serverHelpers/contentTypes.ts +5 -3
  191. package/server/serverHelpers/uwsServer.ts +34 -7
  192. package/server/threads/manageThreads.js +16 -14
  193. package/server/threads/threadServer.js +24 -6
  194. package/studio/web/assets/{Chat-CNEKMllJ.js → Chat-4LMxI0yB.js} +1 -1
  195. package/studio/web/assets/{FloatingChat-CEnWhHeR.js → FloatingChat-kWHApEtF.js} +3 -3
  196. package/studio/web/assets/{SearchDialog-CwhJdEdY.js → SearchDialog-B7ymna97.js} +1 -1
  197. package/studio/web/assets/{apiToken-DC5vsaaO.js → apiToken-CE3AAt9f.js} +1 -1
  198. package/studio/web/assets/{applications-DQZxDy0x.js → applications-DdN7CqsB.js} +1 -1
  199. package/studio/web/assets/{cssMode-Dz2DxF4e.js → cssMode-BpySZUSt.js} +1 -1
  200. package/studio/web/assets/{editor-B3mjd3tz.js → editor-myLCVv9C.js} +1 -1
  201. package/studio/web/assets/{html-BsRGM7tp.js → html-DGNYO55O.js} +1 -1
  202. package/studio/web/assets/{htmlMode-tUKJaA3o.js → htmlMode-BUgpQr41.js} +1 -1
  203. package/studio/web/assets/{index-BuqFjN5K.js → index-FN4KC9va.js} +39 -39
  204. package/studio/web/assets/index-QLZw6xrU.css +1 -0
  205. package/studio/web/assets/{index.lazy-CcpfQFY_.js → index.lazy-DZFuaumG.js} +1 -1
  206. package/studio/web/assets/{javascript-BZ_uRGHl.js → javascript-DiBzh4pj.js} +1 -1
  207. package/studio/web/assets/{jsonMode-DgB-aFMj.js → jsonMode-Bm312nmu.js} +1 -1
  208. package/studio/web/assets/{languageServices-BleuBw17.js → languageServices-Dz2K9rZN.js} +1 -1
  209. package/studio/web/assets/{lspLanguageFeatures-ByVYLSsQ.js → lspLanguageFeatures-CNu1L5xn.js} +1 -1
  210. package/studio/web/assets/{notifications-TuzN0IMi.js → notifications--nwFLGmM.js} +1 -1
  211. package/studio/web/assets/{notifications-D1Nf-Edn.js → notifications-BelKw5bR.js} +1 -1
  212. package/studio/web/assets/{profile-D7C7Lupz.js → profile-AU5FjOBx.js} +1 -1
  213. package/studio/web/assets/{regions-nGQCm-v8.js → regions-C0sTSEHH.js} +1 -1
  214. package/studio/web/assets/{register-C3x9Ze6P.js → register-DyNR2rGk.js} +2 -2
  215. package/studio/web/assets/{setComponentFile-DpjrRqeG.js → setComponentFile-BkWx8AN3.js} +1 -1
  216. package/studio/web/assets/{setup-CGiKLZ9e.js → setup-D6LZS78d.js} +2 -2
  217. package/studio/web/assets/{status-CGJwrNPH.js → status-BCWnn70o.js} +1 -1
  218. package/studio/web/assets/{toggleHighContrast-C-Ne6P7T.js → toggleHighContrast-pyeGJ6W1.js} +1 -1
  219. package/studio/web/assets/{tsMode-CSreYTys.js → tsMode-CGV3iaWC.js} +1 -1
  220. package/studio/web/assets/{typescript-MnxBk89W.js → typescript-Ci7Khvwo.js} +1 -1
  221. package/studio/web/assets/{useEntityRestURL-TNrh77-s.js → useEntityRestURL-DNiz3aZ0.js} +1 -1
  222. package/studio/web/assets/{workers-BfZlUNVI.js → workers-byzxjbqy.js} +1 -1
  223. package/studio/web/assets/{xml-BHd6qGH7.js → xml-DGnHQ3CX.js} +1 -1
  224. package/studio/web/assets/{yaml-C53R8OQq.js → yaml-BUoTnPWb.js} +1 -1
  225. package/studio/web/index.html +2 -2
  226. package/upgrade/directives/5-3-0.ts +10 -3
  227. package/utility/durableFile.ts +96 -0
  228. package/utility/errors/hdbError.ts +9 -0
  229. package/utility/hdbTerms.ts +1 -0
  230. package/validation/configValidator.ts +31 -0
  231. package/studio/web/assets/index-72cEH474.css +0 -1
@@ -45,6 +45,16 @@ export interface DecisionRow {
45
45
  fields?: DecisionRecord['fields'];
46
46
  noMatch?: number;
47
47
  calibrated: boolean;
48
+ /** The backend's scores, kept when a fitted calibration replaced them, so a refit never reads its own output. */
49
+ rawDistribution?: DecisionRecord['distribution'];
50
+ rawFields?: DecisionRecord['fields'];
51
+ /** The fit applied to each field (`field` absent for a leaf schema), with its parameters as applied. */
52
+ calibration?: Array<{ field?: string; fitId: string; t: number; epsilon: number }>;
53
+ /** The decision entry's source fingerprint, or `registered:<name>` for a backend registered from code. */
54
+ entry?: string;
55
+ /** The calibration population, for a decision whose score source is identified. */
56
+ population?: string;
57
+ populationRank?: string;
48
58
  }
49
59
 
50
60
  type Fact = 'truth' | 'action';
@@ -84,6 +94,12 @@ export const DECISION_ATTRIBUTES = [
84
94
  { name: 'fields' },
85
95
  { name: 'noMatch', type: 'number' },
86
96
  { name: 'calibrated', type: 'boolean' },
97
+ { name: 'rawDistribution' },
98
+ { name: 'rawFields' },
99
+ { name: 'calibration' },
100
+ { name: 'entry', type: 'string' },
101
+ { name: 'population', type: 'string' },
102
+ { name: 'populationRank', type: 'string', indexed: true },
87
103
  ];
88
104
 
89
105
  export const OUTCOME_ATTRIBUTES = [
@@ -279,6 +295,10 @@ function visibleTo(row: DecisionRow, tenant: string | undefined): boolean {
279
295
  return tenant == null || row.tenant == null || row.tenant === tenant;
280
296
  }
281
297
 
298
+ export function truthKey(id: string, field?: string): string {
299
+ return factKey(id, 'truth', field);
300
+ }
301
+
282
302
  function factKey(id: string, fact: Fact, field?: string): string {
283
303
  return field == null ? `${id}/${fact}` : `${id}/${fact}/${field}`;
284
304
  }
@@ -394,6 +414,43 @@ export function getModelsConfigHash(): string | undefined {
394
414
  return modelsConfigHash;
395
415
  }
396
416
 
417
+ /** A configured entry's identity without its credentials: the same fingerprint for the same visible configuration. */
418
+ export function sourceFingerprint(kind: string, logicalName: string | undefined, config: unknown): string {
419
+ return createHash('sha256')
420
+ .update(
421
+ canonicalJson({
422
+ kind,
423
+ logicalName: logicalName ?? null,
424
+ config: withoutNamedCredentials(withoutFallback(config)),
425
+ })
426
+ )
427
+ .digest('hex');
428
+ }
429
+
430
+ /**
431
+ * Only fields known to hold credentials. A setting that merely ends in `key` or `token` can change what a source
432
+ * scores, so it stays in the fingerprint; rotating an unrecognized secret therefore starts calibration over.
433
+ */
434
+ const NAMED_CREDENTIAL =
435
+ /^(apiKey|apiSecret|accessKeyId|secretAccessKey|sessionToken|authorization|password|token|bearerToken|credentials?)$/i;
436
+
437
+ function withoutNamedCredentials(value: unknown): unknown {
438
+ if (Array.isArray(value)) return value.map(withoutNamedCredentials);
439
+ if (!value || typeof value !== 'object') return value;
440
+ const kept: Record<string, unknown> = {};
441
+ for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
442
+ if (!NAMED_CREDENTIAL.test(key)) kept[key] = withoutNamedCredentials(entry);
443
+ }
444
+ return kept;
445
+ }
446
+
447
+ /** Which backend serves after this one is routing, not what produces this one's scores. */
448
+ function withoutFallback(config: unknown): unknown {
449
+ if (!config || typeof config !== 'object' || Array.isArray(config)) return config;
450
+ const { fallback: _fallback, ...rest } = config as Record<string, unknown>;
451
+ return rest;
452
+ }
453
+
397
454
  const CREDENTIAL_KEY = /^(apiKey|accessKeyId|authorization|.*secret.*|.*password.*|.*credential.*|.*key|.*token)$/i;
398
455
 
399
456
  function withoutCredentials(value: unknown): unknown {
@@ -11,7 +11,8 @@
11
11
  */
12
12
  import { ServerError } from '../../utility/errors/hdbError.ts';
13
13
  import { composeSignal } from './backendHelpers.ts';
14
- import { setDecision } from './backendRegistry.ts';
14
+ import { SERVED_SOURCE, type ServedSourceHook, setDecision } from './backendRegistry.ts';
15
+ import { isCalibrationEnabled } from './calibrationStore.ts';
15
16
  import {
16
17
  allowedValues,
17
18
  isObjectSchema,
@@ -143,7 +144,8 @@ export function createGenerativeDecisionBackend(
143
144
  state: DecideInput,
144
145
  schema: DecisionSchema,
145
146
  instructions: string | undefined,
146
- signal: AbortSignal | undefined
147
+ signal: AbortSignal | undefined,
148
+ served: object
147
149
  ): Promise<DecisionOutput<unknown>> => {
148
150
  const input = buildInput(
149
151
  state,
@@ -161,6 +163,7 @@ export function createGenerativeDecisionBackend(
161
163
  temperature,
162
164
  signal: sampleSignal,
163
165
  requires,
166
+ ...served,
164
167
  });
165
168
  return parseSample(schema, result.content, logicalName);
166
169
  }),
@@ -176,17 +179,18 @@ export function createGenerativeDecisionBackend(
176
179
  field: string | undefined,
177
180
  leaf: DecisionLeaf,
178
181
  instructions: string | undefined,
179
- signal: AbortSignal
182
+ signal: AbortSignal,
183
+ served: object
180
184
  ): Promise<LeafScore> => {
181
185
  const values = allowedValues(leaf);
182
186
  const choices = values.map((value) => String(value));
183
187
  if (leaf.noMatch !== true) {
184
188
  const input = buildInput(state, schema, instructions, SCORE_SYSTEM_PROMPT, field);
185
- const scored = await score(input, choices, { model: logicalName, signal });
189
+ const scored = await score(input, choices, { model: logicalName, signal, ...served });
186
190
  return { distribution: softmax(values, scored?.logLikelihoods, logicalName) };
187
191
  }
188
192
  const input = buildInput(state, schema, instructions, SCORE_NO_MATCH_SYSTEM_PROMPT, field);
189
- const scored = await score(input, [...choices, noMatchChoice(choices)], { model: logicalName, signal });
193
+ const scored = await score(input, [...choices, noMatchChoice(choices)], { model: logicalName, signal, ...served });
190
194
  const all = softmax([...values, NO_MATCH], scored?.logLikelihoods, logicalName);
191
195
  // Normalized over the allowed values' own scores, not by dividing out the none share, which can underflow to zero.
192
196
  const distribution = softmax(values, (scored!.logLikelihoods as number[]).slice(0, values.length), logicalName);
@@ -199,11 +203,12 @@ export function createGenerativeDecisionBackend(
199
203
  state: DecideInput,
200
204
  schema: DecisionSchema,
201
205
  instructions: string | undefined,
202
- signal: AbortSignal | undefined
206
+ signal: AbortSignal | undefined,
207
+ served: object
203
208
  ): Promise<DecisionOutput<unknown>> => {
204
209
  if (!isObjectSchema(schema)) {
205
210
  const [scored] = await runPool(
206
- [(leafSignal: AbortSignal) => scoreLeaf(state, schema, undefined, schema, instructions, leafSignal)],
211
+ [(leafSignal: AbortSignal) => scoreLeaf(state, schema, undefined, schema, instructions, leafSignal, served)],
207
212
  concurrency,
208
213
  signal
209
214
  );
@@ -214,7 +219,7 @@ export function createGenerativeDecisionBackend(
214
219
  entries.map(
215
220
  ([name, leaf]) =>
216
221
  (leafSignal: AbortSignal) =>
217
- scoreLeaf(state, schema, name, leaf, instructions, leafSignal)
222
+ scoreLeaf(state, schema, name, leaf, instructions, leafSignal, served)
218
223
  ),
219
224
  concurrency,
220
225
  signal
@@ -225,6 +230,12 @@ export function createGenerativeDecisionBackend(
225
230
  const signatureFor = (mode: ScoringMode) =>
226
231
  `generative=${logicalName};mode=${mode};samples=${samples};temperature=${temperature ?? 'default'}`;
227
232
 
233
+ const signed = (mode: ScoringMode, collector: SourceCollector | undefined): string | undefined => {
234
+ if (!collector) return signatureFor(mode);
235
+ if (collector.mixed || collector.source === null) return undefined;
236
+ return `${signatureFor(mode)};source=${collector.source}`;
237
+ };
238
+
228
239
  const requires = config.requireStructuredOutput === false ? undefined : (STRUCTURED_OUTPUT as Capability[]);
229
240
  return {
230
241
  name: 'generative',
@@ -238,23 +249,50 @@ export function createGenerativeDecisionBackend(
238
249
  const mode: ScoringMode =
239
250
  scoring === 'auto' ? (canScore(logicalName, largestLeaf(schema)) ? 'score' : 'vote') : scoring;
240
251
  if (mode === 'score') {
252
+ const collector = isCalibrationEnabled() ? sourceCollector() : undefined;
241
253
  try {
242
- return {
243
- status: 'completed',
244
- output: { ...(await scoreAll(state, schema, opts.instructions, signal)), signature: signatureFor('score') },
245
- };
254
+ const output = await scoreAll(state, schema, opts.instructions, signal, servedOpts(collector));
255
+ return { status: 'completed', output: withSignature(output, signed('score', collector)) };
246
256
  } catch (err) {
247
257
  if (scoring !== 'auto' || !isScoringUnsupported(err)) throw err;
248
258
  }
249
259
  }
250
- return {
251
- status: 'completed',
252
- output: { ...(await vote(state, schema, opts.instructions, signal)), signature: signatureFor('vote') },
253
- };
260
+ const collector = isCalibrationEnabled() ? sourceCollector() : undefined;
261
+ const output = await vote(state, schema, opts.instructions, signal, servedOpts(collector));
262
+ return { status: 'completed', output: withSignature(output, signed('vote', collector)) };
254
263
  },
255
264
  };
256
265
  }
257
266
 
267
+ const NOT_COLLECTING = Object.freeze({});
268
+
269
+ interface SourceCollector {
270
+ source: string | null;
271
+ mixed: boolean;
272
+ hook: ServedSourceHook;
273
+ }
274
+
275
+ function sourceCollector(): SourceCollector {
276
+ const collector: SourceCollector = {
277
+ source: null,
278
+ mixed: false,
279
+ hook: (source) => {
280
+ if (source === undefined) collector.mixed = true;
281
+ else if (collector.source === null) collector.source = source;
282
+ else if (collector.source !== source) collector.mixed = true;
283
+ },
284
+ };
285
+ return collector;
286
+ }
287
+
288
+ function servedOpts(collector: SourceCollector | undefined): object {
289
+ return collector ? { [SERVED_SOURCE]: collector.hook } : NOT_COLLECTING;
290
+ }
291
+
292
+ function withSignature(output: DecisionOutput<unknown>, signature: string | undefined): DecisionOutput<unknown> {
293
+ return signature === undefined ? output : { ...output, signature };
294
+ }
295
+
258
296
  // A candidate that advertises scoring without implementing it counts as able, so the facade reports
259
297
  // that contract failure instead of the decision quietly voting.
260
298
  function routesToScorer(logicalName: string, largestLeafSize: number): boolean {
@@ -7,6 +7,8 @@
7
7
  * Reference: planning artifact `tmp/harper-510-phase-1-detail.md` (canonical shapes).
8
8
  */
9
9
 
10
+ import type { CalibrationBudgets, CalibrationRunResult, CalibrationSummary } from './calibrationStore.ts';
11
+
10
12
  export interface Models {
11
13
  embed(input: string | string[], opts?: EmbedOpts): Promise<Float32Array[]>;
12
14
  generate(input: GenerateInput, opts?: GenerateOpts): Promise<GenerateResult>;
@@ -22,6 +24,10 @@ export interface Models {
22
24
  getDecision<T = unknown>(id: string): Promise<DecisionRecord<T> | undefined>;
23
25
  /** Record what actually happened for a decision: its truth, the action taken, or both. See #2840. */
24
26
  recordOutcome<T = unknown>(id: string, outcome: OutcomeReport): Promise<DecisionRecord<T>>;
27
+ /** Fit calibrations from recorded outcomes now, as the periodic job does. See #2841. */
28
+ calibrate(budgets?: CalibrationBudgets): Promise<CalibrationRunResult>;
29
+ /** The newest calibration of each field of each population the caller's tenant owns. See #2841. */
30
+ getCalibrations(filter?: { model?: string }): Promise<CalibrationSummary[]>;
25
31
  /** Register a custom backend under a logical id, selectable via `opts.model`. See #1325. */
26
32
  registerBackend(kind: ModelKind, id: string, backend: ModelBackend): void;
27
33
  /** Build a `ModelBackend` from a spec; pair with `registerBackend`. See #1325. */