@orkestrel/study 0.0.0-stage → 0.0.2

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.
@@ -0,0 +1,2107 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _orkestrel_contract = require("@orkestrel/contract");
3
+ let _orkestrel_timeout = require("@orkestrel/timeout");
4
+ let _orkestrel_budget = require("@orkestrel/budget");
5
+ let _orkestrel_abort = require("@orkestrel/abort");
6
+ //#region src/core/constants.ts
7
+ /**
8
+ * Keys the own self-identity every study error stamps on itself.
9
+ * @remarks
10
+ * The global symbol registry lets every package, ESM, CommonJS and captured copy read the same
11
+ * key. The registry makes the key discoverable; it is a recognition mechanism, not an
12
+ * unforgeable provenance marker.
13
+ */
14
+ var STUDY_ERROR_BRAND = Symbol.for("@orkestrel/study.error");
15
+ /** Caps a declared trial population before schedule allocation. */
16
+ var STUDY_TRIALS = 4096;
17
+ /** Caps a declared scenario horizon before execution. */
18
+ var STUDY_GOALS = 4096;
19
+ /** Restricts identifiers to portable path-safe names. */
20
+ var STUDY_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
+ /** Lists every measured request role in report order. */
22
+ var STUDY_ROLES = Object.freeze([
23
+ "agent",
24
+ "judge",
25
+ "summary",
26
+ "metadata",
27
+ "tool"
28
+ ]);
29
+ /** Lists every ceiling a run can stop at in report order. */
30
+ var STUDY_LAYERS = Object.freeze([
31
+ "scope",
32
+ "window",
33
+ "custody",
34
+ "meter",
35
+ "budget",
36
+ "model"
37
+ ]);
38
+ /** Lists every operational ending in report order. */
39
+ var STUDY_CAUSES = Object.freeze([
40
+ "capacity",
41
+ "infrastructure",
42
+ "deadline",
43
+ "interrupted",
44
+ "content"
45
+ ]);
46
+ //#endregion
47
+ //#region src/core/helpers.ts
48
+ /**
49
+ * Copies the own entries of a record that hold a value, in their original order.
50
+ * @param value - The record whose undefined entries are absent declarations
51
+ * @returns A new record without the undefined entries
52
+ * @example
53
+ * ```ts
54
+ * omitStudyAbsent({ context: 512, recap: undefined }) // { context: 512 }
55
+ * ```
56
+ */
57
+ function omitStudyAbsent(value) {
58
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
59
+ }
60
+ /**
61
+ * Omits every undefined optional declaration so the profile has one JSON form.
62
+ * @param profile - The validated profile whose optional leaves may be undefined
63
+ * @returns The profile fields with absent qualification, settings, provider, judge and recap removed
64
+ * @example
65
+ * ```ts
66
+ * normalizeStudyProfile(profile) // no `recap` key when the room declares none
67
+ * ```
68
+ */
69
+ function normalizeStudyProfile(profile) {
70
+ const { provider, qualification, settings, room, ...required } = profile;
71
+ return {
72
+ ...required,
73
+ room: omitStudyAbsent(room),
74
+ ...provider === void 0 ? {} : { provider: {
75
+ ...omitStudyAbsent(provider),
76
+ digest: omitStudyAbsent(provider.digest)
77
+ } },
78
+ ...qualification === void 0 ? {} : { qualification },
79
+ ...settings === void 0 ? {} : { settings }
80
+ };
81
+ }
82
+ /**
83
+ * Projects a stop onto JSON with every unreported reading as null.
84
+ * @param stop - The ceiling that stopped a run
85
+ * @returns The stop's layer, id, and readings, with an undefined `max` or `consumed` as null
86
+ * @example
87
+ * ```ts
88
+ * studyStopToJSON({ layer: 'model', id: 'num_predict', max: 2048, consumed: undefined })
89
+ * // { layer: 'model', id: 'num_predict', max: 2048, consumed: null }
90
+ * ```
91
+ */
92
+ function studyStopToJSON(stop) {
93
+ return {
94
+ layer: stop.layer,
95
+ id: stop.id,
96
+ max: stop.max ?? null,
97
+ consumed: stop.consumed ?? null
98
+ };
99
+ }
100
+ /**
101
+ * Matches exact own enumerable data keys without evaluating accessors.
102
+ * @param value - The candidate record
103
+ * @param required - Required keys
104
+ * @param optional - Allowed optional keys
105
+ * @returns True if the record has exactly the allowed data keys; false otherwise
106
+ * @example Check exact keys
107
+ * ```ts
108
+ * import { matchesStudyKeys } from '@orkestrel/study'
109
+ *
110
+ * matchesStudyKeys({ id: 'one' }, ['id']) // true
111
+ * ```
112
+ */
113
+ function matchesStudyKeys(value, required, optional = []) {
114
+ return (0, _orkestrel_contract.holds)(() => {
115
+ if (!(0, _orkestrel_contract.isRecord)(value)) return false;
116
+ const keys = Reflect.ownKeys(value);
117
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => {
118
+ if (typeof key !== "string" || !required.includes(key) && !optional.includes(key)) return false;
119
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
120
+ return descriptor !== void 0 && descriptor.enumerable === true && Object.hasOwn(descriptor, "value");
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * Builds a bounded schedule from explicit repetition rows and declared scenarios.
126
+ * @param definition - The frozen population declaration
127
+ * @param scenarios - The complete scenario declarations
128
+ * @returns Owned trials in repetition, scenario and row-arm order
129
+ * @throws StudyError - Thrown when the population or scenario set is invalid
130
+ * @example
131
+ * ```ts
132
+ * buildStudySchedule(definition, scenarios)
133
+ * ```
134
+ */
135
+ function buildStudySchedule(definition, scenarios) {
136
+ if (!isStudyDefinition(definition) || !scenarios.every(isStudyScenario)) throw new StudyError("CONFIG", "Invalid study population");
137
+ const ids = new Set(scenarios.map((scenario) => scenario.id));
138
+ if (ids.size !== scenarios.length || ids.size !== definition.scenarios.length || !definition.scenarios.every((id) => ids.has(id))) throw new StudyError("CONFIG", "Scenario declarations must match the population exactly");
139
+ return Object.freeze([...definition.order.entries()].flatMap(([repetition, row]) => [...definition.scenarios.entries()].flatMap(([scenario, id]) => row.map((arm) => Object.freeze({
140
+ id: `r${repetition + 1}-s${scenario + 1}-a${definition.arms.indexOf(arm) + 1}`,
141
+ scenario: id,
142
+ arm,
143
+ repetition
144
+ })))));
145
+ }
146
+ /**
147
+ * Validates grading coverage and request room against the declared study inputs.
148
+ * @param definition - The population declaration
149
+ * @param scenarios - The complete scenario declarations
150
+ * @param rubric - The independently held grading declaration
151
+ * @param profile - The execution bounds and model room
152
+ * @returns Nothing when every cross-input invariant holds
153
+ * @throws StudyError - Thrown when any input or cross-reference is invalid, or a quality study has no capacity qualification
154
+ * @example
155
+ * ```ts
156
+ * validateStudyInputs(definition, scenarios, rubric, profile)
157
+ * ```
158
+ */
159
+ function validateStudyInputs(definition, scenarios, rubric, profile) {
160
+ buildStudySchedule(definition, scenarios);
161
+ if (!isStudyRubric(rubric) || !isStudyProfile(profile)) throw new StudyError("CONFIG", "Invalid rubric or profile");
162
+ if (profile.bounds.request.tokens > profile.room.context - profile.room.safety || profile.room.output > profile.bounds.request.tokens) throw new StudyError("CONFIG", "Request allowance does not fit the model room");
163
+ for (const criterion of rubric.criteria) {
164
+ const scenario = scenarios.find((entry) => entry.id === criterion.scenario);
165
+ if (scenario === void 0 || !scenario.goals.some((goal) => goal.id === criterion.goal)) throw new StudyError("CONFIG", "Criterion references an undeclared goal");
166
+ if (definition.purpose === "mechanism" && criterion.method === "semantic") throw new StudyError("CONFIG", "Mechanism studies cannot establish semantic quality");
167
+ }
168
+ const covered = new Set(rubric.criteria.map((criterion) => (0, _orkestrel_contract.canonicalStringify)([criterion.scenario, criterion.goal]) ?? ""));
169
+ for (const scenario of scenarios) for (const goal of scenario.goals) if (!covered.has((0, _orkestrel_contract.canonicalStringify)([scenario.id, goal.id]) ?? "")) throw new StudyError("CONFIG", "Every declared goal needs a criterion");
170
+ if (definition.purpose === "quality" && profile.qualification === void 0) throw new StudyError("CONFIG", "Quality studies require a capacity qualification");
171
+ }
172
+ /**
173
+ * Computes arm intervals over the complete declared criterion horizon.
174
+ * @param manifest - The sealed population and schedule
175
+ * @param rubric - The criterion declaration
176
+ * @param grading - Judgments bound to the complete scheduled attempt set
177
+ * @returns Whole-horizon scores in declared arm order
178
+ * @throws StudyError - Thrown when schedule, attempts or judgments are incompatible
179
+ * @example
180
+ * ```ts
181
+ * computeStudyScores(manifest, rubric, grading)
182
+ * ```
183
+ */
184
+ function computeStudyScores(manifest, rubric, grading) {
185
+ if (!isStudyRubric(rubric) || !isStudyGrading(grading)) throw new StudyError("EVIDENCE", "Invalid rubric or grading");
186
+ const expected = buildStudySchedule(manifest.definition, manifest.scenarios);
187
+ if ((0, _orkestrel_contract.canonicalStringify)(expected) !== (0, _orkestrel_contract.canonicalStringify)(manifest.schedule)) throw new StudyError("EVIDENCE", "Manifest schedule differs from the declared population");
188
+ const trials = new Map(expected.map((trial) => [trial.id, trial]));
189
+ if (grading.attempts.length !== expected.length || !grading.attempts.every((id) => trials.has(id))) throw new StudyError("EVIDENCE", "Grading attempts differ from the declared schedule");
190
+ const criteria = new Map(rubric.criteria.map((criterion) => [criterion.id, criterion]));
191
+ for (const scenario of manifest.scenarios) for (const goal of scenario.goals) if (!rubric.criteria.some((criterion) => criterion.scenario === scenario.id && criterion.goal === goal.id)) throw new StudyError("EVIDENCE", "Rubric omits a declared goal");
192
+ for (const criterion of rubric.criteria) if (!manifest.scenarios.some((scenario) => scenario.id === criterion.scenario && scenario.goals.some((goal) => goal.id === criterion.goal)) || manifest.definition.purpose === "mechanism" && criterion.method === "semantic") throw new StudyError("EVIDENCE", "Criterion is incompatible with the study");
193
+ const keyed = grading.judgments.map((judgment) => ({
194
+ judgment,
195
+ key: (0, _orkestrel_contract.canonicalStringify)([judgment.trial, judgment.criterion]) ?? ""
196
+ }));
197
+ const firsts = new Map([...keyed.entries()].reverse().map(([index, { key }]) => [key, index]));
198
+ for (const [index, { judgment, key }] of keyed.entries()) {
199
+ const trial = trials.get(judgment.trial);
200
+ const criterion = criteria.get(judgment.criterion);
201
+ if (trial === void 0 || criterion === void 0 || criterion.scenario !== trial.scenario || firsts.get(key) !== index) throw new StudyError("EVIDENCE", "Duplicate or foreign trial criterion judgment");
202
+ if (criterion.method === "semantic" ? judgment.method !== "human" && judgment.method !== "model" : judgment.method !== criterion.method) throw new StudyError("EVIDENCE", "Judgment method differs from the criterion");
203
+ }
204
+ const judgments = new Map(keyed.map(({ key, judgment }) => [key, judgment]));
205
+ return Object.freeze(manifest.definition.arms.map((arm) => {
206
+ const pairs = expected.filter((trial) => trial.arm === arm).flatMap((trial) => rubric.criteria.filter((criterion) => criterion.scenario === trial.scenario).map((criterion) => ({
207
+ criterion,
208
+ judgment: judgments.get((0, _orkestrel_contract.canonicalStringify)([trial.id, criterion.id]) ?? "")
209
+ })));
210
+ const refuted = pairs.filter(({ judgment }) => judgment?.verdict === "unsuccessful");
211
+ const total = pairs.length;
212
+ const achieved = pairs.filter(({ judgment }) => judgment?.verdict === "achieved").length;
213
+ const unsuccessful = refuted.length;
214
+ const critical = refuted.filter(({ criterion }) => criterion.critical).length;
215
+ const unknown = total - achieved - unsuccessful;
216
+ return Object.freeze({
217
+ arm,
218
+ total,
219
+ achieved,
220
+ unsuccessful,
221
+ unknown,
222
+ critical,
223
+ lower: achieved / total,
224
+ upper: (achieved + unknown) / total,
225
+ coverage: (achieved + unsuccessful) / total
226
+ });
227
+ }));
228
+ }
229
+ //#endregion
230
+ //#region src/core/validators.ts
231
+ /** Checks whether a value names a measured request role. */
232
+ var isStudyRole = (0, _orkestrel_contract.literalOf)(STUDY_ROLES);
233
+ /** Checks whether a value names an operational ending. */
234
+ var isStudyCause = (0, _orkestrel_contract.literalOf)(STUDY_CAUSES);
235
+ /** Checks whether a value names a ceiling a run can stop at. */
236
+ var isStudyLayer = (0, _orkestrel_contract.literalOf)(STUDY_LAYERS);
237
+ /**
238
+ * Checks whether a value is the exact record of a ceiling that stopped a run.
239
+ * @remarks
240
+ * The guard accepts exact own data keys: `layer` and `id` are required and `max` and `consumed` are
241
+ * optional. `max` and `consumed` are each undefined or a finite number. Finite foreign readings,
242
+ * including negative and fractional values, retain the source's measurement unchanged.
243
+ * @param value - The candidate stop
244
+ * @returns True if the layer is known, the id is nonempty, and every reading is absent or finite; false otherwise
245
+ * @example
246
+ * ```ts
247
+ * isStudyStop({ layer: 'scope', id: 'Goal tokens', max: 100, consumed: 120 }) // true
248
+ * ```
249
+ */
250
+ function isStudyStop(value) {
251
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["layer", "id"], ["max", "consumed"]) && isStudyLayer(value.layer) && (0, _orkestrel_contract.isNonEmptyString)(value.id) && ["max", "consumed"].every((key) => value[key] === void 0 || (0, _orkestrel_contract.isFiniteNumber)(value[key])));
252
+ }
253
+ /**
254
+ * Checks whether an identifier is portable and bounded.
255
+ * @param value - The candidate identifier
256
+ * @returns True if it is a portable study identifier; false otherwise
257
+ * @example
258
+ * ```ts
259
+ * isStudyName('memory') // true
260
+ * ```
261
+ */
262
+ function isStudyName(value) {
263
+ return typeof value === "string" && value.length <= 128 && STUDY_NAME.test(value);
264
+ }
265
+ /**
266
+ * Checks whether all allowances are positive safe integers within timer bounds.
267
+ * @param value - The candidate limit
268
+ * @returns True if it defines finite positive allowances; false otherwise
269
+ * @example
270
+ * ```ts
271
+ * isStudyLimit({ duration: 1000, calls: 1, tokens: 100, bytes: 1024 }) // true
272
+ * ```
273
+ */
274
+ function isStudyLimit(value) {
275
+ return (0, _orkestrel_contract.holds)(() => {
276
+ if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
277
+ "duration",
278
+ "calls",
279
+ "tokens",
280
+ "bytes"
281
+ ])) return false;
282
+ return [
283
+ "duration",
284
+ "calls",
285
+ "tokens",
286
+ "bytes"
287
+ ].every((key) => typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] > 0) && typeof value.duration === "number" && value.duration <= _orkestrel_timeout.MAX_TIMEOUT_MS;
288
+ });
289
+ }
290
+ /**
291
+ * Checks whether nested scopes admit no larger allowance than their parent.
292
+ * @param value - The candidate bounds
293
+ * @returns True if every nested allowance is ordered; false otherwise
294
+ * @example
295
+ * ```ts
296
+ * isStudyBounds({}) // false
297
+ * ```
298
+ */
299
+ function isStudyBounds(value) {
300
+ return (0, _orkestrel_contract.holds)(() => {
301
+ if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
302
+ "request",
303
+ "goal",
304
+ "trial",
305
+ "series"
306
+ ]) || !isStudyLimit(value.request) || !isStudyLimit(value.goal) || !isStudyLimit(value.trial) || !isStudyLimit(value.series)) return false;
307
+ const limits = [
308
+ value.request,
309
+ value.goal,
310
+ value.trial,
311
+ value.series
312
+ ];
313
+ for (const [index, limit] of limits.entries()) {
314
+ const next = limits[index + 1];
315
+ if (next === void 0) continue;
316
+ if (limit.duration > next.duration || limit.calls > next.calls || limit.tokens > next.tokens || limit.bytes > next.bytes) return false;
317
+ }
318
+ return value.request.calls === 1;
319
+ });
320
+ }
321
+ /**
322
+ * Checks whether a goal has a bounded identifier and exact JSON input.
323
+ * @param value - The candidate goal
324
+ * @returns True if the goal is valid; false otherwise
325
+ * @example
326
+ * ```ts
327
+ * isStudyGoal({ id: 'write', input: { text: 'hello' } }) // true
328
+ * ```
329
+ */
330
+ function isStudyGoal(value) {
331
+ return (0, _orkestrel_contract.holds)(() => {
332
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["id", "input"]) && isStudyName(value.id) && (0, _orkestrel_contract.isJSONValue)(value.input);
333
+ });
334
+ }
335
+ /**
336
+ * Checks whether a scenario declares a nonempty horizon with unique goal ids.
337
+ * @param value - The candidate scenario
338
+ * @returns True if the scenario has a valid horizon; false otherwise
339
+ * @example
340
+ * ```ts
341
+ * isStudyScenario({ id: 'shift', goals: [{ id: 'handoff', input: 'Prepare handoff' }] }) // true
342
+ * ```
343
+ */
344
+ function isStudyScenario(value) {
345
+ return (0, _orkestrel_contract.holds)(() => {
346
+ if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, ["id", "goals"]) || !isStudyName(value.id) || !(0, _orkestrel_contract.isArray)(value.goals)) return false;
347
+ return value.goals.length > 0 && value.goals.length <= 4096 && value.goals.every(isStudyGoal) && new Set(value.goals.map((goal) => goal.id)).size === value.goals.length && (0, _orkestrel_contract.isJSONValue)(value.goals);
348
+ });
349
+ }
350
+ /**
351
+ * Checks whether a study freezes a complete arm order for every repetition.
352
+ * @param value - The candidate definition
353
+ * @returns True if its population and repetition orders are valid; false otherwise
354
+ * @example
355
+ * ```ts
356
+ * isStudyDefinition({ name: 'tools' }) // false
357
+ * ```
358
+ */
359
+ function isStudyDefinition(value) {
360
+ return (0, _orkestrel_contract.holds)(() => {
361
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.isJSONValue)(value) || !matchesStudyKeys(value, [
362
+ "name",
363
+ "version",
364
+ "question",
365
+ "purpose",
366
+ "driver",
367
+ "arms",
368
+ "scenarios",
369
+ "order",
370
+ "retries",
371
+ "rubric"
372
+ ]) || !isStudyName(value.name) || !isStudyName(value.driver) || !(0, _orkestrel_contract.isNonEmptyString)(value.version) || !(0, _orkestrel_contract.isNonEmptyString)(value.question) || value.purpose !== "mechanism" && value.purpose !== "quality" && value.purpose !== "capacity" || !(0, _orkestrel_contract.isArray)(value.arms) || value.arms.length === 0 || !value.arms.every(isStudyName) || new Set(value.arms).size !== value.arms.length || !(0, _orkestrel_contract.isArray)(value.scenarios) || value.scenarios.length === 0 || !value.scenarios.every(isStudyName) || new Set(value.scenarios).size !== value.scenarios.length || !(0, _orkestrel_contract.isArray)(value.order) || !(0, _orkestrel_contract.isJSONValue)(value.order) || value.order.length === 0 || value.retries !== 0 || !(0, _orkestrel_contract.isNonEmptyString)(value.rubric)) return false;
373
+ const arms = value.arms;
374
+ if (arms.length * value.scenarios.length * value.order.length > 4096) return false;
375
+ return value.order.every((order) => (0, _orkestrel_contract.isArray)(order) && order.length === arms.length && new Set(order).size === arms.length && order.every((arm) => typeof arm === "string" && arms.includes(arm)));
376
+ });
377
+ }
378
+ /**
379
+ * Checks whether generation room leaves positive context for input.
380
+ * @param value - The candidate room
381
+ * @returns True if input, output and safety allowances fit and any declared recap fits; false otherwise
382
+ * @example
383
+ * ```ts
384
+ * isStudyRoom({ context: 20480, output: 8192, safety: 512, recap: 2048, think: true }) // true
385
+ * isStudyRoom({ context: 20480, output: 8192, safety: 512, think: true }) // true
386
+ * ```
387
+ */
388
+ function isStudyRoom(value) {
389
+ return (0, _orkestrel_contract.holds)(() => {
390
+ if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
391
+ "context",
392
+ "output",
393
+ "safety",
394
+ "think"
395
+ ], ["recap"]) || typeof value.think !== "boolean") return false;
396
+ if (![
397
+ "context",
398
+ "output",
399
+ "safety",
400
+ ...value.recap === void 0 ? [] : ["recap"]
401
+ ].every((key) => typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] > 0)) return false;
402
+ return typeof value.context === "number" && typeof value.output === "number" && typeof value.safety === "number" && value.output + value.safety < value.context && (value.recap === void 0 || typeof value.recap === "number" && value.recap + value.safety < value.context);
403
+ });
404
+ }
405
+ /**
406
+ * Checks whether a provider declaration names artifacts, an HTTP origin and models.
407
+ * @param value - The candidate provider declaration
408
+ * @returns True if the declaration has a supported origin, nonempty names, and a judge declared with its digest or not at all; false otherwise
409
+ * @example
410
+ * ```ts
411
+ * isStudyProvider({ entry: 'dist/index.js' }) // false
412
+ * ```
413
+ */
414
+ function isStudyProvider(value) {
415
+ return (0, _orkestrel_contract.holds)(() => {
416
+ if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
417
+ "entry",
418
+ "url",
419
+ "model",
420
+ "digest"
421
+ ], ["judge"]) || ![
422
+ "entry",
423
+ "url",
424
+ "model"
425
+ ].every((key) => (0, _orkestrel_contract.isNonEmptyString)(value[key])) || typeof value.url !== "string" || !(0, _orkestrel_contract.isRecord)(value.digest) || !matchesStudyKeys(value.digest, ["model"], ["judge"]) || !isStudyDigest(value.digest.model) || value.judge === void 0 !== (value.digest.judge === void 0) || value.judge !== void 0 && (!(0, _orkestrel_contract.isNonEmptyString)(value.judge) || !isStudyDigest(value.digest.judge))) return false;
426
+ const url = new URL(value.url);
427
+ return ["http:", "https:"].includes(url.protocol) && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && url.pathname === "/";
428
+ });
429
+ }
430
+ /**
431
+ * Checks whether a profile carries finite bounds and an optional explicit provider.
432
+ * @param value - The candidate profile
433
+ * @returns True if the profile is structurally valid; false otherwise
434
+ * @example
435
+ * ```ts
436
+ * isStudyProfile({ name: 'local' }) // false
437
+ * ```
438
+ */
439
+ function isStudyProfile(value) {
440
+ return (0, _orkestrel_contract.holds)(() => {
441
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
442
+ "name",
443
+ "agent",
444
+ "subject",
445
+ "roots",
446
+ "bounds",
447
+ "room"
448
+ ], [
449
+ "provider",
450
+ "qualification",
451
+ "settings"
452
+ ]) && isStudyName(value.name) && (0, _orkestrel_contract.isNonEmptyString)(value.agent) && (0, _orkestrel_contract.isArray)(value.roots) && (0, _orkestrel_contract.isJSONValue)(value.roots) && value.roots.length > 0 && value.roots.every((root) => typeof root === "string" && root.trim().length > 0) && new Set(value.roots).size === value.roots.length && (0, _orkestrel_contract.isNonEmptyString)(value.subject) && isStudyBounds(value.bounds) && isStudyRoom(value.room) && (value.provider === void 0 || isStudyProvider(value.provider)) && (value.qualification === void 0 || (0, _orkestrel_contract.isNonEmptyString)(value.qualification)) && (value.settings === void 0 || (0, _orkestrel_contract.isRecord)(value.settings) && (0, _orkestrel_contract.isJSONValue)(value.settings));
453
+ });
454
+ }
455
+ /**
456
+ * Checks whether a byte identity is a lowercase SHA-256 digest.
457
+ * @param value - The candidate identity
458
+ * @returns True if it has exactly 64 hexadecimal digits; false otherwise
459
+ * @example
460
+ * ```ts
461
+ * isStudyDigest('a'.repeat(64)) // true
462
+ * ```
463
+ */
464
+ function isStudyDigest(value) {
465
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
466
+ }
467
+ /**
468
+ * Checks whether a timestamp is a canonical UTC millisecond instant.
469
+ * @param value - The candidate timestamp
470
+ * @returns True if it is a valid canonical timestamp; false otherwise
471
+ * @example
472
+ * ```ts
473
+ * isStudyTimestamp('2026-10-14T12:00:00.000Z') // true
474
+ * ```
475
+ */
476
+ function isStudyTimestamp(value) {
477
+ return (0, _orkestrel_contract.holds)(() => {
478
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && new Date(value).toISOString() === value;
479
+ });
480
+ }
481
+ /**
482
+ * Checks whether request usage contains nonnegative safe measurements.
483
+ * @param value - The candidate usage
484
+ * @returns True if the role and every known measurement are valid; false otherwise
485
+ * @example
486
+ * ```ts
487
+ * isStudyUsage({ role: 'tool', calls: 0, bytes: 0, duration: 0 }) // true
488
+ * ```
489
+ */
490
+ function isStudyUsage(value) {
491
+ return (0, _orkestrel_contract.holds)(() => {
492
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
493
+ "role",
494
+ "calls",
495
+ "duration"
496
+ ], ["tokens", "bytes"]) && isStudyRole(value.role) && ["calls", "duration"].every((key) => typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] >= 0) && (value.bytes === void 0 || typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes >= 0) && (value.tokens === void 0 || typeof value.tokens === "number" && Number.isSafeInteger(value.tokens) && value.tokens >= 0);
497
+ });
498
+ }
499
+ /**
500
+ * Checks a retained public product without awarding task achievement.
501
+ * @param value - The candidate product
502
+ * @returns True if the product carries JSON output and partial status; false otherwise
503
+ * @example
504
+ * ```ts
505
+ * isStudyProduct({ value: { answer: 1 }, partial: true }) // true
506
+ * ```
507
+ */
508
+ function isStudyProduct(value) {
509
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && typeof value.partial === "boolean" && (0, _orkestrel_contract.isJSONValue)(value.value));
510
+ }
511
+ /**
512
+ * Checks whether a criterion declares exact grading inputs and references.
513
+ * @param value - The candidate criterion
514
+ * @returns True if its structural grading declaration is valid; false otherwise
515
+ * @example
516
+ * ```ts
517
+ * isStudyCriterion({ id: 'saved', scenario: 'shift', goal: 'write', method: 'state', critical: true, assertion: true }) // true
518
+ * ```
519
+ */
520
+ function isStudyCriterion(value) {
521
+ return (0, _orkestrel_contract.holds)(() => {
522
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
523
+ "id",
524
+ "scenario",
525
+ "goal",
526
+ "method",
527
+ "critical",
528
+ "assertion"
529
+ ]) && isStudyName(value.id) && isStudyName(value.scenario) && isStudyName(value.goal) && (value.method === "state" || value.method === "trace" || value.method === "semantic") && typeof value.critical === "boolean" && (0, _orkestrel_contract.isJSONValue)(value.assertion);
530
+ });
531
+ }
532
+ /**
533
+ * Checks whether a rubric declares nonempty criteria with unique identities.
534
+ * @param value - The candidate rubric
535
+ * @returns True if its version and criteria are valid; false otherwise
536
+ * @example
537
+ * ```ts
538
+ * isStudyRubric({ version: '1', criteria: [] }) // false
539
+ * ```
540
+ */
541
+ function isStudyRubric(value) {
542
+ return (0, _orkestrel_contract.holds)(() => {
543
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["version", "criteria"]) && typeof value.version === "string" && value.version.trim().length > 0 && (0, _orkestrel_contract.isArray)(value.criteria) && value.criteria.length > 0 && value.criteria.every(isStudyCriterion) && new Set(value.criteria.map((criterion) => criterion.id)).size === value.criteria.length && (0, _orkestrel_contract.isJSONValue)(value.criteria);
544
+ });
545
+ }
546
+ /**
547
+ * Checks whether a judgment carries a supported verdict and evidence references.
548
+ * @param value - The candidate judgment
549
+ * @returns True if the judgment is attributable and structurally valid; false otherwise
550
+ * @example
551
+ * ```ts
552
+ * isStudyJudgment({ trial: 'r1-s1-a1', criterion: 'saved', verdict: 'unknown', method: 'state', reason: 'No evidence', references: [] }) // true
553
+ * ```
554
+ */
555
+ function isStudyJudgment(value) {
556
+ return (0, _orkestrel_contract.holds)(() => {
557
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
558
+ "trial",
559
+ "criterion",
560
+ "verdict",
561
+ "method",
562
+ "reason",
563
+ "references"
564
+ ]) && isStudyName(value.trial) && isStudyName(value.criterion) && (value.verdict === "achieved" || value.verdict === "unsuccessful" || value.verdict === "unknown") && (value.method === "state" || value.method === "trace" || value.method === "human" || value.method === "model") && typeof value.reason === "string" && value.reason.trim().length > 0 && (0, _orkestrel_contract.isArray)(value.references) && (0, _orkestrel_contract.isJSONValue)(value.references) && value.references.every((reference) => typeof reference === "string" && reference.trim().length > 0) && new Set(value.references).size === value.references.length && (value.verdict === "unknown" || value.references.length > 0);
565
+ });
566
+ }
567
+ /**
568
+ * Checks whether imported semantic labels have accountable packet provenance.
569
+ * @param value - The candidate labels
570
+ * @returns True if provenance, usage and semantic judgments are valid; false otherwise
571
+ * @example
572
+ * ```ts
573
+ * isStudyLabeling({}) // false
574
+ * ```
575
+ */
576
+ function isStudyLabeling(value) {
577
+ return (0, _orkestrel_contract.holds)(() => {
578
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
579
+ "packet",
580
+ "labeler",
581
+ "version",
582
+ "created",
583
+ "usage",
584
+ "judgments"
585
+ ]) && isStudyDigest(value.packet) && typeof value.labeler === "string" && value.labeler.trim().length > 0 && typeof value.version === "string" && value.version.trim().length > 0 && isStudyTimestamp(value.created) && (0, _orkestrel_contract.isArray)(value.usage) && Reflect.ownKeys(value.usage).length === value.usage.length + 1 && Array.from(value.usage).every(isStudyUsage) && (0, _orkestrel_contract.isArray)(value.judgments) && value.judgments.every(isStudyJudgment) && value.judgments.every((judgment) => judgment.method === "human" || judgment.method === "model") && new Set(value.judgments.map((judgment) => (0, _orkestrel_contract.canonicalStringify)([judgment.trial, judgment.criterion]))).size === value.judgments.length && (0, _orkestrel_contract.isJSONValue)(value.judgments);
586
+ });
587
+ }
588
+ /**
589
+ * Checks whether a value names a supported study error code.
590
+ * @param value - The candidate code
591
+ * @returns True if it is a declared study error code; false otherwise
592
+ * @example
593
+ * ```ts
594
+ * isStudyErrorCode('CONFIG') // true
595
+ * ```
596
+ */
597
+ function isStudyErrorCode(value) {
598
+ return value === "CONFIG" || value === "EVIDENCE" || value === "CUSTODY" || value === "CAPACITY" || value === "FAULT" || value === "DEADLINE";
599
+ }
600
+ /**
601
+ * Checks whether grading binds accountable judgments to unique attempt identities.
602
+ * @param value - The candidate grading
603
+ * @returns True if its identities, attempts and judgments are valid; false otherwise
604
+ * @example
605
+ * ```ts
606
+ * isStudyGrading({}) // false
607
+ * ```
608
+ */
609
+ function isStudyGrading(value) {
610
+ return (0, _orkestrel_contract.holds)(() => {
611
+ return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
612
+ "result",
613
+ "rubric",
614
+ "grader",
615
+ "instrument",
616
+ "attempts",
617
+ "judgments"
618
+ ], ["labels", "packet"]) && isStudyDigest(value.result) && isStudyDigest(value.rubric) && isStudyDigest(value.instrument) && typeof value.grader === "string" && value.grader.trim().length > 0 && (value.labels === void 0 || isStudyDigest(value.labels)) && (value.packet === void 0 || isStudyDigest(value.packet)) && (0, _orkestrel_contract.isArray)(value.attempts) && (0, _orkestrel_contract.isJSONValue)(value.attempts) && value.attempts.every(isStudyName) && new Set(value.attempts).size === value.attempts.length && (0, _orkestrel_contract.isArray)(value.judgments) && value.judgments.every(isStudyJudgment) && new Set(value.judgments.map((judgment) => (0, _orkestrel_contract.canonicalStringify)([judgment.trial, judgment.criterion]))).size === value.judgments.length && (0, _orkestrel_contract.isJSONValue)(value.judgments);
619
+ });
620
+ }
621
+ //#endregion
622
+ //#region src/core/errors.ts
623
+ /** Reports a study contract or operational failure with a stable code. */
624
+ var StudyError = class extends Error {
625
+ code;
626
+ failure;
627
+ /**
628
+ * Records the failing boundary and optional operational attribution.
629
+ * @param code - The machine-readable boundary
630
+ * @param message - The specific failure
631
+ * @param failure - The optional operational reading
632
+ */
633
+ constructor(code, message, failure) {
634
+ super(message);
635
+ _orkestrel_contract.INTRINSICS.define(this, STUDY_ERROR_BRAND, {
636
+ value: this,
637
+ enumerable: false,
638
+ writable: false,
639
+ configurable: true
640
+ });
641
+ this.name = "StudyError";
642
+ this.code = code;
643
+ this.failure = failure;
644
+ }
645
+ };
646
+ /**
647
+ * Narrows a caught value to a branded study error with valid failure attribution.
648
+ * @remarks
649
+ * Recognition combines the native `Error` base, a subclass prototype, a declared study code,
650
+ * valid optional failure members, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is
651
+ * the error itself. Subclasses can replace the name.
652
+ * The global key is recognized across package, ESM, CommonJS and captured module copies; it is
653
+ * not unforgeable provenance. A transparent proxy is refused because its forwarded descriptor
654
+ * stores the target, and an unbranded copy, an inherited brand, or a property-only lookalike
655
+ * stays outside the type. Hostile property access returns false instead of throwing.
656
+ * @param value - The caught value
657
+ * @returns True if the value is a study error; false otherwise
658
+ * @example
659
+ * ```ts
660
+ * isStudyError(new StudyError('CONFIG', 'missing definition')) // true
661
+ * ```
662
+ */
663
+ function isStudyError(value) {
664
+ return (0, _orkestrel_contract.holds)(() => {
665
+ if (!(0, _orkestrel_contract.isError)(value) || _orkestrel_contract.INTRINSICS.prototype(value) === _orkestrel_contract.INTRINSICS.error.prototype) return false;
666
+ const brand = _orkestrel_contract.INTRINSICS.describe(value, STUDY_ERROR_BRAND)?.value;
667
+ const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
668
+ const failure = _orkestrel_contract.INTRINSICS.reflect.read(value, "failure");
669
+ if (brand !== value || !isStudyErrorCode(code) || typeof _orkestrel_contract.INTRINSICS.reflect.read(value, "name") !== "string" || typeof _orkestrel_contract.INTRINSICS.reflect.read(value, "message") !== "string") return false;
670
+ if (failure === void 0) return true;
671
+ if (!(0, _orkestrel_contract.isObject)(failure)) return false;
672
+ const cause = _orkestrel_contract.INTRINSICS.reflect.read(failure, "cause");
673
+ const message = _orkestrel_contract.INTRINSICS.reflect.read(failure, "message");
674
+ const role = _orkestrel_contract.INTRINSICS.reflect.read(failure, "role");
675
+ const stop = _orkestrel_contract.INTRINSICS.reflect.read(failure, "stop");
676
+ if (!isStudyCause(cause) || typeof message !== "string" || role !== void 0 && !isStudyRole(role)) return false;
677
+ if (stop === void 0) return true;
678
+ if (!(0, _orkestrel_contract.isObject)(stop)) return false;
679
+ const layer = _orkestrel_contract.INTRINSICS.reflect.read(stop, "layer");
680
+ const id = _orkestrel_contract.INTRINSICS.reflect.read(stop, "id");
681
+ const max = _orkestrel_contract.INTRINSICS.reflect.read(stop, "max");
682
+ const consumed = _orkestrel_contract.INTRINSICS.reflect.read(stop, "consumed");
683
+ return isStudyLayer(layer) && typeof id === "string" && (max === void 0 || (0, _orkestrel_contract.isFiniteNumber)(max)) && (consumed === void 0 || (0, _orkestrel_contract.isFiniteNumber)(consumed));
684
+ });
685
+ }
686
+ //#endregion
687
+ //#region src/core/parsers.ts
688
+ /**
689
+ * Parses a foreign reading into a number a stop can retain.
690
+ * @param value - The unknown reading a source reports
691
+ * @returns The value when it is a finite number; undefined otherwise
692
+ * @example
693
+ * ```ts
694
+ * parseStudyReading(2048) // 2048
695
+ * parseStudyReading(Number.NaN) // undefined
696
+ * ```
697
+ */
698
+ function parseStudyReading(value) {
699
+ return (0, _orkestrel_contract.isFiniteNumber)(value) ? value : void 0;
700
+ }
701
+ /**
702
+ * Parses a definition into an independent frozen validated snapshot.
703
+ * @param value - The unknown configuration
704
+ * @returns The owned study definition
705
+ * @throws StudyError - Thrown when configuration is invalid or cannot be owned
706
+ * @example
707
+ * ```ts
708
+ * parseStudyDefinition(configuration)
709
+ * ```
710
+ */
711
+ function parseStudyDefinition(value) {
712
+ try {
713
+ if (!isStudyDefinition(value)) throw new StudyError("CONFIG", "Invalid study definition");
714
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
715
+ if (isStudyDefinition(snapshot)) return snapshot;
716
+ } catch {
717
+ throw new StudyError("CONFIG", "Invalid or unreadable study definition");
718
+ }
719
+ throw new StudyError("CONFIG", "Invalid study definition snapshot");
720
+ }
721
+ /**
722
+ * Parses a scenario into an independent frozen validated snapshot.
723
+ * @param value - The unknown configuration
724
+ * @returns The owned scenario and goal inputs
725
+ * @throws StudyError - Thrown when configuration is invalid or cannot be owned
726
+ * @example
727
+ * ```ts
728
+ * parseStudyScenario({ id: 'shift', goals: [{ id: 'write', input: {} }] })
729
+ * ```
730
+ */
731
+ function parseStudyScenario(value) {
732
+ try {
733
+ if (!isStudyScenario(value)) throw new StudyError("CONFIG", "Invalid study scenario");
734
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
735
+ if (isStudyScenario(snapshot)) return snapshot;
736
+ } catch {
737
+ throw new StudyError("CONFIG", "Invalid or unreadable study scenario");
738
+ }
739
+ throw new StudyError("CONFIG", "Invalid study scenario snapshot");
740
+ }
741
+ /**
742
+ * Parses a profile into an independent frozen validated snapshot.
743
+ * @param value - The unknown configuration
744
+ * @returns The owned profile with absent optional settings preserved
745
+ * @throws StudyError - Thrown when configuration is invalid or cannot be owned
746
+ * @example
747
+ * ```ts
748
+ * parseStudyProfile(configuration)
749
+ * ```
750
+ */
751
+ function parseStudyProfile(value) {
752
+ try {
753
+ if (!isStudyProfile(value)) throw new StudyError("CONFIG", "Invalid study profile");
754
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)(normalizeStudyProfile(value));
755
+ if (isStudyProfile(snapshot)) return snapshot;
756
+ } catch {
757
+ throw new StudyError("CONFIG", "Invalid or unreadable study profile");
758
+ }
759
+ throw new StudyError("CONFIG", "Invalid study profile snapshot");
760
+ }
761
+ /**
762
+ * Parses a rubric into an independent frozen validated snapshot.
763
+ * @param value - The unknown grading declaration
764
+ * @returns The owned rubric and assertion inputs
765
+ * @throws StudyError - Thrown when configuration is invalid or cannot be owned
766
+ * @example
767
+ * ```ts
768
+ * parseStudyRubric(configuration)
769
+ * ```
770
+ */
771
+ function parseStudyRubric(value) {
772
+ try {
773
+ if (!isStudyRubric(value)) throw new StudyError("CONFIG", "Invalid study rubric");
774
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
775
+ if (isStudyRubric(snapshot)) return snapshot;
776
+ } catch {
777
+ throw new StudyError("CONFIG", "Invalid or unreadable study rubric");
778
+ }
779
+ throw new StudyError("CONFIG", "Invalid study rubric snapshot");
780
+ }
781
+ /**
782
+ * Parses grading into an independent frozen validated evidence snapshot.
783
+ * @param value - The unknown grading evidence
784
+ * @returns The owned grading identity and judgments
785
+ * @throws StudyError - Thrown when evidence is invalid or cannot be owned
786
+ * @example
787
+ * ```ts
788
+ * parseStudyGrading(evidence)
789
+ * ```
790
+ */
791
+ function parseStudyGrading(value) {
792
+ try {
793
+ if (!isStudyGrading(value)) throw new StudyError("EVIDENCE", "Invalid study grading");
794
+ const { labels, packet, ...required } = value;
795
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)({
796
+ ...required,
797
+ ...labels === void 0 ? {} : { labels },
798
+ ...packet === void 0 ? {} : { packet }
799
+ });
800
+ if (isStudyGrading(snapshot)) return snapshot;
801
+ } catch {
802
+ throw new StudyError("EVIDENCE", "Invalid or unreadable study grading");
803
+ }
804
+ throw new StudyError("EVIDENCE", "Invalid study grading snapshot");
805
+ }
806
+ /**
807
+ * Parses semantic labels into an independent frozen validated evidence snapshot.
808
+ * @param value - The unknown imported labels
809
+ * @returns The owned packet provenance, usage and judgments
810
+ * @throws StudyError - Thrown when evidence is invalid or cannot be owned
811
+ * @example
812
+ * ```ts
813
+ * parseStudyLabeling(evidence)
814
+ * ```
815
+ */
816
+ function parseStudyLabeling(value) {
817
+ try {
818
+ if (!isStudyLabeling(value)) throw new StudyError("EVIDENCE", "Invalid study labels");
819
+ const snapshot = (0, _orkestrel_contract.cloneJSONValue)({
820
+ ...value,
821
+ usage: value.usage.map(({ tokens, bytes, ...usage }) => ({
822
+ ...usage,
823
+ ...tokens === void 0 ? {} : { tokens },
824
+ ...bytes === void 0 ? {} : { bytes }
825
+ }))
826
+ });
827
+ if (isStudyLabeling(snapshot)) return snapshot;
828
+ } catch {
829
+ throw new StudyError("EVIDENCE", "Invalid or unreadable study labels");
830
+ }
831
+ throw new StudyError("EVIDENCE", "Invalid study labels snapshot");
832
+ }
833
+ //#endregion
834
+ //#region src/core/engines/helpers.ts
835
+ /** Converts an operational reading to its throwable boundary.
836
+ * @param failure - The retained operational reading.
837
+ * @returns An error preserving the reading's cause and responsibility.
838
+ */
839
+ function failureToStudyError(failure) {
840
+ return new StudyError(failure.cause === "capacity" ? "CAPACITY" : failure.cause === "deadline" ? "DEADLINE" : "FAULT", failure.message, failure);
841
+ }
842
+ /** Reads an operational error without inventing a task verdict.
843
+ * @remarks
844
+ * A branded `BudgetError` from any copy of the budget package reads as a `budget` stop, because the
845
+ * error alone does not prove which budget object raised it; the Agent record names a `meter` stop.
846
+ * An unbranded lookalike reads as an infrastructure failure.
847
+ * @param error - The caught error.
848
+ * @returns Existing attribution, the capacity a branded budget error names, the cause a bare
849
+ * capacity or deadline code names, or an infrastructure failure.
850
+ */
851
+ function readStudyFailure(error) {
852
+ if (isStudyError(error)) {
853
+ const { failure, code, message } = error;
854
+ if (failure !== void 0) return failure;
855
+ if (code === "CAPACITY") return {
856
+ cause: "capacity",
857
+ message,
858
+ role: void 0,
859
+ stop: void 0
860
+ };
861
+ if (code === "DEADLINE") return {
862
+ cause: "deadline",
863
+ message,
864
+ role: void 0,
865
+ stop: void 0
866
+ };
867
+ }
868
+ if ((0, _orkestrel_budget.isBudgetError)(error)) {
869
+ const reading = (0, _orkestrel_contract.attempt)(() => {
870
+ const context = error.context;
871
+ return {
872
+ id: context.id,
873
+ max: context.max,
874
+ consumed: context.consumed,
875
+ message: error.message
876
+ };
877
+ });
878
+ if (reading.success) return {
879
+ cause: "capacity",
880
+ message: reading.value.message,
881
+ role: void 0,
882
+ stop: {
883
+ layer: "budget",
884
+ id: (0, _orkestrel_contract.isNonEmptyString)(reading.value.id) ? reading.value.id : "Agent budget",
885
+ max: parseStudyReading(reading.value.max),
886
+ consumed: parseStudyReading(reading.value.consumed)
887
+ }
888
+ };
889
+ }
890
+ return {
891
+ cause: "infrastructure",
892
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : "Study operation rejected without an Error",
893
+ role: void 0,
894
+ stop: void 0
895
+ };
896
+ }
897
+ /**
898
+ * Computes the stop of a request whose actual usage crossed the ceiling it was admitted under.
899
+ * @param charge - The request's admitted ceilings
900
+ * @param usage - The request's measured usage
901
+ * @returns The `scope` stop for the first quantity, tokens then bytes, whose known usage exceeds its admitted ceiling; undefined when none does
902
+ * @example
903
+ * ```ts
904
+ * computeStudyAdmittedStop({ role: 'agent', tokens: 10, bytes: 100 }, usage) // { layer: 'scope', id: 'Admitted tokens', max: 10, consumed: 12 }
905
+ * ```
906
+ */
907
+ function computeStudyAdmittedStop(charge, usage) {
908
+ if (usage.tokens !== void 0 && usage.tokens > charge.tokens) return {
909
+ layer: "scope",
910
+ id: "Admitted tokens",
911
+ max: charge.tokens,
912
+ consumed: usage.tokens
913
+ };
914
+ if (usage.bytes !== void 0 && usage.bytes > charge.bytes) return {
915
+ layer: "scope",
916
+ id: "Admitted bytes",
917
+ max: charge.bytes,
918
+ consumed: usage.bytes
919
+ };
920
+ }
921
+ /**
922
+ * Computes the stop of a product the output allowances cannot hold.
923
+ * @param bytes - The encoded size of the product's value
924
+ * @param limit - The goal's byte allowance
925
+ * @param outputs - The trial and series output budgets, in the order they refuse
926
+ * @returns The `Goal bytes` stop when the goal allowance refuses, else the stop of the first budget whose remaining room is below the product, with its tally including the refused product; undefined when every allowance holds it
927
+ * @example
928
+ * ```ts
929
+ * computeStudyOutputStop(2048, 1024, []) // { layer: 'scope', id: 'Goal bytes', max: 1024, consumed: 2048 }
930
+ * ```
931
+ */
932
+ function computeStudyOutputStop(bytes, limit, outputs) {
933
+ if (bytes > limit) return {
934
+ layer: "scope",
935
+ id: "Goal bytes",
936
+ max: limit,
937
+ consumed: bytes
938
+ };
939
+ const refusal = outputs.find((budget) => bytes > budget.remaining);
940
+ return refusal === void 0 ? void 0 : {
941
+ layer: "scope",
942
+ id: refusal.id,
943
+ max: refusal.max,
944
+ consumed: refusal.consumed + bytes
945
+ };
946
+ }
947
+ /**
948
+ * Records an error that escaped admitted work as the failure of its ticket.
949
+ * @param ticket - The admitted request whose work escaped
950
+ * @param error - The caught value, read through `readStudyFailure`
951
+ * @param role - The responsibility that replaces any role the reading carries; Default: the carried role, else the ticket's admitted role
952
+ * @remarks
953
+ * A ticket that already settled or whose signal aborted is left untouched, because its owner
954
+ * already holds the failure. A remote ticket's aborted signal does not mean it settled locally,
955
+ * so the parent's ownership is respected rather than inferred as local settlement. A refusal
956
+ * thrown by the ticket's own settlement propagates.
957
+ * @throws Thrown when the ticket's settlement refuses the failure.
958
+ * @example Fail an escaped request
959
+ * ```ts
960
+ * import type { StudyTicket } from '@orkestrel/study'
961
+ * import { failStudyTicket } from '@orkestrel/study'
962
+ *
963
+ * declare const ticket: StudyTicket
964
+ *
965
+ * failStudyTicket(ticket, new Error('Provider refused'), 'tool')
966
+ * ```
967
+ */
968
+ function failStudyTicket(ticket, error, role) {
969
+ if (ticket.settled || ticket.signal.aborted) return;
970
+ const failure = readStudyFailure(error);
971
+ ticket.fail(role === void 0 ? failure : {
972
+ ...failure,
973
+ role
974
+ });
975
+ }
976
+ //#endregion
977
+ //#region src/core/engines/StudyControl.ts
978
+ /** Retains the first operational failure while bounding asynchronous waits. */
979
+ var StudyControl = class {
980
+ #abort;
981
+ #listeners = new _orkestrel_abort.Abort();
982
+ #failure;
983
+ /** Links cancellation to the caller without assigning it a task verdict.
984
+ * @param signal - Optional parent cancellation.
985
+ */
986
+ constructor(signal) {
987
+ this.#abort = new _orkestrel_abort.Abort(signal === void 0 ? void 0 : { signal });
988
+ if (this.#abort.aborted) this.#interrupt();
989
+ else this.#abort.signal.addEventListener("abort", () => this.#interrupt(), {
990
+ once: true,
991
+ signal: this.#listeners.signal
992
+ });
993
+ }
994
+ get signal() {
995
+ return this.#abort.signal;
996
+ }
997
+ get failure() {
998
+ return this.#failure;
999
+ }
1000
+ /** Latches an operational failure and cancels all linked work.
1001
+ * @param failure - The first attribution to preserve.
1002
+ */
1003
+ fail(failure) {
1004
+ if (this.#failure !== void 0) return;
1005
+ this.#failure = Object.freeze({
1006
+ ...failure,
1007
+ stop: failure.stop === void 0 ? void 0 : Object.freeze({ ...failure.stop })
1008
+ });
1009
+ this.#abort.abort(failureToStudyError(this.#failure));
1010
+ }
1011
+ /** Races work against cancellation and removes its listener after either ending.
1012
+ * @param work - Already-started work whose rejection remains observed after cancellation.
1013
+ * @returns The work's result if it settles before cancellation.
1014
+ * @throws Thrown with the retained operational attribution when cancelled.
1015
+ */
1016
+ wait(work) {
1017
+ const listeners = new _orkestrel_abort.Abort();
1018
+ return new Promise((resolve, reject) => {
1019
+ Promise.resolve(work).then(resolve, reject);
1020
+ if (this.#abort.aborted) reject(this.#reason());
1021
+ else this.#abort.signal.addEventListener("abort", () => reject(this.#reason()), {
1022
+ once: true,
1023
+ signal: listeners.signal
1024
+ });
1025
+ }).finally(() => listeners.abort());
1026
+ }
1027
+ /** Removes the lifecycle listener without changing the retained failure. */
1028
+ close() {
1029
+ this.#listeners.abort();
1030
+ }
1031
+ #reason() {
1032
+ return failureToStudyError(this.#failure ?? {
1033
+ cause: "interrupted",
1034
+ message: "Study interrupted",
1035
+ role: void 0,
1036
+ stop: void 0
1037
+ });
1038
+ }
1039
+ #interrupt() {
1040
+ if (this.#failure === void 0) this.fail({
1041
+ cause: "interrupted",
1042
+ message: "Study interrupted by parent cancellation",
1043
+ role: void 0,
1044
+ stop: void 0
1045
+ });
1046
+ }
1047
+ };
1048
+ //#endregion
1049
+ //#region src/core/engines/StudyScope.ts
1050
+ /** Reserves finite request ceilings around cumulative actual budgets and one deadline. */
1051
+ var StudyScope = class {
1052
+ #options;
1053
+ #calls;
1054
+ #tokens;
1055
+ #bytes;
1056
+ #timeout;
1057
+ #listeners = new _orkestrel_abort.Abort();
1058
+ #pending = /* @__PURE__ */ new Map();
1059
+ #started = performance.now();
1060
+ #closed = false;
1061
+ /** Starts a timed scope with independent counters.
1062
+ * @param options - Finite limit, shared failure owner, and diagnostic label.
1063
+ */
1064
+ constructor(options) {
1065
+ if (!isStudyLimit(options.limit)) throw new StudyError("CONFIG", "Invalid study scope limit");
1066
+ this.#options = {
1067
+ ...options,
1068
+ limit: { ...options.limit }
1069
+ };
1070
+ this.#calls = new _orkestrel_budget.Budget({
1071
+ max: options.limit.calls,
1072
+ consumer: (value) => value
1073
+ });
1074
+ this.#tokens = new _orkestrel_budget.Budget({
1075
+ max: options.limit.tokens,
1076
+ consumer: (value) => value
1077
+ });
1078
+ this.#bytes = new _orkestrel_budget.Budget({
1079
+ max: options.limit.bytes,
1080
+ consumer: (value) => value
1081
+ });
1082
+ this.#timeout = new _orkestrel_timeout.Timeout({ ms: options.limit.duration });
1083
+ this.#timeout.signal.addEventListener("abort", () => this.#expire(), {
1084
+ once: true,
1085
+ signal: this.#listeners.signal
1086
+ });
1087
+ this.#timeout.start();
1088
+ }
1089
+ /** Checks cumulative consumption and every pending ceiling before admission.
1090
+ * @param charge - One prospective request's ceilings.
1091
+ * @throws Thrown when this scope cannot admit the request.
1092
+ */
1093
+ check(charge) {
1094
+ this.#checkTime();
1095
+ if (this.#closed) throw new StudyError("FAULT", "Study scope is closed");
1096
+ const prior = this.#options.control.failure;
1097
+ if (prior !== void 0) throw failureToStudyError(prior);
1098
+ if (charge === void 0) return;
1099
+ const pending = [...this.#pending.values()];
1100
+ const tokens = pending.reduce((total, entry) => total + entry.tokens, 0);
1101
+ const bytes = pending.reduce((total, entry) => total + entry.bytes, 0);
1102
+ const { label, limit } = this.#options;
1103
+ const refusal = [
1104
+ {
1105
+ id: `${label} calls`,
1106
+ refused: this.#calls.remaining - this.#pending.size < 1,
1107
+ max: limit.calls,
1108
+ consumed: this.#calls.consumed + this.#pending.size + 1
1109
+ },
1110
+ {
1111
+ id: `${label} tokens`,
1112
+ refused: this.#tokens.remaining - tokens < charge.tokens,
1113
+ max: limit.tokens,
1114
+ consumed: this.#tokens.consumed + tokens + charge.tokens
1115
+ },
1116
+ {
1117
+ id: `${label} bytes`,
1118
+ refused: this.#bytes.remaining - bytes < charge.bytes,
1119
+ max: limit.bytes,
1120
+ consumed: this.#bytes.consumed + bytes + charge.bytes
1121
+ }
1122
+ ].find((entry) => entry.refused);
1123
+ if (refusal !== void 0) {
1124
+ const failure = {
1125
+ cause: "capacity",
1126
+ message: `${label} allowance cannot admit the request`,
1127
+ role: charge.role,
1128
+ stop: {
1129
+ layer: "scope",
1130
+ id: refusal.id,
1131
+ max: refusal.max,
1132
+ consumed: refusal.consumed
1133
+ }
1134
+ };
1135
+ this.#options.control.fail(failure);
1136
+ throw failureToStudyError(failure);
1137
+ }
1138
+ }
1139
+ /** Reserves one request after the enclosing meter checked all scopes.
1140
+ * @param id - The request's unique reservation identity.
1141
+ * @param charge - Its admitted ceilings.
1142
+ */
1143
+ reserve(id, charge) {
1144
+ if (this.#pending.has(id)) throw new StudyError("FAULT", "Duplicate study reservation");
1145
+ this.#pending.set(id, { ...charge });
1146
+ }
1147
+ /** Commits known charges while retaining ceilings for missing measurements.
1148
+ * @param id - The pending reservation identity.
1149
+ * @param usage - The request's retained public measurements.
1150
+ */
1151
+ settle(id, usage) {
1152
+ const charge = this.#pending.get(id);
1153
+ if (charge === void 0) throw new StudyError("FAULT", "Study reservation is not pending");
1154
+ this.#pending.delete(id);
1155
+ this.#calls.consume(1);
1156
+ this.#tokens.consume(Math.min(charge.tokens, usage.tokens ?? charge.tokens));
1157
+ this.#bytes.consume(Math.min(charge.bytes, usage.bytes ?? charge.bytes));
1158
+ }
1159
+ /** Checks deadline attribution before clearing the timer. */
1160
+ close() {
1161
+ if (this.#closed) return;
1162
+ this.#checkTime();
1163
+ this.#closed = true;
1164
+ this.#listeners.abort();
1165
+ this.#timeout.clear();
1166
+ }
1167
+ #checkTime() {
1168
+ if (!this.#closed && (this.#timeout.expired || performance.now() - this.#started >= this.#options.limit.duration)) this.#expire();
1169
+ }
1170
+ #expire() {
1171
+ this.#options.control.fail({
1172
+ cause: "deadline",
1173
+ message: `${this.#options.label} deadline expired`,
1174
+ role: void 0,
1175
+ stop: {
1176
+ layer: "scope",
1177
+ id: `${this.#options.label} duration`,
1178
+ max: this.#options.limit.duration,
1179
+ consumed: Math.ceil(performance.now() - this.#started)
1180
+ }
1181
+ });
1182
+ }
1183
+ };
1184
+ //#endregion
1185
+ //#region src/core/engines/StudyDeadline.ts
1186
+ /** Bounds each independent operation with a fresh failure owner and deadline. */
1187
+ var StudyDeadline = class {
1188
+ #options;
1189
+ /** Retains the deadline without starting a timer.
1190
+ * @param options - Finite duration in milliseconds and diagnostic label.
1191
+ */
1192
+ constructor(options) {
1193
+ this.#options = { ...options };
1194
+ }
1195
+ /** Waits for work under a fresh failure owner and releases its timer and listener.
1196
+ * @param work - Already-started work whose late rejection remains observed.
1197
+ * @returns The work's result when it settles before the deadline.
1198
+ * @throws Thrown for an invalid duration, the work's own rejection, or the labelled deadline.
1199
+ */
1200
+ async wait(work) {
1201
+ const control = new StudyControl();
1202
+ const scope = this.#open(control, work);
1203
+ try {
1204
+ const result = await control.wait(work);
1205
+ scope.check();
1206
+ return result;
1207
+ } finally {
1208
+ scope.close();
1209
+ control.close();
1210
+ }
1211
+ }
1212
+ #open(control, work) {
1213
+ try {
1214
+ return new StudyScope({
1215
+ limit: {
1216
+ duration: this.#options.duration,
1217
+ calls: 1,
1218
+ tokens: 1,
1219
+ bytes: 1
1220
+ },
1221
+ control,
1222
+ label: this.#options.label
1223
+ });
1224
+ } catch (error) {
1225
+ Promise.resolve(work).catch(() => void 0);
1226
+ control.close();
1227
+ throw error;
1228
+ }
1229
+ }
1230
+ };
1231
+ //#endregion
1232
+ //#region src/core/engines/StudyRequest.ts
1233
+ /** Settles an admitted request once and propagates operational failure to its owner. */
1234
+ var StudyRequest = class {
1235
+ #options;
1236
+ #timeout;
1237
+ #signal;
1238
+ #listeners = new _orkestrel_abort.Abort();
1239
+ #started = performance.now();
1240
+ #settled = false;
1241
+ /** Starts the request deadline after its enclosing scopes reserve capacity.
1242
+ * @param options - Admitted ceilings, request deadline, and settlement callback.
1243
+ */
1244
+ constructor(options) {
1245
+ this.#options = {
1246
+ ...options,
1247
+ charge: { ...options.charge }
1248
+ };
1249
+ this.#timeout = new _orkestrel_timeout.Timeout({ ms: options.duration });
1250
+ this.#signal = (0, _orkestrel_abort.linkSignal)(this.#timeout.signal, options.control.signal);
1251
+ this.#signal.addEventListener("abort", () => this.#abort(), {
1252
+ once: true,
1253
+ signal: this.#listeners.signal
1254
+ });
1255
+ this.#timeout.start();
1256
+ if (this.#signal.aborted) this.#abort();
1257
+ }
1258
+ get signal() {
1259
+ return this.#signal;
1260
+ }
1261
+ get settled() {
1262
+ return this.#settled;
1263
+ }
1264
+ /** Retains actual measurements and refuses invalid or excessive charges.
1265
+ * @param usage - Measurements attributed to exactly this request.
1266
+ * @throws Thrown when settlement is late, repeated, invalid, or beyond admission.
1267
+ */
1268
+ finish(usage) {
1269
+ this.#checkPending();
1270
+ if (!isStudyUsage(usage) || usage.role !== this.#options.charge.role || usage.calls !== 1) {
1271
+ const failure = {
1272
+ cause: "infrastructure",
1273
+ message: "Request usage is invalid or misattributed",
1274
+ role: this.#options.charge.role,
1275
+ stop: void 0
1276
+ };
1277
+ this.fail(failure);
1278
+ throw failureToStudyError(failure);
1279
+ }
1280
+ const elapsed = performance.now() - this.#started;
1281
+ const admitted = computeStudyAdmittedStop(this.#options.charge, usage);
1282
+ const failure = this.#timeout.expired || elapsed >= this.#options.duration || usage.duration > this.#options.duration ? {
1283
+ cause: "deadline",
1284
+ message: "Request deadline expired",
1285
+ role: usage.role,
1286
+ stop: {
1287
+ layer: "scope",
1288
+ id: "Request duration",
1289
+ max: this.#options.duration,
1290
+ consumed: Math.ceil(Math.max(elapsed, usage.duration))
1291
+ }
1292
+ } : admitted !== void 0 ? {
1293
+ cause: "capacity",
1294
+ message: "Request usage exceeded its admitted ceiling",
1295
+ role: usage.role,
1296
+ stop: admitted
1297
+ } : void 0;
1298
+ this.#complete(usage);
1299
+ if (failure !== void 0) {
1300
+ this.#options.control.fail(failure);
1301
+ throw failureToStudyError(failure);
1302
+ }
1303
+ }
1304
+ /** Retains unknown cost conservatively and latches the first operational failure.
1305
+ * @param failure - The operational request failure.
1306
+ * @throws Thrown when this request was already settled.
1307
+ */
1308
+ fail(failure) {
1309
+ this.#checkPending();
1310
+ this.#complete({
1311
+ role: this.#options.charge.role,
1312
+ calls: 1,
1313
+ tokens: void 0,
1314
+ bytes: void 0,
1315
+ duration: Math.ceil(performance.now() - this.#started)
1316
+ });
1317
+ this.#options.control.fail({
1318
+ ...failure,
1319
+ role: failure.role ?? this.#options.charge.role
1320
+ });
1321
+ }
1322
+ #complete(usage) {
1323
+ this.#settled = true;
1324
+ this.#listeners.abort();
1325
+ this.#timeout.clear();
1326
+ this.#options.settle(Object.freeze({ ...usage }));
1327
+ }
1328
+ #checkPending() {
1329
+ if (this.#settled) {
1330
+ this.#options.control.fail({
1331
+ cause: "infrastructure",
1332
+ message: "Request settlement is late or repeated",
1333
+ role: this.#options.charge.role,
1334
+ stop: void 0
1335
+ });
1336
+ throw new StudyError("FAULT", "Request settlement is late or repeated", this.#options.control.failure);
1337
+ }
1338
+ }
1339
+ #abort() {
1340
+ if (this.#settled) return;
1341
+ const expired = this.#timeout.expired;
1342
+ this.fail(this.#options.control.failure ?? {
1343
+ cause: expired ? "deadline" : "interrupted",
1344
+ message: expired ? "Request deadline expired" : "Request interrupted",
1345
+ role: this.#options.charge.role,
1346
+ stop: expired ? {
1347
+ layer: "scope",
1348
+ id: "Request duration",
1349
+ max: this.#options.duration,
1350
+ consumed: Math.ceil(performance.now() - this.#started)
1351
+ } : void 0
1352
+ });
1353
+ }
1354
+ };
1355
+ //#endregion
1356
+ //#region src/core/engines/StudyMeter.ts
1357
+ /** Admits request ceilings across every enclosing scope before dispatch. */
1358
+ var StudyMeter = class {
1359
+ #options;
1360
+ #pending = /* @__PURE__ */ new Set();
1361
+ #usage = [];
1362
+ #closed = false;
1363
+ /** Holds the goal's shared admission scopes.
1364
+ * @param options - Request bound, enclosing scopes, and shared failure owner.
1365
+ */
1366
+ constructor(options) {
1367
+ if (!isStudyLimit(options.request) || options.request.calls !== 1 || options.scopes.length === 0) throw new StudyError("CONFIG", "Invalid request admission bounds");
1368
+ this.#options = {
1369
+ ...options,
1370
+ request: { ...options.request },
1371
+ scopes: [...options.scopes]
1372
+ };
1373
+ }
1374
+ get signal() {
1375
+ return this.#options.control.signal;
1376
+ }
1377
+ get usage() {
1378
+ return Object.freeze([...this.#usage]);
1379
+ }
1380
+ /** Reserves one request atomically across all enclosing allowances.
1381
+ * @param charge - Safe integer token and byte ceilings with the request's role.
1382
+ * @returns A request ticket that must settle before the goal completes.
1383
+ * @throws Thrown before dispatch if the request exceeds available capacity.
1384
+ */
1385
+ admit(charge) {
1386
+ if (this.#closed) throw new StudyError("FAULT", "Study meter is closed");
1387
+ if (this.#options.control.failure !== void 0) throw failureToStudyError(this.#options.control.failure);
1388
+ if (!isStudyRole(charge.role) || !Number.isSafeInteger(charge.tokens) || charge.tokens < 0 || !Number.isSafeInteger(charge.bytes) || charge.bytes < 0) {
1389
+ const failure = {
1390
+ cause: "infrastructure",
1391
+ message: "Request admission requires safe nonnegative ceilings",
1392
+ role: void 0,
1393
+ stop: void 0
1394
+ };
1395
+ this.#options.control.fail(failure);
1396
+ throw failureToStudyError(failure);
1397
+ }
1398
+ const { request } = this.#options;
1399
+ if (charge.tokens > request.tokens || charge.bytes > request.bytes) {
1400
+ const tokens = charge.tokens > request.tokens;
1401
+ const failure = {
1402
+ cause: "capacity",
1403
+ message: "Request ceiling exceeds the request allowance",
1404
+ role: charge.role,
1405
+ stop: {
1406
+ layer: "scope",
1407
+ id: tokens ? "Request tokens" : "Request bytes",
1408
+ max: tokens ? request.tokens : request.bytes,
1409
+ consumed: tokens ? charge.tokens : charge.bytes
1410
+ }
1411
+ };
1412
+ this.#options.control.fail(failure);
1413
+ throw failureToStudyError(failure);
1414
+ }
1415
+ for (const scope of this.#options.scopes) scope.check(charge);
1416
+ const id = Symbol("request");
1417
+ for (const scope of this.#options.scopes) scope.reserve(id, charge);
1418
+ this.#pending.add(id);
1419
+ return new StudyRequest({
1420
+ charge,
1421
+ duration: this.#options.request.duration,
1422
+ control: this.#options.control,
1423
+ settle: (usage) => {
1424
+ for (const scope of this.#options.scopes) scope.settle(id, usage);
1425
+ this.#pending.delete(id);
1426
+ this.#usage = [...this.#usage, usage];
1427
+ }
1428
+ });
1429
+ }
1430
+ /** Refuses unfinished requests before closing a goal's admission boundary. */
1431
+ close() {
1432
+ if (this.#closed) return;
1433
+ this.#closed = true;
1434
+ if (this.#pending.size > 0) this.#options.control.fail({
1435
+ cause: "infrastructure",
1436
+ message: "Goal completed with unsettled requests",
1437
+ role: void 0,
1438
+ stop: void 0
1439
+ });
1440
+ }
1441
+ };
1442
+ //#endregion
1443
+ //#region src/core/engines/StudyTrialLease.ts
1444
+ /** Holds one trial's evidence custody, admission scope and driver session from opening to cleanup. */
1445
+ var StudyTrialLease = class {
1446
+ #trial;
1447
+ #options;
1448
+ #control;
1449
+ #series;
1450
+ #deadline;
1451
+ #scope;
1452
+ #evidence;
1453
+ #opening;
1454
+ #session;
1455
+ /** Binds one scheduled trial to the series' owned inputs, failure owner, series scope and cleanup deadline.
1456
+ * @param trial - The scheduled trial.
1457
+ * @param options - The series' owned manifest, driver and store.
1458
+ * @param control - The series failure owner.
1459
+ * @param series - The series admission scope.
1460
+ * @param deadline - The custody and cleanup deadline.
1461
+ */
1462
+ constructor(trial, options, control, series, deadline) {
1463
+ this.#trial = trial;
1464
+ this.#options = options;
1465
+ this.#control = control;
1466
+ this.#series = series;
1467
+ this.#deadline = deadline;
1468
+ }
1469
+ /** Reports the trial scope once opened; undefined when the trial inherited a failure. */
1470
+ get scope() {
1471
+ return this.#scope;
1472
+ }
1473
+ /** Reports the trial's evidence sink once custody started. */
1474
+ get evidence() {
1475
+ return this.#evidence;
1476
+ }
1477
+ /** Reports the driver session once it opened. */
1478
+ get session() {
1479
+ return this.#session;
1480
+ }
1481
+ /** Starts evidence custody and, unless a failure is latched, opens the trial scope and driver session.
1482
+ * @returns Nothing after the session opened, or after custody alone started under a latched failure.
1483
+ * @throws StudyError - Thrown with code `CUSTODY` when custody refuses the trial; thrown for a series or trial limit or an opening failure.
1484
+ */
1485
+ async open() {
1486
+ const control = this.#control;
1487
+ if (control.failure !== void 0) {
1488
+ this.#evidence = await this.#start();
1489
+ return;
1490
+ }
1491
+ this.#series.check();
1492
+ const scope = new StudyScope({
1493
+ limit: this.#options.manifest.bounds.trial,
1494
+ control,
1495
+ label: "Trial"
1496
+ });
1497
+ this.#scope = scope;
1498
+ this.#evidence = await this.#start();
1499
+ this.#series.check();
1500
+ scope.check();
1501
+ const opening = this.#launch();
1502
+ this.#opening = opening;
1503
+ this.#session = await control.wait(opening);
1504
+ }
1505
+ /** Closes the opened session, or a session that opened late, then the trial scope.
1506
+ * @returns The cleanup failure, or undefined when cleanup succeeded or repeated the latched failure.
1507
+ */
1508
+ async close() {
1509
+ const cleanup = await this.#release();
1510
+ this.#scope?.close();
1511
+ return cleanup;
1512
+ }
1513
+ async #release() {
1514
+ const session = this.#session;
1515
+ const opening = this.#opening;
1516
+ if (session !== void 0) try {
1517
+ await this.#deadline.wait(session.close());
1518
+ return;
1519
+ } catch (error) {
1520
+ const cleanup = readStudyFailure(error);
1521
+ this.#control.fail(cleanup);
1522
+ return cleanup;
1523
+ }
1524
+ if (opening === void 0) return void 0;
1525
+ try {
1526
+ const late = await this.#deadline.wait(opening);
1527
+ await this.#deadline.wait(late.close());
1528
+ return;
1529
+ } catch (error) {
1530
+ const late = readStudyFailure(error);
1531
+ return late.message !== this.#control.failure?.message ? late : void 0;
1532
+ }
1533
+ }
1534
+ async #launch() {
1535
+ const control = this.#control;
1536
+ const session = await this.#options.driver.open(this.#trial, control.signal);
1537
+ if (control.failure !== void 0) {
1538
+ await this.#deadline.wait(session.close());
1539
+ throw failureToStudyError(control.failure);
1540
+ }
1541
+ return session;
1542
+ }
1543
+ async #start() {
1544
+ const control = this.#control;
1545
+ try {
1546
+ const pending = this.#options.store.start(this.#trial);
1547
+ return control.failure === void 0 ? await control.wait(pending) : await this.#deadline.wait(pending);
1548
+ } catch (error) {
1549
+ control.fail(readStudyFailure(error));
1550
+ const failure = control.failure ?? readStudyFailure(error);
1551
+ throw new StudyError("CUSTODY", failure.message, failure);
1552
+ }
1553
+ }
1554
+ };
1555
+ //#endregion
1556
+ //#region src/core/engines/StudySeries.ts
1557
+ /** Executes a frozen serial study schedule with finite admission and sticky failures. */
1558
+ var StudySeries = class {
1559
+ #options;
1560
+ #executed = false;
1561
+ /** Owns the declared inputs while retaining the concrete driver and custody store.
1562
+ * @param options - Frozen manifest, concrete adapters, and parent cancellation.
1563
+ */
1564
+ constructor(options) {
1565
+ this.#options = {
1566
+ ...options,
1567
+ manifest: structuredClone(options.manifest)
1568
+ };
1569
+ }
1570
+ /** Runs every declared trial once and retains explicit unexecuted observations after failure.
1571
+ * Custody rejection leaves the run unsealed; callers must seal only a fulfilled execution.
1572
+ * @returns Ordered terminal attempts without task success labels.
1573
+ * @throws Thrown for invalid configuration or failed or timed out custody operations.
1574
+ */
1575
+ async execute() {
1576
+ if (this.#executed) throw new StudyError("CONFIG", "Study series can execute only once");
1577
+ this.#executed = true;
1578
+ const manifest = this.#options.manifest;
1579
+ const expected = buildStudySchedule(manifest.definition, manifest.scenarios);
1580
+ if (!isStudyBounds(manifest.bounds) || (0, _orkestrel_contract.canonicalStringify)(expected) !== (0, _orkestrel_contract.canonicalStringify)(manifest.schedule)) throw new StudyError("CONFIG", "Study manifest schedule or bounds are invalid");
1581
+ const control = new StudyControl(this.#options.signal);
1582
+ const scope = new StudyScope({
1583
+ limit: manifest.bounds.series,
1584
+ control,
1585
+ label: "Series"
1586
+ });
1587
+ const deadline = new StudyDeadline({
1588
+ duration: manifest.bounds.request.duration,
1589
+ label: "Custody or cleanup"
1590
+ });
1591
+ const output = new _orkestrel_budget.Budget({
1592
+ id: "Series output",
1593
+ max: manifest.bounds.series.bytes,
1594
+ consumer: (bytes) => bytes
1595
+ });
1596
+ const attempts = [];
1597
+ try {
1598
+ for (const trial of manifest.schedule) attempts.push(await this.#executeTrial(trial, control, scope, output, deadline));
1599
+ } finally {
1600
+ scope.close();
1601
+ control.close();
1602
+ }
1603
+ return Object.freeze(attempts);
1604
+ }
1605
+ async #executeTrial(trial, control, series, output, deadline) {
1606
+ const started = performance.now();
1607
+ const scenario = this.#options.manifest.scenarios.find((entry) => entry.id === trial.scenario);
1608
+ if (scenario === void 0) throw new StudyError("CONFIG", "Scheduled scenario is absent");
1609
+ const prior = control.failure;
1610
+ const lease = new StudyTrialLease(trial, this.#options, control, series, deadline);
1611
+ const executed = await this.#executeGoals(scenario, lease, control, series, output);
1612
+ const cleanup = await this.#release(lease, control, series);
1613
+ const failure = control.failure;
1614
+ const observations = [...executed, ...scenario.goals.slice(executed.length).map((goal) => {
1615
+ const unexecuted = failure === void 0 ? {
1616
+ cause: "infrastructure",
1617
+ message: "Goal unexecuted because the trial did not open",
1618
+ role: void 0,
1619
+ stop: void 0
1620
+ } : {
1621
+ ...failure,
1622
+ message: `Goal unexecuted: ${failure.message}`
1623
+ };
1624
+ return Object.freeze({
1625
+ goal: goal.id,
1626
+ product: void 0,
1627
+ failure: unexecuted,
1628
+ usage: Object.freeze([]),
1629
+ duration: 0,
1630
+ executed: false
1631
+ });
1632
+ })];
1633
+ const attempt = Object.freeze({
1634
+ trial,
1635
+ observations: Object.freeze(observations),
1636
+ failure: cleanup !== void 0 && failure !== void 0 && (prior !== void 0 || failure.message !== cleanup.message) ? {
1637
+ ...failure,
1638
+ message: `${failure.message}; cleanup: ${cleanup.message}`
1639
+ } : failure,
1640
+ duration: Math.ceil(performance.now() - started),
1641
+ executed: prior === void 0
1642
+ });
1643
+ try {
1644
+ await deadline.wait(this.#options.store.finish(attempt));
1645
+ if (attempt.failure === void 0) series.check();
1646
+ } catch (error) {
1647
+ control.fail(readStudyFailure(error));
1648
+ const custody = control.failure ?? readStudyFailure(error);
1649
+ throw new StudyError("CUSTODY", custody.message, custody);
1650
+ }
1651
+ return attempt;
1652
+ }
1653
+ async #executeGoals(scenario, lease, control, series, output) {
1654
+ const observations = [];
1655
+ try {
1656
+ await lease.open();
1657
+ const trialOutput = new _orkestrel_budget.Budget({
1658
+ id: "Trial output",
1659
+ max: this.#options.manifest.bounds.trial.bytes,
1660
+ consumer: (bytes) => bytes
1661
+ });
1662
+ for (const goal of scenario.goals) {
1663
+ const { session, scope, evidence } = lease;
1664
+ if (control.failure !== void 0 || session === void 0 || scope === void 0 || evidence === void 0) break;
1665
+ series.check();
1666
+ scope.check();
1667
+ observations.push(await this.#executeGoal(goal, session, evidence, control, [scope, series], [trialOutput, output]));
1668
+ }
1669
+ } catch (error) {
1670
+ if (isStudyError(error) && error.code === "CUSTODY") {
1671
+ await this.#release(lease, control, series);
1672
+ throw error;
1673
+ }
1674
+ control.fail(readStudyFailure(error));
1675
+ }
1676
+ return observations;
1677
+ }
1678
+ async #release(lease, control, series) {
1679
+ const cleanup = await lease.close();
1680
+ try {
1681
+ series.check();
1682
+ } catch (error) {
1683
+ control.fail(readStudyFailure(error));
1684
+ }
1685
+ return cleanup;
1686
+ }
1687
+ async #executeGoal(goal, session, evidence, control, parents, outputs) {
1688
+ const started = performance.now();
1689
+ const bounds = this.#options.manifest.bounds;
1690
+ const scope = new StudyScope({
1691
+ limit: bounds.goal,
1692
+ control,
1693
+ label: "Goal"
1694
+ });
1695
+ const meter = new StudyMeter({
1696
+ request: bounds.request,
1697
+ scopes: [scope, ...parents],
1698
+ control
1699
+ });
1700
+ const product = await this.#produce(goal, session, evidence, control, scope, meter, parents, outputs);
1701
+ return Object.freeze({
1702
+ goal: goal.id,
1703
+ product,
1704
+ failure: control.failure,
1705
+ usage: meter.usage,
1706
+ duration: Math.ceil(performance.now() - started),
1707
+ executed: true
1708
+ });
1709
+ }
1710
+ async #produce(goal, session, evidence, control, scope, meter, parents, outputs) {
1711
+ const bounds = this.#options.manifest.bounds;
1712
+ try {
1713
+ const value = await control.wait(session.execute(goal, {
1714
+ signal: control.signal,
1715
+ meter,
1716
+ evidence
1717
+ }));
1718
+ if (!isStudyProduct(value)) throw new StudyError("FAULT", "Driver returned no valid study product");
1719
+ const candidate = Object.freeze({
1720
+ value: (0, _orkestrel_contract.cloneJSONValue)(value.value),
1721
+ partial: value.partial
1722
+ });
1723
+ const bytes = new TextEncoder().encode(JSON.stringify(candidate.value)).byteLength;
1724
+ const stop = computeStudyOutputStop(bytes, bounds.goal.bytes, outputs);
1725
+ const product = stop === void 0 ? candidate : void 0;
1726
+ try {
1727
+ for (const parent of parents) parent.check();
1728
+ scope.check();
1729
+ if (stop !== void 0) control.fail({
1730
+ cause: "capacity",
1731
+ message: "Study product exceeded its output allowance",
1732
+ role: void 0,
1733
+ stop
1734
+ });
1735
+ else for (const budget of outputs) budget.consume(bytes);
1736
+ } catch (error) {
1737
+ control.fail(readStudyFailure(error));
1738
+ }
1739
+ return product;
1740
+ } catch (error) {
1741
+ control.fail(readStudyFailure(error));
1742
+ return;
1743
+ } finally {
1744
+ meter.close();
1745
+ scope.close();
1746
+ }
1747
+ }
1748
+ };
1749
+ //#endregion
1750
+ //#region src/core/agents/helpers.ts
1751
+ /**
1752
+ * Prices a runtime estimate through an explicit gauge.
1753
+ * @param gauge - The declared scale and overhead
1754
+ * @param estimate - The runtime's estimate of one complete request
1755
+ * @returns The projected prompt tokens, rounded up
1756
+ */
1757
+ function projectStudyAgentRequest(gauge, estimate) {
1758
+ return Math.ceil(gauge.overhead + gauge.scale * estimate);
1759
+ }
1760
+ /**
1761
+ * Checks that a successful provider call reported a usable actual prompt count.
1762
+ * @remarks
1763
+ * The check compares nothing with a forecast; room enforcement stays with the transport.
1764
+ * @param usage - The usage a successful provider call reported
1765
+ * @param role - The responsibility whose request was dispatched; the failure retains it
1766
+ * @returns Nothing when the prompt count is finite and positive
1767
+ * @throws StudyError - Thrown with code `FAULT` when the prompt count is missing, not finite, or not positive
1768
+ */
1769
+ function validateStudyAgentUsage(usage, role) {
1770
+ const prompt = usage?.prompt;
1771
+ if (typeof prompt !== "number" || !Number.isFinite(prompt) || prompt <= 0) {
1772
+ const message = "Gauged request returned no usable prompt count";
1773
+ throw new StudyError("FAULT", message, {
1774
+ cause: "infrastructure",
1775
+ message,
1776
+ role,
1777
+ stop: void 0
1778
+ });
1779
+ }
1780
+ }
1781
+ /**
1782
+ * Translates only a nominal Agent window refusal into the attributable capacity failure.
1783
+ * @remarks
1784
+ * The failure's stop is the `window` layer with the id `Agent window`; its `max` is the window and
1785
+ * its `consumed` the measured request, each read from the error's context when it is a finite
1786
+ * number.
1787
+ * @param error - The caught or reported value
1788
+ * @param runtime - The admitted Agent namespace owning the nominal guard
1789
+ * @returns A capacity StudyError for an Agent `WINDOW` error; every other value with its identity
1790
+ */
1791
+ function translateStudyAgentError(error, runtime) {
1792
+ if (!runtime.isAgentError(error) || error.code !== "WINDOW") return error;
1793
+ const context = error.context;
1794
+ return new StudyError("CAPACITY", error.message, {
1795
+ cause: "capacity",
1796
+ message: error.message,
1797
+ role: "agent",
1798
+ stop: {
1799
+ layer: "window",
1800
+ id: "Agent window",
1801
+ max: parseStudyReading(context?.max),
1802
+ consumed: parseStudyReading(context?.consumed)
1803
+ }
1804
+ });
1805
+ }
1806
+ /**
1807
+ * Translates an Agent run's stop record into the attributable capacity failure of the layer that stopped it.
1808
+ * @remarks
1809
+ * The record is foreign data from the admitted runtime, so the helper reads it once, each member
1810
+ * once, inside one contained boundary, and validates only the members it dereferences. A `meter`
1811
+ * or `budget` stop always leaves the run partial; a `model` stop is a complete answer, so a
1812
+ * consumer whose role admits a `length` finish keeps it. The consumer throws the returned error
1813
+ * when its policy refuses the stop. A `model` stop reports no cap because the Agent does not see
1814
+ * one; its `consumed` is the completion the stopped call reported, when finite.
1815
+ * @param stop - The `stop` member of an Agent or Ledger result
1816
+ * @returns Undefined when `stop` is undefined; a `CAPACITY` StudyError with role `agent` and the Agent layer for a meter, budget, or model stop; a `FAULT` StudyError with role `agent` for a malformed record
1817
+ * @example
1818
+ * ```ts
1819
+ * translateStudyAgentStop({ layer: 'meter', id: 'meter', max: 2000, consumed: 2001 })
1820
+ * // a CAPACITY StudyError whose failure.stop is { layer: 'meter', id: 'meter', max: 2000, consumed: 2001 }
1821
+ * ```
1822
+ */
1823
+ function translateStudyAgentStop(stop) {
1824
+ if (stop === void 0) return void 0;
1825
+ const reading = (0, _orkestrel_contract.attempt)(() => {
1826
+ if (!(0, _orkestrel_contract.isObject)(stop)) return void 0;
1827
+ const layer = Reflect.get(stop, "layer");
1828
+ const id = Reflect.get(stop, "id");
1829
+ if (!(0, _orkestrel_contract.isNonEmptyString)(id)) return void 0;
1830
+ if (layer === "meter" || layer === "budget") {
1831
+ const max = Reflect.get(stop, "max");
1832
+ const consumed = Reflect.get(stop, "consumed");
1833
+ const limit = parseStudyReading(max);
1834
+ const used = parseStudyReading(consumed);
1835
+ return limit === void 0 || used === void 0 ? void 0 : {
1836
+ layer,
1837
+ id,
1838
+ max: limit,
1839
+ consumed: used
1840
+ };
1841
+ }
1842
+ if (layer === "model") {
1843
+ const usage = Reflect.get(stop, "usage");
1844
+ return {
1845
+ layer,
1846
+ id,
1847
+ max: void 0,
1848
+ consumed: (0, _orkestrel_contract.isObject)(usage) ? parseStudyReading(Reflect.get(usage, "completion")) : void 0
1849
+ };
1850
+ }
1851
+ });
1852
+ const parsed = reading.success ? reading.value : void 0;
1853
+ if (parsed === void 0) {
1854
+ const message = "Agent stop record is malformed";
1855
+ return new StudyError("FAULT", message, {
1856
+ cause: "infrastructure",
1857
+ message,
1858
+ role: "agent",
1859
+ stop: void 0
1860
+ });
1861
+ }
1862
+ const message = parsed.layer === "meter" ? "Agent meter allowance reached its ceiling" : parsed.layer === "budget" ? "Agent budget reached its ceiling" : "Agent model reached its own token limit";
1863
+ return new StudyError("CAPACITY", message, {
1864
+ cause: "capacity",
1865
+ message,
1866
+ role: "agent",
1867
+ stop: parsed
1868
+ });
1869
+ }
1870
+ /**
1871
+ * Projects a request's structural components onto plain JSON without storing a total.
1872
+ * @param measure - The runtime's disjoint per-message, tools and schema estimates
1873
+ * @returns The same components in request order
1874
+ */
1875
+ function buildStudyAgentParts(measure) {
1876
+ return {
1877
+ messages: measure.messages.map((message) => ({
1878
+ id: message.id,
1879
+ role: message.role,
1880
+ content: message.content,
1881
+ framing: message.framing,
1882
+ calls: message.calls,
1883
+ thinking: message.thinking,
1884
+ images: message.images
1885
+ })),
1886
+ tools: measure.tools,
1887
+ schema: measure.schema
1888
+ };
1889
+ }
1890
+ /**
1891
+ * Builds the evidence join of a wire request to its canonical dispatch.
1892
+ * @param dispatch - The measured dispatch, or undefined when the caller measured none
1893
+ * @returns The dispatch id and revision, each null when unknown
1894
+ */
1895
+ function buildStudyAgentDispatch(dispatch) {
1896
+ return {
1897
+ canonical: dispatch?.id ?? null,
1898
+ revision: dispatch?.revision ?? null
1899
+ };
1900
+ }
1901
+ //#endregion
1902
+ //#region src/core/agents/validators.ts
1903
+ /**
1904
+ * Checks whether a value is a gauge with a finite positive scale and a finite nonnegative overhead.
1905
+ * @param value - The candidate gauge; additional members are admitted
1906
+ * @returns True if the gauge readings are finite and within their bounds; false otherwise
1907
+ * @example
1908
+ * ```ts
1909
+ * isStudyAgentGauge({ scale: 1, overhead: 0 }) // true
1910
+ * ```
1911
+ */
1912
+ function isStudyAgentGauge(value) {
1913
+ return (0, _orkestrel_contract.holds)(() => {
1914
+ if (!(0, _orkestrel_contract.isObject)(value) || !("scale" in value) || !("overhead" in value)) return false;
1915
+ const { scale, overhead } = value;
1916
+ return (0, _orkestrel_contract.isFiniteNumber)(scale) && scale > 0 && (0, _orkestrel_contract.isFiniteNumber)(overhead) && overhead >= 0;
1917
+ });
1918
+ }
1919
+ /**
1920
+ * Checks whether a namespace exposes the Agent capabilities the instrumentation invokes.
1921
+ * @remarks
1922
+ * The guard reads only `estimateRequest`, `measureRequest`, and `isAgentError`, so a namespace
1923
+ * without Ledger or conversation factories passes. A throwing accessor returns false.
1924
+ * @param value - The admitted namespace
1925
+ * @returns True if every required capability is a function; false otherwise
1926
+ */
1927
+ function isStudyAgentRuntime(value) {
1928
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isObject)(value) && "estimateRequest" in value && typeof value.estimateRequest === "function" && "measureRequest" in value && typeof value.measureRequest === "function" && "isAgentError" in value && typeof value.isAgentError === "function");
1929
+ }
1930
+ //#endregion
1931
+ //#region src/core/agents/StudyAgentObserver.ts
1932
+ /** Reads each Agent request's size before delegating it unchanged to the real provider. */
1933
+ var StudyAgentObserver = class {
1934
+ #options;
1935
+ /** Binds the real provider, the measuring runtime, the explicit gauge and usage policy, and the reading recorder.
1936
+ * @param options - Delegate, measurement seam, and policies.
1937
+ */
1938
+ constructor(options) {
1939
+ this.#options = { ...options };
1940
+ }
1941
+ /** Reports the delegate's trace label. */
1942
+ get id() {
1943
+ return this.#options.provider.id;
1944
+ }
1945
+ /** Reports the delegate's backend name. */
1946
+ get name() {
1947
+ return this.#options.provider.name;
1948
+ }
1949
+ /** Reports the delegate's thinking replay policy; an absent policy is the declared `none`. */
1950
+ get replay() {
1951
+ return this.#options.provider.replay ?? "none";
1952
+ }
1953
+ /** Records one reading, then awaits the real generation.
1954
+ * @param messages - The conversation sent.
1955
+ * @param signal - Bounds the request.
1956
+ * @param tools - The tools advertised this turn.
1957
+ * @param options - The per-call options.
1958
+ * @returns The real provider's result.
1959
+ */
1960
+ async generate(messages, signal, tools, options) {
1961
+ this.#measure(messages, tools, options);
1962
+ const result = await this.#options.provider.generate(messages, signal, tools, options);
1963
+ if (this.#options.enforced && !signal.aborted) validateStudyAgentUsage(result.usage, "agent");
1964
+ return result;
1965
+ }
1966
+ /** Records one reading, then relays the real stream.
1967
+ * @param messages - The conversation sent.
1968
+ * @param signal - Bounds the request.
1969
+ * @param tools - The tools advertised this turn.
1970
+ * @param options - The per-call options.
1971
+ * @returns The real deltas and result.
1972
+ */
1973
+ stream(messages, signal, tools, options) {
1974
+ this.#measure(messages, tools, options);
1975
+ return this.#relay(this.#options.provider.stream(messages, signal, tools, options), signal);
1976
+ }
1977
+ #measure(messages, tools, options) {
1978
+ const request = {
1979
+ messages,
1980
+ ...tools === void 0 ? {} : { tools },
1981
+ ...options === void 0 ? {} : { options }
1982
+ };
1983
+ const estimate = this.#options.runtime.estimateRequest(request);
1984
+ const gauge = this.#options.gauge;
1985
+ const projected = gauge === void 0 ? void 0 : projectStudyAgentRequest(gauge, estimate);
1986
+ this.#options.record({
1987
+ estimate,
1988
+ projected
1989
+ }, request);
1990
+ }
1991
+ async *#relay(source, signal) {
1992
+ const result = yield* source;
1993
+ if (this.#options.enforced && !signal.aborted) validateStudyAgentUsage(result.usage, "agent");
1994
+ return result;
1995
+ }
1996
+ };
1997
+ //#endregion
1998
+ //#region src/core/agents/StudyAgentJudge.ts
1999
+ /** Latches a real judge's failure before an enclosing runtime can treat it as absent. */
2000
+ var StudyAgentJudge = class {
2001
+ #judge;
2002
+ #latch;
2003
+ /** Binds a real public judge to the trial's failure owner.
2004
+ * @param judge - The admitted judge every question is delegated to.
2005
+ * @param latch - The owner that retains the first operational failure.
2006
+ */
2007
+ constructor(judge, latch) {
2008
+ this.#judge = judge;
2009
+ this.#latch = latch;
2010
+ }
2011
+ /** Reports the delegate's trace label. */
2012
+ get id() {
2013
+ return this.#judge.id;
2014
+ }
2015
+ /** Reports the delegate's backend name. */
2016
+ get name() {
2017
+ return this.#judge.name;
2018
+ }
2019
+ /** Reports the delegate's model identity. */
2020
+ get model() {
2021
+ return this.#judge.model;
2022
+ }
2023
+ /** Refuses dispatch after a retained failure and retains a thrown failure with its identity.
2024
+ * @param request - The real classification request.
2025
+ * @param signal - The caller's cancellation.
2026
+ * @returns The unmodified judge result.
2027
+ */
2028
+ async ask(request, signal) {
2029
+ this.#latch.check();
2030
+ try {
2031
+ return await this.#judge.ask(request, signal);
2032
+ } catch (error) {
2033
+ this.#latch.fail(error);
2034
+ throw error;
2035
+ }
2036
+ }
2037
+ };
2038
+ //#endregion
2039
+ exports.STUDY_CAUSES = STUDY_CAUSES;
2040
+ exports.STUDY_ERROR_BRAND = STUDY_ERROR_BRAND;
2041
+ exports.STUDY_GOALS = STUDY_GOALS;
2042
+ exports.STUDY_LAYERS = STUDY_LAYERS;
2043
+ exports.STUDY_NAME = STUDY_NAME;
2044
+ exports.STUDY_ROLES = STUDY_ROLES;
2045
+ exports.STUDY_TRIALS = STUDY_TRIALS;
2046
+ exports.StudyAgentJudge = StudyAgentJudge;
2047
+ exports.StudyAgentObserver = StudyAgentObserver;
2048
+ exports.StudyControl = StudyControl;
2049
+ exports.StudyDeadline = StudyDeadline;
2050
+ exports.StudyError = StudyError;
2051
+ exports.StudyMeter = StudyMeter;
2052
+ exports.StudyRequest = StudyRequest;
2053
+ exports.StudyScope = StudyScope;
2054
+ exports.StudySeries = StudySeries;
2055
+ exports.buildStudyAgentDispatch = buildStudyAgentDispatch;
2056
+ exports.buildStudyAgentParts = buildStudyAgentParts;
2057
+ exports.buildStudySchedule = buildStudySchedule;
2058
+ exports.computeStudyAdmittedStop = computeStudyAdmittedStop;
2059
+ exports.computeStudyOutputStop = computeStudyOutputStop;
2060
+ exports.computeStudyScores = computeStudyScores;
2061
+ exports.failStudyTicket = failStudyTicket;
2062
+ exports.failureToStudyError = failureToStudyError;
2063
+ exports.isStudyAgentGauge = isStudyAgentGauge;
2064
+ exports.isStudyAgentRuntime = isStudyAgentRuntime;
2065
+ exports.isStudyBounds = isStudyBounds;
2066
+ exports.isStudyCause = isStudyCause;
2067
+ exports.isStudyCriterion = isStudyCriterion;
2068
+ exports.isStudyDefinition = isStudyDefinition;
2069
+ exports.isStudyDigest = isStudyDigest;
2070
+ exports.isStudyError = isStudyError;
2071
+ exports.isStudyErrorCode = isStudyErrorCode;
2072
+ exports.isStudyGoal = isStudyGoal;
2073
+ exports.isStudyGrading = isStudyGrading;
2074
+ exports.isStudyJudgment = isStudyJudgment;
2075
+ exports.isStudyLabeling = isStudyLabeling;
2076
+ exports.isStudyLayer = isStudyLayer;
2077
+ exports.isStudyLimit = isStudyLimit;
2078
+ exports.isStudyName = isStudyName;
2079
+ exports.isStudyProduct = isStudyProduct;
2080
+ exports.isStudyProfile = isStudyProfile;
2081
+ exports.isStudyProvider = isStudyProvider;
2082
+ exports.isStudyRole = isStudyRole;
2083
+ exports.isStudyRoom = isStudyRoom;
2084
+ exports.isStudyRubric = isStudyRubric;
2085
+ exports.isStudyScenario = isStudyScenario;
2086
+ exports.isStudyStop = isStudyStop;
2087
+ exports.isStudyTimestamp = isStudyTimestamp;
2088
+ exports.isStudyUsage = isStudyUsage;
2089
+ exports.matchesStudyKeys = matchesStudyKeys;
2090
+ exports.normalizeStudyProfile = normalizeStudyProfile;
2091
+ exports.omitStudyAbsent = omitStudyAbsent;
2092
+ exports.parseStudyDefinition = parseStudyDefinition;
2093
+ exports.parseStudyGrading = parseStudyGrading;
2094
+ exports.parseStudyLabeling = parseStudyLabeling;
2095
+ exports.parseStudyProfile = parseStudyProfile;
2096
+ exports.parseStudyReading = parseStudyReading;
2097
+ exports.parseStudyRubric = parseStudyRubric;
2098
+ exports.parseStudyScenario = parseStudyScenario;
2099
+ exports.projectStudyAgentRequest = projectStudyAgentRequest;
2100
+ exports.readStudyFailure = readStudyFailure;
2101
+ exports.studyStopToJSON = studyStopToJSON;
2102
+ exports.translateStudyAgentError = translateStudyAgentError;
2103
+ exports.translateStudyAgentStop = translateStudyAgentStop;
2104
+ exports.validateStudyAgentUsage = validateStudyAgentUsage;
2105
+ exports.validateStudyInputs = validateStudyInputs;
2106
+
2107
+ //# sourceMappingURL=index.cjs.map