@orkestrel/study 0.0.1 → 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.
@@ -1,671 +1,35 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _src_core = require("../core/index.cjs");
2
3
  let _orkestrel_contract = require("@orkestrel/contract");
3
4
  let node_crypto = require("node:crypto");
4
5
  let node_path = require("node:path");
5
- let _orkestrel_timeout = require("@orkestrel/timeout");
6
6
  let node_fs = require("node:fs");
7
7
  let node_module = require("node:module");
8
8
  let node_url = require("node:url");
9
9
  let node_fs_promises = require("node:fs/promises");
10
- let _orkestrel_budget = require("@orkestrel/budget");
11
- let _orkestrel_abort = require("@orkestrel/abort");
12
10
  let node_child_process = require("node:child_process");
13
11
  let node_timers_promises = require("node:timers/promises");
14
12
  let _orkestrel_tool = require("@orkestrel/tool");
15
- //#region src/core/constants.ts
16
- /**
17
- * Keys the own self-identity every study error stamps on itself.
18
- * @remarks
19
- * The global symbol registry lets every package, ESM, CommonJS and captured copy read the same
20
- * key. The registry makes the key discoverable; it is a recognition mechanism, not an
21
- * unforgeable provenance marker.
22
- */
23
- var STUDY_ERROR_BRAND = Symbol.for("@orkestrel/study.error");
24
- /** Restricts identifiers to portable path-safe names. */
25
- var STUDY_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26
- /** Lists every measured request role in report order. */
27
- var STUDY_ROLES = Object.freeze([
28
- "agent",
29
- "judge",
30
- "summary",
31
- "metadata",
32
- "tool"
33
- ]);
34
- /** Lists every ceiling a run can stop at in report order. */
35
- var STUDY_LAYERS = Object.freeze([
36
- "scope",
37
- "window",
38
- "custody",
39
- "meter",
40
- "budget",
41
- "model"
42
- ]);
43
- /** Lists every operational ending in report order. */
44
- var STUDY_CAUSES = Object.freeze([
45
- "capacity",
46
- "infrastructure",
47
- "deadline",
48
- "interrupted",
49
- "content"
50
- ]);
51
- //#endregion
52
- //#region src/core/helpers.ts
53
- /**
54
- * Copies the own entries of a record that hold a value, in their original order.
55
- * @param value - The record whose undefined entries are absent declarations
56
- * @returns A new record without the undefined entries
57
- * @example
58
- * ```ts
59
- * omitStudyAbsent({ context: 512, recap: undefined }) // { context: 512 }
60
- * ```
61
- */
62
- function omitStudyAbsent(value) {
63
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
64
- }
65
- /**
66
- * Omits every undefined optional declaration so the profile has one JSON form.
67
- * @param profile - The validated profile whose optional leaves may be undefined
68
- * @returns The profile fields with absent qualification, settings, provider, judge and recap removed
69
- * @example
70
- * ```ts
71
- * normalizeStudyProfile(profile) // no `recap` key when the room declares none
72
- * ```
73
- */
74
- function normalizeStudyProfile(profile) {
75
- const { provider, qualification, settings, room, ...required } = profile;
76
- return {
77
- ...required,
78
- room: omitStudyAbsent(room),
79
- ...provider === void 0 ? {} : { provider: {
80
- ...omitStudyAbsent(provider),
81
- digest: omitStudyAbsent(provider.digest)
82
- } },
83
- ...qualification === void 0 ? {} : { qualification },
84
- ...settings === void 0 ? {} : { settings }
85
- };
86
- }
87
- /**
88
- * Projects a stop onto JSON with every unreported reading as null.
89
- * @param stop - The ceiling that stopped a run
90
- * @returns The stop's layer, id, and readings, with an undefined `max` or `consumed` as null
91
- * @example
92
- * ```ts
93
- * studyStopToJSON({ layer: 'model', id: 'num_predict', max: 2048, consumed: undefined })
94
- * // { layer: 'model', id: 'num_predict', max: 2048, consumed: null }
95
- * ```
96
- */
97
- function studyStopToJSON(stop) {
98
- return {
99
- layer: stop.layer,
100
- id: stop.id,
101
- max: stop.max ?? null,
102
- consumed: stop.consumed ?? null
103
- };
104
- }
105
- /**
106
- * Matches exact own enumerable data keys without evaluating accessors.
107
- * @param value - The candidate record
108
- * @param required - Required keys
109
- * @param optional - Allowed optional keys
110
- * @returns True if the record has exactly the allowed data keys; false otherwise
111
- * @example Check exact keys
13
+ let _orkestrel_budget = require("@orkestrel/budget");
14
+ //#region src/server/helpers.ts
15
+ /** Reports whether a caught value is a Node error carrying the given system code.
16
+ * @remarks Requires an `Error` from this realm. Structured cloning, such as a worker's `postMessage`,
17
+ * drops a Node error's system code, so the cloned error does not match.
18
+ * @param value - The caught value.
19
+ * @param code - The system error code to match.
20
+ * @returns True if the value is an Error from this realm carrying the code; false otherwise.
21
+ * @example Match a system error
112
22
  * ```ts
113
- * import { matchesStudyKeys } from '@orkestrel/study'
23
+ * import { matchesSystemError } from '@orkestrel/study/server'
114
24
  *
115
- * matchesStudyKeys({ id: 'one' }, ['id']) // true
116
- * ```
117
- */
118
- function matchesStudyKeys(value, required, optional = []) {
119
- return (0, _orkestrel_contract.holds)(() => {
120
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
121
- const keys = Reflect.ownKeys(value);
122
- return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => {
123
- if (typeof key !== "string" || !required.includes(key) && !optional.includes(key)) return false;
124
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
125
- return descriptor !== void 0 && descriptor.enumerable === true && Object.hasOwn(descriptor, "value");
126
- });
127
- });
128
- }
129
- /**
130
- * Builds a bounded schedule from explicit repetition rows and declared scenarios.
131
- * @param definition - The frozen population declaration
132
- * @param scenarios - The complete scenario declarations
133
- * @returns Owned trials in repetition, scenario and row-arm order
134
- * @throws StudyError - Thrown when the population or scenario set is invalid
135
- * @example
136
- * ```ts
137
- * buildStudySchedule(definition, scenarios)
138
- * ```
139
- */
140
- function buildStudySchedule(definition, scenarios) {
141
- if (!isStudyDefinition(definition) || !scenarios.every(isStudyScenario)) throw new StudyError("CONFIG", "Invalid study population");
142
- const ids = new Set(scenarios.map((scenario) => scenario.id));
143
- 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");
144
- return Object.freeze([...definition.order.entries()].flatMap(([repetition, row]) => [...definition.scenarios.entries()].flatMap(([scenario, id]) => row.map((arm) => Object.freeze({
145
- id: `r${repetition + 1}-s${scenario + 1}-a${definition.arms.indexOf(arm) + 1}`,
146
- scenario: id,
147
- arm,
148
- repetition
149
- })))));
150
- }
151
- /**
152
- * Validates grading coverage and request room against the declared study inputs.
153
- * @param definition - The population declaration
154
- * @param scenarios - The complete scenario declarations
155
- * @param rubric - The independently held grading declaration
156
- * @param profile - The execution bounds and model room
157
- * @returns Nothing when every cross-input invariant holds
158
- * @throws StudyError - Thrown when any input or cross-reference is invalid
159
- * @example
160
- * ```ts
161
- * validateStudyInputs(definition, scenarios, rubric, profile)
162
- * ```
163
- */
164
- function validateStudyInputs(definition, scenarios, rubric, profile) {
165
- buildStudySchedule(definition, scenarios);
166
- if (!isStudyRubric(rubric) || !isStudyProfile(profile)) throw new StudyError("CONFIG", "Invalid rubric or profile");
167
- 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");
168
- for (const criterion of rubric.criteria) {
169
- const scenario = scenarios.find((entry) => entry.id === criterion.scenario);
170
- if (scenario === void 0 || !scenario.goals.some((goal) => goal.id === criterion.goal)) throw new StudyError("CONFIG", "Criterion references an undeclared goal");
171
- if (definition.purpose === "mechanism" && criterion.method === "semantic") throw new StudyError("CONFIG", "Mechanism studies cannot establish semantic quality");
172
- }
173
- const covered = new Set(rubric.criteria.map((criterion) => (0, _orkestrel_contract.canonicalStringify)([criterion.scenario, criterion.goal]) ?? ""));
174
- 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");
175
- }
176
- /**
177
- * Computes arm intervals over the complete declared criterion horizon.
178
- * @param manifest - The sealed population and schedule
179
- * @param rubric - The criterion declaration
180
- * @param grading - Judgments bound to the complete scheduled attempt set
181
- * @returns Whole-horizon scores in declared arm order
182
- * @throws StudyError - Thrown when schedule, attempts or judgments are incompatible
183
- * @example
184
- * ```ts
185
- * computeStudyScores(manifest, rubric, grading)
186
- * ```
187
- */
188
- function computeStudyScores(manifest, rubric, grading) {
189
- if (!isStudyRubric(rubric) || !isStudyGrading(grading)) throw new StudyError("EVIDENCE", "Invalid rubric or grading");
190
- const expected = buildStudySchedule(manifest.definition, manifest.scenarios);
191
- if ((0, _orkestrel_contract.canonicalStringify)(expected) !== (0, _orkestrel_contract.canonicalStringify)(manifest.schedule)) throw new StudyError("EVIDENCE", "Manifest schedule differs from the declared population");
192
- const trials = new Map(expected.map((trial) => [trial.id, trial]));
193
- if (grading.attempts.length !== expected.length || !grading.attempts.every((id) => trials.has(id))) throw new StudyError("EVIDENCE", "Grading attempts differ from the declared schedule");
194
- const criteria = new Map(rubric.criteria.map((criterion) => [criterion.id, criterion]));
195
- 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");
196
- 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");
197
- const keyed = grading.judgments.map((judgment) => ({
198
- judgment,
199
- key: (0, _orkestrel_contract.canonicalStringify)([judgment.trial, judgment.criterion]) ?? ""
200
- }));
201
- const firsts = new Map([...keyed.entries()].reverse().map(([index, { key }]) => [key, index]));
202
- for (const [index, { judgment, key }] of keyed.entries()) {
203
- const trial = trials.get(judgment.trial);
204
- const criterion = criteria.get(judgment.criterion);
205
- 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");
206
- if (criterion.method === "semantic" ? judgment.method !== "human" && judgment.method !== "model" : judgment.method !== criterion.method) throw new StudyError("EVIDENCE", "Judgment method differs from the criterion");
207
- }
208
- const judgments = new Map(keyed.map(({ key, judgment }) => [key, judgment]));
209
- return Object.freeze(manifest.definition.arms.map((arm) => {
210
- const pairs = expected.filter((trial) => trial.arm === arm).flatMap((trial) => rubric.criteria.filter((criterion) => criterion.scenario === trial.scenario).map((criterion) => ({
211
- criterion,
212
- judgment: judgments.get((0, _orkestrel_contract.canonicalStringify)([trial.id, criterion.id]) ?? "")
213
- })));
214
- const refuted = pairs.filter(({ judgment }) => judgment?.verdict === "unsuccessful");
215
- const total = pairs.length;
216
- const achieved = pairs.filter(({ judgment }) => judgment?.verdict === "achieved").length;
217
- const unsuccessful = refuted.length;
218
- const critical = refuted.filter(({ criterion }) => criterion.critical).length;
219
- const unknown = total - achieved - unsuccessful;
220
- return Object.freeze({
221
- arm,
222
- total,
223
- achieved,
224
- unsuccessful,
225
- unknown,
226
- critical,
227
- lower: achieved / total,
228
- upper: (achieved + unknown) / total,
229
- coverage: (achieved + unsuccessful) / total
230
- });
231
- }));
232
- }
233
- //#endregion
234
- //#region src/core/validators.ts
235
- /** Checks whether a value names a measured request role. */
236
- var isStudyRole = (0, _orkestrel_contract.literalOf)(STUDY_ROLES);
237
- /** Checks whether a value names an operational ending. */
238
- var isStudyCause = (0, _orkestrel_contract.literalOf)(STUDY_CAUSES);
239
- /** Checks whether a value names a ceiling a run can stop at. */
240
- var isStudyLayer = (0, _orkestrel_contract.literalOf)(STUDY_LAYERS);
241
- /**
242
- * Checks whether a value is the exact record of a ceiling that stopped a run.
243
- * @remarks
244
- * The guard accepts exact own data keys: `layer` and `id` are required and `max` and `consumed` are
245
- * optional. `max` and `consumed` are each undefined or a finite number. Finite foreign readings,
246
- * including negative and fractional values, retain the source's measurement unchanged.
247
- * @param value - The candidate stop
248
- * @returns True if the layer is known, the id is nonempty, and every reading is absent or finite; false otherwise
249
- * @example
250
- * ```ts
251
- * isStudyStop({ layer: 'scope', id: 'Goal tokens', max: 100, consumed: 120 }) // true
252
- * ```
253
- */
254
- function isStudyStop(value) {
255
- 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])));
256
- }
257
- /**
258
- * Checks whether an identifier is portable and bounded.
259
- * @param value - The candidate identifier
260
- * @returns True if it is a portable study identifier; false otherwise
261
- * @example
262
- * ```ts
263
- * isStudyName('memory') // true
264
- * ```
265
- */
266
- function isStudyName(value) {
267
- return typeof value === "string" && value.length <= 128 && STUDY_NAME.test(value);
268
- }
269
- /**
270
- * Checks whether all allowances are positive safe integers within timer bounds.
271
- * @param value - The candidate limit
272
- * @returns True if it defines finite positive allowances; false otherwise
273
- * @example
274
- * ```ts
275
- * isStudyLimit({ duration: 1000, calls: 1, tokens: 100, bytes: 1024 }) // true
276
- * ```
277
- */
278
- function isStudyLimit(value) {
279
- return (0, _orkestrel_contract.holds)(() => {
280
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
281
- "duration",
282
- "calls",
283
- "tokens",
284
- "bytes"
285
- ])) return false;
286
- return [
287
- "duration",
288
- "calls",
289
- "tokens",
290
- "bytes"
291
- ].every((key) => typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] > 0) && typeof value.duration === "number" && value.duration <= _orkestrel_timeout.MAX_TIMEOUT_MS;
292
- });
293
- }
294
- /**
295
- * Checks whether nested scopes admit no larger allowance than their parent.
296
- * @param value - The candidate bounds
297
- * @returns True if every nested allowance is ordered; false otherwise
298
- * @example
299
- * ```ts
300
- * isStudyBounds({}) // false
301
- * ```
302
- */
303
- function isStudyBounds(value) {
304
- return (0, _orkestrel_contract.holds)(() => {
305
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
306
- "request",
307
- "goal",
308
- "trial",
309
- "series"
310
- ]) || !isStudyLimit(value.request) || !isStudyLimit(value.goal) || !isStudyLimit(value.trial) || !isStudyLimit(value.series)) return false;
311
- const limits = [
312
- value.request,
313
- value.goal,
314
- value.trial,
315
- value.series
316
- ];
317
- for (const [index, limit] of limits.entries()) {
318
- const next = limits[index + 1];
319
- if (next === void 0) continue;
320
- if (limit.duration > next.duration || limit.calls > next.calls || limit.tokens > next.tokens || limit.bytes > next.bytes) return false;
321
- }
322
- return value.request.calls === 1;
323
- });
324
- }
325
- /**
326
- * Checks whether a goal has a bounded identifier and exact JSON input.
327
- * @param value - The candidate goal
328
- * @returns True if the goal is valid; false otherwise
329
- * @example
330
- * ```ts
331
- * isStudyGoal({ id: 'write', input: { text: 'hello' } }) // true
332
- * ```
333
- */
334
- function isStudyGoal(value) {
335
- return (0, _orkestrel_contract.holds)(() => {
336
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["id", "input"]) && isStudyName(value.id) && (0, _orkestrel_contract.isJSONValue)(value.input);
337
- });
338
- }
339
- /**
340
- * Checks whether a scenario declares a nonempty horizon with unique goal ids.
341
- * @param value - The candidate scenario
342
- * @returns True if the scenario has a valid horizon; false otherwise
343
- * @example
344
- * ```ts
345
- * isStudyScenario({ id: 'shift', goals: [{ id: 'handoff', input: 'Prepare handoff' }] }) // true
346
- * ```
347
- */
348
- function isStudyScenario(value) {
349
- return (0, _orkestrel_contract.holds)(() => {
350
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, ["id", "goals"]) || !isStudyName(value.id) || !(0, _orkestrel_contract.isArray)(value.goals)) return false;
351
- 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);
352
- });
353
- }
354
- /**
355
- * Checks whether a study freezes a complete arm order for every repetition.
356
- * @param value - The candidate definition
357
- * @returns True if its population and repetition orders are valid; false otherwise
358
- * @example
359
- * ```ts
360
- * isStudyDefinition({ name: 'tools' }) // false
361
- * ```
362
- */
363
- function isStudyDefinition(value) {
364
- return (0, _orkestrel_contract.holds)(() => {
365
- if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.isJSONValue)(value) || !matchesStudyKeys(value, [
366
- "name",
367
- "version",
368
- "question",
369
- "purpose",
370
- "driver",
371
- "arms",
372
- "scenarios",
373
- "order",
374
- "retries",
375
- "rubric"
376
- ]) || !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;
377
- const arms = value.arms;
378
- if (arms.length * value.scenarios.length * value.order.length > 4096) return false;
379
- 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)));
380
- });
381
- }
382
- /**
383
- * Checks whether generation room leaves positive context for input.
384
- * @param value - The candidate room
385
- * @returns True if input, output and safety allowances fit and any declared recap fits; false otherwise
386
- * @example
387
- * ```ts
388
- * isStudyRoom({ context: 20480, output: 8192, safety: 512, recap: 2048, think: true }) // true
389
- * isStudyRoom({ context: 20480, output: 8192, safety: 512, think: true }) // true
390
- * ```
391
- */
392
- function isStudyRoom(value) {
393
- return (0, _orkestrel_contract.holds)(() => {
394
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
395
- "context",
396
- "output",
397
- "safety",
398
- "think"
399
- ], ["recap"]) || typeof value.think !== "boolean") return false;
400
- if (![
401
- "context",
402
- "output",
403
- "safety",
404
- ...value.recap === void 0 ? [] : ["recap"]
405
- ].every((key) => typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] > 0)) return false;
406
- 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);
407
- });
408
- }
409
- /**
410
- * Checks whether a provider declaration names artifacts, an HTTP origin and models.
411
- * @param value - The candidate provider declaration
412
- * @returns True if the declaration has a supported origin, nonempty names, and a judge declared with its digest or not at all; false otherwise
413
- * @example
414
- * ```ts
415
- * isStudyProvider({ entry: 'dist/index.js' }) // false
416
- * ```
417
- */
418
- function isStudyProvider(value) {
419
- return (0, _orkestrel_contract.holds)(() => {
420
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
421
- "entry",
422
- "url",
423
- "model",
424
- "digest"
425
- ], ["judge"]) || ![
426
- "entry",
427
- "url",
428
- "model"
429
- ].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;
430
- const url = new URL(value.url);
431
- return ["http:", "https:"].includes(url.protocol) && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && url.pathname === "/";
432
- });
433
- }
434
- /**
435
- * Checks whether a profile carries finite bounds and an optional explicit provider.
436
- * @param value - The candidate profile
437
- * @returns True if the profile is structurally valid; false otherwise
438
- * @example
439
- * ```ts
440
- * isStudyProfile({ name: 'local' }) // false
441
- * ```
442
- */
443
- function isStudyProfile(value) {
444
- return (0, _orkestrel_contract.holds)(() => {
445
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
446
- "name",
447
- "agent",
448
- "subject",
449
- "roots",
450
- "bounds",
451
- "room"
452
- ], [
453
- "provider",
454
- "qualification",
455
- "settings"
456
- ]) && 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));
457
- });
458
- }
459
- /**
460
- * Checks whether a byte identity is a lowercase SHA-256 digest.
461
- * @param value - The candidate identity
462
- * @returns True if it has exactly 64 hexadecimal digits; false otherwise
463
- * @example
464
- * ```ts
465
- * isStudyDigest('a'.repeat(64)) // true
466
- * ```
467
- */
468
- function isStudyDigest(value) {
469
- return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
470
- }
471
- /**
472
- * Checks whether a timestamp is a canonical UTC millisecond instant.
473
- * @param value - The candidate timestamp
474
- * @returns True if it is a valid canonical timestamp; false otherwise
475
- * @example
476
- * ```ts
477
- * isStudyTimestamp('2026-10-14T12:00:00.000Z') // true
478
- * ```
479
- */
480
- function isStudyTimestamp(value) {
481
- return (0, _orkestrel_contract.holds)(() => {
482
- 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;
483
- });
484
- }
485
- /**
486
- * Checks whether request usage contains nonnegative safe measurements.
487
- * @param value - The candidate usage
488
- * @returns True if the role and every known measurement are valid; false otherwise
489
- * @example
490
- * ```ts
491
- * isStudyUsage({ role: 'tool', calls: 0, bytes: 0, duration: 0 }) // true
492
- * ```
493
- */
494
- function isStudyUsage(value) {
495
- return (0, _orkestrel_contract.holds)(() => {
496
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
497
- "role",
498
- "calls",
499
- "duration"
500
- ], ["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);
501
- });
502
- }
503
- /**
504
- * Checks a retained public product without awarding task achievement.
505
- * @param value - The candidate product
506
- * @returns True if the product carries JSON output and partial status; false otherwise
507
- * @example
508
- * ```ts
509
- * isStudyProduct({ value: { answer: 1 }, partial: true }) // true
510
- * ```
511
- */
512
- function isStudyProduct(value) {
513
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && typeof value.partial === "boolean" && (0, _orkestrel_contract.isJSONValue)(value.value));
514
- }
515
- /**
516
- * Checks whether a criterion declares exact grading inputs and references.
517
- * @param value - The candidate criterion
518
- * @returns True if its structural grading declaration is valid; false otherwise
519
- * @example
520
- * ```ts
521
- * isStudyCriterion({ id: 'saved', scenario: 'shift', goal: 'write', method: 'state', critical: true, assertion: true }) // true
522
- * ```
523
- */
524
- function isStudyCriterion(value) {
525
- return (0, _orkestrel_contract.holds)(() => {
526
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
527
- "id",
528
- "scenario",
529
- "goal",
530
- "method",
531
- "critical",
532
- "assertion"
533
- ]) && 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);
534
- });
535
- }
536
- /**
537
- * Checks whether a rubric declares nonempty criteria with unique identities.
538
- * @param value - The candidate rubric
539
- * @returns True if its version and criteria are valid; false otherwise
540
- * @example
541
- * ```ts
542
- * isStudyRubric({ version: '1', criteria: [] }) // false
543
- * ```
544
- */
545
- function isStudyRubric(value) {
546
- return (0, _orkestrel_contract.holds)(() => {
547
- 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);
548
- });
549
- }
550
- /**
551
- * Checks whether a judgment carries a supported verdict and evidence references.
552
- * @param value - The candidate judgment
553
- * @returns True if the judgment is attributable and structurally valid; false otherwise
554
- * @example
555
- * ```ts
556
- * isStudyJudgment({ trial: 'r1-s1-a1', criterion: 'saved', verdict: 'unknown', method: 'state', reason: 'No evidence', references: [] }) // true
557
- * ```
558
- */
559
- function isStudyJudgment(value) {
560
- return (0, _orkestrel_contract.holds)(() => {
561
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
562
- "trial",
563
- "criterion",
564
- "verdict",
565
- "method",
566
- "reason",
567
- "references"
568
- ]) && 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);
569
- });
570
- }
571
- /**
572
- * Checks whether imported semantic labels have accountable packet provenance.
573
- * @param value - The candidate labels
574
- * @returns True if provenance, usage and semantic judgments are valid; false otherwise
575
- * @example
576
- * ```ts
577
- * isStudyLabeling({}) // false
578
- * ```
579
- */
580
- function isStudyLabeling(value) {
581
- return (0, _orkestrel_contract.holds)(() => {
582
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
583
- "packet",
584
- "labeler",
585
- "version",
586
- "created",
587
- "usage",
588
- "judgments"
589
- ]) && 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);
590
- });
591
- }
592
- /**
593
- * Checks whether a value names a supported study error code.
594
- * @param value - The candidate code
595
- * @returns True if it is a declared study error code; false otherwise
596
- * @example
597
- * ```ts
598
- * isStudyErrorCode('CONFIG') // true
599
- * ```
600
- */
601
- function isStudyErrorCode(value) {
602
- return value === "CONFIG" || value === "EVIDENCE" || value === "CUSTODY" || value === "CAPACITY" || value === "FAULT" || value === "DEADLINE";
603
- }
604
- /**
605
- * Checks whether grading binds accountable judgments to unique attempt identities.
606
- * @param value - The candidate grading
607
- * @returns True if its identities, attempts and judgments are valid; false otherwise
608
- * @example
609
- * ```ts
610
- * isStudyGrading({}) // false
25
+ * const error = Object.assign(new Error('Missing declaration'), { code: 'ENOENT' })
26
+ * matchesSystemError(error, 'ENOENT') // true
27
+ * matchesSystemError({ code: 'ENOENT' }, 'ENOENT') // false
611
28
  * ```
612
29
  */
613
- function isStudyGrading(value) {
614
- return (0, _orkestrel_contract.holds)(() => {
615
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
616
- "result",
617
- "rubric",
618
- "grader",
619
- "instrument",
620
- "attempts",
621
- "judgments"
622
- ], ["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);
623
- });
624
- }
625
- //#endregion
626
- //#region src/core/errors.ts
627
- /** Reports a study contract or operational failure with a stable code. */
628
- var StudyError = class extends Error {
629
- code;
630
- failure;
631
- /**
632
- * Records the failing boundary and optional operational attribution.
633
- * @param code - The machine-readable boundary
634
- * @param message - The specific failure
635
- * @param failure - The optional operational reading
636
- */
637
- constructor(code, message, failure) {
638
- super(message);
639
- Object.defineProperty(this, STUDY_ERROR_BRAND, {
640
- value: this,
641
- configurable: true
642
- });
643
- this.name = "StudyError";
644
- this.code = code;
645
- this.failure = failure;
646
- }
647
- };
648
- /**
649
- * Narrows a caught value to an attributable study error.
650
- * @remarks
651
- * Recognition combines the native `Error` base, a subclass prototype, the fixed name, a declared
652
- * study code, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is the error itself.
653
- * The global key is recognized across package, ESM, CommonJS and captured module copies; it is
654
- * not unforgeable provenance. A transparent proxy is refused because its forwarded descriptor
655
- * stores the target, and an unbranded copy, an inherited brand, or a property-only lookalike
656
- * stays outside the type. Hostile property access returns false instead of throwing.
657
- * @param value - The caught value
658
- * @returns True if the value is a study error; false otherwise
659
- * @example
660
- * ```ts
661
- * isStudyError(new StudyError('CONFIG', 'missing definition')) // true
662
- * ```
663
- */
664
- function isStudyError(value) {
665
- return (0, _orkestrel_contract.isError)(value) && (0, _orkestrel_contract.holds)(() => Object.getPrototypeOf(value) !== Error.prototype && value.name === "StudyError" && Object.getOwnPropertyDescriptor(value, STUDY_ERROR_BRAND)?.value === value && "code" in value && isStudyErrorCode(value.code));
30
+ function matchesSystemError(value, code) {
31
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isError)(value) && "code" in value && value.code === code);
666
32
  }
667
- //#endregion
668
- //#region src/server/helpers.ts
669
33
  /** Checks strict lexical containment under a selected directory.
670
34
  * @param root - The canonical directory.
671
35
  * @param path - The candidate absolute file.
@@ -704,7 +68,7 @@ function hashStudyBytes(input) {
704
68
  function encodeStudyJSON(value) {
705
69
  const wire = JSON.stringify(value);
706
70
  const text = wire === void 0 ? void 0 : (0, _orkestrel_contract.canonicalStringify)((0, _orkestrel_contract.parseJSON)(wire));
707
- if (text === void 0) throw new StudyError("EVIDENCE", "Record cannot be encoded");
71
+ if (text === void 0) throw new _src_core.StudyError("EVIDENCE", "Record cannot be encoded");
708
72
  return Buffer.from(`${text}\n`, "utf8");
709
73
  }
710
74
  /**
@@ -740,7 +104,7 @@ var STUDY_PACKET_DISCLOSURE = "Identifiers omit treatment and model names. Input
740
104
  * ```
741
105
  */
742
106
  function isStudyCustodyName(value) {
743
- return isStudyName(value) && !STUDY_RESERVED_NAME.test(value);
107
+ return (0, _src_core.isStudyName)(value) && !STUDY_RESERVED_NAME.test(value);
744
108
  }
745
109
  /**
746
110
  * Checks whether a manifest exactly binds its declared population and identities.
@@ -753,7 +117,7 @@ function isStudyCustodyName(value) {
753
117
  */
754
118
  function isStudyCustodyManifest(value) {
755
119
  return (0, _orkestrel_contract.holds)(() => {
756
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
120
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _src_core.matchesStudyKeys)(value, [
757
121
  "id",
758
122
  "created",
759
123
  "definition",
@@ -762,13 +126,13 @@ function isStudyCustodyManifest(value) {
762
126
  "schedule",
763
127
  "bounds",
764
128
  "host"
765
- ], ["qualification"]) || !isStudyCustodyName(value.id) || !isStudyTimestamp(value.created) || !isStudyDefinition(value.definition) || !(0, _orkestrel_contract.isArray)(value.scenarios) || !value.scenarios.every(isStudyScenario) || !isStudyBounds(value.bounds) || !(0, _orkestrel_contract.isRecord)(value.identity) || !matchesStudyKeys(value.identity, [
129
+ ], ["qualification"]) || !isStudyCustodyName(value.id) || !(0, _src_core.isStudyTimestamp)(value.created) || !(0, _src_core.isStudyDefinition)(value.definition) || !(0, _orkestrel_contract.isArray)(value.scenarios) || !value.scenarios.every(_src_core.isStudyScenario) || !(0, _src_core.isStudyBounds)(value.bounds) || !(0, _orkestrel_contract.isRecord)(value.identity) || !(0, _src_core.matchesStudyKeys)(value.identity, [
766
130
  "agent",
767
131
  "subject",
768
132
  "instrument",
769
133
  "fixtures",
770
134
  "profile"
771
- ], ["provider"]) || value.identity.provider !== void 0 && !isStudyDigest(value.identity.provider) || value.qualification !== void 0 && !isStudyDigest(value.qualification) || !(0, _orkestrel_contract.isRecord)(value.host) || !Object.values(value.host).every((entry) => typeof entry === "string") || !(0, _orkestrel_contract.isArray)(value.schedule)) return false;
135
+ ], ["provider"]) || value.identity.provider !== void 0 && !(0, _src_core.isStudyDigest)(value.identity.provider) || value.qualification !== void 0 && !(0, _src_core.isStudyDigest)(value.qualification) || !(0, _orkestrel_contract.isRecord)(value.host) || !Object.values(value.host).every((entry) => typeof entry === "string") || !(0, _orkestrel_contract.isArray)(value.schedule)) return false;
772
136
  const identity = value.identity;
773
137
  if (![
774
138
  "agent",
@@ -776,8 +140,8 @@ function isStudyCustodyManifest(value) {
776
140
  "instrument",
777
141
  "fixtures",
778
142
  "profile"
779
- ].every((key) => isStudyDigest(identity[key]))) return false;
780
- const schedule = buildStudySchedule(value.definition, value.scenarios);
143
+ ].every((key) => (0, _src_core.isStudyDigest)(identity[key]))) return false;
144
+ const schedule = (0, _src_core.buildStudySchedule)(value.definition, value.scenarios);
781
145
  return value.schedule.every(isStudyCustodyTrial) && (0, _orkestrel_contract.canonicalStringify)(schedule) === (0, _orkestrel_contract.canonicalStringify)(value.schedule);
782
146
  });
783
147
  }
@@ -792,12 +156,12 @@ function isStudyCustodyManifest(value) {
792
156
  */
793
157
  function isStudyCustodyTrial(value) {
794
158
  return (0, _orkestrel_contract.holds)(() => {
795
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
159
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, [
796
160
  "id",
797
161
  "scenario",
798
162
  "arm",
799
163
  "repetition"
800
- ]) && isStudyCustodyName(value.id) && isStudyName(value.scenario) && isStudyName(value.arm) && typeof value.repetition === "number" && Number.isSafeInteger(value.repetition) && value.repetition >= 0;
164
+ ]) && isStudyCustodyName(value.id) && (0, _src_core.isStudyName)(value.scenario) && (0, _src_core.isStudyName)(value.arm) && typeof value.repetition === "number" && Number.isSafeInteger(value.repetition) && value.repetition >= 0;
801
165
  });
802
166
  }
803
167
  /**
@@ -811,11 +175,11 @@ function isStudyCustodyTrial(value) {
811
175
  */
812
176
  function isStudyCustodyEvidence(value) {
813
177
  return (0, _orkestrel_contract.holds)(() => {
814
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
178
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, [
815
179
  "event",
816
180
  "goal",
817
181
  "value"
818
- ]) && isStudyName(value.event) && isStudyName(value.goal) && (0, _orkestrel_contract.isJSONValue)(value.value);
182
+ ]) && (0, _src_core.isStudyName)(value.event) && (0, _src_core.isStudyName)(value.goal) && (0, _orkestrel_contract.isJSONValue)(value.value);
819
183
  });
820
184
  }
821
185
  /**
@@ -832,7 +196,7 @@ function isStudyCustodyEvidence(value) {
832
196
  */
833
197
  function isStudyCustodyFailure(value) {
834
198
  return (0, _orkestrel_contract.holds)(() => {
835
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["cause", "message"], ["role", "stop"]) && isStudyCause(value.cause) && (0, _orkestrel_contract.isNonEmptyString)(value.message) && (value.role === void 0 || isStudyRole(value.role)) && (value.stop === void 0 || (value.cause === "capacity" || value.cause === "deadline") && isStudyStop(value.stop));
199
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, ["cause", "message"], ["role", "stop"]) && (0, _src_core.isStudyCause)(value.cause) && (0, _orkestrel_contract.isNonEmptyString)(value.message) && (value.role === void 0 || (0, _src_core.isStudyRole)(value.role)) && (value.stop === void 0 || (value.cause === "capacity" || value.cause === "deadline") && (0, _src_core.isStudyStop)(value.stop));
836
200
  });
837
201
  }
838
202
  /**
@@ -846,14 +210,14 @@ function isStudyCustodyFailure(value) {
846
210
  */
847
211
  function isStudyCustodyObservation(value) {
848
212
  return (0, _orkestrel_contract.holds)(() => {
849
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
213
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _src_core.matchesStudyKeys)(value, [
850
214
  "goal",
851
215
  "usage",
852
216
  "duration",
853
217
  "executed"
854
- ], ["product", "failure"]) || !isStudyName(value.goal) || !(0, _orkestrel_contract.isArray)(value.usage) || !value.usage.every(isStudyUsage) || typeof value.duration !== "number" || !Number.isSafeInteger(value.duration) || value.duration < 0 || value.failure !== void 0 && !isStudyCustodyFailure(value.failure) || typeof value.executed !== "boolean" || value.executed === false && (value.product !== void 0 || value.failure === void 0 || value.usage.length !== 0 || value.duration !== 0)) return false;
218
+ ], ["product", "failure"]) || !(0, _src_core.isStudyName)(value.goal) || !(0, _orkestrel_contract.isArray)(value.usage) || !value.usage.every(_src_core.isStudyUsage) || typeof value.duration !== "number" || !Number.isSafeInteger(value.duration) || value.duration < 0 || value.failure !== void 0 && !isStudyCustodyFailure(value.failure) || typeof value.executed !== "boolean" || value.executed === false && (value.product !== void 0 || value.failure === void 0 || value.usage.length !== 0 || value.duration !== 0)) return false;
855
219
  if (value.product === void 0) return value.failure !== void 0;
856
- return (0, _orkestrel_contract.isRecord)(value.product) && matchesStudyKeys(value.product, ["value", "partial"]) && (0, _orkestrel_contract.isJSONValue)(value.product.value) && typeof value.product.partial === "boolean";
220
+ return (0, _orkestrel_contract.isRecord)(value.product) && (0, _src_core.matchesStudyKeys)(value.product, ["value", "partial"]) && (0, _orkestrel_contract.isJSONValue)(value.product.value) && typeof value.product.partial === "boolean";
857
221
  });
858
222
  }
859
223
  /**
@@ -872,7 +236,7 @@ function isStudyCustodyObservation(value) {
872
236
  */
873
237
  function isStudyCustodyAttempt(value, manifest) {
874
238
  return (0, _orkestrel_contract.holds)(() => {
875
- if (!(0, _orkestrel_contract.isRecord)(value) || !matchesStudyKeys(value, [
239
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _src_core.matchesStudyKeys)(value, [
876
240
  "trial",
877
241
  "observations",
878
242
  "duration",
@@ -901,11 +265,11 @@ function isStudyCustodyAttempt(value, manifest) {
901
265
  */
902
266
  function isStudyCustodyFile(value) {
903
267
  return (0, _orkestrel_contract.holds)(() => {
904
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
268
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, [
905
269
  "path",
906
270
  "bytes",
907
271
  "digest"
908
- ]) && typeof value.path === "string" && (value.path === "manifest.json" || /^trials\/[a-z0-9]+(?:-[a-z0-9]+)*\/(?:evidence.jsonl|attempt.json)$/.test(value.path)) && value.path.split("/").every((name) => !STUDY_RESERVED_NAME.test(name)) && typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes >= 0 && isStudyDigest(value.digest);
272
+ ]) && typeof value.path === "string" && (value.path === "manifest.json" || /^trials\/[a-z0-9]+(?:-[a-z0-9]+)*\/(?:evidence.jsonl|attempt.json)$/.test(value.path)) && value.path.split("/").every((name) => !STUDY_RESERVED_NAME.test(name)) && typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes >= 0 && (0, _src_core.isStudyDigest)(value.digest);
909
273
  });
910
274
  }
911
275
  /**
@@ -919,11 +283,11 @@ function isStudyCustodyFile(value) {
919
283
  */
920
284
  function isStudyCustodySeal(value) {
921
285
  return (0, _orkestrel_contract.holds)(() => {
922
- return (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, [
286
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, [
923
287
  "result",
924
288
  "id",
925
289
  "files"
926
- ]) && isStudyCustodyName(value.result) && isStudyDigest(value.id) && (0, _orkestrel_contract.isArray)(value.files) && value.files.length > 0 && value.files.every(isStudyCustodyFile) && new Set(value.files.map((entry) => entry.path)).size === value.files.length;
290
+ ]) && isStudyCustodyName(value.result) && (0, _src_core.isStudyDigest)(value.id) && (0, _orkestrel_contract.isArray)(value.files) && value.files.length > 0 && value.files.every(isStudyCustodyFile) && new Set(value.files.map((entry) => entry.path)).size === value.files.length;
927
291
  });
928
292
  }
929
293
  //#endregion
@@ -950,9 +314,9 @@ function isStudyAdapter(value) {
950
314
  * @returns The validated adapter instance.
951
315
  */
952
316
  function readStudyAdapter(namespace) {
953
- if (!(0, _orkestrel_contract.isRecord)(namespace) || typeof namespace.createStudyAdapter !== "function") throw new StudyError("CONFIG", "Consumer module must export createStudyAdapter");
317
+ if (!(0, _orkestrel_contract.isRecord)(namespace) || typeof namespace.createStudyAdapter !== "function") throw new _src_core.StudyError("CONFIG", "Consumer module must export createStudyAdapter");
954
318
  const adapter = namespace.createStudyAdapter();
955
- if (!isStudyAdapter(adapter)) throw new StudyError("CONFIG", "Consumer factory returned an incomplete study adapter");
319
+ if (!isStudyAdapter(adapter)) throw new _src_core.StudyError("CONFIG", "Consumer factory returned an incomplete study adapter");
956
320
  return adapter;
957
321
  }
958
322
  /**
@@ -972,13 +336,13 @@ function readStudyAdapter(namespace) {
972
336
  * ```
973
337
  */
974
338
  function buildStudyReference(target, reference) {
975
- if (!(0, _orkestrel_contract.isRecord)(reference)) throw new StudyError("EVIDENCE", "Grading reference must be a record");
976
- if (target.attempt === void 0) throw new StudyError("EVIDENCE", "Grading reference names a trial that was not retained");
977
- if (reference.file !== "attempt" && reference.file !== "evidence") throw new StudyError("EVIDENCE", "Grading reference names an unknown retained file");
339
+ if (!(0, _orkestrel_contract.isRecord)(reference)) throw new _src_core.StudyError("EVIDENCE", "Grading reference must be a record");
340
+ if (target.attempt === void 0) throw new _src_core.StudyError("EVIDENCE", "Grading reference names a trial that was not retained");
341
+ if (reference.file !== "attempt" && reference.file !== "evidence") throw new _src_core.StudyError("EVIDENCE", "Grading reference names an unknown retained file");
978
342
  const file = reference.file === "attempt" ? "attempt.json" : "evidence.jsonl";
979
343
  const path = `trials/${target.trial.id}/${file}`;
980
344
  if (reference.row === void 0) return path;
981
- if (reference.file !== "evidence" || !Number.isSafeInteger(reference.row) || reference.row < 0 || reference.row >= target.evidence.length || !isStudyCustodyEvidence(target.evidence[reference.row])) throw new StudyError("EVIDENCE", "Grading reference names a retained row that does not exist");
345
+ if (reference.file !== "evidence" || !Number.isSafeInteger(reference.row) || reference.row < 0 || reference.row >= target.evidence.length || !isStudyCustodyEvidence(target.evidence[reference.row])) throw new _src_core.StudyError("EVIDENCE", "Grading reference names a retained row that does not exist");
982
346
  return `${path}#${reference.row + 1}`;
983
347
  }
984
348
  /**
@@ -1005,17 +369,17 @@ function buildStudyReference(target, reference) {
1005
369
  * ```
1006
370
  */
1007
371
  function gradeStudyEvidence(input, grade) {
1008
- if (!(0, _orkestrel_contract.isRecord)(input)) throw new StudyError("CONFIG", "Invalid grading input");
372
+ if (!(0, _orkestrel_contract.isRecord)(input)) throw new _src_core.StudyError("CONFIG", "Invalid grading input");
1009
373
  const { manifest, rubric, attempts, evidence } = input;
1010
- if (!isStudyRubric(rubric)) throw new StudyError("CONFIG", "Invalid grading rubric");
1011
- if (!isStudyCustodyManifest(manifest)) throw new StudyError("CONFIG", "Invalid grading manifest or schedule");
374
+ if (!(0, _src_core.isStudyRubric)(rubric)) throw new _src_core.StudyError("CONFIG", "Invalid grading rubric");
375
+ if (!isStudyCustodyManifest(manifest)) throw new _src_core.StudyError("CONFIG", "Invalid grading manifest or schedule");
1012
376
  const schedule = manifest.schedule;
1013
- if (!(0, _orkestrel_contract.isArray)(attempts) || !attempts.every((attempt) => isStudyCustodyAttempt(attempt, manifest))) throw new StudyError("EVIDENCE", "Invalid or duplicate grading attempts");
377
+ if (!(0, _orkestrel_contract.isArray)(attempts) || !attempts.every((attempt) => isStudyCustodyAttempt(attempt, manifest))) throw new _src_core.StudyError("EVIDENCE", "Invalid or duplicate grading attempts");
1014
378
  const retained = new Map(attempts.map((attempt) => [attempt.trial.id, attempt]));
1015
- if (retained.size !== attempts.length) throw new StudyError("EVIDENCE", "Invalid or duplicate grading attempts");
379
+ if (retained.size !== attempts.length) throw new _src_core.StudyError("EVIDENCE", "Invalid or duplicate grading attempts");
1016
380
  const known = new Set(schedule.map((trial) => trial.id));
1017
- if (!(0, _orkestrel_contract.isRecord)(evidence) || !Object.entries(evidence).every(([id, rows]) => known.has(id) && (0, _orkestrel_contract.isArray)(rows) && Array.from(rows).every(isStudyCustodyEvidence))) throw new StudyError("EVIDENCE", "Invalid or foreign grading evidence");
1018
- for (const criterion of rubric.criteria) if (!manifest.scenarios.some((scenario) => scenario.id === criterion.scenario && scenario.goals.some((goal) => goal.id === criterion.goal))) throw new StudyError("CONFIG", "Grading criterion references an undeclared goal");
381
+ if (!(0, _orkestrel_contract.isRecord)(evidence) || !Object.entries(evidence).every(([id, rows]) => known.has(id) && (0, _orkestrel_contract.isArray)(rows) && Array.from(rows).every(isStudyCustodyEvidence))) throw new _src_core.StudyError("EVIDENCE", "Invalid or foreign grading evidence");
382
+ for (const criterion of rubric.criteria) if (!manifest.scenarios.some((scenario) => scenario.id === criterion.scenario && scenario.goals.some((goal) => goal.id === criterion.goal))) throw new _src_core.StudyError("CONFIG", "Grading criterion references an undeclared goal");
1019
383
  return Object.freeze(schedule.values().flatMap((trial) => {
1020
384
  const attempt = retained.get(trial.id);
1021
385
  const rows = attempt === void 0 ? Object.freeze([]) : evidence[trial.id] ?? Object.freeze([]);
@@ -1027,7 +391,7 @@ function gradeStudyEvidence(input, grade) {
1027
391
  evidence: rows
1028
392
  });
1029
393
  const reading = grade(target);
1030
- if (!(0, _orkestrel_contract.isRecord)(reading) || !(0, _orkestrel_contract.isArray)(reading.references)) throw new StudyError("EVIDENCE", "Grading predicate returned an invalid reading");
394
+ if (!(0, _orkestrel_contract.isRecord)(reading) || !(0, _orkestrel_contract.isArray)(reading.references)) throw new _src_core.StudyError("EVIDENCE", "Grading predicate returned an invalid reading");
1031
395
  const judgment = {
1032
396
  trial: trial.id,
1033
397
  criterion: criterion.id,
@@ -1036,7 +400,7 @@ function gradeStudyEvidence(input, grade) {
1036
400
  reason: reading.reason,
1037
401
  references: Object.freeze(reading.references.map((reference) => buildStudyReference(target, reference)))
1038
402
  };
1039
- if (!isStudyJudgment(judgment)) throw new StudyError("EVIDENCE", "Grading predicate returned an invalid judgment");
403
+ if (!(0, _src_core.isStudyJudgment)(judgment)) throw new _src_core.StudyError("EVIDENCE", "Grading predicate returned an invalid judgment");
1040
404
  return Object.freeze(judgment);
1041
405
  });
1042
406
  }).toArray());
@@ -1458,7 +822,7 @@ function buildStudyPacketLinks(result, rubric) {
1458
822
  }
1459
823
  };
1460
824
  const bytes = (records.at(-1)?.bytes ?? 256) + encodeStudyJSON(link).byteLength;
1461
- if (bytes > 16777216) throw new StudyError("CAPACITY", "Semantic packet mapping exceeds its custody allowance");
825
+ if (bytes > 16777216) throw new _src_core.StudyError("CAPACITY", "Semantic packet mapping exceeds its custody allowance");
1462
826
  records.push({
1463
827
  link,
1464
828
  bytes
@@ -1492,7 +856,7 @@ function buildStudyPacket(result, rubric, digest) {
1492
856
  const criterion = rubric.criteria.find((entry) => entry.id === link.source.criterion);
1493
857
  const goal = result.manifest.scenarios.find((entry) => entry.id === trial?.scenario)?.goals.find((entry) => entry.id === criterion?.goal);
1494
858
  const observation = result.attempts.find((entry) => entry.trial.id === link.source.trial)?.observations.find((entry) => entry.goal === criterion?.goal);
1495
- if (criterion === void 0 || goal === void 0 || observation === void 0) throw new StudyError("EVIDENCE", "Semantic packet refers to an absent sealed horizon");
859
+ if (criterion === void 0 || goal === void 0 || observation === void 0) throw new _src_core.StudyError("EVIDENCE", "Semantic packet refers to an absent sealed horizon");
1496
860
  const entry = {
1497
861
  trial: link.trial,
1498
862
  criterion: link.criterion,
@@ -1501,7 +865,7 @@ function buildStudyPacket(result, rubric, digest) {
1501
865
  assertion: criterion.assertion
1502
866
  };
1503
867
  const length = (records.at(-1)?.length ?? opening) + encodeStudyJSON(entry).byteLength;
1504
- if (length > 16777216) throw new StudyError("CAPACITY", "Semantic packet content exceeds its custody allowance");
868
+ if (length > 16777216) throw new _src_core.StudyError("CAPACITY", "Semantic packet content exceeds its custody allowance");
1505
869
  records.push({
1506
870
  entry,
1507
871
  length
@@ -1550,11 +914,11 @@ function computeStudyPacketDigest(packet) {
1550
914
  */
1551
915
  function translateStudyPacketLabeling(result, rubric, digest, labels) {
1552
916
  const packet = buildStudyPacket(result, rubric, digest);
1553
- if (packet === void 0 || labels.packet !== hashStudyBytes(packet.content)) throw new StudyError("EVIDENCE", "Labels bind a different semantic packet");
917
+ if (packet === void 0 || labels.packet !== hashStudyBytes(packet.content)) throw new _src_core.StudyError("EVIDENCE", "Labels bind a different semantic packet");
1554
918
  const links = buildStudyPacketLinks(result, rubric);
1555
919
  return Object.freeze(labels.judgments.map((judgment) => {
1556
920
  const link = links.find((entry) => entry.trial === judgment.trial && entry.criterion === judgment.criterion);
1557
- if (link === void 0 || judgment.method !== "human" && judgment.method !== "model") throw new StudyError("EVIDENCE", "Label refers to a foreign semantic packet identity");
921
+ if (link === void 0 || judgment.method !== "human" && judgment.method !== "model") throw new _src_core.StudyError("EVIDENCE", "Label refers to a foreign semantic packet identity");
1558
922
  return {
1559
923
  ...judgment,
1560
924
  trial: link.source.trial,
@@ -1578,7 +942,7 @@ async function checkStudyDirectory(directory) {
1578
942
  const names = (0, node_path.relative)(root, absolute).split(node_path.sep).filter(Boolean);
1579
943
  for (const [index] of names.entries()) {
1580
944
  const stat = await (0, node_fs_promises.lstat)((0, node_path.join)(root, ...names.slice(0, index + 1)));
1581
- if (!stat.isDirectory() || stat.isSymbolicLink()) throw new StudyError("CUSTODY", "Linked or invalid custody directory");
945
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new _src_core.StudyError("CUSTODY", "Linked or invalid custody directory");
1582
946
  }
1583
947
  return absolute;
1584
948
  }
@@ -1606,16 +970,16 @@ async function readStudyHandle(handle, target, offset = 0) {
1606
970
  * ```
1607
971
  */
1608
972
  async function readStudyBytes(path, limit) {
1609
- if (!Number.isSafeInteger(limit) || limit < 0) throw new StudyError("CONFIG", "Invalid custody byte allowance");
973
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new _src_core.StudyError("CONFIG", "Invalid custody byte allowance");
1610
974
  await checkStudyDirectory((0, node_path.dirname)(path));
1611
975
  const prior = await (0, node_fs_promises.lstat)(path);
1612
- if (prior.isSymbolicLink() || !prior.isFile() || prior.size > limit) throw new StudyError("CUSTODY", "Invalid or oversized custody file");
976
+ if (prior.isSymbolicLink() || !prior.isFile() || prior.size > limit) throw new _src_core.StudyError("CUSTODY", "Invalid or oversized custody file");
1613
977
  const handle = await (0, node_fs_promises.open)(path, node_fs.constants.O_RDONLY | (node_fs.constants.O_NOFOLLOW ?? 0));
1614
978
  try {
1615
979
  const stat = await handle.stat();
1616
- if (!stat.isFile() || stat.size > limit || stat.dev !== prior.dev || stat.ino !== prior.ino) throw new StudyError("CUSTODY", "Custody file changed during admission");
980
+ if (!stat.isFile() || stat.size > limit || stat.dev !== prior.dev || stat.ino !== prior.ino) throw new _src_core.StudyError("CUSTODY", "Custody file changed during admission");
1617
981
  const bytes = await readStudyHandle(handle, Buffer.alloc(stat.size + 1));
1618
- if (bytes.length !== stat.size) throw new StudyError("CUSTODY", "Custody file changed while reading");
982
+ if (bytes.length !== stat.size) throw new _src_core.StudyError("CUSTODY", "Custody file changed while reading");
1619
983
  return bytes;
1620
984
  } finally {
1621
985
  await handle.close();
@@ -1671,14 +1035,14 @@ async function scanStudyTree(directory, prefix, known, allowance) {
1671
1035
  const directories = [];
1672
1036
  for (const entry of await (0, node_fs_promises.readdir)((0, node_path.join)(directory, ...prefix.split("/").filter(Boolean)), { withFileTypes: true })) {
1673
1037
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
1674
- if (entry.isSymbolicLink()) throw new StudyError("CUSTODY", "Execution contains a symbolic link");
1038
+ if (entry.isSymbolicLink()) throw new _src_core.StudyError("CUSTODY", "Execution contains a symbolic link");
1675
1039
  if (entry.isDirectory()) {
1676
- if (path !== "trials" && !(prefix === "trials" && known.has(entry.name))) throw new StudyError("CUSTODY", "Execution contains an unknown directory");
1040
+ if (path !== "trials" && !(prefix === "trials" && known.has(entry.name))) throw new _src_core.StudyError("CUSTODY", "Execution contains an unknown directory");
1677
1041
  directories.push(path);
1678
1042
  continue;
1679
1043
  }
1680
1044
  const names = path.split("/");
1681
- if (!entry.isFile() || !(path === "manifest.json" || names.length === 3 && names[0] === "trials" && known.has(names[1] ?? "") && (names[2] === "attempt.json" || names[2] === "evidence.jsonl"))) throw new StudyError("CUSTODY", "Execution contains an unknown file");
1045
+ if (!entry.isFile() || !(path === "manifest.json" || names.length === 3 && names[0] === "trials" && known.has(names[1] ?? "") && (names[2] === "attempt.json" || names[2] === "evidence.jsonl"))) throw new _src_core.StudyError("CUSTODY", "Execution contains an unknown file");
1682
1046
  const bytes = await readStudyBytes((0, node_path.join)(directory, ...names), files.reduce((left, file) => left - file.bytes, allowance));
1683
1047
  files.push(Object.freeze({
1684
1048
  path,
@@ -1713,9 +1077,9 @@ async function scanStudyTree(directory, prefix, known, allowance) {
1713
1077
  function validateStudyEvidenceBytes(bytes, manifest, trial) {
1714
1078
  const scheduled = manifest.schedule.find((entry) => entry.id === trial);
1715
1079
  const scenario = manifest.scenarios.find((entry) => entry.id === scheduled?.scenario);
1716
- if (scenario === void 0 || bytes.byteLength > manifest.bounds.trial.bytes) throw new StudyError("CUSTODY", "Evidence exceeds the declared trial");
1080
+ if (scenario === void 0 || bytes.byteLength > manifest.bounds.trial.bytes) throw new _src_core.StudyError("CUSTODY", "Evidence exceeds the declared trial");
1717
1081
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1718
- if (text !== "" && !text.endsWith("\n")) throw new StudyError("CUSTODY", "Evidence ends with a partial record");
1082
+ if (text !== "" && !text.endsWith("\n")) throw new _src_core.StudyError("CUSTODY", "Evidence ends with a partial record");
1719
1083
  const records = text.split(/\r\n|\n/).slice(0, -1).map((line) => {
1720
1084
  const evidence = (0, _orkestrel_contract.parseJSON)(line);
1721
1085
  return {
@@ -1725,8 +1089,8 @@ function validateStudyEvidenceBytes(bytes, manifest, trial) {
1725
1089
  });
1726
1090
  const invalid = records.findIndex(({ goal }) => goal === void 0);
1727
1091
  const admitted = invalid === -1 ? records : records.slice(0, invalid);
1728
- if ([...Map.groupBy(admitted, ({ goal }) => goal).values()].some((group) => group.reduce((total, { size }) => total + size, 0) > manifest.bounds.goal.bytes)) throw new StudyError("CUSTODY", "Evidence exceeds a goal allowance");
1729
- if (invalid !== -1) throw new StudyError("CUSTODY", "Evidence refers to an invalid goal");
1092
+ if ([...Map.groupBy(admitted, ({ goal }) => goal).values()].some((group) => group.reduce((total, { size }) => total + size, 0) > manifest.bounds.goal.bytes)) throw new _src_core.StudyError("CUSTODY", "Evidence exceeds a goal allowance");
1093
+ if (invalid !== -1) throw new _src_core.StudyError("CUSTODY", "Evidence refers to an invalid goal");
1730
1094
  }
1731
1095
  /**
1732
1096
  * Computes an execution seal identity from its exact sorted inventory.
@@ -1758,12 +1122,12 @@ async function readStudyResult(directory) {
1758
1122
  const absolute = await checkStudyDirectory(directory);
1759
1123
  const root = await (0, node_fs_promises.readdir)(absolute, { withFileTypes: true });
1760
1124
  for (const entry of root) {
1761
- if (entry.isSymbolicLink()) throw new StudyError("CUSTODY", "Result contains a symbolic link");
1762
- if (entry.name === "execution" || entry.name === "grades" || entry.name === "reports" ? !entry.isDirectory() : entry.name !== "seal.json" || !entry.isFile()) throw new StudyError("CUSTODY", "Result contains an unknown path");
1125
+ if (entry.isSymbolicLink()) throw new _src_core.StudyError("CUSTODY", "Result contains a symbolic link");
1126
+ if (entry.name === "execution" || entry.name === "grades" || entry.name === "reports" ? !entry.isDirectory() : entry.name !== "seal.json" || !entry.isFile()) throw new _src_core.StudyError("CUSTODY", "Result contains an unknown path");
1763
1127
  }
1764
1128
  const sealed = root.some((entry) => entry.name === "seal.json");
1765
1129
  if (!root.some((entry) => entry.name === "execution")) {
1766
- if (sealed) throw new StudyError("CUSTODY", "Sealed result is missing execution");
1130
+ if (sealed) throw new _src_core.StudyError("CUSTODY", "Sealed result is missing execution");
1767
1131
  return Object.freeze({
1768
1132
  status: "interrupted",
1769
1133
  directory: absolute,
@@ -1774,7 +1138,7 @@ async function readStudyResult(directory) {
1774
1138
  const execution = (0, node_path.join)(absolute, "execution");
1775
1139
  const entries = await (0, node_fs_promises.readdir)(execution, { withFileTypes: true });
1776
1140
  if (!entries.some((entry) => entry.name === "manifest.json")) {
1777
- if (entries.length !== 0 || sealed) throw new StudyError("CUSTODY", "Execution is missing its manifest");
1141
+ if (entries.length !== 0 || sealed) throw new _src_core.StudyError("CUSTODY", "Execution is missing its manifest");
1778
1142
  return Object.freeze({
1779
1143
  status: "interrupted",
1780
1144
  directory: absolute,
@@ -1787,7 +1151,7 @@ async function readStudyResult(directory) {
1787
1151
  if (!decoded.success && sealed) throw decoded.error;
1788
1152
  const manifest = decoded.success ? decoded.value : void 0;
1789
1153
  if (!isStudyCustodyManifest(manifest)) {
1790
- if (sealed) throw new StudyError("CUSTODY", "Invalid execution manifest");
1154
+ if (sealed) throw new _src_core.StudyError("CUSTODY", "Invalid execution manifest");
1791
1155
  return Object.freeze({
1792
1156
  status: "interrupted",
1793
1157
  directory: absolute,
@@ -1796,25 +1160,25 @@ async function readStudyResult(directory) {
1796
1160
  });
1797
1161
  }
1798
1162
  const files = await scanStudyExecution(execution, manifest);
1799
- if (files.find((entry) => entry.path === "manifest.json")?.digest !== hashStudyBytes(manifestBytes)) throw new StudyError("CUSTODY", "Manifest changed during inventory reading");
1163
+ if (files.find((entry) => entry.path === "manifest.json")?.digest !== hashStudyBytes(manifestBytes)) throw new _src_core.StudyError("CUSTODY", "Manifest changed during inventory reading");
1800
1164
  const attempts = [];
1801
1165
  for (const trial of manifest.schedule) {
1802
1166
  const evidence = files.find((entry) => entry.path === `trials/${trial.id}/evidence.jsonl`);
1803
1167
  if (evidence !== void 0 && sealed) {
1804
1168
  const bytes = await readStudyBytes((0, node_path.join)(execution, "trials", trial.id, "evidence.jsonl"), manifest.bounds.trial.bytes);
1805
- if (hashStudyBytes(bytes) !== evidence.digest) throw new StudyError("CUSTODY", "Evidence changed during inventory reading");
1169
+ if (hashStudyBytes(bytes) !== evidence.digest) throw new _src_core.StudyError("CUSTODY", "Evidence changed during inventory reading");
1806
1170
  validateStudyEvidenceBytes(bytes, manifest, trial.id);
1807
1171
  }
1808
1172
  const terminal = files.find((entry) => entry.path === `trials/${trial.id}/attempt.json`);
1809
1173
  if (terminal === void 0) continue;
1810
- if (evidence === void 0) throw new StudyError("CUSTODY", "Terminal attempt is missing its evidence file");
1174
+ if (evidence === void 0) throw new _src_core.StudyError("CUSTODY", "Terminal attempt is missing its evidence file");
1811
1175
  const bytes = await readStudyBytes((0, node_path.join)(execution, "trials", trial.id, "attempt.json"), STUDY_CUSTODY_BYTES);
1812
- if (hashStudyBytes(bytes) !== terminal.digest) throw new StudyError("CUSTODY", "Attempt changed during inventory reading");
1176
+ if (hashStudyBytes(bytes) !== terminal.digest) throw new _src_core.StudyError("CUSTODY", "Attempt changed during inventory reading");
1813
1177
  const reading = (0, _orkestrel_contract.attempt)(() => decodeStudyJSON(bytes));
1814
1178
  if (!reading.success && sealed) throw reading.error;
1815
1179
  const record = reading.success ? reading.value : void 0;
1816
1180
  if (!isStudyCustodyAttempt(record, manifest) || record.trial.id !== trial.id) {
1817
- if (sealed) throw new StudyError("CUSTODY", "Invalid or foreign terminal attempt");
1181
+ if (sealed) throw new _src_core.StudyError("CUSTODY", "Invalid or foreign terminal attempt");
1818
1182
  continue;
1819
1183
  }
1820
1184
  attempts.push(record);
@@ -1826,9 +1190,9 @@ async function readStudyResult(directory) {
1826
1190
  attempts: Object.freeze(attempts)
1827
1191
  });
1828
1192
  const sealBytes = await readStudyBytes((0, node_path.join)(absolute, "seal.json"), STUDY_CUSTODY_BYTES);
1829
- if (files.reduce((total, file) => total + file.bytes, sealBytes.byteLength) > manifest.bounds.series.bytes) throw new StudyError("CUSTODY", "Seal and execution exceed the series allowance");
1193
+ if (files.reduce((total, file) => total + file.bytes, sealBytes.byteLength) > manifest.bounds.series.bytes) throw new _src_core.StudyError("CUSTODY", "Seal and execution exceed the series allowance");
1830
1194
  const seal = decodeStudyJSON(sealBytes);
1831
- if (!isStudyCustodySeal(seal) || seal.result !== manifest.id || seal.id !== computeStudySealId(manifest.id, files) || (0, _orkestrel_contract.canonicalStringify)(seal.files) !== (0, _orkestrel_contract.canonicalStringify)(files) || attempts.length !== manifest.schedule.length || files.length !== 1 + manifest.schedule.length * 2) throw new StudyError("CUSTODY", "Execution seal does not match the complete byte inventory");
1195
+ if (!isStudyCustodySeal(seal) || seal.result !== manifest.id || seal.id !== computeStudySealId(manifest.id, files) || (0, _orkestrel_contract.canonicalStringify)(seal.files) !== (0, _orkestrel_contract.canonicalStringify)(files) || attempts.length !== manifest.schedule.length || files.length !== 1 + manifest.schedule.length * 2) throw new _src_core.StudyError("CUSTODY", "Execution seal does not match the complete byte inventory");
1832
1196
  return Object.freeze({
1833
1197
  status: "sealed",
1834
1198
  directory: absolute,
@@ -1865,14 +1229,14 @@ var StudyEvidenceSink = class {
1865
1229
  */
1866
1230
  append(evidence) {
1867
1231
  try {
1868
- if (this.#closing !== void 0) throw new StudyError("CUSTODY", "Evidence admission is closed");
1869
- if (!isStudyCustodyEvidence(evidence) || !this.#options.goals.includes(evidence.goal)) throw new StudyError("EVIDENCE", "Evidence refers to an undeclared goal");
1232
+ if (this.#closing !== void 0) throw new _src_core.StudyError("CUSTODY", "Evidence admission is closed");
1233
+ if (!isStudyCustodyEvidence(evidence) || !this.#options.goals.includes(evidence.goal)) throw new _src_core.StudyError("EVIDENCE", "Evidence refers to an undeclared goal");
1870
1234
  const bytes = encodeStudyJSON(evidence);
1871
1235
  const used = (this.#goals.get(evidence.goal) ?? 0) + bytes.byteLength;
1872
1236
  const trial = this.#bytes + bytes.byteLength;
1873
1237
  if (used > this.#options.goal || trial > this.#options.trial) {
1874
1238
  const message = "Encoded evidence exceeds its allowance";
1875
- throw new StudyError("CAPACITY", message, {
1239
+ throw new _src_core.StudyError("CAPACITY", message, {
1876
1240
  cause: "capacity",
1877
1241
  message,
1878
1242
  role: void 0,
@@ -1939,9 +1303,9 @@ var StudyResultStore = class {
1939
1303
  * @throws StudyError - Thrown when the manifest is invalid
1940
1304
  */
1941
1305
  constructor(options) {
1942
- if (!isStudyCustodyManifest(options.manifest)) throw new StudyError("CONFIG", "Invalid execution manifest");
1306
+ if (!isStudyCustodyManifest(options.manifest)) throw new _src_core.StudyError("CONFIG", "Invalid execution manifest");
1943
1307
  const snapshot = decodeStudyJSON(encodeStudyJSON(options.manifest));
1944
- if (!isStudyCustodyManifest(snapshot)) throw new StudyError("CONFIG", "Invalid manifest snapshot");
1308
+ if (!isStudyCustodyManifest(snapshot)) throw new _src_core.StudyError("CONFIG", "Invalid manifest snapshot");
1945
1309
  this.#manifest = snapshot;
1946
1310
  this.#directory = (0, node_path.resolve)(options.directory);
1947
1311
  }
@@ -1951,11 +1315,11 @@ var StudyResultStore = class {
1951
1315
  * @throws StudyError - Thrown when creation repeats or custody bounds are exceeded
1952
1316
  */
1953
1317
  async create() {
1954
- if (this.#phase !== "new") throw new StudyError("CUSTODY", "Result creation has already begun");
1318
+ if (this.#phase !== "new") throw new _src_core.StudyError("CUSTODY", "Result creation has already begun");
1955
1319
  this.#phase = "creating";
1956
1320
  try {
1957
1321
  const bytes = encodeStudyJSON(this.#manifest);
1958
- if (bytes.byteLength > 16777216) throw new StudyError("CAPACITY", "Manifest exceeds the metadata allowance");
1322
+ if (bytes.byteLength > 16777216) throw new _src_core.StudyError("CAPACITY", "Manifest exceeds the metadata allowance");
1959
1323
  this.#reserve(bytes.byteLength);
1960
1324
  await checkStudyDirectory((0, node_path.dirname)(this.#directory));
1961
1325
  await (0, node_fs_promises.mkdir)(this.#directory);
@@ -1975,10 +1339,10 @@ var StudyResultStore = class {
1975
1339
  * @throws StudyError - Thrown when the trial is foreign, repeated, or late
1976
1340
  */
1977
1341
  async start(trial) {
1978
- if (this.#phase !== "ready" || !isStudyCustodyTrial(trial) || this.#trials.has(trial.id) || this.#starting.has(trial.id)) throw new StudyError("CUSTODY", "Trial cannot start in the current state");
1342
+ if (this.#phase !== "ready" || !isStudyCustodyTrial(trial) || this.#trials.has(trial.id) || this.#starting.has(trial.id)) throw new _src_core.StudyError("CUSTODY", "Trial cannot start in the current state");
1979
1343
  const scheduled = this.#manifest.schedule.find((entry) => entry.id === trial.id);
1980
1344
  const scenario = this.#manifest.scenarios.find((entry) => entry.id === trial.scenario);
1981
- if (scheduled === void 0 || scenario === void 0 || (0, _orkestrel_contract.canonicalStringify)(scheduled) !== (0, _orkestrel_contract.canonicalStringify)(trial)) throw new StudyError("CUSTODY", "Trial differs from the execution schedule");
1345
+ if (scheduled === void 0 || scenario === void 0 || (0, _orkestrel_contract.canonicalStringify)(scheduled) !== (0, _orkestrel_contract.canonicalStringify)(trial)) throw new _src_core.StudyError("CUSTODY", "Trial differs from the execution schedule");
1982
1346
  this.#starting.add(scheduled.id);
1983
1347
  try {
1984
1348
  const directory = (0, node_path.join)(this.#directory, "execution", "trials", scheduled.id);
@@ -2011,11 +1375,11 @@ var StudyResultStore = class {
2011
1375
  */
2012
1376
  finish(attempt) {
2013
1377
  try {
2014
- if (this.#phase !== "ready" || !isStudyCustodyAttempt(attempt, this.#manifest)) throw new StudyError("CUSTODY", "Attempt differs from the declared horizon");
1378
+ if (this.#phase !== "ready" || !isStudyCustodyAttempt(attempt, this.#manifest)) throw new _src_core.StudyError("CUSTODY", "Attempt differs from the declared horizon");
2015
1379
  const custody = this.#trials.get(attempt.trial.id);
2016
- if (custody === void 0 || custody.terminal !== void 0) throw new StudyError("CUSTODY", "Trial is unknown or already terminal");
1380
+ if (custody === void 0 || custody.terminal !== void 0) throw new _src_core.StudyError("CUSTODY", "Trial is unknown or already terminal");
2017
1381
  const bytes = encodeStudyJSON(attempt);
2018
- if (bytes.byteLength > 16777216) throw new StudyError("CAPACITY", "Attempt exceeds the metadata allowance");
1382
+ if (bytes.byteLength > 16777216) throw new _src_core.StudyError("CAPACITY", "Attempt exceeds the metadata allowance");
2019
1383
  const drained = custody.sink.close();
2020
1384
  const terminal = this.#finish(attempt.trial.id, bytes, drained);
2021
1385
  this.#trials.set(attempt.trial.id, {
@@ -2033,14 +1397,14 @@ var StudyResultStore = class {
2033
1397
  * @throws StudyError - Thrown when custody is incomplete, invalid, or already sealed
2034
1398
  */
2035
1399
  async seal() {
2036
- if (this.#phase !== "ready" || this.#starting.size !== 0 || this.#trials.size !== this.#manifest.schedule.length || [...this.#trials.values()].some((trial) => trial.terminal === void 0)) throw new StudyError("CUSTODY", "Execution is not terminally accounted");
1400
+ if (this.#phase !== "ready" || this.#starting.size !== 0 || this.#trials.size !== this.#manifest.schedule.length || [...this.#trials.values()].some((trial) => trial.terminal === void 0)) throw new _src_core.StudyError("CUSTODY", "Execution is not terminally accounted");
2037
1401
  this.#phase = "sealing";
2038
1402
  try {
2039
1403
  await Promise.all([...this.#trials.values()].map((trial) => trial.terminal));
2040
1404
  const reading = await readStudyResult(this.#directory);
2041
- if (reading.status !== "interrupted" || reading.manifest === void 0 || reading.attempts.length !== this.#manifest.schedule.length || (0, _orkestrel_contract.canonicalStringify)(reading.manifest) !== (0, _orkestrel_contract.canonicalStringify)(this.#manifest)) throw new StudyError("CUSTODY", "Execution changed before sealing");
1405
+ if (reading.status !== "interrupted" || reading.manifest === void 0 || reading.attempts.length !== this.#manifest.schedule.length || (0, _orkestrel_contract.canonicalStringify)(reading.manifest) !== (0, _orkestrel_contract.canonicalStringify)(this.#manifest)) throw new _src_core.StudyError("CUSTODY", "Execution changed before sealing");
2042
1406
  const files = await scanStudyExecution((0, node_path.join)(this.#directory, "execution"), this.#manifest);
2043
- if (files.length !== 1 + this.#manifest.schedule.length * 2) throw new StudyError("CUSTODY", "Execution inventory is incomplete");
1407
+ if (files.length !== 1 + this.#manifest.schedule.length * 2) throw new _src_core.StudyError("CUSTODY", "Execution inventory is incomplete");
2044
1408
  for (const trial of this.#manifest.schedule) validateStudyEvidenceBytes(await readStudyBytes((0, node_path.join)(this.#directory, "execution", "trials", trial.id, "evidence.jsonl"), this.#manifest.bounds.trial.bytes), this.#manifest, trial.id);
2045
1409
  const seal = Object.freeze({
2046
1410
  result: this.#manifest.id,
@@ -2048,12 +1412,12 @@ var StudyResultStore = class {
2048
1412
  files
2049
1413
  });
2050
1414
  const bytes = encodeStudyJSON(seal);
2051
- if (bytes.byteLength > 16777216) throw new StudyError("CAPACITY", "Seal exceeds the metadata allowance");
2052
- if (files.reduce((total, file) => total + file.bytes, bytes.byteLength) > this.#manifest.bounds.series.bytes) throw new StudyError("CAPACITY", "Seal and execution exceed the series allowance");
1415
+ if (bytes.byteLength > 16777216) throw new _src_core.StudyError("CAPACITY", "Seal exceeds the metadata allowance");
1416
+ if (files.reduce((total, file) => total + file.bytes, bytes.byteLength) > this.#manifest.bounds.series.bytes) throw new _src_core.StudyError("CAPACITY", "Seal and execution exceed the series allowance");
2053
1417
  this.#reserve(bytes.byteLength);
2054
1418
  await writeStudyBytes((0, node_path.join)(this.#directory, "seal.json"), bytes);
2055
1419
  const verified = await readStudyResult(this.#directory);
2056
- if (verified.status !== "sealed" || verified.seal.id !== seal.id) throw new StudyError("CUSTODY", "Execution changed while sealing");
1420
+ if (verified.status !== "sealed" || verified.seal.id !== seal.id) throw new _src_core.StudyError("CUSTODY", "Execution changed while sealing");
2057
1421
  this.#phase = "sealed";
2058
1422
  return seal;
2059
1423
  } catch (error) {
@@ -2062,7 +1426,7 @@ var StudyResultStore = class {
2062
1426
  }
2063
1427
  }
2064
1428
  #reserve(bytes) {
2065
- if (this.#bytes + bytes > this.#manifest.bounds.series.bytes) throw new StudyError("CAPACITY", "Execution bytes exceed the series allowance");
1429
+ if (this.#bytes + bytes > this.#manifest.bounds.series.bytes) throw new _src_core.StudyError("CAPACITY", "Execution bytes exceed the series allowance");
2066
1430
  this.#bytes += bytes;
2067
1431
  }
2068
1432
  async #finish(trial, bytes, drained) {
@@ -2077,153 +1441,6 @@ var StudyResultStore = class {
2077
1441
  }
2078
1442
  };
2079
1443
  //#endregion
2080
- //#region src/core/parsers.ts
2081
- /**
2082
- * Parses a foreign reading into a number a stop can retain.
2083
- * @param value - The unknown reading a source reports
2084
- * @returns The value when it is a finite number; undefined otherwise
2085
- * @example
2086
- * ```ts
2087
- * parseStudyReading(2048) // 2048
2088
- * parseStudyReading(Number.NaN) // undefined
2089
- * ```
2090
- */
2091
- function parseStudyReading(value) {
2092
- return (0, _orkestrel_contract.isFiniteNumber)(value) ? value : void 0;
2093
- }
2094
- /**
2095
- * Parses a definition into an independent frozen validated snapshot.
2096
- * @param value - The unknown configuration
2097
- * @returns The owned study definition
2098
- * @throws StudyError - Thrown when configuration is invalid or cannot be owned
2099
- * @example
2100
- * ```ts
2101
- * parseStudyDefinition(configuration)
2102
- * ```
2103
- */
2104
- function parseStudyDefinition(value) {
2105
- try {
2106
- if (!isStudyDefinition(value)) throw new StudyError("CONFIG", "Invalid study definition");
2107
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
2108
- if (isStudyDefinition(snapshot)) return snapshot;
2109
- } catch {
2110
- throw new StudyError("CONFIG", "Invalid or unreadable study definition");
2111
- }
2112
- throw new StudyError("CONFIG", "Invalid study definition snapshot");
2113
- }
2114
- /**
2115
- * Parses a scenario into an independent frozen validated snapshot.
2116
- * @param value - The unknown configuration
2117
- * @returns The owned scenario and goal inputs
2118
- * @throws StudyError - Thrown when configuration is invalid or cannot be owned
2119
- * @example
2120
- * ```ts
2121
- * parseStudyScenario({ id: 'shift', goals: [{ id: 'write', input: {} }] })
2122
- * ```
2123
- */
2124
- function parseStudyScenario(value) {
2125
- try {
2126
- if (!isStudyScenario(value)) throw new StudyError("CONFIG", "Invalid study scenario");
2127
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
2128
- if (isStudyScenario(snapshot)) return snapshot;
2129
- } catch {
2130
- throw new StudyError("CONFIG", "Invalid or unreadable study scenario");
2131
- }
2132
- throw new StudyError("CONFIG", "Invalid study scenario snapshot");
2133
- }
2134
- /**
2135
- * Parses a profile into an independent frozen validated snapshot.
2136
- * @param value - The unknown configuration
2137
- * @returns The owned profile with absent optional settings preserved
2138
- * @throws StudyError - Thrown when configuration is invalid or cannot be owned
2139
- * @example
2140
- * ```ts
2141
- * parseStudyProfile(configuration)
2142
- * ```
2143
- */
2144
- function parseStudyProfile(value) {
2145
- try {
2146
- if (!isStudyProfile(value)) throw new StudyError("CONFIG", "Invalid study profile");
2147
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)(normalizeStudyProfile(value));
2148
- if (isStudyProfile(snapshot)) return snapshot;
2149
- } catch {
2150
- throw new StudyError("CONFIG", "Invalid or unreadable study profile");
2151
- }
2152
- throw new StudyError("CONFIG", "Invalid study profile snapshot");
2153
- }
2154
- /**
2155
- * Parses a rubric into an independent frozen validated snapshot.
2156
- * @param value - The unknown grading declaration
2157
- * @returns The owned rubric and assertion inputs
2158
- * @throws StudyError - Thrown when configuration is invalid or cannot be owned
2159
- * @example
2160
- * ```ts
2161
- * parseStudyRubric(configuration)
2162
- * ```
2163
- */
2164
- function parseStudyRubric(value) {
2165
- try {
2166
- if (!isStudyRubric(value)) throw new StudyError("CONFIG", "Invalid study rubric");
2167
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)(value);
2168
- if (isStudyRubric(snapshot)) return snapshot;
2169
- } catch {
2170
- throw new StudyError("CONFIG", "Invalid or unreadable study rubric");
2171
- }
2172
- throw new StudyError("CONFIG", "Invalid study rubric snapshot");
2173
- }
2174
- /**
2175
- * Parses grading into an independent frozen validated evidence snapshot.
2176
- * @param value - The unknown grading evidence
2177
- * @returns The owned grading identity and judgments
2178
- * @throws StudyError - Thrown when evidence is invalid or cannot be owned
2179
- * @example
2180
- * ```ts
2181
- * parseStudyGrading(evidence)
2182
- * ```
2183
- */
2184
- function parseStudyGrading(value) {
2185
- try {
2186
- if (!isStudyGrading(value)) throw new StudyError("EVIDENCE", "Invalid study grading");
2187
- const { labels, packet, ...required } = value;
2188
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)({
2189
- ...required,
2190
- ...labels === void 0 ? {} : { labels },
2191
- ...packet === void 0 ? {} : { packet }
2192
- });
2193
- if (isStudyGrading(snapshot)) return snapshot;
2194
- } catch {
2195
- throw new StudyError("EVIDENCE", "Invalid or unreadable study grading");
2196
- }
2197
- throw new StudyError("EVIDENCE", "Invalid study grading snapshot");
2198
- }
2199
- /**
2200
- * Parses semantic labels into an independent frozen validated evidence snapshot.
2201
- * @param value - The unknown imported labels
2202
- * @returns The owned packet provenance, usage and judgments
2203
- * @throws StudyError - Thrown when evidence is invalid or cannot be owned
2204
- * @example
2205
- * ```ts
2206
- * parseStudyLabeling(evidence)
2207
- * ```
2208
- */
2209
- function parseStudyLabeling(value) {
2210
- try {
2211
- if (!isStudyLabeling(value)) throw new StudyError("EVIDENCE", "Invalid study labels");
2212
- const snapshot = (0, _orkestrel_contract.cloneJSONValue)({
2213
- ...value,
2214
- usage: value.usage.map(({ tokens, bytes, ...usage }) => ({
2215
- ...usage,
2216
- ...tokens === void 0 ? {} : { tokens },
2217
- ...bytes === void 0 ? {} : { bytes }
2218
- }))
2219
- });
2220
- if (isStudyLabeling(snapshot)) return snapshot;
2221
- } catch {
2222
- throw new StudyError("EVIDENCE", "Invalid or unreadable study labels");
2223
- }
2224
- throw new StudyError("EVIDENCE", "Invalid study labels snapshot");
2225
- }
2226
- //#endregion
2227
1444
  //#region src/server/stores/StudyGradingStore.ts
2228
1445
  /** Retains immutable deterministic grading versions independently of execution bytes. */
2229
1446
  var StudyGradingStore = class {
@@ -2243,17 +1460,17 @@ var StudyGradingStore = class {
2243
1460
  * @throws StudyError - Thrown when imported bytes exceed bounds or fail validation
2244
1461
  */
2245
1462
  constructor(options) {
2246
- if (options.result.status !== "sealed") throw new StudyError("CUSTODY", "Grading requires a sealed execution");
2247
- if (options.rubric.byteLength > 16777216 || options.labels !== void 0 && options.labels.byteLength > 16777216) throw new StudyError("CAPACITY", "Imported grading bytes exceed the metadata allowance");
1463
+ if (options.result.status !== "sealed") throw new _src_core.StudyError("CUSTODY", "Grading requires a sealed execution");
1464
+ if (options.rubric.byteLength > 16777216 || options.labels !== void 0 && options.labels.byteLength > 16777216) throw new _src_core.StudyError("CAPACITY", "Imported grading bytes exceed the metadata allowance");
2248
1465
  this.#directory = options.result.directory;
2249
1466
  this.#seal = options.result.seal.id;
2250
1467
  this.#rubricBytes = new Uint8Array(options.rubric);
2251
1468
  this.#rubricDigest = hashStudyBytes(this.#rubricBytes);
2252
- this.#rubric = parseStudyRubric(decodeStudyJSON(this.#rubricBytes));
1469
+ this.#rubric = (0, _src_core.parseStudyRubric)(decodeStudyJSON(this.#rubricBytes));
2253
1470
  this.#labelsBytes = options.labels === void 0 ? void 0 : new Uint8Array(options.labels);
2254
1471
  this.#labelsDigest = this.#labelsBytes === void 0 ? void 0 : hashStudyBytes(this.#labelsBytes);
2255
- this.#labels = this.#labelsBytes === void 0 ? void 0 : parseStudyLabeling(decodeStudyJSON(this.#labelsBytes));
2256
- if (options.packet !== void 0 && (options.packet.content.byteLength > 16777216 || options.packet.mapping.byteLength > 16777216)) throw new StudyError("CAPACITY", "Packet exceeds the grading metadata allowance");
1472
+ this.#labels = this.#labelsBytes === void 0 ? void 0 : (0, _src_core.parseStudyLabeling)(decodeStudyJSON(this.#labelsBytes));
1473
+ if (options.packet !== void 0 && (options.packet.content.byteLength > 16777216 || options.packet.mapping.byteLength > 16777216)) throw new _src_core.StudyError("CAPACITY", "Packet exceeds the grading metadata allowance");
2257
1474
  this.#packet = options.packet === void 0 ? void 0 : {
2258
1475
  content: new Uint8Array(options.packet.content),
2259
1476
  mapping: new Uint8Array(options.packet.mapping)
@@ -2267,9 +1484,9 @@ var StudyGradingStore = class {
2267
1484
  */
2268
1485
  write(grading) {
2269
1486
  try {
2270
- const snapshot = parseStudyGrading(grading);
1487
+ const snapshot = (0, _src_core.parseStudyGrading)(grading);
2271
1488
  const bytes = encodeStudyJSON(snapshot);
2272
- if (bytes.byteLength > 16777216) throw new StudyError("CAPACITY", "Grading exceeds the metadata allowance");
1489
+ if (bytes.byteLength > 16777216) throw new _src_core.StudyError("CAPACITY", "Grading exceeds the metadata allowance");
2273
1490
  const operation = Promise.allSettled([...this.#writes]).then(() => this.#write(snapshot, bytes));
2274
1491
  this.#writes.add(operation);
2275
1492
  operation.then(() => this.#writes.delete(operation), () => this.#writes.delete(operation));
@@ -2285,19 +1502,19 @@ var StudyGradingStore = class {
2285
1502
  * @throws StudyError - Thrown when an existing version is partial or tampered
2286
1503
  */
2287
1504
  async read(id) {
2288
- if (!isStudyDigest(id)) throw new StudyError("EVIDENCE", "Invalid grading identity");
1505
+ if (!(0, _src_core.isStudyDigest)(id)) throw new _src_core.StudyError("EVIDENCE", "Invalid grading identity");
2289
1506
  const result = await this.#verifyResult();
2290
1507
  const directory = (0, node_path.join)(result.directory, "grades", id);
2291
1508
  try {
2292
1509
  await (0, node_fs_promises.lstat)(directory);
2293
1510
  } catch (error) {
2294
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
1511
+ if (matchesSystemError(error, "ENOENT")) return void 0;
2295
1512
  throw error;
2296
1513
  }
2297
1514
  await checkStudyDirectory(directory);
2298
1515
  const bytes = await readStudyBytes((0, node_path.join)(directory, "grading.json"), STUDY_CUSTODY_BYTES);
2299
- if (hashStudyBytes(bytes) !== id) throw new StudyError("CUSTODY", "Grading identity differs from its exact bytes");
2300
- const grading = parseStudyGrading(decodeStudyJSON(bytes));
1516
+ if (hashStudyBytes(bytes) !== id) throw new _src_core.StudyError("CUSTODY", "Grading identity differs from its exact bytes");
1517
+ const grading = (0, _src_core.parseStudyGrading)(decodeStudyJSON(bytes));
2301
1518
  const expected = [
2302
1519
  "grading.json",
2303
1520
  `${grading.rubric}.rubric.json`,
@@ -2305,10 +1522,10 @@ var StudyGradingStore = class {
2305
1522
  ...grading.packet === void 0 ? [] : [`${grading.packet}.packet.json`, `${grading.packet}.mapping.json`]
2306
1523
  ];
2307
1524
  const entries = await (0, node_fs_promises.readdir)(directory, { withFileTypes: true });
2308
- if (entries.length !== expected.length || entries.some((entry) => !entry.isFile() || entry.isSymbolicLink() || !expected.includes(entry.name))) throw new StudyError("CUSTODY", "Grading inventory differs from its bound artifacts");
1525
+ if (entries.length !== expected.length || entries.some((entry) => !entry.isFile() || entry.isSymbolicLink() || !expected.includes(entry.name))) throw new _src_core.StudyError("CUSTODY", "Grading inventory differs from its bound artifacts");
2309
1526
  const rubricBytes = await readStudyBytes((0, node_path.join)(directory, `${grading.rubric}.rubric.json`), STUDY_CUSTODY_BYTES);
2310
- if (hashStudyBytes(rubricBytes) !== grading.rubric) throw new StudyError("CUSTODY", "Rubric bytes differ from the grading identity");
2311
- const rubric = parseStudyRubric(decodeStudyJSON(rubricBytes));
1527
+ if (hashStudyBytes(rubricBytes) !== grading.rubric) throw new _src_core.StudyError("CUSTODY", "Rubric bytes differ from the grading identity");
1528
+ const rubric = (0, _src_core.parseStudyRubric)(decodeStudyJSON(rubricBytes));
2312
1529
  const labels = grading.labels === void 0 ? void 0 : await this.#retrieve(directory, grading.labels);
2313
1530
  const packet = grading.packet === void 0 ? void 0 : {
2314
1531
  content: await readStudyBytes((0, node_path.join)(directory, `${grading.packet}.packet.json`), STUDY_CUSTODY_BYTES),
@@ -2320,28 +1537,28 @@ var StudyGradingStore = class {
2320
1537
  }
2321
1538
  async #retrieve(directory, digest) {
2322
1539
  const bytes = await readStudyBytes((0, node_path.join)(directory, `${digest}.labels.json`), STUDY_CUSTODY_BYTES);
2323
- if (hashStudyBytes(bytes) !== digest) throw new StudyError("CUSTODY", "Labels bytes differ from the grading identity");
2324
- return parseStudyLabeling(decodeStudyJSON(bytes));
1540
+ if (hashStudyBytes(bytes) !== digest) throw new _src_core.StudyError("CUSTODY", "Labels bytes differ from the grading identity");
1541
+ return (0, _src_core.parseStudyLabeling)(decodeStudyJSON(bytes));
2325
1542
  }
2326
1543
  async #write(grading, bytes) {
2327
1544
  const result = await this.#verifyResult();
2328
- if (grading.rubric !== this.#rubricDigest || grading.labels !== this.#labelsDigest) throw new StudyError("EVIDENCE", "Grading does not bind the imported artifact bytes");
1545
+ if (grading.rubric !== this.#rubricDigest || grading.labels !== this.#labelsDigest) throw new _src_core.StudyError("EVIDENCE", "Grading does not bind the imported artifact bytes");
2329
1546
  this.#validate(grading, result, this.#rubric, this.#labels, this.#packet);
2330
1547
  const id = hashStudyBytes(bytes);
2331
1548
  const root = (0, node_path.join)(result.directory, "grades");
2332
1549
  try {
2333
1550
  await (0, node_fs_promises.mkdir)(root);
2334
1551
  } catch (error) {
2335
- if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
1552
+ if (!matchesSystemError(error, "EEXIST")) throw error;
2336
1553
  }
2337
1554
  await checkStudyDirectory(root);
2338
1555
  const directory = (0, node_path.join)(root, id);
2339
1556
  try {
2340
1557
  await (0, node_fs_promises.mkdir)(directory);
2341
1558
  } catch (error) {
2342
- if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
1559
+ if (!matchesSystemError(error, "EEXIST")) throw error;
2343
1560
  const existing = await this.read(id);
2344
- if (existing === void 0 || (0, _orkestrel_contract.canonicalStringify)(existing) !== (0, _orkestrel_contract.canonicalStringify)(grading) || hashStudyBytes(await readStudyBytes((0, node_path.join)(directory, "grading.json"), 16777216)) !== id) throw new StudyError("CUSTODY", "Existing grading version is incompatible");
1561
+ if (existing === void 0 || (0, _orkestrel_contract.canonicalStringify)(existing) !== (0, _orkestrel_contract.canonicalStringify)(grading) || hashStudyBytes(await readStudyBytes((0, node_path.join)(directory, "grading.json"), 16777216)) !== id) throw new _src_core.StudyError("CUSTODY", "Existing grading version is incompatible");
2345
1562
  return id;
2346
1563
  }
2347
1564
  await writeStudyBytes((0, node_path.join)(directory, `${this.#rubricDigest}.rubric.json`), this.#rubricBytes);
@@ -2356,26 +1573,26 @@ var StudyGradingStore = class {
2356
1573
  }
2357
1574
  async #verifyResult() {
2358
1575
  const result = await readStudyResult(this.#directory);
2359
- if (result.status !== "sealed" || result.seal.id !== this.#seal) throw new StudyError("CUSTODY", "Execution no longer matches the verified seal");
1576
+ if (result.status !== "sealed" || result.seal.id !== this.#seal) throw new _src_core.StudyError("CUSTODY", "Execution no longer matches the verified seal");
2360
1577
  return result;
2361
1578
  }
2362
1579
  #validate(grading, result, rubric, labels, packet) {
2363
- if (grading.result !== result.seal.id) throw new StudyError("EVIDENCE", "Grading binds a different execution seal");
2364
- computeStudyScores(result.manifest, rubric, grading);
2365
- if (grading.packet !== (packet === void 0 ? void 0 : computeStudyPacketDigest(packet))) throw new StudyError("CUSTODY", "Grading packet bytes do not match their bundle identity");
1580
+ if (grading.result !== result.seal.id) throw new _src_core.StudyError("EVIDENCE", "Grading binds a different execution seal");
1581
+ (0, _src_core.computeStudyScores)(result.manifest, rubric, grading);
1582
+ if (grading.packet !== (packet === void 0 ? void 0 : computeStudyPacketDigest(packet))) throw new _src_core.StudyError("CUSTODY", "Grading packet bytes do not match their bundle identity");
2366
1583
  if (packet !== void 0) {
2367
1584
  const expected = buildStudyPacket(result, rubric, grading.rubric);
2368
- if (expected === void 0 || hashStudyBytes(expected.content) !== hashStudyBytes(packet.content) || hashStudyBytes(expected.mapping) !== hashStudyBytes(packet.mapping)) throw new StudyError("CUSTODY", "Grading packet differs from the sealed semantic horizon");
1585
+ if (expected === void 0 || hashStudyBytes(expected.content) !== hashStudyBytes(packet.content) || hashStudyBytes(expected.mapping) !== hashStudyBytes(packet.mapping)) throw new _src_core.StudyError("CUSTODY", "Grading packet differs from the sealed semantic horizon");
2369
1586
  }
2370
- if (labels !== void 0 && packet === void 0) throw new StudyError("EVIDENCE", "Imported labels require a retained semantic packet");
1587
+ if (labels !== void 0 && packet === void 0) throw new _src_core.StudyError("EVIDENCE", "Imported labels require a retained semantic packet");
2371
1588
  const translated = labels === void 0 ? [] : translateStudyPacketLabeling(result, rubric, grading.rubric, labels);
2372
1589
  for (const trial of result.manifest.schedule) for (const criterion of rubric.criteria) {
2373
1590
  if (criterion.scenario !== trial.scenario) continue;
2374
1591
  const judgment = grading.judgments.find((entry) => entry.trial === trial.id && entry.criterion === criterion.id);
2375
- if (judgment === void 0) throw new StudyError("EVIDENCE", "Grading must record every scheduled criterion judgment");
2376
- if (criterion.method === "semantic" && judgment.verdict !== "unknown" && !translated.some((entry) => (0, _orkestrel_contract.canonicalStringify)(entry) === (0, _orkestrel_contract.canonicalStringify)(judgment))) throw new StudyError("EVIDENCE", "Semantic verdict has no bound imported label");
1592
+ if (judgment === void 0) throw new _src_core.StudyError("EVIDENCE", "Grading must record every scheduled criterion judgment");
1593
+ if (criterion.method === "semantic" && judgment.verdict !== "unknown" && !translated.some((entry) => (0, _orkestrel_contract.canonicalStringify)(entry) === (0, _orkestrel_contract.canonicalStringify)(judgment))) throw new _src_core.StudyError("EVIDENCE", "Semantic verdict has no bound imported label");
2377
1594
  }
2378
- for (const judgment of translated) if (!grading.judgments.some((entry) => (0, _orkestrel_contract.canonicalStringify)(entry) === (0, _orkestrel_contract.canonicalStringify)(judgment))) throw new StudyError("EVIDENCE", "Imported label is absent or differs from its recorded judgment");
1595
+ for (const judgment of translated) if (!grading.judgments.some((entry) => (0, _orkestrel_contract.canonicalStringify)(entry) === (0, _orkestrel_contract.canonicalStringify)(judgment))) throw new _src_core.StudyError("EVIDENCE", "Imported label is absent or differs from its recorded judgment");
2379
1596
  }
2380
1597
  };
2381
1598
  //#endregion
@@ -2391,6 +1608,7 @@ study grade <result> --rubric <path>
2391
1608
  study label <result> --grading <sha256> --labels <path>
2392
1609
  study report <result> --grading <sha256>
2393
1610
  study --help
1611
+ study --version
2394
1612
 
2395
1613
  Profiles default to local. Flags require values and may appear only once.
2396
1614
  Results default to tmp/studies/<result>; scratch also uses tmp/studies.
@@ -2406,6 +1624,81 @@ Exit codes: 0 success, 64 invalid input, 124 deadline, 130 interruption, 1 opera
2406
1624
  `;
2407
1625
  //#endregion
2408
1626
  //#region src/server/commands/helpers.ts
1627
+ /** Reads one declaration file and refuses a missing or linked path with its root-relative path.
1628
+ * @param root - The consumer root used to describe a missing or linked declaration.
1629
+ * @param path - The already resolved and contained declaration path.
1630
+ * @returns The exact declaration bytes under the custody metadata allowance.
1631
+ * @throws StudyError - Thrown with code `CONFIG` when the path is missing or linked; other custody refusals retain their codes.
1632
+ * @example Read declaration bytes
1633
+ * ```ts
1634
+ * import { readStudyDeclarationBytes } from '@orkestrel/study/server'
1635
+ *
1636
+ * declare const root: string
1637
+ * declare const path: string
1638
+ * await readStudyDeclarationBytes(root, path)
1639
+ * ```
1640
+ */
1641
+ async function readStudyDeclarationBytes(root, path) {
1642
+ try {
1643
+ const absolute = (0, node_path.resolve)(path);
1644
+ const origin = (0, node_path.parse)(absolute).root;
1645
+ const names = (0, node_path.relative)(origin, absolute).split(node_path.sep).filter(Boolean);
1646
+ for (const [index] of names.entries()) {
1647
+ const candidate = (0, node_path.join)(origin, ...names.slice(0, index + 1));
1648
+ const stat = await (0, node_fs_promises.lstat)(candidate);
1649
+ if (stat.isSymbolicLink()) throw new _src_core.StudyError("CONFIG", `Declaration path is linked: ${(0, node_path.relative)(root, candidate).split(node_path.sep).join("/")}`);
1650
+ if (!stat.isDirectory()) break;
1651
+ }
1652
+ return await readStudyBytes(path, STUDY_CUSTODY_BYTES);
1653
+ } catch (error) {
1654
+ if (matchesSystemError(error, "ENOENT")) throw new _src_core.StudyError("CONFIG", `Declaration is missing: ${(0, node_path.relative)(root, path).split(node_path.sep).join("/")}`);
1655
+ throw error;
1656
+ }
1657
+ }
1658
+ /** Reads a study declaration, its driver's scenarios, and its rubric as the command resolves them.
1659
+ * @param root - The consumer root containing the studies directory.
1660
+ * @param study - The selected study name.
1661
+ * @param signal - Optional cancellation checked between scenario reads.
1662
+ * @returns The parsed declaration, driver bundle, ordered scenarios, fixtures, and selected rubric.
1663
+ * @throws StudyError - Thrown when declarations are missing, invalid, or incompatible.
1664
+ * @example Read a study declaration
1665
+ * ```ts
1666
+ * import type { StudyDeclaration } from '@orkestrel/study/server'
1667
+ * import { readStudyDeclaration } from '@orkestrel/study/server'
1668
+ *
1669
+ * declare const root: string
1670
+ * const declaration: StudyDeclaration = await readStudyDeclaration(root, 'assessment')
1671
+ * ```
1672
+ */
1673
+ async function readStudyDeclaration(root, study, signal) {
1674
+ const directory = resolveStudyPath((0, node_path.join)(root, "studies"), study);
1675
+ const definition = (0, _src_core.parseStudyDefinition)(decodeStudyJSON(await readStudyDeclarationBytes(root, resolveStudyPath(directory, "study.json"))));
1676
+ if (definition.name !== study) throw new _src_core.StudyError("CONFIG", "Study name differs from its selected directory");
1677
+ const bundle = resolveStudyPath((0, node_path.join)(root, "studies"), definition.driver);
1678
+ const steps = [];
1679
+ for (const id of definition.scenarios) {
1680
+ signal?.throwIfAborted();
1681
+ const path = `scenarios/${id}.json`;
1682
+ const bytes = await readStudyDeclarationBytes(root, resolveStudyPath(bundle, path));
1683
+ const scenario = (0, _src_core.parseStudyScenario)(decodeStudyJSON(bytes));
1684
+ if (scenario.id !== id) throw new _src_core.StudyError("CONFIG", "Scenario identity is incompatible");
1685
+ steps.push({
1686
+ scenario,
1687
+ fixture: {
1688
+ path,
1689
+ digest: hashStudyBytes(bytes)
1690
+ }
1691
+ });
1692
+ }
1693
+ const rubric = (0, _src_core.parseStudyRubric)(decodeStudyJSON(await readStudyDeclarationBytes(root, resolveStudyPath(directory, definition.rubric))));
1694
+ return {
1695
+ bundle,
1696
+ definition,
1697
+ scenarios: Object.freeze(steps.map((step) => step.scenario)),
1698
+ fixtures: Object.freeze(steps.map((step) => step.fixture)),
1699
+ rubric
1700
+ };
1701
+ }
2409
1702
  /**
2410
1703
  * Resolves a portable relative artifact path strictly inside its selected directory.
2411
1704
  * @param directory - The selected custody or study directory
@@ -2419,9 +1712,9 @@ Exit codes: 0 success, 64 invalid input, 124 deadline, 130 interruption, 1 opera
2419
1712
  */
2420
1713
  function resolveStudyPath(directory, path) {
2421
1714
  const names = path.split("/");
2422
- if (path === "" || (0, node_path.isAbsolute)(path) || /[\\<>:"|?*]/.test(path) || Array.from(path).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || names.some((name) => name === "" || name === "." || name === ".." || name.endsWith(".") || name.endsWith(" ") || STUDY_RESERVED_NAME.test(name.split(".")[0] ?? ""))) throw new StudyError("CONFIG", "Artifact path must be a portable contained relative path");
1715
+ if (path === "" || (0, node_path.isAbsolute)(path) || /[\\<>:"|?*]/.test(path) || Array.from(path).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || names.some((name) => name === "" || name === "." || name === ".." || name.endsWith(".") || name.endsWith(" ") || STUDY_RESERVED_NAME.test(name.split(".")[0] ?? ""))) throw new _src_core.StudyError("CONFIG", "Artifact path must be a portable contained relative path");
2423
1716
  const absolute = (0, node_path.resolve)(directory, ...names);
2424
- if (!containsArtifact((0, node_path.resolve)(directory), absolute)) throw new StudyError("CONFIG", "Artifact path escapes its selected directory");
1717
+ if (!containsArtifact((0, node_path.resolve)(directory), absolute)) throw new _src_core.StudyError("CONFIG", "Artifact path escapes its selected directory");
2425
1718
  return absolute;
2426
1719
  }
2427
1720
  /**
@@ -2500,12 +1793,12 @@ async function collectStudyEvidence(result) {
2500
1793
  const path = `trials/${trial.id}/evidence.jsonl`;
2501
1794
  const file = result.seal.files.find((entry) => entry.path === path);
2502
1795
  const bytes = await readStudyBytes((0, node_path.join)(result.directory, "execution", "trials", trial.id, "evidence.jsonl"), result.manifest.bounds.trial.bytes);
2503
- if (file === void 0 || hashStudyBytes(bytes) !== file.digest) throw new StudyError("CUSTODY", "Evidence changed after execution verification");
1796
+ if (file === void 0 || hashStudyBytes(bytes) !== file.digest) throw new _src_core.StudyError("CUSTODY", "Evidence changed after execution verification");
2504
1797
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2505
- if (text !== "" && !text.endsWith("\n")) throw new StudyError("CUSTODY", "Evidence is incomplete");
1798
+ if (text !== "" && !text.endsWith("\n")) throw new _src_core.StudyError("CUSTODY", "Evidence is incomplete");
2506
1799
  const rows = text.split(/\r\n|\n/).slice(0, -1).map((line) => {
2507
1800
  const row = (0, _orkestrel_contract.parseJSON)(line);
2508
- if (!isStudyCustodyEvidence(row)) throw new StudyError("CUSTODY", "Evidence row is invalid");
1801
+ if (!isStudyCustodyEvidence(row)) throw new _src_core.StudyError("CUSTODY", "Evidence row is invalid");
2509
1802
  return row;
2510
1803
  });
2511
1804
  retained.push([trial.id, Object.freeze(rows)]);
@@ -2545,7 +1838,7 @@ function computeStudyPartition(records) {
2545
1838
  * ```
2546
1839
  */
2547
1840
  function computeStudyAttributions(sources) {
2548
- return STUDY_CAUSES.flatMap((cause) => [...STUDY_ROLES, void 0].flatMap((role) => [...STUDY_LAYERS, void 0].flatMap((layer) => {
1841
+ return _src_core.STUDY_CAUSES.flatMap((cause) => [..._src_core.STUDY_ROLES, void 0].flatMap((role) => [..._src_core.STUDY_LAYERS, void 0].flatMap((layer) => {
2549
1842
  const group = sources.filter((source) => source.failure.cause === cause && source.failure.role === role && source.failure.stop?.layer === layer);
2550
1843
  if (group.length === 0) return [];
2551
1844
  return [{
@@ -2558,7 +1851,7 @@ function computeStudyAttributions(sources) {
2558
1851
  return stop === void 0 ? [] : [{
2559
1852
  trial: source.trial,
2560
1853
  ...source.goal === void 0 ? {} : { goal: source.goal },
2561
- ...Object.fromEntries(Object.entries(studyStopToJSON(stop) ?? {}).filter(([key]) => key !== "layer"))
1854
+ ...Object.fromEntries(Object.entries((0, _src_core.studyStopToJSON)(stop) ?? {}).filter(([key]) => key !== "layer"))
2562
1855
  }];
2563
1856
  })
2564
1857
  }];
@@ -2581,7 +1874,7 @@ function computeStudyAttributions(sources) {
2581
1874
  * ```
2582
1875
  */
2583
1876
  function computeStudyReport(result, rubric, grading) {
2584
- const scores = computeStudyScores(result.manifest, rubric, grading);
1877
+ const scores = (0, _src_core.computeStudyScores)(result.manifest, rubric, grading);
2585
1878
  const observations = result.attempts.flatMap((attempt) => attempt.observations);
2586
1879
  const usage = observations.flatMap((observation) => observation.usage);
2587
1880
  const completed = new Set(observations.filter((observation) => observation.product !== void 0 && !observation.product.partial && observation.failure === void 0));
@@ -2638,7 +1931,7 @@ function computeStudyReport(result, rubric, grading) {
2638
1931
  terminal: result.attempts.length,
2639
1932
  ...computeStudyPartition(result.attempts),
2640
1933
  completed: result.attempts.filter((attempt) => attempt.failure === void 0 && attempt.observations.every((observation) => completed.has(observation))).length,
2641
- faults: STUDY_CAUSES.map((cause) => ({
1934
+ faults: _src_core.STUDY_CAUSES.map((cause) => ({
2642
1935
  cause,
2643
1936
  ...computeStudyPartition(result.attempts.filter((attempt) => attempt.failure?.cause === cause))
2644
1937
  })),
@@ -2655,7 +1948,7 @@ function computeStudyReport(result, rubric, grading) {
2655
1948
  products: observations.filter((observation) => observation.product !== void 0).length,
2656
1949
  partial: observations.filter((observation) => observation.product?.partial === true).length,
2657
1950
  completed: observations.filter((observation) => completed.has(observation)).length,
2658
- faults: STUDY_CAUSES.map((cause) => ({
1951
+ faults: _src_core.STUDY_CAUSES.map((cause) => ({
2659
1952
  cause,
2660
1953
  ...computeStudyPartition(observations.filter((observation) => observation.failure?.cause === cause))
2661
1954
  })),
@@ -2669,7 +1962,7 @@ function computeStudyReport(result, rubric, grading) {
2669
1962
  },
2670
1963
  costs: {
2671
1964
  duration: result.attempts.reduce((total, attempt) => total + attempt.duration, 0),
2672
- roles: STUDY_ROLES.map((role) => {
1965
+ roles: _src_core.STUDY_ROLES.map((role) => {
2673
1966
  const charges = usage.filter((entry) => entry.role === role);
2674
1967
  return {
2675
1968
  role,
@@ -2687,1013 +1980,97 @@ function computeStudyReport(result, rubric, grading) {
2687
1980
  };
2688
1981
  })
2689
1982
  }
2690
- };
2691
- }
2692
- //#endregion
2693
- //#region src/server/commands/parsers.ts
2694
- /** Parses public command tokens with operation-specific grammar diagnostics.
2695
- * @param arguments_ - Exact command tokens, after the CLI help boundary.
2696
- * @returns The independent operation request.
2697
- * @throws StudyError when a token, required flag, or portable path is invalid.
2698
- * @example Parse a study command
2699
- * ```ts
2700
- * import { parseStudyArguments } from '@orkestrel/study/server'
2701
- *
2702
- * parseStudyArguments(['check', 'files', '--profile', 'local'])
2703
- * ```
2704
- */
2705
- function parseStudyArguments(arguments_) {
2706
- const command = arguments_[0];
2707
- const target = arguments_[1];
2708
- const usage = "study check <study> [--profile <name>]\nstudy execute <study> [--profile <name>] [--out <path>] [--expected <sha256>]\nstudy grade <result> --rubric <path>\nstudy label <result> --grading <sha256> --labels <path>\nstudy report <result> --grading <sha256>\nstudy --help\n\nProfiles default to local. Flags require values and may appear only once.\nResults default to tmp/studies/<result>; scratch also uses tmp/studies.\nA result is its id under tmp/studies, a root-relative path, or an absolute path.\nRelative paths, including --out, must stay inside the consumer working directory.\nOutput directories must be new; existing results are never overwritten.\n--expected pins the fingerprint returned by check. --grading selects an exact grading.\n--rubric selects independent criteria. --labels imports caller-supplied semantic labels.\nSuccess writes one JSON document; next contains suggested argv, never executed automatically.\ncheck, grade, label and report have a fixed allowance of 60 seconds.\nexecute checks first, then uses the declared series duration plus 15 seconds for cleanup.\nExit codes: 0 success, 64 invalid input, 124 deadline, 130 interruption, 1 operational failure.\n".split("\n").find((line) => line.startsWith("study " + command + " ")) ?? "Expected study check, execute, grade, label, or report; use study --help.";
2709
- if (![
2710
- "check",
2711
- "execute",
2712
- "grade",
2713
- "label",
2714
- "report"
2715
- ].includes(command ?? "")) throw new StudyError("CONFIG", "Unknown or missing command " + JSON.stringify(command ?? "<command>") + ". " + usage);
2716
- if (target === void 0 || target === "" || target.startsWith("-")) throw new StudyError("CONFIG", "Missing <" + (command === "check" || command === "execute" ? "study" : "result") + "> before " + JSON.stringify(target ?? "<end>") + ". " + usage);
2717
- const allowed = command === "check" ? ["--profile"] : command === "execute" ? [
2718
- "--profile",
2719
- "--out",
2720
- "--expected"
2721
- ] : command === "grade" ? ["--rubric"] : command === "label" ? ["--grading", "--labels"] : ["--grading"];
2722
- const tokens = arguments_.slice(2);
2723
- const pairs = [...tokens.entries()].flatMap(([index, flag]) => index % 2 === 0 ? [{
2724
- flag,
2725
- value: tokens[index + 1]
2726
- }] : []);
2727
- const firsts = new Map(pairs.map(({ flag }, index) => [flag, index]).reverse());
2728
- for (const [index, { flag, value }] of pairs.entries()) {
2729
- if (flag === void 0 || !allowed.includes(flag)) throw new StudyError("CONFIG", JSON.stringify(flag ?? "<end>") + " is not a " + command + " flag; accepts " + allowed.join(", ") + ". " + usage);
2730
- if (firsts.get(flag) !== index) throw new StudyError("CONFIG", flag + " is duplicate. " + usage);
2731
- if (value === void 0 || value === "" || value.startsWith("-")) throw new StudyError("CONFIG", flag + " requires a value before " + JSON.stringify(value ?? "<end>") + ". " + usage);
2732
- }
2733
- const flags = new Map(pairs.flatMap(({ flag, value }) => flag === void 0 || value === void 0 ? [] : [[flag, value]]));
2734
- if (command === "check" || command === "execute") {
2735
- if (!isStudyCustodyName(target)) throw new StudyError("CONFIG", "Invalid study token " + JSON.stringify(target) + "; use a portable study name. " + usage);
2736
- const profile = flags.get("--profile") ?? "local";
2737
- const out = flags.get("--out");
2738
- const expected = flags.get("--expected");
2739
- if (!isStudyCustodyName(profile)) throw new StudyError("CONFIG", "--profile " + JSON.stringify(profile) + " must be a portable name. " + usage);
2740
- if (out !== void 0 && !(0, node_path.isAbsolute)(out)) try {
2741
- resolveStudyPath((0, node_path.resolve)("."), out);
2742
- } catch {
2743
- throw new StudyError("CONFIG", "--out " + JSON.stringify(out) + " must be a portable contained path and cannot escape the consumer root. " + usage);
2744
- }
2745
- if (expected !== void 0 && !isStudyDigest(expected)) throw new StudyError("CONFIG", "--expected " + JSON.stringify(expected) + " must be a SHA-256 fingerprint. " + usage);
2746
- return {
2747
- command,
2748
- target,
2749
- profile,
2750
- out,
2751
- expected
2752
- };
2753
- }
2754
- if (command === "grade") {
2755
- const rubric = flags.get("--rubric");
2756
- if (rubric === void 0) throw new StudyError("CONFIG", "Missing --rubric. " + usage);
2757
- return {
2758
- command,
2759
- target,
2760
- rubric
2761
- };
2762
- }
2763
- const grading = flags.get("--grading");
2764
- if (!isStudyDigest(grading)) throw new StudyError("CONFIG", "--grading " + JSON.stringify(grading ?? "<missing>") + " must be an exact SHA-256 grading identity. " + usage);
2765
- if (command === "label") {
2766
- const labels = flags.get("--labels");
2767
- if (labels === void 0) throw new StudyError("CONFIG", "Missing --labels. " + usage);
2768
- return {
2769
- command,
2770
- target,
2771
- grading,
2772
- labels
2773
- };
2774
- }
2775
- return {
2776
- command: "report",
2777
- target,
2778
- grading
2779
- };
2780
- }
2781
- //#endregion
2782
- //#region src/core/engines/helpers.ts
2783
- /** Converts an operational reading to its throwable boundary.
2784
- * @param failure - The retained operational reading.
2785
- * @returns An error preserving the reading's cause and responsibility.
2786
- */
2787
- function failureToStudyError(failure) {
2788
- return new StudyError(failure.cause === "capacity" ? "CAPACITY" : failure.cause === "deadline" ? "DEADLINE" : "FAULT", failure.message, failure);
2789
- }
2790
- /** Reads an operational error without inventing a task verdict.
2791
- * @remarks
2792
- * A branded `BudgetError` from any copy of the budget package reads as a `budget` stop, because the
2793
- * error alone does not prove which budget object raised it; the Agent record names a `meter` stop.
2794
- * An unbranded lookalike reads as an infrastructure failure.
2795
- * @param error - The caught error.
2796
- * @returns Existing attribution, the capacity a branded budget error names, the cause a bare
2797
- * capacity or deadline code names, or an infrastructure failure.
2798
- */
2799
- function readStudyFailure(error) {
2800
- if (isStudyError(error)) {
2801
- const { failure, code, message } = error;
2802
- if (failure !== void 0) return failure;
2803
- if (code === "CAPACITY") return {
2804
- cause: "capacity",
2805
- message,
2806
- role: void 0,
2807
- stop: void 0
2808
- };
2809
- if (code === "DEADLINE") return {
2810
- cause: "deadline",
2811
- message,
2812
- role: void 0,
2813
- stop: void 0
2814
- };
2815
- }
2816
- if ((0, _orkestrel_budget.isBudgetError)(error)) {
2817
- const reading = (0, _orkestrel_contract.attempt)(() => {
2818
- const context = error.context;
2819
- return {
2820
- id: context.id,
2821
- max: context.max,
2822
- consumed: context.consumed,
2823
- message: error.message
2824
- };
2825
- });
2826
- if (reading.success) return {
2827
- cause: "capacity",
2828
- message: reading.value.message,
2829
- role: void 0,
2830
- stop: {
2831
- layer: "budget",
2832
- id: (0, _orkestrel_contract.isNonEmptyString)(reading.value.id) ? reading.value.id : "Agent budget",
2833
- max: parseStudyReading(reading.value.max),
2834
- consumed: parseStudyReading(reading.value.consumed)
2835
- }
2836
- };
2837
- }
2838
- return {
2839
- cause: "infrastructure",
2840
- message: (0, _orkestrel_contract.isError)(error) ? error.message : "Study operation rejected without an Error",
2841
- role: void 0,
2842
- stop: void 0
2843
- };
2844
- }
2845
- /**
2846
- * Computes the stop of a request whose actual usage crossed the ceiling it was admitted under.
2847
- * @param charge - The request's admitted ceilings
2848
- * @param usage - The request's measured usage
2849
- * @returns The `scope` stop for the first quantity, tokens then bytes, whose known usage exceeds its admitted ceiling; undefined when none does
2850
- * @example
2851
- * ```ts
2852
- * computeStudyAdmittedStop({ role: 'agent', tokens: 10, bytes: 100 }, usage) // { layer: 'scope', id: 'Admitted tokens', max: 10, consumed: 12 }
2853
- * ```
2854
- */
2855
- function computeStudyAdmittedStop(charge, usage) {
2856
- if (usage.tokens !== void 0 && usage.tokens > charge.tokens) return {
2857
- layer: "scope",
2858
- id: "Admitted tokens",
2859
- max: charge.tokens,
2860
- consumed: usage.tokens
2861
- };
2862
- if (usage.bytes !== void 0 && usage.bytes > charge.bytes) return {
2863
- layer: "scope",
2864
- id: "Admitted bytes",
2865
- max: charge.bytes,
2866
- consumed: usage.bytes
2867
- };
2868
- }
2869
- /**
2870
- * Computes the stop of a product the output allowances cannot hold.
2871
- * @param bytes - The encoded size of the product's value
2872
- * @param limit - The goal's byte allowance
2873
- * @param outputs - The trial and series output budgets, in the order they refuse
2874
- * @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
2875
- * @example
2876
- * ```ts
2877
- * computeStudyOutputStop(2048, 1024, []) // { layer: 'scope', id: 'Goal bytes', max: 1024, consumed: 2048 }
2878
- * ```
2879
- */
2880
- function computeStudyOutputStop(bytes, limit, outputs) {
2881
- if (bytes > limit) return {
2882
- layer: "scope",
2883
- id: "Goal bytes",
2884
- max: limit,
2885
- consumed: bytes
2886
- };
2887
- const refusal = outputs.find((budget) => bytes > budget.remaining);
2888
- return refusal === void 0 ? void 0 : {
2889
- layer: "scope",
2890
- id: refusal.id,
2891
- max: refusal.max,
2892
- consumed: refusal.consumed + bytes
2893
- };
2894
- }
2895
- /**
2896
- * Records an error that escaped admitted work as the failure of its ticket.
2897
- * @param ticket - The admitted request whose work escaped
2898
- * @param error - The caught value, read through `readStudyFailure`
2899
- * @param role - The responsibility that replaces any role the reading carries; Default: the carried role, else the ticket's admitted role
2900
- * @remarks
2901
- * A ticket that already settled or whose signal aborted is left untouched, because its owner
2902
- * already holds the failure. A remote ticket's aborted signal does not mean it settled locally,
2903
- * so the parent's ownership is respected rather than inferred as local settlement. A refusal
2904
- * thrown by the ticket's own settlement propagates.
2905
- * @throws Thrown when the ticket's settlement refuses the failure.
2906
- * @example Fail an escaped request
2907
- * ```ts
2908
- * import type { StudyTicket } from '@orkestrel/study'
2909
- * import { failStudyTicket } from '@orkestrel/study'
2910
- *
2911
- * declare const ticket: StudyTicket
2912
- *
2913
- * failStudyTicket(ticket, new Error('Provider refused'), 'tool')
2914
- * ```
2915
- */
2916
- function failStudyTicket(ticket, error, role) {
2917
- if (ticket.settled || ticket.signal.aborted) return;
2918
- const failure = readStudyFailure(error);
2919
- ticket.fail(role === void 0 ? failure : {
2920
- ...failure,
2921
- role
2922
- });
2923
- }
2924
- //#endregion
2925
- //#region src/core/engines/StudyControl.ts
2926
- /** Retains the first operational failure while bounding asynchronous waits. */
2927
- var StudyControl = class {
2928
- #abort;
2929
- #listeners = new _orkestrel_abort.Abort();
2930
- #failure;
2931
- /** Links cancellation to the caller without assigning it a task verdict.
2932
- * @param signal - Optional parent cancellation.
2933
- */
2934
- constructor(signal) {
2935
- this.#abort = new _orkestrel_abort.Abort(signal === void 0 ? void 0 : { signal });
2936
- if (this.#abort.aborted) this.#interrupt();
2937
- else this.#abort.signal.addEventListener("abort", () => this.#interrupt(), {
2938
- once: true,
2939
- signal: this.#listeners.signal
2940
- });
2941
- }
2942
- get signal() {
2943
- return this.#abort.signal;
2944
- }
2945
- get failure() {
2946
- return this.#failure;
2947
- }
2948
- /** Latches an operational failure and cancels all linked work.
2949
- * @param failure - The first attribution to preserve.
2950
- */
2951
- fail(failure) {
2952
- if (this.#failure !== void 0) return;
2953
- this.#failure = Object.freeze({
2954
- ...failure,
2955
- stop: failure.stop === void 0 ? void 0 : Object.freeze({ ...failure.stop })
2956
- });
2957
- this.#abort.abort(failureToStudyError(this.#failure));
2958
- }
2959
- /** Races work against cancellation and removes its listener after either ending.
2960
- * @param work - Already-started work whose rejection remains observed after cancellation.
2961
- * @returns The work's result if it settles before cancellation.
2962
- * @throws Thrown with the retained operational attribution when cancelled.
2963
- */
2964
- wait(work) {
2965
- const listeners = new _orkestrel_abort.Abort();
2966
- return new Promise((resolve, reject) => {
2967
- Promise.resolve(work).then(resolve, reject);
2968
- if (this.#abort.aborted) reject(this.#reason());
2969
- else this.#abort.signal.addEventListener("abort", () => reject(this.#reason()), {
2970
- once: true,
2971
- signal: listeners.signal
2972
- });
2973
- }).finally(() => listeners.abort());
2974
- }
2975
- /** Removes the lifecycle listener without changing the retained failure. */
2976
- close() {
2977
- this.#listeners.abort();
2978
- }
2979
- #reason() {
2980
- return failureToStudyError(this.#failure ?? {
2981
- cause: "interrupted",
2982
- message: "Study interrupted",
2983
- role: void 0,
2984
- stop: void 0
2985
- });
2986
- }
2987
- #interrupt() {
2988
- if (this.#failure === void 0) this.fail({
2989
- cause: "interrupted",
2990
- message: "Study interrupted by parent cancellation",
2991
- role: void 0,
2992
- stop: void 0
2993
- });
2994
- }
2995
- };
2996
- //#endregion
2997
- //#region src/core/engines/StudyScope.ts
2998
- /** Reserves finite request ceilings around cumulative actual budgets and one deadline. */
2999
- var StudyScope = class {
3000
- #options;
3001
- #calls;
3002
- #tokens;
3003
- #bytes;
3004
- #timeout;
3005
- #listeners = new _orkestrel_abort.Abort();
3006
- #pending = /* @__PURE__ */ new Map();
3007
- #started = performance.now();
3008
- #closed = false;
3009
- /** Starts a timed scope with independent counters.
3010
- * @param options - Finite limit, shared failure owner, and diagnostic label.
3011
- */
3012
- constructor(options) {
3013
- if (!isStudyLimit(options.limit)) throw new StudyError("CONFIG", "Invalid study scope limit");
3014
- this.#options = {
3015
- ...options,
3016
- limit: { ...options.limit }
3017
- };
3018
- this.#calls = new _orkestrel_budget.Budget({
3019
- max: options.limit.calls,
3020
- consumer: (value) => value
3021
- });
3022
- this.#tokens = new _orkestrel_budget.Budget({
3023
- max: options.limit.tokens,
3024
- consumer: (value) => value
3025
- });
3026
- this.#bytes = new _orkestrel_budget.Budget({
3027
- max: options.limit.bytes,
3028
- consumer: (value) => value
3029
- });
3030
- this.#timeout = new _orkestrel_timeout.Timeout({ ms: options.limit.duration });
3031
- this.#timeout.signal.addEventListener("abort", () => this.#expire(), {
3032
- once: true,
3033
- signal: this.#listeners.signal
3034
- });
3035
- this.#timeout.start();
3036
- }
3037
- /** Checks cumulative consumption and every pending ceiling before admission.
3038
- * @param charge - One prospective request's ceilings.
3039
- * @throws Thrown when this scope cannot admit the request.
3040
- */
3041
- check(charge) {
3042
- this.#checkTime();
3043
- if (this.#closed) throw new StudyError("FAULT", "Study scope is closed");
3044
- const prior = this.#options.control.failure;
3045
- if (prior !== void 0) throw failureToStudyError(prior);
3046
- if (charge === void 0) return;
3047
- const pending = [...this.#pending.values()];
3048
- const tokens = pending.reduce((total, entry) => total + entry.tokens, 0);
3049
- const bytes = pending.reduce((total, entry) => total + entry.bytes, 0);
3050
- const { label, limit } = this.#options;
3051
- const refusal = [
3052
- {
3053
- id: `${label} calls`,
3054
- refused: this.#calls.remaining - this.#pending.size < 1,
3055
- max: limit.calls,
3056
- consumed: this.#calls.consumed + this.#pending.size + 1
3057
- },
3058
- {
3059
- id: `${label} tokens`,
3060
- refused: this.#tokens.remaining - tokens < charge.tokens,
3061
- max: limit.tokens,
3062
- consumed: this.#tokens.consumed + tokens + charge.tokens
3063
- },
3064
- {
3065
- id: `${label} bytes`,
3066
- refused: this.#bytes.remaining - bytes < charge.bytes,
3067
- max: limit.bytes,
3068
- consumed: this.#bytes.consumed + bytes + charge.bytes
3069
- }
3070
- ].find((entry) => entry.refused);
3071
- if (refusal !== void 0) {
3072
- const failure = {
3073
- cause: "capacity",
3074
- message: `${label} allowance cannot admit the request`,
3075
- role: charge.role,
3076
- stop: {
3077
- layer: "scope",
3078
- id: refusal.id,
3079
- max: refusal.max,
3080
- consumed: refusal.consumed
3081
- }
3082
- };
3083
- this.#options.control.fail(failure);
3084
- throw failureToStudyError(failure);
3085
- }
3086
- }
3087
- /** Reserves one request after the enclosing meter checked all scopes.
3088
- * @param id - The request's unique reservation identity.
3089
- * @param charge - Its admitted ceilings.
3090
- */
3091
- reserve(id, charge) {
3092
- if (this.#pending.has(id)) throw new StudyError("FAULT", "Duplicate study reservation");
3093
- this.#pending.set(id, { ...charge });
3094
- }
3095
- /** Commits known charges while retaining ceilings for missing measurements.
3096
- * @param id - The pending reservation identity.
3097
- * @param usage - The request's retained public measurements.
3098
- */
3099
- settle(id, usage) {
3100
- const charge = this.#pending.get(id);
3101
- if (charge === void 0) throw new StudyError("FAULT", "Study reservation is not pending");
3102
- this.#pending.delete(id);
3103
- this.#calls.consume(1);
3104
- this.#tokens.consume(Math.min(charge.tokens, usage.tokens ?? charge.tokens));
3105
- this.#bytes.consume(Math.min(charge.bytes, usage.bytes ?? charge.bytes));
3106
- }
3107
- /** Checks deadline attribution before clearing the timer. */
3108
- close() {
3109
- if (this.#closed) return;
3110
- this.#checkTime();
3111
- this.#closed = true;
3112
- this.#listeners.abort();
3113
- this.#timeout.clear();
3114
- }
3115
- #checkTime() {
3116
- if (!this.#closed && (this.#timeout.expired || performance.now() - this.#started >= this.#options.limit.duration)) this.#expire();
3117
- }
3118
- #expire() {
3119
- this.#options.control.fail({
3120
- cause: "deadline",
3121
- message: `${this.#options.label} deadline expired`,
3122
- role: void 0,
3123
- stop: {
3124
- layer: "scope",
3125
- id: `${this.#options.label} duration`,
3126
- max: this.#options.limit.duration,
3127
- consumed: Math.ceil(performance.now() - this.#started)
3128
- }
3129
- });
3130
- }
3131
- };
3132
- //#endregion
3133
- //#region src/core/engines/StudyDeadline.ts
3134
- /** Bounds each independent operation with a fresh failure owner and deadline. */
3135
- var StudyDeadline = class {
3136
- #options;
3137
- /** Retains the deadline without starting a timer.
3138
- * @param options - Finite duration in milliseconds and diagnostic label.
3139
- */
3140
- constructor(options) {
3141
- this.#options = { ...options };
3142
- }
3143
- /** Waits for work under a fresh failure owner and releases its timer and listener.
3144
- * @param work - Already-started work whose late rejection remains observed.
3145
- * @returns The work's result when it settles before the deadline.
3146
- * @throws Thrown for an invalid duration, the work's own rejection, or the labelled deadline.
3147
- */
3148
- async wait(work) {
3149
- const control = new StudyControl();
3150
- const scope = this.#open(control, work);
3151
- try {
3152
- const result = await control.wait(work);
3153
- scope.check();
3154
- return result;
3155
- } finally {
3156
- scope.close();
3157
- control.close();
3158
- }
3159
- }
3160
- #open(control, work) {
3161
- try {
3162
- return new StudyScope({
3163
- limit: {
3164
- duration: this.#options.duration,
3165
- calls: 1,
3166
- tokens: 1,
3167
- bytes: 1
3168
- },
3169
- control,
3170
- label: this.#options.label
3171
- });
3172
- } catch (error) {
3173
- Promise.resolve(work).catch(() => void 0);
3174
- control.close();
3175
- throw error;
3176
- }
3177
- }
3178
- };
3179
- //#endregion
3180
- //#region src/core/engines/StudyRequest.ts
3181
- /** Settles an admitted request once and propagates operational failure to its owner. */
3182
- var StudyRequest = class {
3183
- #options;
3184
- #timeout;
3185
- #signal;
3186
- #listeners = new _orkestrel_abort.Abort();
3187
- #started = performance.now();
3188
- #settled = false;
3189
- /** Starts the request deadline after its enclosing scopes reserve capacity.
3190
- * @param options - Admitted ceilings, request deadline, and settlement callback.
3191
- */
3192
- constructor(options) {
3193
- this.#options = {
3194
- ...options,
3195
- charge: { ...options.charge }
3196
- };
3197
- this.#timeout = new _orkestrel_timeout.Timeout({ ms: options.duration });
3198
- this.#signal = (0, _orkestrel_abort.linkSignal)(this.#timeout.signal, options.control.signal);
3199
- this.#signal.addEventListener("abort", () => this.#abort(), {
3200
- once: true,
3201
- signal: this.#listeners.signal
3202
- });
3203
- this.#timeout.start();
3204
- if (this.#signal.aborted) this.#abort();
3205
- }
3206
- get signal() {
3207
- return this.#signal;
3208
- }
3209
- get settled() {
3210
- return this.#settled;
3211
- }
3212
- /** Retains actual measurements and refuses invalid or excessive charges.
3213
- * @param usage - Measurements attributed to exactly this request.
3214
- * @throws Thrown when settlement is late, repeated, invalid, or beyond admission.
3215
- */
3216
- finish(usage) {
3217
- this.#checkPending();
3218
- if (!isStudyUsage(usage) || usage.role !== this.#options.charge.role || usage.calls !== 1) {
3219
- const failure = {
3220
- cause: "infrastructure",
3221
- message: "Request usage is invalid or misattributed",
3222
- role: this.#options.charge.role,
3223
- stop: void 0
3224
- };
3225
- this.fail(failure);
3226
- throw failureToStudyError(failure);
3227
- }
3228
- const elapsed = performance.now() - this.#started;
3229
- const admitted = computeStudyAdmittedStop(this.#options.charge, usage);
3230
- const failure = this.#timeout.expired || elapsed >= this.#options.duration || usage.duration > this.#options.duration ? {
3231
- cause: "deadline",
3232
- message: "Request deadline expired",
3233
- role: usage.role,
3234
- stop: {
3235
- layer: "scope",
3236
- id: "Request duration",
3237
- max: this.#options.duration,
3238
- consumed: Math.ceil(Math.max(elapsed, usage.duration))
3239
- }
3240
- } : admitted !== void 0 ? {
3241
- cause: "capacity",
3242
- message: "Request usage exceeded its admitted ceiling",
3243
- role: usage.role,
3244
- stop: admitted
3245
- } : void 0;
3246
- this.#complete(usage);
3247
- if (failure !== void 0) {
3248
- this.#options.control.fail(failure);
3249
- throw failureToStudyError(failure);
3250
- }
3251
- }
3252
- /** Retains unknown cost conservatively and latches the first operational failure.
3253
- * @param failure - The operational request failure.
3254
- * @throws Thrown when this request was already settled.
3255
- */
3256
- fail(failure) {
3257
- this.#checkPending();
3258
- this.#complete({
3259
- role: this.#options.charge.role,
3260
- calls: 1,
3261
- tokens: void 0,
3262
- bytes: void 0,
3263
- duration: Math.ceil(performance.now() - this.#started)
3264
- });
3265
- this.#options.control.fail({
3266
- ...failure,
3267
- role: failure.role ?? this.#options.charge.role
3268
- });
3269
- }
3270
- #complete(usage) {
3271
- this.#settled = true;
3272
- this.#listeners.abort();
3273
- this.#timeout.clear();
3274
- this.#options.settle(Object.freeze({ ...usage }));
3275
- }
3276
- #checkPending() {
3277
- if (this.#settled) {
3278
- this.#options.control.fail({
3279
- cause: "infrastructure",
3280
- message: "Request settlement is late or repeated",
3281
- role: this.#options.charge.role,
3282
- stop: void 0
3283
- });
3284
- throw new StudyError("FAULT", "Request settlement is late or repeated", this.#options.control.failure);
3285
- }
3286
- }
3287
- #abort() {
3288
- if (this.#settled) return;
3289
- const expired = this.#timeout.expired;
3290
- this.fail(this.#options.control.failure ?? {
3291
- cause: expired ? "deadline" : "interrupted",
3292
- message: expired ? "Request deadline expired" : "Request interrupted",
3293
- role: this.#options.charge.role,
3294
- stop: expired ? {
3295
- layer: "scope",
3296
- id: "Request duration",
3297
- max: this.#options.duration,
3298
- consumed: Math.ceil(performance.now() - this.#started)
3299
- } : void 0
3300
- });
3301
- }
3302
- };
3303
- //#endregion
3304
- //#region src/core/engines/StudyMeter.ts
3305
- /** Admits request ceilings across every enclosing scope before dispatch. */
3306
- var StudyMeter = class {
3307
- #options;
3308
- #pending = /* @__PURE__ */ new Set();
3309
- #usage = [];
3310
- #closed = false;
3311
- /** Holds the goal's shared admission scopes.
3312
- * @param options - Request bound, enclosing scopes, and shared failure owner.
3313
- */
3314
- constructor(options) {
3315
- if (!isStudyLimit(options.request) || options.request.calls !== 1 || options.scopes.length === 0) throw new StudyError("CONFIG", "Invalid request admission bounds");
3316
- this.#options = {
3317
- ...options,
3318
- request: { ...options.request },
3319
- scopes: [...options.scopes]
3320
- };
3321
- }
3322
- get signal() {
3323
- return this.#options.control.signal;
3324
- }
3325
- get usage() {
3326
- return Object.freeze([...this.#usage]);
3327
- }
3328
- /** Reserves one request atomically across all enclosing allowances.
3329
- * @param charge - Safe integer token and byte ceilings with the request's role.
3330
- * @returns A request ticket that must settle before the goal completes.
3331
- * @throws Thrown before dispatch if the request exceeds available capacity.
3332
- */
3333
- admit(charge) {
3334
- if (this.#closed) throw new StudyError("FAULT", "Study meter is closed");
3335
- if (this.#options.control.failure !== void 0) throw failureToStudyError(this.#options.control.failure);
3336
- if (!isStudyRole(charge.role) || !Number.isSafeInteger(charge.tokens) || charge.tokens < 0 || !Number.isSafeInteger(charge.bytes) || charge.bytes < 0) {
3337
- const failure = {
3338
- cause: "infrastructure",
3339
- message: "Request admission requires safe nonnegative ceilings",
3340
- role: void 0,
3341
- stop: void 0
3342
- };
3343
- this.#options.control.fail(failure);
3344
- throw failureToStudyError(failure);
3345
- }
3346
- const { request } = this.#options;
3347
- if (charge.tokens > request.tokens || charge.bytes > request.bytes) {
3348
- const tokens = charge.tokens > request.tokens;
3349
- const failure = {
3350
- cause: "capacity",
3351
- message: "Request ceiling exceeds the request allowance",
3352
- role: charge.role,
3353
- stop: {
3354
- layer: "scope",
3355
- id: tokens ? "Request tokens" : "Request bytes",
3356
- max: tokens ? request.tokens : request.bytes,
3357
- consumed: tokens ? charge.tokens : charge.bytes
3358
- }
3359
- };
3360
- this.#options.control.fail(failure);
3361
- throw failureToStudyError(failure);
3362
- }
3363
- for (const scope of this.#options.scopes) scope.check(charge);
3364
- const id = Symbol("request");
3365
- for (const scope of this.#options.scopes) scope.reserve(id, charge);
3366
- this.#pending.add(id);
3367
- return new StudyRequest({
3368
- charge,
3369
- duration: this.#options.request.duration,
3370
- control: this.#options.control,
3371
- settle: (usage) => {
3372
- for (const scope of this.#options.scopes) scope.settle(id, usage);
3373
- this.#pending.delete(id);
3374
- this.#usage = [...this.#usage, usage];
3375
- }
3376
- });
3377
- }
3378
- /** Refuses unfinished requests before closing a goal's admission boundary. */
3379
- close() {
3380
- if (this.#closed) return;
3381
- this.#closed = true;
3382
- if (this.#pending.size > 0) this.#options.control.fail({
3383
- cause: "infrastructure",
3384
- message: "Goal completed with unsettled requests",
3385
- role: void 0,
3386
- stop: void 0
3387
- });
3388
- }
3389
- };
1983
+ };
1984
+ }
3390
1985
  //#endregion
3391
- //#region src/core/engines/StudyTrialLease.ts
3392
- /** Holds one trial's evidence custody, admission scope and driver session from opening to cleanup. */
3393
- var StudyTrialLease = class {
3394
- #trial;
3395
- #options;
3396
- #control;
3397
- #series;
3398
- #deadline;
3399
- #scope;
3400
- #evidence;
3401
- #opening;
3402
- #session;
3403
- /** Binds one scheduled trial to the series' owned inputs, failure owner, series scope and cleanup deadline.
3404
- * @param trial - The scheduled trial.
3405
- * @param options - The series' owned manifest, driver and store.
3406
- * @param control - The series failure owner.
3407
- * @param series - The series admission scope.
3408
- * @param deadline - The custody and cleanup deadline.
3409
- */
3410
- constructor(trial, options, control, series, deadline) {
3411
- this.#trial = trial;
3412
- this.#options = options;
3413
- this.#control = control;
3414
- this.#series = series;
3415
- this.#deadline = deadline;
3416
- }
3417
- /** Reports the trial scope once opened; undefined when the trial inherited a failure. */
3418
- get scope() {
3419
- return this.#scope;
3420
- }
3421
- /** Reports the trial's evidence sink once custody started. */
3422
- get evidence() {
3423
- return this.#evidence;
3424
- }
3425
- /** Reports the driver session once it opened. */
3426
- get session() {
3427
- return this.#session;
3428
- }
3429
- /** Starts evidence custody and, unless a failure is latched, opens the trial scope and driver session.
3430
- * @returns Nothing after the session opened, or after custody alone started under a latched failure.
3431
- * @throws StudyError - Thrown with code `CUSTODY` when custody refuses the trial; thrown for a series or trial limit or an opening failure.
3432
- */
3433
- async open() {
3434
- const control = this.#control;
3435
- if (control.failure !== void 0) {
3436
- this.#evidence = await this.#start();
3437
- return;
3438
- }
3439
- this.#series.check();
3440
- const scope = new StudyScope({
3441
- limit: this.#options.manifest.bounds.trial,
3442
- control,
3443
- label: "Trial"
3444
- });
3445
- this.#scope = scope;
3446
- this.#evidence = await this.#start();
3447
- this.#series.check();
3448
- scope.check();
3449
- const opening = this.#launch();
3450
- this.#opening = opening;
3451
- this.#session = await control.wait(opening);
3452
- }
3453
- /** Closes the opened session, or a session that opened late, then the trial scope.
3454
- * @returns The cleanup failure, or undefined when cleanup succeeded or repeated the latched failure.
3455
- */
3456
- async close() {
3457
- const cleanup = await this.#release();
3458
- this.#scope?.close();
3459
- return cleanup;
3460
- }
3461
- async #release() {
3462
- const session = this.#session;
3463
- const opening = this.#opening;
3464
- if (session !== void 0) try {
3465
- await this.#deadline.wait(session.close());
3466
- return;
3467
- } catch (error) {
3468
- const cleanup = readStudyFailure(error);
3469
- this.#control.fail(cleanup);
3470
- return cleanup;
3471
- }
3472
- if (opening === void 0) return void 0;
3473
- try {
3474
- const late = await this.#deadline.wait(opening);
3475
- await this.#deadline.wait(late.close());
3476
- return;
3477
- } catch (error) {
3478
- const late = readStudyFailure(error);
3479
- return late.message !== this.#control.failure?.message ? late : void 0;
3480
- }
3481
- }
3482
- async #launch() {
3483
- const control = this.#control;
3484
- const session = await this.#options.driver.open(this.#trial, control.signal);
3485
- if (control.failure !== void 0) {
3486
- await this.#deadline.wait(session.close());
3487
- throw failureToStudyError(control.failure);
3488
- }
3489
- return session;
1986
+ //#region src/server/commands/parsers.ts
1987
+ /** Parses public command tokens with operation-specific grammar diagnostics.
1988
+ * @param arguments_ - Exact command tokens, after the CLI help boundary.
1989
+ * @returns The independent operation request.
1990
+ * @throws StudyError when a token, required flag, or portable path is invalid.
1991
+ * @example Parse a study command
1992
+ * ```ts
1993
+ * import { parseStudyArguments } from '@orkestrel/study/server'
1994
+ *
1995
+ * parseStudyArguments(['check', 'files', '--profile', 'local'])
1996
+ * ```
1997
+ */
1998
+ function parseStudyArguments(arguments_) {
1999
+ const command = arguments_[0];
2000
+ const target = arguments_[1];
2001
+ const usage = "study check <study> [--profile <name>]\nstudy execute <study> [--profile <name>] [--out <path>] [--expected <sha256>]\nstudy grade <result> --rubric <path>\nstudy label <result> --grading <sha256> --labels <path>\nstudy report <result> --grading <sha256>\nstudy --help\nstudy --version\n\nProfiles default to local. Flags require values and may appear only once.\nResults default to tmp/studies/<result>; scratch also uses tmp/studies.\nA result is its id under tmp/studies, a root-relative path, or an absolute path.\nRelative paths, including --out, must stay inside the consumer working directory.\nOutput directories must be new; existing results are never overwritten.\n--expected pins the fingerprint returned by check. --grading selects an exact grading.\n--rubric selects independent criteria. --labels imports caller-supplied semantic labels.\nSuccess writes one JSON document; next contains suggested argv, never executed automatically.\ncheck, grade, label and report have a fixed allowance of 60 seconds.\nexecute checks first, then uses the declared series duration plus 15 seconds for cleanup.\nExit codes: 0 success, 64 invalid input, 124 deadline, 130 interruption, 1 operational failure.\n".split("\n").find((line) => line.startsWith("study " + command + " ")) ?? "Expected study check, execute, grade, label, or report; use study --help.";
2002
+ if (![
2003
+ "check",
2004
+ "execute",
2005
+ "grade",
2006
+ "label",
2007
+ "report"
2008
+ ].includes(command ?? "")) throw new _src_core.StudyError("CONFIG", "Unknown or missing command " + JSON.stringify(command ?? "<command>") + ". " + usage);
2009
+ if (target === void 0 || target === "" || target.startsWith("-")) throw new _src_core.StudyError("CONFIG", "Missing <" + (command === "check" || command === "execute" ? "study" : "result") + "> before " + JSON.stringify(target ?? "<end>") + ". " + usage);
2010
+ const allowed = command === "check" ? ["--profile"] : command === "execute" ? [
2011
+ "--profile",
2012
+ "--out",
2013
+ "--expected"
2014
+ ] : command === "grade" ? ["--rubric"] : command === "label" ? ["--grading", "--labels"] : ["--grading"];
2015
+ const tokens = arguments_.slice(2);
2016
+ const pairs = [...tokens.entries()].flatMap(([index, flag]) => index % 2 === 0 ? [{
2017
+ flag,
2018
+ value: tokens[index + 1]
2019
+ }] : []);
2020
+ const firsts = new Map(pairs.map(({ flag }, index) => [flag, index]).reverse());
2021
+ for (const [index, { flag, value }] of pairs.entries()) {
2022
+ if (flag === void 0 || !allowed.includes(flag)) throw new _src_core.StudyError("CONFIG", JSON.stringify(flag ?? "<end>") + " is not a " + command + " flag; accepts " + allowed.join(", ") + ". " + usage);
2023
+ if (firsts.get(flag) !== index) throw new _src_core.StudyError("CONFIG", flag + " is duplicate. " + usage);
2024
+ if (value === void 0 || value === "" || value.startsWith("-")) throw new _src_core.StudyError("CONFIG", flag + " requires a value before " + JSON.stringify(value ?? "<end>") + ". " + usage);
3490
2025
  }
3491
- async #start() {
3492
- const control = this.#control;
3493
- try {
3494
- const pending = this.#options.store.start(this.#trial);
3495
- return control.failure === void 0 ? await control.wait(pending) : await this.#deadline.wait(pending);
3496
- } catch (error) {
3497
- control.fail(readStudyFailure(error));
3498
- const failure = control.failure ?? readStudyFailure(error);
3499
- throw new StudyError("CUSTODY", failure.message, failure);
2026
+ const flags = new Map(pairs.flatMap(({ flag, value }) => flag === void 0 || value === void 0 ? [] : [[flag, value]]));
2027
+ if (command === "check" || command === "execute") {
2028
+ if (!isStudyCustodyName(target)) throw new _src_core.StudyError("CONFIG", "Invalid study token " + JSON.stringify(target) + "; use a portable study name. " + usage);
2029
+ const profile = flags.get("--profile") ?? "local";
2030
+ const out = flags.get("--out");
2031
+ const expected = flags.get("--expected");
2032
+ if (!isStudyCustodyName(profile)) throw new _src_core.StudyError("CONFIG", "--profile " + JSON.stringify(profile) + " must be a portable name. " + usage);
2033
+ if (out !== void 0 && !(0, node_path.isAbsolute)(out)) try {
2034
+ resolveStudyPath((0, node_path.resolve)("."), out);
2035
+ } catch {
2036
+ throw new _src_core.StudyError("CONFIG", "--out " + JSON.stringify(out) + " must be a portable contained path and cannot escape the consumer root. " + usage);
3500
2037
  }
3501
- }
3502
- };
3503
- //#endregion
3504
- //#region src/core/engines/StudySeries.ts
3505
- /** Executes a frozen serial study schedule with finite admission and sticky failures. */
3506
- var StudySeries = class {
3507
- #options;
3508
- #executed = false;
3509
- /** Owns the declared inputs while retaining the concrete driver and custody store.
3510
- * @param options - Frozen manifest, concrete adapters, and parent cancellation.
3511
- */
3512
- constructor(options) {
3513
- this.#options = {
3514
- ...options,
3515
- manifest: structuredClone(options.manifest)
2038
+ if (expected !== void 0 && !(0, _src_core.isStudyDigest)(expected)) throw new _src_core.StudyError("CONFIG", "--expected " + JSON.stringify(expected) + " must be a SHA-256 fingerprint. " + usage);
2039
+ return {
2040
+ command,
2041
+ target,
2042
+ profile,
2043
+ out,
2044
+ expected
3516
2045
  };
3517
2046
  }
3518
- /** Runs every declared trial once and retains explicit unexecuted observations after failure.
3519
- * Custody rejection leaves the run unsealed; callers must seal only a fulfilled execution.
3520
- * @returns Ordered terminal attempts without task success labels.
3521
- * @throws Thrown for invalid configuration or failed or timed out custody operations.
3522
- */
3523
- async execute() {
3524
- if (this.#executed) throw new StudyError("CONFIG", "Study series can execute only once");
3525
- this.#executed = true;
3526
- const manifest = this.#options.manifest;
3527
- const expected = buildStudySchedule(manifest.definition, manifest.scenarios);
3528
- 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");
3529
- const control = new StudyControl(this.#options.signal);
3530
- const scope = new StudyScope({
3531
- limit: manifest.bounds.series,
3532
- control,
3533
- label: "Series"
3534
- });
3535
- const deadline = new StudyDeadline({
3536
- duration: manifest.bounds.request.duration,
3537
- label: "Custody or cleanup"
3538
- });
3539
- const output = new _orkestrel_budget.Budget({
3540
- id: "Series output",
3541
- max: manifest.bounds.series.bytes,
3542
- consumer: (bytes) => bytes
3543
- });
3544
- const attempts = [];
3545
- try {
3546
- for (const trial of manifest.schedule) attempts.push(await this.#executeTrial(trial, control, scope, output, deadline));
3547
- } finally {
3548
- scope.close();
3549
- control.close();
3550
- }
3551
- return Object.freeze(attempts);
3552
- }
3553
- async #executeTrial(trial, control, series, output, deadline) {
3554
- const started = performance.now();
3555
- const scenario = this.#options.manifest.scenarios.find((entry) => entry.id === trial.scenario);
3556
- if (scenario === void 0) throw new StudyError("CONFIG", "Scheduled scenario is absent");
3557
- const prior = control.failure;
3558
- const lease = new StudyTrialLease(trial, this.#options, control, series, deadline);
3559
- const executed = await this.#executeGoals(scenario, lease, control, series, output);
3560
- const cleanup = await this.#release(lease, control, series);
3561
- const failure = control.failure;
3562
- const observations = [...executed, ...scenario.goals.slice(executed.length).map((goal) => {
3563
- const unexecuted = failure === void 0 ? {
3564
- cause: "infrastructure",
3565
- message: "Goal unexecuted because the trial did not open",
3566
- role: void 0,
3567
- stop: void 0
3568
- } : {
3569
- ...failure,
3570
- message: `Goal unexecuted: ${failure.message}`
3571
- };
3572
- return Object.freeze({
3573
- goal: goal.id,
3574
- product: void 0,
3575
- failure: unexecuted,
3576
- usage: Object.freeze([]),
3577
- duration: 0,
3578
- executed: false
3579
- });
3580
- })];
3581
- const attempt = Object.freeze({
3582
- trial,
3583
- observations: Object.freeze(observations),
3584
- failure: cleanup !== void 0 && failure !== void 0 && (prior !== void 0 || failure.message !== cleanup.message) ? {
3585
- ...failure,
3586
- message: `${failure.message}; cleanup: ${cleanup.message}`
3587
- } : failure,
3588
- duration: Math.ceil(performance.now() - started),
3589
- executed: prior === void 0
3590
- });
3591
- try {
3592
- await deadline.wait(this.#options.store.finish(attempt));
3593
- if (attempt.failure === void 0) series.check();
3594
- } catch (error) {
3595
- control.fail(readStudyFailure(error));
3596
- const custody = control.failure ?? readStudyFailure(error);
3597
- throw new StudyError("CUSTODY", custody.message, custody);
3598
- }
3599
- return attempt;
3600
- }
3601
- async #executeGoals(scenario, lease, control, series, output) {
3602
- const observations = [];
3603
- try {
3604
- await lease.open();
3605
- const trialOutput = new _orkestrel_budget.Budget({
3606
- id: "Trial output",
3607
- max: this.#options.manifest.bounds.trial.bytes,
3608
- consumer: (bytes) => bytes
3609
- });
3610
- for (const goal of scenario.goals) {
3611
- const { session, scope, evidence } = lease;
3612
- if (control.failure !== void 0 || session === void 0 || scope === void 0 || evidence === void 0) break;
3613
- series.check();
3614
- scope.check();
3615
- observations.push(await this.#executeGoal(goal, session, evidence, control, [scope, series], [trialOutput, output]));
3616
- }
3617
- } catch (error) {
3618
- if (isStudyError(error) && error.code === "CUSTODY") {
3619
- await this.#release(lease, control, series);
3620
- throw error;
3621
- }
3622
- control.fail(readStudyFailure(error));
3623
- }
3624
- return observations;
3625
- }
3626
- async #release(lease, control, series) {
3627
- const cleanup = await lease.close();
3628
- try {
3629
- series.check();
3630
- } catch (error) {
3631
- control.fail(readStudyFailure(error));
3632
- }
3633
- return cleanup;
3634
- }
3635
- async #executeGoal(goal, session, evidence, control, parents, outputs) {
3636
- const started = performance.now();
3637
- const bounds = this.#options.manifest.bounds;
3638
- const scope = new StudyScope({
3639
- limit: bounds.goal,
3640
- control,
3641
- label: "Goal"
3642
- });
3643
- const meter = new StudyMeter({
3644
- request: bounds.request,
3645
- scopes: [scope, ...parents],
3646
- control
3647
- });
3648
- const product = await this.#produce(goal, session, evidence, control, scope, meter, parents, outputs);
3649
- return Object.freeze({
3650
- goal: goal.id,
3651
- product,
3652
- failure: control.failure,
3653
- usage: meter.usage,
3654
- duration: Math.ceil(performance.now() - started),
3655
- executed: true
3656
- });
2047
+ if (command === "grade") {
2048
+ const rubric = flags.get("--rubric");
2049
+ if (rubric === void 0) throw new _src_core.StudyError("CONFIG", "Missing --rubric. " + usage);
2050
+ return {
2051
+ command,
2052
+ target,
2053
+ rubric
2054
+ };
3657
2055
  }
3658
- async #produce(goal, session, evidence, control, scope, meter, parents, outputs) {
3659
- const bounds = this.#options.manifest.bounds;
3660
- try {
3661
- const value = await control.wait(session.execute(goal, {
3662
- signal: control.signal,
3663
- meter,
3664
- evidence
3665
- }));
3666
- if (!isStudyProduct(value)) throw new StudyError("FAULT", "Driver returned no valid study product");
3667
- const candidate = Object.freeze({
3668
- value: (0, _orkestrel_contract.cloneJSONValue)(value.value),
3669
- partial: value.partial
3670
- });
3671
- const bytes = new TextEncoder().encode(JSON.stringify(candidate.value)).byteLength;
3672
- const stop = computeStudyOutputStop(bytes, bounds.goal.bytes, outputs);
3673
- const product = stop === void 0 ? candidate : void 0;
3674
- try {
3675
- for (const parent of parents) parent.check();
3676
- scope.check();
3677
- if (stop !== void 0) control.fail({
3678
- cause: "capacity",
3679
- message: "Study product exceeded its output allowance",
3680
- role: void 0,
3681
- stop
3682
- });
3683
- else for (const budget of outputs) budget.consume(bytes);
3684
- } catch (error) {
3685
- control.fail(readStudyFailure(error));
3686
- }
3687
- return product;
3688
- } catch (error) {
3689
- control.fail(readStudyFailure(error));
3690
- return;
3691
- } finally {
3692
- meter.close();
3693
- scope.close();
3694
- }
2056
+ const grading = flags.get("--grading");
2057
+ if (!(0, _src_core.isStudyDigest)(grading)) throw new _src_core.StudyError("CONFIG", "--grading " + JSON.stringify(grading ?? "<missing>") + " must be an exact SHA-256 grading identity. " + usage);
2058
+ if (command === "label") {
2059
+ const labels = flags.get("--labels");
2060
+ if (labels === void 0) throw new _src_core.StudyError("CONFIG", "Missing --labels. " + usage);
2061
+ return {
2062
+ command,
2063
+ target,
2064
+ grading,
2065
+ labels
2066
+ };
3695
2067
  }
3696
- };
2068
+ return {
2069
+ command: "report",
2070
+ target,
2071
+ grading
2072
+ };
2073
+ }
3697
2074
  //#endregion
3698
2075
  //#region src/server/processes/constants.ts
3699
2076
  /** Limits each serialized IPC envelope, including its JSON framing. */
@@ -3728,7 +2105,7 @@ var STUDY_TASK_DURATION = 6e4;
3728
2105
  */
3729
2106
  function valueToWire(value) {
3730
2107
  const parsed = JSON.parse(JSON.stringify(value));
3731
- if (!(0, _orkestrel_contract.isJSONValue)(parsed)) throw new StudyError("FAULT", "Invalid study wire value");
2108
+ if (!(0, _orkestrel_contract.isJSONValue)(parsed)) throw new _src_core.StudyError("FAULT", "Invalid study wire value");
3732
2109
  return parsed;
3733
2110
  }
3734
2111
  /** Creates attributable protocol infrastructure failure.
@@ -3749,7 +2126,7 @@ function createStudyProcessFailure(message) {
3749
2126
  */
3750
2127
  function computeStudyProfileDigest(profile) {
3751
2128
  const encoded = (0, _orkestrel_contract.canonicalStringify)(valueToWire(profile));
3752
- if (encoded === void 0) throw new StudyError("CONFIG", "Study profile is not JSON-compatible");
2129
+ if (encoded === void 0) throw new _src_core.StudyError("CONFIG", "Study profile is not JSON-compatible");
3753
2130
  return hashStudyBytes(encoded);
3754
2131
  }
3755
2132
  /** Collects the executable entries a profile selects, each canonical path once.
@@ -3783,9 +2160,26 @@ function resolveStudyInstrument(entry) {
3783
2160
  const value = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
3784
2161
  return (0, _orkestrel_contract.isRecord)(value) && value.name === "@orkestrel/study";
3785
2162
  });
3786
- if (root === void 0) throw new StudyError("CONFIG", "Cannot locate the study runtime package");
2163
+ if (root === void 0) throw new _src_core.StudyError("CONFIG", "Cannot locate the study runtime package");
3787
2164
  return root;
3788
2165
  }
2166
+ /** Reads the version of the study package that owns an executable entry.
2167
+ * @param entry - The installed runtime's executable entry.
2168
+ * @returns The non-empty version declared by the owning study package.
2169
+ * @throws StudyError - Thrown when no study package owns the entry or its version is absent or empty.
2170
+ * @example Read the installed version
2171
+ * ```ts
2172
+ * import { readStudyVersion } from '@orkestrel/study/server'
2173
+ *
2174
+ * declare const entry: string
2175
+ * readStudyVersion(entry)
2176
+ * ```
2177
+ */
2178
+ function readStudyVersion(entry) {
2179
+ const value = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(resolveStudyInstrument(entry), "package.json"), "utf8"));
2180
+ if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.version !== "string" || value.version.length === 0) throw new _src_core.StudyError("CONFIG", "Study runtime package declares no version");
2181
+ return value.version;
2182
+ }
3789
2183
  /** Collects a directory and each of its ancestors up to the filesystem root, nearest first.
3790
2184
  * @param directory - The absolute directory to start from.
3791
2185
  * @returns The directory followed by every parent directory, ending at the root.
@@ -3805,11 +2199,11 @@ function validateStudyRuntime(instrument, roots) {
3805
2199
  const root = pending.pop();
3806
2200
  if (root === void 0 || visited.has(root)) continue;
3807
2201
  visited.add(root);
3808
- if (!roots.includes(root)) throw new StudyError("CONFIG", `Profile roots omit a runtime dependency: ${root}`);
2202
+ if (!roots.includes(root)) throw new _src_core.StudyError("CONFIG", `Profile roots omit a runtime dependency: ${root}`);
3809
2203
  const manifest = (0, node_path.join)(root, "package.json");
3810
2204
  const value = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
3811
2205
  if (!(0, _orkestrel_contract.isRecord)(value) || value.dependencies === void 0) continue;
3812
- if (!(0, _orkestrel_contract.isRecord)(value.dependencies)) throw new StudyError("CONFIG", "Runtime dependency declarations are invalid");
2206
+ if (!(0, _orkestrel_contract.isRecord)(value.dependencies)) throw new _src_core.StudyError("CONFIG", "Runtime dependency declarations are invalid");
3813
2207
  const resolver = (0, node_module.createRequire)(manifest);
3814
2208
  for (const dependency of Object.keys(value.dependencies)) pending.push((0, node_path.dirname)((0, node_fs.realpathSync)(resolver.resolve(`${dependency}/package.json`))));
3815
2209
  }
@@ -3857,14 +2251,14 @@ function terminateStudyChild(child) {
3857
2251
  for (const pid of [...descendants].reverse()) try {
3858
2252
  process.kill(pid, "SIGKILL");
3859
2253
  } catch (error) {
3860
- if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) throw error;
2254
+ if (!matchesSystemError(error, "ESRCH")) throw error;
3861
2255
  }
3862
2256
  }
3863
2257
  } catch (error) {
3864
2258
  try {
3865
2259
  process.kill(child.pid, 0);
3866
2260
  } catch (status) {
3867
- if (status instanceof Error && "code" in status && status.code === "ESRCH") return;
2261
+ if (matchesSystemError(status, "ESRCH")) return;
3868
2262
  throw status;
3869
2263
  }
3870
2264
  if (child.exitCode === null && child.signalCode === null) {
@@ -3893,7 +2287,7 @@ var StudyArtifactLoader = class {
3893
2287
  options.adapter,
3894
2288
  ...collectStudyEntries(options.profile),
3895
2289
  ...options.profile.roots
3896
- ].every(node_path.isAbsolute)) throw new StudyError("CONFIG", "Study artifact paths must be absolute");
2290
+ ].every(node_path.isAbsolute)) throw new _src_core.StudyError("CONFIG", "Study artifact paths must be absolute");
3897
2291
  }
3898
2292
  /** Inventories bytes without importing selected code.
3899
2293
  * @returns Bound executable and instrumentation identities.
@@ -3940,7 +2334,7 @@ var StudyArtifactLoader = class {
3940
2334
  */
3941
2335
  async adapter() {
3942
2336
  await this.inspect();
3943
- if (this.#loader === void 0) throw new StudyError("CONFIG", "Artifact inspection is required");
2337
+ if (this.#loader === void 0) throw new _src_core.StudyError("CONFIG", "Artifact inspection is required");
3944
2338
  return readStudyAdapter(await this.#loader.load(this.#options.adapter));
3945
2339
  }
3946
2340
  /** Loads admitted built namespaces inside the trial child.
@@ -3948,7 +2342,7 @@ var StudyArtifactLoader = class {
3948
2342
  */
3949
2343
  async load() {
3950
2344
  await this.inspect();
3951
- if (this.#loader === void 0) throw new StudyError("CONFIG", "Artifact inspection is required");
2345
+ if (this.#loader === void 0) throw new _src_core.StudyError("CONFIG", "Artifact inspection is required");
3952
2346
  const profile = this.#options.profile;
3953
2347
  const loaded = [];
3954
2348
  for (const entry of collectStudyEntries(profile)) loaded.push([entry, await this.#loader.load(entry)]);
@@ -3963,8 +2357,8 @@ var StudyArtifactLoader = class {
3963
2357
  }
3964
2358
  /** Refuses instrument and inventory drift after inspection. */
3965
2359
  verify() {
3966
- if (this.#loader === void 0 || this.#instrument === void 0) throw new StudyError("CONFIG", "Artifact inspection is required");
3967
- if (hashStudyBytes((0, node_fs.readFileSync)(this.#options.entry)) !== this.#entry) throw new StudyError("FAULT", "Study instrument bytes drifted");
2360
+ if (this.#loader === void 0 || this.#instrument === void 0) throw new _src_core.StudyError("CONFIG", "Artifact inspection is required");
2361
+ if (hashStudyBytes((0, node_fs.readFileSync)(this.#options.entry)) !== this.#entry) throw new _src_core.StudyError("FAULT", "Study instrument bytes drifted");
3968
2362
  this.#loader.verify();
3969
2363
  this.#instrument.verify();
3970
2364
  }
@@ -3994,42 +2388,42 @@ function isStudyFrame(value) {
3994
2388
  * @returns True if the failure carries a known cause, an optional role, and an optional stop that satisfies `isStudyStop` on a `capacity` or `deadline` cause; false otherwise.
3995
2389
  */
3996
2390
  function isStudyProcessFailure(value) {
3997
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && typeof value.message === "string" && isStudyCause(value.cause) && (value.role === void 0 || isStudyRole(value.role)) && (value.stop === void 0 || (value.cause === "capacity" || value.cause === "deadline") && isStudyStop(value.stop)));
2391
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && typeof value.message === "string" && (0, _src_core.isStudyCause)(value.cause) && (value.role === void 0 || (0, _src_core.isStudyRole)(value.role)) && (value.stop === void 0 || (value.cause === "capacity" || value.cause === "deadline") && (0, _src_core.isStudyStop)(value.stop)));
3998
2392
  }
3999
2393
  /** Checks a request's declared role and finite cost ceilings.
4000
2394
  * @param value - Decoded charge.
4001
2395
  * @returns True if all charge leaves are valid; false otherwise.
4002
2396
  */
4003
2397
  function isStudyCharge(value) {
4004
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && isStudyRole(value.role) && typeof value.tokens === "number" && Number.isSafeInteger(value.tokens) && value.tokens >= 0 && typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes >= 0);
2398
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.isStudyRole)(value.role) && typeof value.tokens === "number" && Number.isSafeInteger(value.tokens) && value.tokens >= 0 && typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes >= 0);
4005
2399
  }
4006
2400
  /** Checks append-only public evidence.
4007
2401
  * @param value - Decoded evidence.
4008
2402
  * @returns True if event, goal and payload are valid; false otherwise.
4009
2403
  */
4010
2404
  function isStudyEvidence(value) {
4011
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && isStudyName(value.event) && isStudyName(value.goal) && (0, _orkestrel_contract.isJSONValue)(value.value));
2405
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.isStudyName)(value.event) && (0, _src_core.isStudyName)(value.goal) && (0, _orkestrel_contract.isJSONValue)(value.value));
4012
2406
  }
4013
2407
  /** Checks a fresh trial's declared identifiers.
4014
2408
  * @param value - Decoded trial.
4015
2409
  * @returns True if every identifier and repetition is valid; false otherwise.
4016
2410
  */
4017
2411
  function isStudyTrial(value) {
4018
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && isStudyName(value.id) && isStudyName(value.scenario) && isStudyName(value.arm) && typeof value.repetition === "number" && Number.isSafeInteger(value.repetition) && value.repetition >= 0);
2412
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.isStudyName)(value.id) && (0, _src_core.isStudyName)(value.scenario) && (0, _src_core.isStudyName)(value.arm) && typeof value.repetition === "number" && Number.isSafeInteger(value.repetition) && value.repetition >= 0);
4019
2413
  }
4020
2414
  /** Checks the exact digest fields carried by the execution declaration.
4021
2415
  * @param value - Decoded identity.
4022
2416
  * @returns True if all required identities are digests; false otherwise.
4023
2417
  */
4024
2418
  function isStudyIdentity(value) {
4025
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && isStudyDigest(value.agent) && isStudyDigest(value.subject) && (value.provider === void 0 || isStudyDigest(value.provider)) && isStudyDigest(value.instrument) && isStudyDigest(value.fixtures) && isStudyDigest(value.profile));
2419
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.isStudyDigest)(value.agent) && (0, _src_core.isStudyDigest)(value.subject) && (value.provider === void 0 || (0, _src_core.isStudyDigest)(value.provider)) && (0, _src_core.isStudyDigest)(value.instrument) && (0, _src_core.isStudyDigest)(value.fixtures) && (0, _src_core.isStudyDigest)(value.profile));
4026
2420
  }
4027
2421
  //#endregion
4028
2422
  //#region src/server/processes/StudyChannel.ts
4029
2423
  /** Correlates bounded IPC work and rejects every outstanding request on transport failure. */
4030
2424
  var StudyChannel = class {
4031
2425
  #options;
4032
- #control = new StudyControl();
2426
+ #control = new _src_core.StudyControl();
4033
2427
  #pending = /* @__PURE__ */ new Map();
4034
2428
  #next = 0;
4035
2429
  #received = 0;
@@ -4056,7 +2450,7 @@ var StudyChannel = class {
4056
2450
  this.#receive(value).catch((error) => this.close(error));
4057
2451
  }
4058
2452
  #onDisconnect() {
4059
- this.close(failureToStudyError(createStudyProcessFailure("Study process disconnected")));
2453
+ this.close((0, _src_core.failureToStudyError)(createStudyProcessFailure("Study process disconnected")));
4060
2454
  }
4061
2455
  /** Sends one correlated domain request.
4062
2456
  * @param operation - The domain operation expected by the peer.
@@ -4064,8 +2458,8 @@ var StudyChannel = class {
4064
2458
  * @returns The peer's acknowledged result.
4065
2459
  */
4066
2460
  request(operation, value) {
4067
- if (this.#control.failure !== void 0) return Promise.reject(failureToStudyError(this.#control.failure));
4068
- if (this.#pending.size >= 64) return Promise.reject(failureToStudyError(createStudyProcessFailure("Study IPC pending limit exceeded")));
2461
+ if (this.#control.failure !== void 0) return Promise.reject((0, _src_core.failureToStudyError)(this.#control.failure));
2462
+ if (this.#pending.size >= 64) return Promise.reject((0, _src_core.failureToStudyError)(createStudyProcessFailure("Study IPC pending limit exceeded")));
4069
2463
  const id = ++this.#next;
4070
2464
  return new Promise((resolve, reject) => {
4071
2465
  this.#pending.set(id, {
@@ -4092,41 +2486,41 @@ var StudyChannel = class {
4092
2486
  /** Removes endpoint handlers and rejects all unresolved calls.
4093
2487
  * @param error - The first transport or owning lifecycle failure.
4094
2488
  */
4095
- close(error = failureToStudyError(createStudyProcessFailure("Study channel closed"))) {
4096
- this.#control.fail(readStudyFailure(error));
2489
+ close(error = (0, _src_core.failureToStudyError)(createStudyProcessFailure("Study channel closed"))) {
2490
+ this.#control.fail((0, _src_core.readStudyFailure)(error));
4097
2491
  this.#options.endpoint.removeListener("message", this.#message);
4098
2492
  this.#options.endpoint.removeListener("disconnect", this.#disconnect);
4099
2493
  this.#options.endpoint.removeListener("error", this.#error);
4100
- for (const pending of this.#pending.values()) pending.reject(failureToStudyError(this.#control.failure ?? readStudyFailure(error)));
2494
+ for (const pending of this.#pending.values()) pending.reject((0, _src_core.failureToStudyError)(this.#control.failure ?? (0, _src_core.readStudyFailure)(error)));
4101
2495
  this.#pending.clear();
4102
2496
  this.#control.close();
4103
2497
  }
4104
2498
  async #send(frame) {
4105
- if (Buffer.byteLength(JSON.stringify(frame)) > 16777216) throw failureToStudyError(createStudyProcessFailure("Study IPC frame limit exceeded"));
4106
- if (!this.#options.endpoint.connected || this.#options.endpoint.send === void 0) throw failureToStudyError(createStudyProcessFailure("Study IPC is unavailable"));
2499
+ if (Buffer.byteLength(JSON.stringify(frame)) > 16777216) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Study IPC frame limit exceeded"));
2500
+ if (!this.#options.endpoint.connected || this.#options.endpoint.send === void 0) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Study IPC is unavailable"));
4107
2501
  await new Promise((resolve, reject) => {
4108
2502
  this.#options.endpoint.send?.(frame, void 0, {}, (error) => error === null ? resolve() : reject(error));
4109
2503
  });
4110
2504
  }
4111
2505
  async #receive(value) {
4112
- if (!isStudyFrame(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216) throw failureToStudyError(createStudyProcessFailure("Malformed study IPC frame"));
2506
+ if (!isStudyFrame(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Malformed study IPC frame"));
4113
2507
  if (value.operation === "abort") {
4114
- if (value.id !== 0) throw failureToStudyError(createStudyProcessFailure("Foreign study cancellation"));
2508
+ if (value.id !== 0) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Foreign study cancellation"));
4115
2509
  this.#options.abort(value.value);
4116
2510
  return;
4117
2511
  }
4118
2512
  if (value.operation === "reply" || value.operation === "error") {
4119
2513
  const pending = this.#pending.get(value.id);
4120
- if (pending === void 0) throw failureToStudyError(createStudyProcessFailure("Unknown study IPC reply"));
2514
+ if (pending === void 0) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Unknown study IPC reply"));
4121
2515
  if (value.operation === "reply") pending.resolve(value.value);
4122
2516
  else {
4123
- if (!isStudyProcessFailure(value.value)) throw failureToStudyError(createStudyProcessFailure("Invalid study IPC failure"));
4124
- pending.reject(failureToStudyError(value.value));
2517
+ if (!isStudyProcessFailure(value.value)) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Invalid study IPC failure"));
2518
+ pending.reject((0, _src_core.failureToStudyError)(value.value));
4125
2519
  }
4126
2520
  this.#pending.delete(value.id);
4127
2521
  return;
4128
2522
  }
4129
- if (value.id !== this.#received + 1 || this.#working.size >= 64) throw failureToStudyError(createStudyProcessFailure("Out-of-order or excessive study IPC request"));
2523
+ if (value.id !== this.#received + 1 || this.#working.size >= 64) throw (0, _src_core.failureToStudyError)(createStudyProcessFailure("Out-of-order or excessive study IPC request"));
4130
2524
  const id = value.id;
4131
2525
  this.#received = id;
4132
2526
  const working = Symbol("request");
@@ -4142,7 +2536,7 @@ var StudyChannel = class {
4142
2536
  await this.#send({
4143
2537
  id,
4144
2538
  operation: "error",
4145
- value: valueToWire(readStudyFailure(error))
2539
+ value: valueToWire((0, _src_core.readStudyFailure)(error))
4146
2540
  });
4147
2541
  } finally {
4148
2542
  this.#working.delete(working);
@@ -4215,20 +2609,20 @@ var StudyProcessSession = class {
4215
2609
  ...structuredClone(configuration),
4216
2610
  signal
4217
2611
  };
4218
- this.#control = new StudyControl(signal);
2612
+ this.#control = new _src_core.StudyControl(signal);
4219
2613
  }
4220
2614
  /** Starts the child and verifies its artifact-bound readiness. */
4221
2615
  async start() {
4222
- if (this.#started || this.#closing !== void 0) throw new StudyError("FAULT", "Study process start is single use");
2616
+ if (this.#started || this.#closing !== void 0) throw new _src_core.StudyError("FAULT", "Study process start is single use");
4223
2617
  this.#started = true;
4224
- if (this.#control.failure !== void 0) throw failureToStudyError(this.#control.failure);
2618
+ if (this.#control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#control.failure);
4225
2619
  if (![
4226
2620
  this.#options.entry,
4227
2621
  this.#options.root,
4228
2622
  this.#options.directory
4229
- ].every(node_path.isAbsolute)) throw new StudyError("CONFIG", "Study process paths must be absolute");
2623
+ ].every(node_path.isAbsolute)) throw new _src_core.StudyError("CONFIG", "Study process paths must be absolute");
4230
2624
  const directory = await (0, node_fs_promises.mkdtemp)((0, node_path.join)(this.#options.directory, "trial-"));
4231
- if (this.#control.failure !== void 0) throw failureToStudyError(this.#control.failure);
2625
+ if (this.#control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#control.failure);
4232
2626
  const child = (0, node_child_process.fork)(this.#options.entry, [STUDY_WORKER_OPERATION], {
4233
2627
  cwd: this.#options.root,
4234
2628
  execArgv: [...process.execArgv],
@@ -4251,20 +2645,20 @@ var StudyProcessSession = class {
4251
2645
  consumed: void 0
4252
2646
  }
4253
2647
  });
4254
- this.#terminate().catch((error) => this.#control.fail(readStudyFailure(error)));
2648
+ this.#terminate().catch((error) => this.#control.fail((0, _src_core.readStudyFailure)(error)));
4255
2649
  }, this.#options.profile.bounds.trial.duration);
4256
2650
  this.#exit = new Promise((resolve) => child.once("close", () => resolve()));
4257
2651
  this.#channel = new StudyChannel({
4258
2652
  endpoint: child,
4259
2653
  handle: (operation, value) => this.#handle(operation, value).catch((error) => {
4260
- this.#control.fail(readStudyFailure(error));
2654
+ this.#control.fail((0, _src_core.readStudyFailure)(error));
4261
2655
  throw error;
4262
2656
  }),
4263
2657
  abort: () => {
4264
- throw new StudyError("FAULT", "Worker cannot cancel parent authority");
2658
+ throw new _src_core.StudyError("FAULT", "Worker cannot cancel parent authority");
4265
2659
  }
4266
2660
  });
4267
- this.#channel.signal.addEventListener("abort", () => this.#control.fail(readStudyFailure(this.#channel?.signal.reason)), {
2661
+ this.#channel.signal.addEventListener("abort", () => this.#control.fail((0, _src_core.readStudyFailure)(this.#channel?.signal.reason)), {
4268
2662
  once: true,
4269
2663
  signal: this.#listeners.signal
4270
2664
  });
@@ -4274,7 +2668,7 @@ var StudyProcessSession = class {
4274
2668
  });
4275
2669
  child.stdout?.on("data", (value) => this.#readLog(value));
4276
2670
  child.stderr?.on("data", (value) => this.#readLog(value));
4277
- const ready = await this.#control.wait(new StudyDeadline({
2671
+ const ready = await this.#control.wait(new _src_core.StudyDeadline({
4278
2672
  duration: this.#options.profile.bounds.trial.duration,
4279
2673
  label: STUDY_PROCESS_LABEL
4280
2674
  }).wait(this.#channel.request("open", valueToWire({
@@ -4282,7 +2676,7 @@ var StudyProcessSession = class {
4282
2676
  signal: void 0,
4283
2677
  directory
4284
2678
  }))));
4285
- if (!(0, _orkestrel_contract.isRecord)(ready) || ready.pid !== child.pid || ready.agent !== this.#options.identity.agent || ready.subject !== this.#options.identity.subject || ready.instrument !== this.#options.identity.instrument) throw new StudyError("FAULT", "Worker readiness identity differs");
2679
+ if (!(0, _orkestrel_contract.isRecord)(ready) || ready.pid !== child.pid || ready.agent !== this.#options.identity.agent || ready.subject !== this.#options.identity.subject || ready.instrument !== this.#options.identity.instrument) throw new _src_core.StudyError("FAULT", "Worker readiness identity differs");
4286
2680
  }
4287
2681
  /** Runs one ordered goal through parent-authorized requests and acknowledged evidence.
4288
2682
  * @param goal - Public goal without grading truth.
@@ -4290,12 +2684,12 @@ var StudyProcessSession = class {
4290
2684
  * @returns The retained child product after every admitted operation settles.
4291
2685
  */
4292
2686
  async execute(goal, context) {
4293
- if (this.#goals.size !== 0 || this.#closing !== void 0 || this.#channel === void 0) throw new StudyError("FAULT", "Study process cannot execute in its current state");
4294
- if (this.#control.failure !== void 0) throw failureToStudyError(this.#control.failure);
2687
+ if (this.#goals.size !== 0 || this.#closing !== void 0 || this.#channel === void 0) throw new _src_core.StudyError("FAULT", "Study process cannot execute in its current state");
2688
+ if (this.#control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#control.failure);
4295
2689
  const running = new StudyProcessGoal(context);
4296
2690
  this.#goals.add(running);
4297
2691
  const listeners = new AbortController();
4298
- const control = new StudyControl(context.signal);
2692
+ const control = new _src_core.StudyControl(context.signal);
4299
2693
  this.#control.signal.addEventListener("abort", () => control.fail(this.#control.failure ?? createStudyProcessFailure("Study process failed")), {
4300
2694
  once: true,
4301
2695
  signal: listeners.signal
@@ -4306,12 +2700,12 @@ var StudyProcessSession = class {
4306
2700
  });
4307
2701
  try {
4308
2702
  const product = await control.wait(this.#channel.request("goal", valueToWire(goal)));
4309
- if (this.#tickets.size !== 0 || this.#appends.size !== 0) throw new StudyError("FAULT", "Worker goal ended with unsettled admission or evidence");
4310
- if (!isStudyProduct(product)) throw new StudyError("FAULT", "Worker product is invalid");
2703
+ if (this.#tickets.size !== 0 || this.#appends.size !== 0) throw new _src_core.StudyError("FAULT", "Worker goal ended with unsettled admission or evidence");
2704
+ if (!(0, _src_core.isStudyProduct)(product)) throw new _src_core.StudyError("FAULT", "Worker product is invalid");
4311
2705
  return product;
4312
2706
  } catch (error) {
4313
- this.#control.fail(readStudyFailure(error));
4314
- throw failureToStudyError(this.#control.failure ?? readStudyFailure(error));
2707
+ this.#control.fail((0, _src_core.readStudyFailure)(error));
2708
+ throw (0, _src_core.failureToStudyError)(this.#control.failure ?? (0, _src_core.readStudyFailure)(error));
4315
2709
  } finally {
4316
2710
  listeners.abort();
4317
2711
  control.close();
@@ -4325,12 +2719,12 @@ var StudyProcessSession = class {
4325
2719
  }
4326
2720
  async #handle(operation, value) {
4327
2721
  const [running] = this.#goals;
4328
- if (running === void 0) throw new StudyError("FAULT", "Worker requested work outside a goal");
2722
+ if (running === void 0) throw new _src_core.StudyError("FAULT", "Worker requested work outside a goal");
4329
2723
  const context = running.context;
4330
2724
  if (operation === "admit") {
4331
- if (!(0, _orkestrel_contract.isRecord)(value) || !isStudyCharge(value.charge)) throw new StudyError("FAULT", "Invalid worker admission");
2725
+ if (!(0, _orkestrel_contract.isRecord)(value) || !isStudyCharge(value.charge)) throw new _src_core.StudyError("FAULT", "Invalid worker admission");
4332
2726
  const id = running.issue(value.id);
4333
- if (id === void 0) throw new StudyError("FAULT", "Invalid worker admission");
2727
+ if (id === void 0) throw new _src_core.StudyError("FAULT", "Invalid worker admission");
4334
2728
  const ticket = await context.meter.admit(value.charge);
4335
2729
  const listeners = new AbortController();
4336
2730
  this.#tickets.set(id, {
@@ -4339,7 +2733,7 @@ var StudyProcessSession = class {
4339
2733
  });
4340
2734
  ticket.signal.addEventListener("abort", () => this.#channel?.abort(valueToWire({
4341
2735
  id,
4342
- failure: readStudyFailure(ticket.signal.reason)
2736
+ failure: (0, _src_core.readStudyFailure)(ticket.signal.reason)
4343
2737
  })), {
4344
2738
  once: true,
4345
2739
  signal: listeners.signal
@@ -4348,18 +2742,18 @@ var StudyProcessSession = class {
4348
2742
  return { id };
4349
2743
  }
4350
2744
  if (operation === "finish" || operation === "fail") {
4351
- if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.id !== "number") throw new StudyError("FAULT", "Invalid worker settlement");
2745
+ if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.id !== "number") throw new _src_core.StudyError("FAULT", "Invalid worker settlement");
4352
2746
  const entry = this.#tickets.get(value.id);
4353
- if (entry === void 0) throw new StudyError("FAULT", "Foreign or repeated worker settlement");
4354
- if (operation === "finish" && !isStudyUsage(value.usage) || operation === "fail" && !isStudyProcessFailure(value.failure)) throw new StudyError("FAULT", "Invalid worker settlement payload");
2747
+ if (entry === void 0) throw new _src_core.StudyError("FAULT", "Foreign or repeated worker settlement");
2748
+ if (operation === "finish" && !(0, _src_core.isStudyUsage)(value.usage) || operation === "fail" && !isStudyProcessFailure(value.failure)) throw new _src_core.StudyError("FAULT", "Invalid worker settlement payload");
4355
2749
  this.#tickets.delete(value.id);
4356
2750
  entry.listeners.abort();
4357
- if (operation === "finish" && isStudyUsage(value.usage)) entry.ticket.finish(value.usage);
2751
+ if (operation === "finish" && (0, _src_core.isStudyUsage)(value.usage)) entry.ticket.finish(value.usage);
4358
2752
  else if (isStudyProcessFailure(value.failure)) entry.ticket.fail(value.failure);
4359
2753
  return true;
4360
2754
  }
4361
2755
  if (operation === "evidence") {
4362
- if (!isStudyEvidence(value)) throw new StudyError("FAULT", "Invalid worker evidence");
2756
+ if (!isStudyEvidence(value)) throw new _src_core.StudyError("FAULT", "Invalid worker evidence");
4363
2757
  const appending = context.evidence.append(value);
4364
2758
  this.#appends.add(appending);
4365
2759
  try {
@@ -4369,7 +2763,7 @@ var StudyProcessSession = class {
4369
2763
  this.#appends.delete(appending);
4370
2764
  }
4371
2765
  }
4372
- throw new StudyError("FAULT", "Worker requested a forbidden operation");
2766
+ throw new _src_core.StudyError("FAULT", "Worker requested a forbidden operation");
4373
2767
  }
4374
2768
  #readLog(value) {
4375
2769
  this.#logs += value.byteLength;
@@ -4396,10 +2790,10 @@ var StudyProcessSession = class {
4396
2790
  async #acknowledge() {
4397
2791
  try {
4398
2792
  if (this.#channel !== void 0 && !this.#channel.signal.aborted) {
4399
- if (await new StudyDeadline({
2793
+ if (await new _src_core.StudyDeadline({
4400
2794
  duration: this.#options.profile.bounds.request.duration,
4401
2795
  label: "Process lifecycle"
4402
- }).wait(this.#channel.request("close", true)) !== true) throw new StudyError("FAULT", "Worker cleanup acknowledgment is invalid");
2796
+ }).wait(this.#channel.request("close", true)) !== true) throw new _src_core.StudyError("FAULT", "Worker cleanup acknowledgment is invalid");
4403
2797
  }
4404
2798
  return;
4405
2799
  } catch (error) {
@@ -4422,7 +2816,7 @@ var StudyProcessSession = class {
4422
2816
  }, (error) => error === null || child.exitCode !== null || child.signalCode !== null ? resolve() : reject(error)));
4423
2817
  else process.kill(-child.pid, "SIGKILL");
4424
2818
  }
4425
- if (this.#exit !== void 0) await new StudyDeadline({
2819
+ if (this.#exit !== void 0) await new _src_core.StudyDeadline({
4426
2820
  duration: 2e3,
4427
2821
  label: STUDY_PROCESS_LABEL
4428
2822
  }).wait(this.#exit);
@@ -4486,7 +2880,7 @@ var StudyTaskChild = class {
4486
2880
  * @throws StudyError - Thrown with code `CONFIG` when this child is already supervised.
4487
2881
  */
4488
2882
  execute(input) {
4489
- if (this.#executed) throw new StudyError("CONFIG", "Study task child can execute only once");
2883
+ if (this.#executed) throw new _src_core.StudyError("CONFIG", "Study task child can execute only once");
4490
2884
  this.#executed = true;
4491
2885
  const signal = this.#signal;
4492
2886
  return new Promise((resolve, reject) => {
@@ -4501,7 +2895,7 @@ var StudyTaskChild = class {
4501
2895
  drainStudyChild(child);
4502
2896
  const listeners = new AbortController();
4503
2897
  const timer = setTimeout(() => {
4504
- this.#failure = new StudyError("FAULT", "Adapter deadline exceeded", {
2898
+ this.#failure = new _src_core.StudyError("FAULT", "Adapter deadline exceeded", {
4505
2899
  cause: "deadline",
4506
2900
  message: "Adapter deadline exceeded",
4507
2901
  role: void 0,
@@ -4523,9 +2917,9 @@ var StudyTaskChild = class {
4523
2917
  });
4524
2918
  child.on("message", (value) => {
4525
2919
  try {
4526
- if (this.#result !== void 0 || !(0, _orkestrel_contract.isRecord)(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216) throw new StudyError("FAULT", "Adapter response is oversized or repeated");
4527
- if (typeof value.error === "string") throw new StudyError(value.code === "CONFIG" ? "CONFIG" : "FAULT", value.error);
4528
- if (!isStudyDigest(value.instrument) || !(0, _orkestrel_contract.isJSONValue)(value.value)) throw new StudyError("FAULT", "Adapter response is malformed");
2920
+ if (this.#result !== void 0 || !(0, _orkestrel_contract.isRecord)(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216) throw new _src_core.StudyError("FAULT", "Adapter response is oversized or repeated");
2921
+ if (typeof value.error === "string") throw new _src_core.StudyError(value.code === "CONFIG" ? "CONFIG" : "FAULT", value.error);
2922
+ if (!(0, _src_core.isStudyDigest)(value.instrument) || !(0, _orkestrel_contract.isJSONValue)(value.value)) throw new _src_core.StudyError("FAULT", "Adapter response is malformed");
4529
2923
  this.#result = {
4530
2924
  instrument: value.instrument,
4531
2925
  value: value.value
@@ -4538,7 +2932,7 @@ var StudyTaskChild = class {
4538
2932
  for (const stream of [child.stdout, child.stderr]) stream?.on("data", (chunk) => {
4539
2933
  this.#logs += chunk.byteLength;
4540
2934
  if (this.#logs <= 65536) return;
4541
- this.#failure = new StudyError("FAULT", "Adapter output exceeds the log allowance");
2935
+ this.#failure = new _src_core.StudyError("FAULT", "Adapter output exceeds the log allowance");
4542
2936
  this.#terminate(child);
4543
2937
  });
4544
2938
  child.once("error", (error) => {
@@ -4549,7 +2943,7 @@ var StudyTaskChild = class {
4549
2943
  listeners.abort();
4550
2944
  const result = this.#result;
4551
2945
  if (this.#failure !== void 0) reject(this.#failure);
4552
- else if (result === void 0) reject(new StudyError("FAULT", "Adapter exited without a complete response"));
2946
+ else if (result === void 0) reject(new _src_core.StudyError("FAULT", "Adapter exited without a complete response"));
4553
2947
  else resolve(result);
4554
2948
  });
4555
2949
  child.send(input, (error) => {
@@ -4590,13 +2984,13 @@ var StudyTask = class {
4590
2984
  execute(request, signal) {
4591
2985
  signal.throwIfAborted();
4592
2986
  const duration = this.#options.duration ?? 6e4;
4593
- if (!Number.isSafeInteger(duration) || duration <= 0 || duration > 6e4) throw new StudyError("CONFIG", "Adapter deadline must be finite and at most 60 seconds");
2987
+ if (!Number.isSafeInteger(duration) || duration <= 0 || duration > 6e4) throw new _src_core.StudyError("CONFIG", "Adapter deadline must be finite and at most 60 seconds");
4594
2988
  const input = valueToWire({
4595
2989
  ...this.#options,
4596
2990
  ...request
4597
2991
  });
4598
- if (!(0, _orkestrel_contract.isRecord)(input)) throw new StudyError("CONFIG", "Adapter input must be an object");
4599
- if (Buffer.byteLength(JSON.stringify(input)) > 16777216) throw new StudyError("CONFIG", "Adapter input exceeds the frame allowance");
2992
+ if (!(0, _orkestrel_contract.isRecord)(input)) throw new _src_core.StudyError("CONFIG", "Adapter input must be an object");
2993
+ if (Buffer.byteLength(JSON.stringify(input)) > 16777216) throw new _src_core.StudyError("CONFIG", "Adapter input exceeds the frame allowance");
4600
2994
  return new StudyTaskChild(this.#options, duration, signal).execute(input);
4601
2995
  }
4602
2996
  };
@@ -4629,38 +3023,16 @@ var StudyCommand = class {
4629
3023
  if (request.command === "grade") return this.#grade(request, signal);
4630
3024
  if (request.command === "label") return this.#label(request, signal);
4631
3025
  if (request.command === "report") return this.#report(request, signal);
4632
- throw new StudyError("CONFIG", "Unsupported study operation");
3026
+ throw new _src_core.StudyError("CONFIG", "Unsupported study operation");
4633
3027
  }
4634
3028
  async #prepare(request, signal) {
4635
- const directory = resolveStudyPath((0, node_path.join)(this.#root, "studies"), request.target);
4636
- const definition = parseStudyDefinition(decodeStudyJSON(await readStudyBytes(resolveStudyPath(directory, "study.json"), STUDY_CUSTODY_BYTES)));
4637
- if (definition.name !== request.target) throw new StudyError("CONFIG", "Study name differs from its selected directory");
4638
- const bundle = resolveStudyPath((0, node_path.join)(this.#root, "studies"), definition.driver);
4639
- const steps = [];
4640
- for (const id of definition.scenarios) {
4641
- signal.throwIfAborted();
4642
- const path = `scenarios/${id}.json`;
4643
- const bytes = await readStudyBytes(resolveStudyPath(bundle, path), STUDY_CUSTODY_BYTES);
4644
- const scenario = parseStudyScenario(decodeStudyJSON(bytes));
4645
- if (scenario.id !== id) throw new StudyError("CONFIG", "Scenario identity is incompatible");
4646
- steps.push({
4647
- scenario,
4648
- fixture: {
4649
- path,
4650
- digest: hashStudyBytes(bytes)
4651
- }
4652
- });
4653
- }
4654
- const scenarios = steps.map((step) => step.scenario);
4655
- const fixtures = steps.map((step) => step.fixture);
4656
- const profileBytes = await readStudyBytes(resolveStudyPath(bundle, `profiles/${request.profile}.json`), STUDY_CUSTODY_BYTES);
4657
- const source = parseStudyProfile(decodeStudyJSON(profileBytes));
4658
- if (source.name !== request.profile) throw new StudyError("CONFIG", "Profile name differs from its selected file");
4659
- if (source.bounds.series.duration > 864e5) throw new StudyError("CONFIG", "Series duration exceeds the 24-hour host ceiling");
3029
+ const { bundle, definition, scenarios, fixtures, rubric } = await readStudyDeclaration(this.#root, request.target, signal);
3030
+ const profileBytes = await readStudyDeclarationBytes(this.#root, resolveStudyPath(bundle, `profiles/${request.profile}.json`));
3031
+ const source = (0, _src_core.parseStudyProfile)(decodeStudyJSON(profileBytes));
3032
+ if (source.name !== request.profile) throw new _src_core.StudyError("CONFIG", "Profile name differs from its selected file");
3033
+ if (source.bounds.series.duration > 864e5) throw new _src_core.StudyError("CONFIG", "Series duration exceeds the 24-hour host ceiling");
4660
3034
  const profile = resolveStudyProfile(this.#root, source);
4661
- const rubric = parseStudyRubric(decodeStudyJSON(await readStudyBytes(resolveStudyPath(directory, definition.rubric), STUDY_CUSTODY_BYTES)));
4662
- validateStudyInputs(definition, scenarios, rubric, profile);
4663
- if (definition.purpose === "quality" && profile.qualification === void 0) throw new StudyError("CONFIG", "Quality studies require a capacity qualification");
3035
+ (0, _src_core.validateStudyInputs)(definition, scenarios, rubric, profile);
4664
3036
  signal.throwIfAborted();
4665
3037
  const artifacts = new StudyArtifactLoader({
4666
3038
  root: this.#root,
@@ -4683,10 +3055,10 @@ var StudyCommand = class {
4683
3055
  profile
4684
3056
  }
4685
3057
  }, signal);
4686
- if (validation.value !== true || validation.instrument !== selected.instrument) throw new StudyError("FAULT", "Adapter validation identity differs");
3058
+ if (validation.value !== true || validation.instrument !== selected.instrument) throw new _src_core.StudyError("FAULT", "Adapter validation identity differs");
4687
3059
  artifacts.verify();
4688
3060
  return {
4689
- directory: bundle,
3061
+ bundle,
4690
3062
  definition,
4691
3063
  scenarios: Object.freeze(scenarios),
4692
3064
  rubric,
@@ -4720,13 +3092,13 @@ var StudyCommand = class {
4720
3092
  "--expected",
4721
3093
  fingerprint
4722
3094
  ],
4723
- schedule: buildStudySchedule(preparation.definition, preparation.scenarios)
3095
+ schedule: (0, _src_core.buildStudySchedule)(preparation.definition, preparation.scenarios)
4724
3096
  });
4725
3097
  }
4726
3098
  async #execution(request, signal) {
4727
3099
  const preparation = await this.#prepare(request, signal);
4728
3100
  const fingerprint = computeStudyCommandFingerprint(preparation.identity, preparation.profile.bounds, preparation.definition);
4729
- if (request.expected !== void 0 && request.expected !== fingerprint) throw new StudyError("CONFIG", "Validated execution changed after the supervisor check");
3101
+ if (request.expected !== void 0 && request.expected !== fingerprint) throw new _src_core.StudyError("CONFIG", "Validated execution changed after the supervisor check");
4730
3102
  signal.throwIfAborted();
4731
3103
  const id = (0, node_crypto.randomUUID)();
4732
3104
  const qualification = await this.#qualification(preparation);
@@ -4740,7 +3112,7 @@ var StudyCommand = class {
4740
3112
  definition: preparation.definition,
4741
3113
  scenarios: preparation.scenarios,
4742
3114
  identity: preparation.identity,
4743
- schedule: buildStudySchedule(preparation.definition, preparation.scenarios),
3115
+ schedule: (0, _src_core.buildStudySchedule)(preparation.definition, preparation.scenarios),
4744
3116
  bounds: preparation.profile.bounds,
4745
3117
  qualification,
4746
3118
  host: {
@@ -4758,17 +3130,18 @@ var StudyCommand = class {
4758
3130
  await store.create();
4759
3131
  const scratch = await (0, node_fs_promises.mkdtemp)((0, node_path.join)(parent, "scratch-"));
4760
3132
  try {
4761
- const attempts = await new StudySeries({
3133
+ const driver = new StudyProcessDriver({
3134
+ root: this.#root,
3135
+ entry: this.#entry,
3136
+ profile: preparation.profile,
3137
+ identity: preparation.identity,
3138
+ driver: preparation.definition.driver,
3139
+ adapter: (0, node_path.join)(preparation.bundle, "index.ts"),
3140
+ directory: scratch
3141
+ });
3142
+ const attempts = await new _src_core.StudySeries({
4762
3143
  manifest,
4763
- driver: new StudyProcessDriver({
4764
- root: this.#root,
4765
- entry: this.#entry,
4766
- profile: preparation.profile,
4767
- identity: preparation.identity,
4768
- driver: preparation.definition.driver,
4769
- adapter: (0, node_path.join)(preparation.directory, "index.ts"),
4770
- directory: scratch
4771
- }),
3144
+ driver,
4772
3145
  store,
4773
3146
  signal
4774
3147
  }).execute();
@@ -4799,11 +3172,11 @@ var StudyCommand = class {
4799
3172
  async #grade(request, signal) {
4800
3173
  const result = await this.#result(request.target);
4801
3174
  const bytes = await readStudyBytes(this.#file(request.rubric), STUDY_CUSTODY_BYTES);
4802
- const rubric = parseStudyRubric(decodeStudyJSON(bytes));
3175
+ const rubric = (0, _src_core.parseStudyRubric)(decodeStudyJSON(bytes));
4803
3176
  const evidence = await collectStudyEvidence(result);
4804
3177
  signal.throwIfAborted();
4805
3178
  const roots = (0, _orkestrel_contract.parseJSON)(result.manifest.host.roots ?? "");
4806
- if (!(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(roots)) throw new StudyError("CONFIG", "Execution does not retain adapter dependency roots");
3179
+ if (!(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(roots)) throw new _src_core.StudyError("CONFIG", "Execution does not retain adapter dependency roots");
4807
3180
  const evaluated = await new StudyTask({
4808
3181
  root: this.#root,
4809
3182
  entry: this.#entry,
@@ -4819,7 +3192,7 @@ var StudyCommand = class {
4819
3192
  }
4820
3193
  }, signal);
4821
3194
  const judgments = evaluated.value;
4822
- if (!(0, _orkestrel_contract.isArray)(judgments) || !judgments.every(isStudyJudgment)) throw new StudyError("FAULT", "Grader returned invalid judgments");
3195
+ if (!(0, _orkestrel_contract.isArray)(judgments) || !judgments.every(_src_core.isStudyJudgment)) throw new _src_core.StudyError("FAULT", "Grader returned invalid judgments");
4823
3196
  const digest = hashStudyBytes(bytes);
4824
3197
  const packet = buildStudyPacket(result, rubric, digest);
4825
3198
  const grading = {
@@ -4857,9 +3230,9 @@ var StudyCommand = class {
4857
3230
  async #label(request, signal) {
4858
3231
  const result = await this.#result(request.target);
4859
3232
  const previous = await this.#grading(result, request.grading);
4860
- if (previous.packet === void 0 || previous.grading.packet === void 0) throw new StudyError("EVIDENCE", "Selected grading has no semantic packet");
3233
+ if (previous.packet === void 0 || previous.grading.packet === void 0) throw new _src_core.StudyError("EVIDENCE", "Selected grading has no semantic packet");
4861
3234
  const bytes = await readStudyBytes(this.#file(request.labels), STUDY_CUSTODY_BYTES);
4862
- const labels = parseStudyLabeling(decodeStudyJSON(bytes));
3235
+ const labels = (0, _src_core.parseStudyLabeling)(decodeStudyJSON(bytes));
4863
3236
  const translated = translateStudyPacketLabeling(result, previous.rubric, previous.grading.rubric, labels);
4864
3237
  signal.throwIfAborted();
4865
3238
  const grading = {
@@ -4903,7 +3276,7 @@ var StudyCommand = class {
4903
3276
  const result = await this.#result(request.target);
4904
3277
  const context = await this.#grading(result, request.grading);
4905
3278
  signal.throwIfAborted();
4906
- const labels = context.labels === void 0 ? void 0 : parseStudyLabeling(decodeStudyJSON(context.labels));
3279
+ const labels = context.labels === void 0 ? void 0 : (0, _src_core.parseStudyLabeling)(decodeStudyJSON(context.labels));
4907
3280
  return valueToWire({
4908
3281
  command: "report",
4909
3282
  grading: request.grading,
@@ -4916,31 +3289,34 @@ var StudyCommand = class {
4916
3289
  });
4917
3290
  }
4918
3291
  async #result(target) {
4919
- const result = await readStudyResult((0, node_path.isAbsolute)(target) ? (0, node_path.resolve)(target) : target.includes("/") ? resolveStudyPath(this.#root, target) : resolveStudyPath((0, node_path.join)(this.#root, "tmp", "studies"), target));
4920
- if (result.status !== "sealed") throw new StudyError("CUSTODY", "Interrupted execution cannot be graded or reported");
3292
+ const result = await readStudyResult((0, node_path.isAbsolute)(target) ? (0, node_path.resolve)(target) : target.includes("/") ? resolveStudyPath(this.#root, target) : resolveStudyPath((0, node_path.join)(this.#root, "tmp", "studies"), target)).catch((error) => {
3293
+ if (matchesSystemError(error, "ENOENT")) throw new _src_core.StudyError("CUSTODY", `Selected result is missing: ${target}`);
3294
+ throw error;
3295
+ });
3296
+ if (result.status !== "sealed") throw new _src_core.StudyError("CUSTODY", "Interrupted execution cannot be graded or reported");
4921
3297
  return result;
4922
3298
  }
4923
3299
  async #grading(result, id) {
4924
3300
  const directory = (0, node_path.join)(result.directory, "grades", id);
4925
3301
  const raw = await readStudyBytes((0, node_path.join)(directory, "grading.json"), STUDY_CUSTODY_BYTES).catch((error) => {
4926
- if (error instanceof Error && "code" in error && error.code === "ENOENT") throw new StudyError("CUSTODY", `Selected grading is missing: ${(0, node_path.join)(directory, "grading.json")}`);
3302
+ if (matchesSystemError(error, "ENOENT")) throw new _src_core.StudyError("CUSTODY", `Selected grading is missing: ${(0, node_path.join)(directory, "grading.json")}`);
4927
3303
  throw error;
4928
3304
  });
4929
- if (hashStudyBytes(raw) !== id) throw new StudyError("CUSTODY", "Selected grading differs from its byte identity");
4930
- const declared = parseStudyGrading(decodeStudyJSON(raw));
3305
+ if (hashStudyBytes(raw) !== id) throw new _src_core.StudyError("CUSTODY", "Selected grading differs from its byte identity");
3306
+ const declared = (0, _src_core.parseStudyGrading)(decodeStudyJSON(raw));
4931
3307
  const bytes = await readStudyBytes((0, node_path.join)(directory, `${declared.rubric}.rubric.json`), STUDY_CUSTODY_BYTES);
4932
- const rubric = parseStudyRubric(decodeStudyJSON(bytes));
3308
+ const rubric = (0, _src_core.parseStudyRubric)(decodeStudyJSON(bytes));
4933
3309
  const grading = await new StudyGradingStore({
4934
3310
  result,
4935
3311
  rubric: bytes
4936
3312
  }).read(id);
4937
- if (grading === void 0) throw new StudyError("CUSTODY", `Selected grading is missing: ${directory}`);
3313
+ if (grading === void 0) throw new _src_core.StudyError("CUSTODY", `Selected grading is missing: ${directory}`);
4938
3314
  const packet = grading.packet === void 0 ? void 0 : {
4939
3315
  content: await readStudyBytes((0, node_path.join)(directory, `${grading.packet}.packet.json`), STUDY_CUSTODY_BYTES),
4940
3316
  mapping: await readStudyBytes((0, node_path.join)(directory, `${grading.packet}.mapping.json`), STUDY_CUSTODY_BYTES)
4941
3317
  };
4942
3318
  const labels = grading.labels === void 0 ? void 0 : await readStudyBytes((0, node_path.join)(directory, `${grading.labels}.labels.json`), STUDY_CUSTODY_BYTES);
4943
- if (hashStudyBytes(bytes) !== grading.rubric || packet !== void 0 && computeStudyPacketDigest(packet) !== grading.packet || labels !== void 0 && hashStudyBytes(labels) !== grading.labels) throw new StudyError("CUSTODY", "Grading artifacts changed while reading their verified context");
3319
+ if (hashStudyBytes(bytes) !== grading.rubric || packet !== void 0 && computeStudyPacketDigest(packet) !== grading.packet || labels !== void 0 && hashStudyBytes(labels) !== grading.labels) throw new _src_core.StudyError("CUSTODY", "Grading artifacts changed while reading their verified context");
4944
3320
  return {
4945
3321
  grading,
4946
3322
  rubric,
@@ -4958,7 +3334,7 @@ var StudyCommand = class {
4958
3334
  try {
4959
3335
  await (0, node_fs_promises.mkdir)(directory);
4960
3336
  } catch (error) {
4961
- if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
3337
+ if (!matchesSystemError(error, "EEXIST")) throw error;
4962
3338
  }
4963
3339
  return checkStudyDirectory(directory);
4964
3340
  }, checkStudyDirectory(this.#root));
@@ -4966,11 +3342,14 @@ var StudyCommand = class {
4966
3342
  async #qualification(preparation) {
4967
3343
  if (preparation.profile.qualification === void 0 || preparation.definition.purpose === "capacity") return void 0;
4968
3344
  const result = await this.#result(preparation.profile.qualification);
4969
- if (result.manifest.definition.purpose !== "capacity" || hashStudyBytes(encodeStudyJSON(result.manifest.identity)) !== hashStudyBytes(encodeStudyJSON(preparation.identity)) || hashStudyBytes(encodeStudyJSON(result.manifest.bounds)) !== hashStudyBytes(encodeStudyJSON(preparation.profile.bounds)) || hashStudyBytes(encodeStudyJSON(result.manifest.schedule)) !== hashStudyBytes(encodeStudyJSON(buildStudySchedule(preparation.definition, preparation.scenarios))) || hashStudyBytes(encodeStudyJSON(result.manifest.scenarios)) !== hashStudyBytes(encodeStudyJSON(preparation.scenarios)) || result.attempts.some((attempt) => attempt.failure !== void 0 || attempt.observations.some((observation) => observation.failure !== void 0 || observation.product === void 0))) throw new StudyError("CONFIG", "Qualification requires a compatible complete capacity result with no operational failures");
3345
+ if (result.manifest.definition.purpose !== "capacity" || hashStudyBytes(encodeStudyJSON(result.manifest.identity)) !== hashStudyBytes(encodeStudyJSON(preparation.identity)) || hashStudyBytes(encodeStudyJSON(result.manifest.bounds)) !== hashStudyBytes(encodeStudyJSON(preparation.profile.bounds)) || hashStudyBytes(encodeStudyJSON(result.manifest.schedule)) !== hashStudyBytes(encodeStudyJSON((0, _src_core.buildStudySchedule)(preparation.definition, preparation.scenarios))) || hashStudyBytes(encodeStudyJSON(result.manifest.scenarios)) !== hashStudyBytes(encodeStudyJSON(preparation.scenarios)) || result.attempts.some((attempt) => attempt.failure !== void 0 || attempt.observations.some((observation) => observation.failure !== void 0 || observation.product === void 0))) throw new _src_core.StudyError("CONFIG", "Qualification requires a compatible complete capacity result with no operational failures");
4970
3346
  return result.seal.id;
4971
3347
  }
4972
3348
  async #instrument() {
4973
- return hashStudyBytes(await readStudyBytes(this.#entry, STUDY_CUSTODY_BYTES));
3349
+ return computeStudyInstrumentDigest(hashStudyBytes(await readStudyBytes(this.#entry, STUDY_CUSTODY_BYTES)), new ArtifactLoader({
3350
+ entries: [this.#entry],
3351
+ roots: [resolveStudyInstrument(this.#entry)]
3352
+ }).inspect().digest);
4974
3353
  }
4975
3354
  };
4976
3355
  //#endregion
@@ -5011,7 +3390,7 @@ var StudyRemoteTicket = class {
5011
3390
  role: void 0,
5012
3391
  stop: void 0
5013
3392
  });
5014
- throw new StudyError("FAULT", "Worker ticket settled more than once");
3393
+ throw new _src_core.StudyError("FAULT", "Worker ticket settled more than once");
5015
3394
  }
5016
3395
  this.#settled = true;
5017
3396
  this.#options.settle(operation, value);
@@ -5045,7 +3424,7 @@ var StudyRemoteMeter = class {
5045
3424
  * @returns A ticket only after authoritative admission succeeds.
5046
3425
  */
5047
3426
  async admit(charge) {
5048
- if (this.#closed || this.#options.control.failure !== void 0) throw failureToStudyError(this.#options.control.failure ?? createStudyProcessFailure("Worker meter is closed"));
3427
+ if (this.#closed || this.#options.control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#options.control.failure ?? createStudyProcessFailure("Worker meter is closed"));
5049
3428
  const id = ++this.#next;
5050
3429
  this.#admissions++;
5051
3430
  try {
@@ -5053,8 +3432,8 @@ var StudyRemoteMeter = class {
5053
3432
  id,
5054
3433
  charge
5055
3434
  })));
5056
- if (!(0, _orkestrel_contract.isRecord)(response) || response.id !== id) throw new StudyError("FAULT", "Worker admission grant is invalid");
5057
- if (this.#closed) throw new StudyError("FAULT", "Worker goal ended during admission");
3435
+ if (!(0, _orkestrel_contract.isRecord)(response) || response.id !== id) throw new _src_core.StudyError("FAULT", "Worker admission grant is invalid");
3436
+ if (this.#closed) throw new _src_core.StudyError("FAULT", "Worker goal ended during admission");
5058
3437
  const controller = new AbortController();
5059
3438
  this.#tickets.set(id, controller);
5060
3439
  return new StudyRemoteTicket({
@@ -5063,7 +3442,7 @@ var StudyRemoteMeter = class {
5063
3442
  settle: (operation, value) => this.#settle(id, operation, value)
5064
3443
  });
5065
3444
  } catch (error) {
5066
- this.#options.control.fail(readStudyFailure(error));
3445
+ this.#options.control.fail((0, _src_core.readStudyFailure)(error));
5067
3446
  throw error;
5068
3447
  } finally {
5069
3448
  this.#admissions--;
@@ -5075,8 +3454,8 @@ var StudyRemoteMeter = class {
5075
3454
  */
5076
3455
  abort(id, failure) {
5077
3456
  const ticket = this.#tickets.get(id);
5078
- if (ticket === void 0) throw new StudyError("FAULT", "Cancellation names an unknown worker ticket");
5079
- ticket.abort(failureToStudyError(failure));
3457
+ if (ticket === void 0) throw new _src_core.StudyError("FAULT", "Cancellation names an unknown worker ticket");
3458
+ ticket.abort((0, _src_core.failureToStudyError)(failure));
5080
3459
  this.#options.control.fail(failure);
5081
3460
  }
5082
3461
  /** Closes admission and requires every ticket and acknowledgment to finish. */
@@ -5084,12 +3463,12 @@ var StudyRemoteMeter = class {
5084
3463
  this.#closed = true;
5085
3464
  if (this.#tickets.size !== 0 || this.#admissions !== 0) this.#options.control.fail(createStudyProcessFailure("Worker goal ended with unfinished requests"));
5086
3465
  await Promise.all(this.#pending);
5087
- if (this.#options.control.failure !== void 0) throw failureToStudyError(this.#options.control.failure);
3466
+ if (this.#options.control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#options.control.failure);
5088
3467
  }
5089
3468
  #settle(id, operation, value) {
5090
3469
  if (!this.#tickets.delete(id)) {
5091
3470
  this.#options.control.fail(createStudyProcessFailure("Worker ticket settlement is repeated or foreign"));
5092
- throw failureToStudyError(this.#options.control.failure ?? createStudyProcessFailure("Worker settlement failed"));
3471
+ throw (0, _src_core.failureToStudyError)(this.#options.control.failure ?? createStudyProcessFailure("Worker settlement failed"));
5093
3472
  }
5094
3473
  const accepted = Object.freeze({ ...value });
5095
3474
  const work = this.#options.channel.request(operation, valueToWire(operation === "finish" ? {
@@ -5099,9 +3478,9 @@ var StudyRemoteMeter = class {
5099
3478
  id,
5100
3479
  failure: accepted
5101
3480
  })).then(() => {
5102
- if (operation === "finish" && isStudyUsage(accepted)) this.#usage.push(accepted);
3481
+ if (operation === "finish" && (0, _src_core.isStudyUsage)(accepted)) this.#usage.push(accepted);
5103
3482
  }, (error) => {
5104
- this.#options.control.fail(readStudyFailure(error));
3483
+ this.#options.control.fail((0, _src_core.readStudyFailure)(error));
5105
3484
  });
5106
3485
  this.#pending.add(work);
5107
3486
  work.finally(() => this.#pending.delete(work));
@@ -5112,7 +3491,7 @@ var StudyRemoteMeter = class {
5112
3491
  //#region src/server/processes/StudyWorker.ts
5113
3492
  /** Executes one verified trial behind parent-authorized admission and custody. */
5114
3493
  var StudyWorker = class {
5115
- #control = new StudyControl();
3494
+ #control = new _src_core.StudyControl();
5116
3495
  #channel;
5117
3496
  #artifacts;
5118
3497
  #session;
@@ -5126,7 +3505,7 @@ var StudyWorker = class {
5126
3505
  * @returns Completion after channel and acquired session cleanup.
5127
3506
  */
5128
3507
  async execute() {
5129
- if (this.#executed || process.send === void 0 || !process.connected) throw new StudyError("CONFIG", "Study worker requires one inherited IPC channel");
3508
+ if (this.#executed || process.send === void 0 || !process.connected) throw new _src_core.StudyError("CONFIG", "Study worker requires one inherited IPC channel");
5130
3509
  this.#executed = true;
5131
3510
  this.#channel = new StudyChannel({
5132
3511
  endpoint: process,
@@ -5137,7 +3516,7 @@ var StudyWorker = class {
5137
3516
  await new Promise((resolve) => this.#channel?.signal.addEventListener("abort", () => resolve(), { once: true }));
5138
3517
  } finally {
5139
3518
  this.#control.fail(createStudyProcessFailure("Study worker disconnected"));
5140
- if (this.#session !== void 0) await new StudyDeadline({
3519
+ if (this.#session !== void 0) await new _src_core.StudyDeadline({
5141
3520
  duration: this.#duration,
5142
3521
  label: STUDY_PROCESS_LABEL
5143
3522
  }).wait(this.#session.close()).catch(() => void 0);
@@ -5151,7 +3530,7 @@ var StudyWorker = class {
5151
3530
  if (operation === "open") return await this.#open(value);
5152
3531
  if (operation === "goal") return await this.#goal(value);
5153
3532
  if (operation === "close") {
5154
- if (this.#closing) throw new StudyError("FAULT", "Worker cleanup repeated");
3533
+ if (this.#closing) throw new _src_core.StudyError("FAULT", "Worker cleanup repeated");
5155
3534
  this.#closing = true;
5156
3535
  this.#control.fail({
5157
3536
  cause: "interrupted",
@@ -5159,7 +3538,7 @@ var StudyWorker = class {
5159
3538
  role: void 0,
5160
3539
  stop: void 0
5161
3540
  });
5162
- if (this.#session !== void 0) await new StudyDeadline({
3541
+ if (this.#session !== void 0) await new _src_core.StudyDeadline({
5163
3542
  duration: this.#duration,
5164
3543
  label: STUDY_PROCESS_LABEL
5165
3544
  }).wait(this.#session.close());
@@ -5167,16 +3546,16 @@ var StudyWorker = class {
5167
3546
  this.#artifacts?.verify();
5168
3547
  return true;
5169
3548
  }
5170
- throw new StudyError("FAULT", "Parent requested a forbidden worker operation");
3549
+ throw new _src_core.StudyError("FAULT", "Parent requested a forbidden worker operation");
5171
3550
  } catch (error) {
5172
- this.#control.fail(readStudyFailure(error));
3551
+ this.#control.fail((0, _src_core.readStudyFailure)(error));
5173
3552
  throw error;
5174
3553
  }
5175
3554
  }
5176
3555
  async #open(value) {
5177
- if (this.#opening || this.#closing || this.#control.failure !== void 0) throw new StudyError("FAULT", "Worker trial can open only once");
3556
+ if (this.#opening || this.#closing || this.#control.failure !== void 0) throw new _src_core.StudyError("FAULT", "Worker trial can open only once");
5178
3557
  this.#opening = true;
5179
- if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.entry !== "string" || typeof value.root !== "string" || typeof value.directory !== "string" || typeof value.driver !== "string" || typeof value.adapter !== "string" || !isStudyProfile(value.profile) || !isStudyIdentity(value.identity) || !isStudyTrial(value.trial)) throw new StudyError("CONFIG", "Worker opening declaration is invalid or driver is unsupported");
3558
+ if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.entry !== "string" || typeof value.root !== "string" || typeof value.directory !== "string" || typeof value.driver !== "string" || typeof value.adapter !== "string" || !(0, _src_core.isStudyProfile)(value.profile) || !isStudyIdentity(value.identity) || !isStudyTrial(value.trial)) throw new _src_core.StudyError("CONFIG", "Worker opening declaration is invalid or driver is unsupported");
5180
3559
  this.#duration = value.profile.bounds.request.duration;
5181
3560
  this.#artifacts = new StudyArtifactLoader({
5182
3561
  entry: value.entry,
@@ -5185,7 +3564,7 @@ var StudyWorker = class {
5185
3564
  adapter: value.adapter
5186
3565
  });
5187
3566
  const identity = await this.#artifacts.inspect();
5188
- if (identity.agent !== value.identity.agent || identity.subject !== value.identity.subject || identity.provider !== value.identity.provider || identity.instrument !== value.identity.instrument || computeStudyProfileDigest(value.profile) !== value.identity.profile) throw new StudyError("FAULT", "Worker artifact identity differs from parent declaration");
3567
+ if (identity.agent !== value.identity.agent || identity.subject !== value.identity.subject || identity.provider !== value.identity.provider || identity.instrument !== value.identity.instrument || computeStudyProfileDigest(value.profile) !== value.identity.profile) throw new _src_core.StudyError("FAULT", "Worker artifact identity differs from parent declaration");
5189
3568
  const modules = await this.#artifacts.load();
5190
3569
  const driver = (await this.#artifacts.adapter()).driver({
5191
3570
  ...modules,
@@ -5193,14 +3572,14 @@ var StudyWorker = class {
5193
3572
  directory: value.directory
5194
3573
  });
5195
3574
  this.#session = await driver.open(value.trial, this.#control.signal);
5196
- if (this.#control.failure !== void 0) throw failureToStudyError(this.#control.failure);
3575
+ if (this.#control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#control.failure);
5197
3576
  return valueToWire({
5198
3577
  ...identity,
5199
3578
  pid: process.pid
5200
3579
  });
5201
3580
  }
5202
3581
  async #goal(value) {
5203
- if (this.#session === void 0 || this.#channel === void 0 || this.#meters.size !== 0 || this.#closing || this.#control.failure !== void 0 || !isStudyGoal(value)) throw new StudyError("FAULT", "Worker goal is invalid or out of order");
3582
+ if (this.#session === void 0 || this.#channel === void 0 || this.#meters.size !== 0 || this.#closing || this.#control.failure !== void 0 || !(0, _src_core.isStudyGoal)(value)) throw new _src_core.StudyError("FAULT", "Worker goal is invalid or out of order");
5204
3583
  const meter = new StudyRemoteMeter({
5205
3584
  channel: this.#channel,
5206
3585
  control: this.#control
@@ -5214,19 +3593,19 @@ var StudyWorker = class {
5214
3593
  }));
5215
3594
  await meter.close();
5216
3595
  await Promise.all(this.#evidence);
5217
- if (this.#control.failure !== void 0) throw failureToStudyError(this.#control.failure);
5218
- if (!isStudyProduct(product)) throw new StudyError("FAULT", "Worker driver returned an invalid product");
3596
+ if (this.#control.failure !== void 0) throw (0, _src_core.failureToStudyError)(this.#control.failure);
3597
+ if (!(0, _src_core.isStudyProduct)(product)) throw new _src_core.StudyError("FAULT", "Worker driver returned an invalid product");
5219
3598
  return valueToWire(product);
5220
3599
  } finally {
5221
3600
  this.#meters.delete(meter);
5222
3601
  }
5223
3602
  }
5224
3603
  #append(value) {
5225
- if (this.#channel === void 0 || this.#meters.size === 0 || this.#control.failure !== void 0) return Promise.reject(new StudyError("FAULT", "Worker evidence is outside an active goal"));
3604
+ if (this.#channel === void 0 || this.#meters.size === 0 || this.#control.failure !== void 0) return Promise.reject(new _src_core.StudyError("FAULT", "Worker evidence is outside an active goal"));
5226
3605
  const pending = this.#channel.request("evidence", value).then((acknowledged) => {
5227
- if (acknowledged !== true) throw new StudyError("FAULT", "Worker evidence was not acknowledged");
3606
+ if (acknowledged !== true) throw new _src_core.StudyError("FAULT", "Worker evidence was not acknowledged");
5228
3607
  }).catch((error) => {
5229
- this.#control.fail(readStudyFailure(error));
3608
+ this.#control.fail((0, _src_core.readStudyFailure)(error));
5230
3609
  throw error;
5231
3610
  });
5232
3611
  this.#evidence.add(pending);
@@ -5234,11 +3613,11 @@ var StudyWorker = class {
5234
3613
  return pending;
5235
3614
  }
5236
3615
  #abort(value) {
5237
- if (!(0, _orkestrel_contract.isRecord)(value) || !isStudyProcessFailure(value.failure)) throw new StudyError("FAULT", "Worker cancellation is malformed");
3616
+ if (!(0, _orkestrel_contract.isRecord)(value) || !isStudyProcessFailure(value.failure)) throw new _src_core.StudyError("FAULT", "Worker cancellation is malformed");
5238
3617
  if (value.id === void 0) this.#control.fail(value.failure);
5239
3618
  else {
5240
3619
  const [meter] = this.#meters;
5241
- if (typeof value.id !== "number" || meter === void 0) throw new StudyError("FAULT", "Worker ticket cancellation is foreign");
3620
+ if (typeof value.id !== "number" || meter === void 0) throw new _src_core.StudyError("FAULT", "Worker ticket cancellation is foreign");
5242
3621
  meter.abort(value.id, value.failure);
5243
3622
  }
5244
3623
  }
@@ -5249,12 +3628,12 @@ var StudyWorker = class {
5249
3628
  var StudyTaskWorker = class {
5250
3629
  /** Processes exactly one inherited request and returns a bounded response. */
5251
3630
  async execute() {
5252
- if (process.send === void 0 || !process.connected) throw new StudyError("CONFIG", "Adapter worker requires inherited IPC");
3631
+ if (process.send === void 0 || !process.connected) throw new _src_core.StudyError("CONFIG", "Adapter worker requires inherited IPC");
5253
3632
  process.channel?.ref();
5254
3633
  const value = await new Promise((resolve) => process.once("message", resolve));
5255
3634
  const outcome = await this.#execute(value).catch((error) => ({
5256
3635
  error: error instanceof Error ? error.message : String(error),
5257
- code: isStudyError(error) ? error.code : "FAULT"
3636
+ code: (0, _src_core.isStudyError)(error) ? error.code : "FAULT"
5258
3637
  }));
5259
3638
  const response = Buffer.byteLength(JSON.stringify(outcome)) > 16777216 ? {
5260
3639
  error: "Adapter result exceeds the frame allowance",
@@ -5263,11 +3642,11 @@ var StudyTaskWorker = class {
5263
3642
  await new Promise((resolve, reject) => process.send?.(response, (error) => error === null ? resolve() : reject(error)));
5264
3643
  }
5265
3644
  async #execute(value) {
5266
- if (!(0, _orkestrel_contract.isRecord)(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216 || typeof value.adapter !== "string" || typeof value.entry !== "string" || !(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(value.roots) || !(0, _orkestrel_contract.isRecord)(value.input)) throw new StudyError("CONFIG", "Adapter request is malformed");
3645
+ if (!(0, _orkestrel_contract.isRecord)(value) || Buffer.byteLength(JSON.stringify(value)) > 16777216 || typeof value.adapter !== "string" || typeof value.entry !== "string" || !(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(value.roots) || !(0, _orkestrel_contract.isRecord)(value.input)) throw new _src_core.StudyError("CONFIG", "Adapter request is malformed");
5267
3646
  const runtime = resolveStudyInstrument(value.entry);
5268
3647
  const roots = [.../* @__PURE__ */ new Set([...value.roots, runtime])];
5269
3648
  validateStudyRuntime(runtime, roots);
5270
- const profile = value.operation === "validate" ? parseStudyProfile(value.input.profile) : void 0;
3649
+ const profile = value.operation === "validate" ? (0, _src_core.parseStudyProfile)(value.input.profile) : void 0;
5271
3650
  const loader = new ArtifactLoader({
5272
3651
  entries: [],
5273
3652
  roots,
@@ -5280,7 +3659,7 @@ var StudyTaskWorker = class {
5280
3659
  });
5281
3660
  const instrument = computeStudyInstrumentDigest(hashStudyBytes(await readStudyBytes(value.entry, STUDY_CUSTODY_BYTES)), loader.inspect().digest);
5282
3661
  const adapter = readStudyAdapter(await loader.load(value.adapter));
5283
- if (profile === void 0 && value.operation !== "grade") throw new StudyError("CONFIG", "Unknown adapter operation");
3662
+ if (profile === void 0 && value.operation !== "grade") throw new _src_core.StudyError("CONFIG", "Unknown adapter operation");
5284
3663
  const output = profile === void 0 ? this.#grade(adapter, value.input) : this.#validate(adapter, value.input, profile);
5285
3664
  loader.verify();
5286
3665
  return valueToWire({
@@ -5289,31 +3668,31 @@ var StudyTaskWorker = class {
5289
3668
  });
5290
3669
  }
5291
3670
  #validate(adapter, input, profile) {
5292
- if (!(0, _orkestrel_contract.isArray)(input.scenarios)) throw new StudyError("CONFIG", "Adapter scenarios are invalid");
3671
+ if (!(0, _orkestrel_contract.isArray)(input.scenarios)) throw new _src_core.StudyError("CONFIG", "Adapter scenarios are invalid");
5293
3672
  adapter.validate({
5294
- definition: parseStudyDefinition(input.definition),
5295
- scenarios: input.scenarios.map(parseStudyScenario),
5296
- rubric: parseStudyRubric(input.rubric),
3673
+ definition: (0, _src_core.parseStudyDefinition)(input.definition),
3674
+ scenarios: input.scenarios.map(_src_core.parseStudyScenario),
3675
+ rubric: (0, _src_core.parseStudyRubric)(input.rubric),
5297
3676
  profile
5298
3677
  });
5299
3678
  return true;
5300
3679
  }
5301
3680
  #grade(adapter, input) {
5302
- if (!isStudyCustodyManifest(input.manifest) || !(0, _orkestrel_contract.isArray)(input.attempts) || !(0, _orkestrel_contract.isRecord)(input.evidence)) throw new StudyError("CONFIG", "Adapter grading input is invalid");
3681
+ if (!isStudyCustodyManifest(input.manifest) || !(0, _orkestrel_contract.isArray)(input.attempts) || !(0, _orkestrel_contract.isRecord)(input.evidence)) throw new _src_core.StudyError("CONFIG", "Adapter grading input is invalid");
5303
3682
  const manifest = input.manifest;
5304
3683
  const attempts = input.attempts;
5305
- if (!attempts.every((retained) => isStudyCustodyAttempt(retained, manifest))) throw new StudyError("CONFIG", "Adapter attempts are invalid");
3684
+ if (!attempts.every((retained) => isStudyCustodyAttempt(retained, manifest))) throw new _src_core.StudyError("CONFIG", "Adapter attempts are invalid");
5306
3685
  const evidence = Object.fromEntries(Object.entries(input.evidence).map(([trial, records]) => {
5307
- if (!(0, _orkestrel_contract.isArray)(records) || !records.every(isStudyCustodyEvidence)) throw new StudyError("CONFIG", "Adapter evidence is invalid");
3686
+ if (!(0, _orkestrel_contract.isArray)(records) || !records.every(isStudyCustodyEvidence)) throw new _src_core.StudyError("CONFIG", "Adapter evidence is invalid");
5308
3687
  return [trial, records];
5309
3688
  }));
5310
3689
  const output = adapter.grade({
5311
3690
  manifest,
5312
3691
  attempts,
5313
- rubric: parseStudyRubric(input.rubric),
3692
+ rubric: (0, _src_core.parseStudyRubric)(input.rubric),
5314
3693
  evidence
5315
3694
  });
5316
- if (!(0, _orkestrel_contract.isArray)(output) || !output.every(isStudyJudgment)) throw new StudyError("FAULT", "Adapter returned invalid judgments");
3695
+ if (!(0, _orkestrel_contract.isArray)(output) || !output.every(_src_core.isStudyJudgment)) throw new _src_core.StudyError("FAULT", "Adapter returned invalid judgments");
5317
3696
  return output;
5318
3697
  }
5319
3698
  };
@@ -5355,7 +3734,7 @@ var StudyLaunchChild = class {
5355
3734
  * @throws StudyError - Thrown with code `CONFIG` when this child is already supervised.
5356
3735
  */
5357
3736
  execute() {
5358
- if (this.#executed) throw new StudyError("CONFIG", "Study launch can execute only once");
3737
+ if (this.#executed) throw new _src_core.StudyError("CONFIG", "Study launch can execute only once");
5359
3738
  this.#executed = true;
5360
3739
  const child = this.#child;
5361
3740
  const signal = this.#signal;
@@ -5446,10 +3825,10 @@ var StudyLauncher = class {
5446
3825
  return reading.code;
5447
3826
  }
5448
3827
  const value = JSON.parse(reading.output);
5449
- if (!(0, _orkestrel_contract.isRecord)(value) || !isStudyBounds(value.bounds) || value.bounds.series.duration > 864e5) throw new Error("Study execution requires a validated series duration within the 24-hour host ceiling");
5450
- if (!isStudyDigest(value.fingerprint)) throw new Error("Study check did not return an execution fingerprint");
3828
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _src_core.isStudyBounds)(value.bounds) || value.bounds.series.duration > 864e5) throw new Error("Study execution requires a validated series duration within the 24-hour host ceiling");
3829
+ if (!(0, _src_core.isStudyDigest)(value.fingerprint)) throw new Error("Study check did not return an execution fingerprint");
5451
3830
  const duration = value.bounds.series.duration + STUDY_LAUNCH_CLEANUP;
5452
- if (request.expected !== void 0 && request.expected !== value.fingerprint) throw new StudyError("CONFIG", "Validated execution differs from the requested fingerprint");
3831
+ if (request.expected !== void 0 && request.expected !== value.fingerprint) throw new _src_core.StudyError("CONFIG", "Validated execution differs from the requested fingerprint");
5453
3832
  const execution = [
5454
3833
  "execute",
5455
3834
  request.target,
@@ -5507,7 +3886,7 @@ var StudyCLI = class {
5507
3886
  "study-command",
5508
3887
  "study-worker",
5509
3888
  "study-adapter"
5510
- ].includes(arguments_[0] ?? "") && (process.send === void 0 || !process.connected)) throw new StudyError("CONFIG", `Private route ${JSON.stringify(arguments_[0])} requires supervised native IPC; use study check, execute, grade, label, or report. Use study --help for grammar.`);
3889
+ ].includes(arguments_[0] ?? "") && (process.send === void 0 || !process.connected)) throw new _src_core.StudyError("CONFIG", `Private route ${JSON.stringify(arguments_[0])} requires supervised native IPC; use study check, execute, grade, label, or report. Use study --help for grammar.`);
5511
3890
  if (arguments_[0] === "study-worker") {
5512
3891
  await new StudyWorker().execute();
5513
3892
  return 0;
@@ -5521,6 +3900,10 @@ var StudyCLI = class {
5521
3900
  process.stdout.write(STUDY_COMMAND_HELP);
5522
3901
  return 0;
5523
3902
  }
3903
+ if (arguments_.length === 1 && arguments_[0] === "--version") {
3904
+ process.stdout.write(`${readStudyVersion(this.#options.entry)}\n`);
3905
+ return 0;
3906
+ }
5524
3907
  if (arguments_.length === 0) {
5525
3908
  process.stderr.write(STUDY_COMMAND_HELP);
5526
3909
  return 64;
@@ -5542,13 +3925,105 @@ var StudyCLI = class {
5542
3925
  } catch (error) {
5543
3926
  const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "FAULT";
5544
3927
  process.stderr.write(`study: ${code}: ${error instanceof Error ? error.message : String(error)}\n`);
5545
- return error instanceof StudyError ? error.code === "CONFIG" ? 64 : error.failure?.cause === "deadline" ? 124 : error.failure?.cause === "interrupted" ? 130 : 1 : 1;
3928
+ return error instanceof _src_core.StudyError ? error.code === "CONFIG" ? 64 : error.failure?.cause === "deadline" ? 124 : error.failure?.cause === "interrupted" ? 130 : 1 : 1;
5546
3929
  }
5547
3930
  }
5548
3931
  };
5549
3932
  //#endregion
5550
3933
  //#region src/server/agents/helpers.ts
5551
3934
  /**
3935
+ * Reads an Agent tool call into a validated envelope from one read of each foreign field.
3936
+ * @remarks The returned envelope owns its fields; arguments retain their original record identity.
3937
+ * JSON validation and copying belong to the executed-result projection.
3938
+ * @param call - The call received from the admitted Agent
3939
+ * @returns A fresh ToolCall envelope with the original arguments record
3940
+ * @throws StudyError - Thrown with code `FAULT` when a field cannot be read or the envelope is malformed
3941
+ * @example Read an Agent tool call
3942
+ * ```ts
3943
+ * import { readStudyAgentToolCall } from '@orkestrel/study/server'
3944
+ *
3945
+ * const call = readStudyAgentToolCall({ id: 'c1', name: 'write', arguments: { path: 'note.txt' } })
3946
+ * // { id: 'c1', name: 'write', arguments: { path: 'note.txt' } }
3947
+ * ```
3948
+ */
3949
+ function readStudyAgentToolCall(call) {
3950
+ const observed = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.isRecord)(call) ? {
3951
+ id: "id" in call ? call.id : void 0,
3952
+ name: "name" in call ? call.name : void 0,
3953
+ arguments: "arguments" in call ? call.arguments : void 0
3954
+ } : void 0);
3955
+ if (!observed.success || !(0, _orkestrel_tool.isToolCall)(observed.value)) throw new _src_core.StudyError("FAULT", "Admitted Agent supplied invalid tool observation");
3956
+ return observed.value;
3957
+ }
3958
+ /**
3959
+ * Builds the JSON evidence form of an Agent tool call and its executed result.
3960
+ * @param call - The call received from the admitted Agent
3961
+ * @param result - The executed tool result
3962
+ * @returns The call identity, owned arguments, success, and owned value or error text
3963
+ * @throws StudyError - Thrown with code `FAULT` when the call, result, arguments, or value is malformed
3964
+ * @example
3965
+ * ```ts
3966
+ * buildStudyAgentToolResult(call, { success: true, value: 'stored' })
3967
+ * ```
3968
+ */
3969
+ function buildStudyAgentToolResult(call, result) {
3970
+ const { id, name, arguments: arguments_ } = readStudyAgentToolCall(call);
3971
+ const returned = (0, _orkestrel_contract.attempt)(() => {
3972
+ if (!(0, _orkestrel_contract.isObject)(result)) return void 0;
3973
+ const success = "success" in result ? result.success : void 0;
3974
+ if (typeof success !== "boolean") return void 0;
3975
+ return success ? {
3976
+ success,
3977
+ value: "value" in result ? result.value : void 0
3978
+ } : {
3979
+ success,
3980
+ error: "error" in result ? result.error : void 0
3981
+ };
3982
+ });
3983
+ if (!returned.success || returned.value === void 0) throw new _src_core.StudyError("FAULT", "Admitted Agent supplied invalid tool result");
3984
+ const argumentsCopy = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONValue)(arguments_));
3985
+ if (!argumentsCopy.success) throw new _src_core.StudyError("FAULT", "Admitted Agent supplied non-JSON tool arguments");
3986
+ const outcome = returned.value;
3987
+ if (outcome.success) {
3988
+ const valueCopy = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONValue)(outcome.value));
3989
+ if (!valueCopy.success) throw new _src_core.StudyError("FAULT", "Admitted Agent supplied non-JSON tool value");
3990
+ return {
3991
+ id,
3992
+ name,
3993
+ arguments: argumentsCopy.value,
3994
+ success: true,
3995
+ value: valueCopy.value
3996
+ };
3997
+ }
3998
+ if (typeof outcome.error !== "string") throw new _src_core.StudyError("FAULT", "Admitted Agent supplied invalid tool error");
3999
+ return {
4000
+ id,
4001
+ name,
4002
+ arguments: argumentsCopy.value,
4003
+ success: false,
4004
+ error: outcome.error
4005
+ };
4006
+ }
4007
+ /**
4008
+ * Builds the JSON evidence form of an Agent tool call the Agent denied before dispatch.
4009
+ * @param call - The call received from the admitted Agent
4010
+ * @param reason - The denial reason, retained only when it is a string
4011
+ * @returns The call identity with the optional string reason
4012
+ * @throws StudyError - Thrown with code `FAULT` when the call is malformed
4013
+ * @example
4014
+ * ```ts
4015
+ * buildStudyAgentToolDenial(call, 'refused')
4016
+ * ```
4017
+ */
4018
+ function buildStudyAgentToolDenial(call, reason) {
4019
+ const { id, name } = readStudyAgentToolCall(call);
4020
+ return {
4021
+ id,
4022
+ name,
4023
+ ...typeof reason === "string" ? { reason } : {}
4024
+ };
4025
+ }
4026
+ /**
5552
4027
  * Builds a request revision by hashing its canonical rendering exactly as rendered.
5553
4028
  * @remarks
5554
4029
  * The request is rendered once with key-sorted canonical JSON and hashed as rendered; no
@@ -5586,7 +4061,7 @@ function buildStudyAgentReadings(result) {
5586
4061
  function resolveStudyAgentContent(request, response) {
5587
4062
  if (response.consume === void 0) return response.content;
5588
4063
  const prior = request.findLast((message) => (0, _orkestrel_contract.isRecord)(message) && message.role === "tool" && message.call === response.consume);
5589
- if (!(0, _orkestrel_contract.isRecord)(prior) || typeof prior.content !== "string") throw new StudyError("FAULT", "Continuation has no actual referenced tool result");
4064
+ if (!(0, _orkestrel_contract.isRecord)(prior) || typeof prior.content !== "string") throw new _src_core.StudyError("FAULT", "Continuation has no actual referenced tool result");
5590
4065
  return prior.content;
5591
4066
  }
5592
4067
  /**
@@ -5611,13 +4086,13 @@ async function readStudyAgentResult(stream) {
5611
4086
  * ```
5612
4087
  */
5613
4088
  function readStudyAgentMessages(value) {
5614
- if (!(0, _orkestrel_contract.isArray)(value)) throw new StudyError("FAULT", "Provider messages are not an array");
4089
+ if (!(0, _orkestrel_contract.isArray)(value)) throw new _src_core.StudyError("FAULT", "Provider messages are not an array");
5615
4090
  return Object.freeze(Array.from(value).map((message) => {
5616
- if (!(0, _orkestrel_contract.isObject)(message)) throw new StudyError("FAULT", "Provider message fields are invalid");
4091
+ if (!(0, _orkestrel_contract.isObject)(message)) throw new _src_core.StudyError("FAULT", "Provider message fields are invalid");
5617
4092
  const role = "role" in message ? message.role : void 0;
5618
4093
  const content = "content" in message ? message.content : void 0;
5619
4094
  const call = "call" in message ? message.call : void 0;
5620
- if (typeof role !== "string" || typeof content !== "string" || call !== void 0 && typeof call !== "string") throw new StudyError("FAULT", "Provider message fields are invalid");
4095
+ if (typeof role !== "string" || typeof content !== "string" || call !== void 0 && typeof call !== "string") throw new _src_core.StudyError("FAULT", "Provider message fields are invalid");
5621
4096
  return Object.freeze({
5622
4097
  role,
5623
4098
  content,
@@ -5642,7 +4117,7 @@ function readStudyAgentMessages(value) {
5642
4117
  * ```
5643
4118
  */
5644
4119
  function isStudyAgentResponse(value) {
5645
- return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && matchesStudyKeys(value, ["content", "calls"], [
4120
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _src_core.matchesStudyKeys)(value, ["content", "calls"], [
5646
4121
  "consume",
5647
4122
  "abort",
5648
4123
  "tokens",
@@ -5651,94 +4126,6 @@ function isStudyAgentResponse(value) {
5651
4126
  ]) && typeof value.content === "string" && (0, _orkestrel_contract.isArray)(value.calls) && value.calls.every(_orkestrel_tool.isToolCall) && (0, _orkestrel_contract.isJSONValue)(value.calls) && (value.consume === void 0 || typeof value.consume === "string" && value.consume.length > 0) && (value.abort === void 0 || typeof value.abort === "boolean") && (value.truncated === void 0 || typeof value.truncated === "boolean") && ["tokens", "bytes"].every((key) => value[key] === void 0 || typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] >= 0));
5652
4127
  }
5653
4128
  //#endregion
5654
- //#region src/core/agents/helpers.ts
5655
- /**
5656
- * Prices a runtime estimate through an explicit gauge.
5657
- * @param gauge - The declared scale and overhead
5658
- * @param estimate - The runtime's estimate of one complete request
5659
- * @returns The projected prompt tokens, rounded up
5660
- */
5661
- function projectStudyAgentRequest(gauge, estimate) {
5662
- return Math.ceil(gauge.overhead + gauge.scale * estimate);
5663
- }
5664
- /**
5665
- * Checks that a successful provider call reported a usable actual prompt count.
5666
- * @remarks
5667
- * The check compares nothing with a forecast; room enforcement stays with the transport.
5668
- * @param usage - The usage a successful provider call reported
5669
- * @param role - The responsibility whose request was dispatched; the failure retains it
5670
- * @returns Nothing when the prompt count is finite and positive
5671
- * @throws StudyError - Thrown with code `FAULT` when the prompt count is missing, not finite, or not positive
5672
- */
5673
- function validateStudyAgentUsage(usage, role) {
5674
- const prompt = usage?.prompt;
5675
- if (typeof prompt !== "number" || !Number.isFinite(prompt) || prompt <= 0) {
5676
- const message = "Gauged request returned no usable prompt count";
5677
- throw new StudyError("FAULT", message, {
5678
- cause: "infrastructure",
5679
- message,
5680
- role,
5681
- stop: void 0
5682
- });
5683
- }
5684
- }
5685
- /**
5686
- * Translates only a nominal Agent window refusal into the attributable capacity failure.
5687
- * @remarks
5688
- * The failure's stop is the `window` layer with the id `Agent window`; its `max` is the window and
5689
- * its `consumed` the measured request, each read from the error's context when it is a finite
5690
- * number.
5691
- * @param error - The caught or reported value
5692
- * @param runtime - The admitted Agent namespace owning the nominal guard
5693
- * @returns A capacity StudyError for an Agent `WINDOW` error; every other value with its identity
5694
- */
5695
- function translateStudyAgentError(error, runtime) {
5696
- if (!runtime.isAgentError(error) || error.code !== "WINDOW") return error;
5697
- const context = error.context;
5698
- return new StudyError("CAPACITY", error.message, {
5699
- cause: "capacity",
5700
- message: error.message,
5701
- role: "agent",
5702
- stop: {
5703
- layer: "window",
5704
- id: "Agent window",
5705
- max: parseStudyReading(context?.max),
5706
- consumed: parseStudyReading(context?.consumed)
5707
- }
5708
- });
5709
- }
5710
- /**
5711
- * Projects a request's structural components onto plain JSON without storing a total.
5712
- * @param measure - The runtime's disjoint per-message, tools and schema estimates
5713
- * @returns The same components in request order
5714
- */
5715
- function buildStudyAgentParts(measure) {
5716
- return {
5717
- messages: measure.messages.map((message) => ({
5718
- id: message.id,
5719
- role: message.role,
5720
- content: message.content,
5721
- framing: message.framing,
5722
- calls: message.calls,
5723
- thinking: message.thinking,
5724
- images: message.images
5725
- })),
5726
- tools: measure.tools,
5727
- schema: measure.schema
5728
- };
5729
- }
5730
- /**
5731
- * Builds the evidence join of a wire request to its canonical dispatch.
5732
- * @param dispatch - The measured dispatch, or undefined when the caller measured none
5733
- * @returns The dispatch id and revision, each null when unknown
5734
- */
5735
- function buildStudyAgentDispatch(dispatch) {
5736
- return {
5737
- canonical: dispatch?.id ?? null,
5738
- revision: dispatch?.revision ?? null
5739
- };
5740
- }
5741
- //#endregion
5742
4129
  //#region src/server/agents/StudyAgentTraceGoal.ts
5743
4130
  /** Holds one goal's retained trace rows, its latest admission and its last refused identity. */
5744
4131
  var StudyAgentTraceGoal = class {
@@ -5816,7 +4203,7 @@ var StudyAgentTrace = class {
5816
4203
  * @throws StudyError - Thrown with code `CONFIG` when a goal is already active; the active goal and its rows stay unchanged.
5817
4204
  */
5818
4205
  start(goal) {
5819
- if (this.#goal !== void 0) throw new StudyError("CONFIG", "Trace goal is already active");
4206
+ if (this.#goal !== void 0) throw new _src_core.StudyError("CONFIG", "Trace goal is already active");
5820
4207
  this.#goal = new StudyAgentTraceGoal(goal);
5821
4208
  }
5822
4209
  /** Retains one domain row for the active goal, whether or not observation is enabled; a row outside a goal is dropped.
@@ -5834,7 +4221,7 @@ var StudyAgentTrace = class {
5834
4221
  async flush(evidence) {
5835
4222
  const running = this.#goal;
5836
4223
  if (running === void 0) return;
5837
- if (this.#flushes.size !== 0) throw new StudyError("CONFIG", "Trace flush is already pending");
4224
+ if (this.#flushes.size !== 0) throw new _src_core.StudyError("CONFIG", "Trace flush is already pending");
5838
4225
  this.#flushes.add(running);
5839
4226
  try {
5840
4227
  for (const row of running.rows) await evidence.append(row);
@@ -5850,7 +4237,7 @@ var StudyAgentTrace = class {
5850
4237
  */
5851
4238
  admit(request, gauge) {
5852
4239
  const estimate = this.#options.runtime.estimateRequest(request);
5853
- const projected = projectStudyAgentRequest(gauge, estimate);
4240
+ const projected = (0, _src_core.projectStudyAgentRequest)(gauge, estimate);
5854
4241
  const stamp = this.measure("admission", "agent", request, {
5855
4242
  estimate,
5856
4243
  projected
@@ -5894,7 +4281,7 @@ var StudyAgentTrace = class {
5894
4281
  estimate: reading.estimate,
5895
4282
  projected: reading.projected ?? null,
5896
4283
  ...dispatch === void 0 ? {} : { dispatch },
5897
- parts: buildStudyAgentParts(this.#options.runtime.measureRequest(request)),
4284
+ parts: (0, _src_core.buildStudyAgentParts)(this.#options.runtime.measureRequest(request)),
5898
4285
  origins: this.#options.origins(request.messages)
5899
4286
  });
5900
4287
  return {
@@ -5923,7 +4310,7 @@ var StudyAgentTrace = class {
5923
4310
  * @returns The translated failure, or the same value when it is not an Agent window refusal.
5924
4311
  */
5925
4312
  translate(error) {
5926
- const translated = translateStudyAgentError(error, this.#options.runtime);
4313
+ const translated = (0, _src_core.translateStudyAgentError)(error, this.#options.runtime);
5927
4314
  this.#refuse(error, translated);
5928
4315
  this.#options.latch.fail(translated);
5929
4316
  return translated;
@@ -5967,15 +4354,16 @@ var StudyAgentSummary = class {
5967
4354
  constructor(options) {
5968
4355
  const { limit } = options;
5969
4356
  const { scale, overhead } = options.gauge;
5970
- if (!Number.isSafeInteger(limit) || limit <= 0) throw new StudyError("CONFIG", "Summary limit must be a positive safe integer");
5971
- if (!(0, _orkestrel_contract.isFiniteNumber)(scale) || scale <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(overhead) || overhead < 0) throw new StudyError("CONFIG", "Summary gauge scale must be finite and positive and its overhead finite and nonnegative");
4357
+ const gauge = {
4358
+ scale,
4359
+ overhead
4360
+ };
4361
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new _src_core.StudyError("CONFIG", "Summary limit must be a positive safe integer");
4362
+ if (!(0, _src_core.isStudyAgentGauge)(gauge)) throw new _src_core.StudyError("CONFIG", "Summary gauge scale must be finite and positive and its overhead finite and nonnegative");
5972
4363
  this.#options = {
5973
4364
  ...options,
5974
4365
  limit,
5975
- gauge: {
5976
- scale,
5977
- overhead
5978
- }
4366
+ gauge
5979
4367
  };
5980
4368
  }
5981
4369
  /** Measures one canonical request, refuses it at the limit, and sends the very same array to the provider.
@@ -5992,7 +4380,7 @@ var StudyAgentSummary = class {
5992
4380
  const canonical = [...messages, instruction];
5993
4381
  const request = { messages: canonical };
5994
4382
  const estimate = runtime.estimateRequest(request);
5995
- const projected = projectStudyAgentRequest(gauge, estimate);
4383
+ const projected = (0, _src_core.projectStudyAgentRequest)(gauge, estimate);
5996
4384
  const reading = {
5997
4385
  estimate,
5998
4386
  projected
@@ -6008,7 +4396,7 @@ var StudyAgentSummary = class {
6008
4396
  consumed: projected,
6009
4397
  max: limit
6010
4398
  });
6011
- throw new StudyError("CAPACITY", message, {
4399
+ throw new _src_core.StudyError("CAPACITY", message, {
6012
4400
  cause: "capacity",
6013
4401
  message,
6014
4402
  role: "summary",
@@ -6022,18 +4410,18 @@ var StudyAgentSummary = class {
6022
4410
  }
6023
4411
  trace.dispatch("summary", request, reading);
6024
4412
  const result = await provider.generate(canonical, bound);
6025
- if (!bound.aborted) validateStudyAgentUsage(result.usage, "summary");
4413
+ if (!bound.aborted) (0, _src_core.validateStudyAgentUsage)(result.usage, "summary");
6026
4414
  latch.check();
6027
- if (trace.goal === void 0 || typeof result.content !== "string") throw new StudyError("FAULT", "Summary outside an active goal or invalid content");
4415
+ if (trace.goal === void 0 || typeof result.content !== "string") throw new _src_core.StudyError("FAULT", "Summary outside an active goal or invalid content");
6028
4416
  const value = {
6029
4417
  sources: messages.map((message) => message.id),
6030
4418
  content: result.content
6031
4419
  };
6032
- if (!(0, _orkestrel_contract.isJSONValue)(value)) throw new StudyError("FAULT", "Summary evidence is not JSON");
4420
+ if (!(0, _orkestrel_contract.isJSONValue)(value)) throw new _src_core.StudyError("FAULT", "Summary evidence is not JSON");
6033
4421
  trace.append("summary", (0, _orkestrel_contract.cloneJSONValue)(value));
6034
4422
  if (result.content.trim() === "") {
6035
4423
  const message = "Summary returned no usable content";
6036
- throw new StudyError("FAULT", message, {
4424
+ throw new _src_core.StudyError("FAULT", message, {
6037
4425
  cause: "content",
6038
4426
  message,
6039
4427
  role: "summary",
@@ -6083,7 +4471,7 @@ var StudyAgentScript = class {
6083
4471
  * @throws StudyError - Thrown when the bound is not a nonnegative safe integer
6084
4472
  */
6085
4473
  constructor(options) {
6086
- if (!Number.isSafeInteger(options.bytes) || options.bytes < 0) throw new StudyError("CONFIG", "Scripted byte bound must be a nonnegative safe integer");
4474
+ if (!Number.isSafeInteger(options.bytes) || options.bytes < 0) throw new _src_core.StudyError("CONFIG", "Scripted byte bound must be a nonnegative safe integer");
6087
4475
  this.#bytes = options.bytes;
6088
4476
  }
6089
4477
  /**
@@ -6093,14 +4481,14 @@ var StudyAgentScript = class {
6093
4481
  * @throws StudyError - Thrown with code `CONFIG` when a response is still running, a goal is already prepared, or a declared response is invalid; a prepared goal stays prepared
6094
4482
  */
6095
4483
  prepare(plan) {
6096
- if (this.#serving.size !== 0) throw new StudyError("CONFIG", "Cannot replace a running scripted workload");
6097
- if (this.#goal !== void 0) throw new StudyError("CONFIG", "Scripted goal is already prepared");
6098
- if (!(0, _orkestrel_contract.isArray)(plan.responses) || !plan.responses.every(isStudyAgentResponse)) throw new StudyError("CONFIG", "Scripted response sequence is invalid");
4484
+ if (this.#serving.size !== 0) throw new _src_core.StudyError("CONFIG", "Cannot replace a running scripted workload");
4485
+ if (this.#goal !== void 0) throw new _src_core.StudyError("CONFIG", "Scripted goal is already prepared");
4486
+ if (!(0, _orkestrel_contract.isArray)(plan.responses) || !plan.responses.every(isStudyAgentResponse)) throw new _src_core.StudyError("CONFIG", "Scripted response sequence is invalid");
6099
4487
  const owned = plan.responses.map((response) => ({
6100
4488
  ...response,
6101
4489
  calls: (0, _orkestrel_contract.cloneJSONValue)(response.calls)
6102
4490
  }));
6103
- if (!(0, _orkestrel_contract.isArray)(owned) || !owned.every(isStudyAgentResponse)) throw new StudyError("CONFIG", "Scripted response sequence is invalid");
4491
+ if (!(0, _orkestrel_contract.isArray)(owned) || !owned.every(isStudyAgentResponse)) throw new _src_core.StudyError("CONFIG", "Scripted response sequence is invalid");
6104
4492
  this.#goal = new StudyAgentScriptGoal({
6105
4493
  ...plan,
6106
4494
  responses: Object.freeze(owned)
@@ -6135,9 +4523,9 @@ var StudyAgentScript = class {
6135
4523
  */
6136
4524
  async *stream(messages, signal) {
6137
4525
  const goal = this.#goal;
6138
- if (goal === void 0 || this.#serving.size !== 0) throw new StudyError("CONFIG", "Scripted provider is unavailable");
4526
+ if (goal === void 0 || this.#serving.size !== 0) throw new _src_core.StudyError("CONFIG", "Scripted provider is unavailable");
6139
4527
  const response = goal.take();
6140
- if (response === void 0) throw new StudyError("CONFIG", "Scripted provider response sequence is exhausted");
4528
+ if (response === void 0) throw new _src_core.StudyError("CONFIG", "Scripted provider response sequence is exhausted");
6141
4529
  const plan = goal.plan;
6142
4530
  this.#serving.add(response);
6143
4531
  const started = performance.now();
@@ -6155,7 +4543,7 @@ var StudyAgentScript = class {
6155
4543
  }).byteLength;
6156
4544
  if (bytes > this.#bytes) {
6157
4545
  const message = "Scripted request exceeds its encoded workload bound";
6158
- throw new StudyError("CAPACITY", message, {
4546
+ throw new _src_core.StudyError("CAPACITY", message, {
6159
4547
  cause: "capacity",
6160
4548
  message,
6161
4549
  role: void 0,
@@ -6214,11 +4602,11 @@ var StudyAgentScript = class {
6214
4602
  return result;
6215
4603
  } catch (error) {
6216
4604
  if ((0, _orkestrel_budget.isBudgetError)(error)) this.#cut(ticket, bytes, started);
6217
- else failStudyTicket(ticket, error, "agent");
4605
+ else (0, _src_core.failStudyTicket)(ticket, error, "agent");
6218
4606
  throw error;
6219
4607
  } finally {
6220
4608
  if ((0, _orkestrel_budget.isBudgetError)(signal.reason)) this.#cut(ticket, bytes, started);
6221
- else failStudyTicket(ticket, signal.reason ?? new StudyError("FAULT", "Scripted response was abandoned before completion"), "agent");
4609
+ else (0, _src_core.failStudyTicket)(ticket, signal.reason ?? new _src_core.StudyError("FAULT", "Scripted response was abandoned before completion"), "agent");
6222
4610
  }
6223
4611
  } finally {
6224
4612
  this.#serving.delete(response);
@@ -6304,10 +4692,10 @@ function validateStudyOllamaRoles(roles, models) {
6304
4692
  const names = Object.values(models).map((model) => model.name);
6305
4693
  for (const [role, policy] of Object.entries(roles)) {
6306
4694
  if (policy === void 0) continue;
6307
- if (!names.includes(policy.model)) throw new StudyError("CONFIG", "Ollama role policy names a model the inventory does not pin");
4695
+ if (!names.includes(policy.model)) throw new _src_core.StudyError("CONFIG", "Ollama role policy names a model the inventory does not pin");
6308
4696
  const output = policy.options.num_predict;
6309
- if (output === void 0 || !Number.isSafeInteger(output) || output <= 0) throw new StudyError("CONFIG", "Ollama role policy declares no positive num_predict allowance");
6310
- if (role === "judge" && policy.strict) throw new StudyError("CONFIG", "Ollama strict fields apply only to chat roles");
4697
+ if (output === void 0 || !Number.isSafeInteger(output) || output <= 0) throw new _src_core.StudyError("CONFIG", "Ollama role policy declares no positive num_predict allowance");
4698
+ if (role === "judge" && policy.strict) throw new _src_core.StudyError("CONFIG", "Ollama strict fields apply only to chat roles");
6311
4699
  }
6312
4700
  }
6313
4701
  /**
@@ -6322,15 +4710,15 @@ function validateStudyOllamaRoles(roles, models) {
6322
4710
  * @throws StudyError - Thrown with code `CONFIG` when the endpoint, model, options, flags, or strict fields differ from the declaration, or the role declares no policy
6323
4711
  */
6324
4712
  function readStudyOllamaRequest(options, role, input, init) {
6325
- if (input instanceof Request) throw new StudyError("CONFIG", "Ollama transport requires explicit URL requests");
4713
+ if (input instanceof Request) throw new _src_core.StudyError("CONFIG", "Ollama transport requires explicit URL requests");
6326
4714
  const url = new URL(input);
6327
4715
  const path = role === "metadata" ? init?.method === "POST" ? "/api/show" : "/api/tags" : role === "judge" ? "/api/generate" : "/api/chat";
6328
- if (url.origin !== new URL(options.url).origin || url.pathname !== path || url.search !== "" || url.hash !== "" || url.username !== "" || url.password !== "") throw new StudyError("CONFIG", "Ollama transport endpoint differs from the admitted origin and path");
4716
+ if (url.origin !== new URL(options.url).origin || url.pathname !== path || url.search !== "" || url.hash !== "" || url.username !== "" || url.password !== "") throw new _src_core.StudyError("CONFIG", "Ollama transport endpoint differs from the admitted origin and path");
6329
4717
  if (role === "metadata") {
6330
4718
  if (path === "/api/show") {
6331
- if (typeof init?.body !== "string") throw new StudyError("CONFIG", "Invalid model metadata request");
4719
+ if (typeof init?.body !== "string") throw new _src_core.StudyError("CONFIG", "Invalid model metadata request");
6332
4720
  const body = JSON.parse(init.body);
6333
- if (!(0, _orkestrel_contract.isRecord)(body) || Object.keys(body).length !== 1 || typeof body.model !== "string" || !Object.values(options.models).some((model) => model.name === body.model)) throw new StudyError("CONFIG", "Invalid model metadata identity");
4721
+ if (!(0, _orkestrel_contract.isRecord)(body) || Object.keys(body).length !== 1 || typeof body.model !== "string" || !Object.values(options.models).some((model) => model.name === body.model)) throw new _src_core.StudyError("CONFIG", "Invalid model metadata identity");
6334
4722
  return {
6335
4723
  role,
6336
4724
  path,
@@ -6342,7 +4730,7 @@ function readStudyOllamaRequest(options, role, input, init) {
6342
4730
  length: false
6343
4731
  };
6344
4732
  }
6345
- if (init?.method !== "GET" || init.body !== void 0) throw new StudyError("CONFIG", "Invalid inventory request");
4733
+ if (init?.method !== "GET" || init.body !== void 0) throw new _src_core.StudyError("CONFIG", "Invalid inventory request");
6346
4734
  return {
6347
4735
  role,
6348
4736
  path,
@@ -6354,16 +4742,16 @@ function readStudyOllamaRequest(options, role, input, init) {
6354
4742
  length: false
6355
4743
  };
6356
4744
  }
6357
- if (init?.method !== "POST" || typeof init.body !== "string") throw new StudyError("CONFIG", "Ollama transport requires an encoded JSON POST");
4745
+ if (init?.method !== "POST" || typeof init.body !== "string") throw new _src_core.StudyError("CONFIG", "Ollama transport requires an encoded JSON POST");
6358
4746
  const policy = role === "tool" ? void 0 : options.roles[role];
6359
- if (policy === void 0) throw new StudyError("CONFIG", "Ollama transport role has no declared wire policy");
4747
+ if (policy === void 0) throw new _src_core.StudyError("CONFIG", "Ollama transport role has no declared wire policy");
6360
4748
  const output = policy.options.num_predict;
6361
- if (output === void 0 || !Number.isSafeInteger(output) || output <= 0) throw new StudyError("CONFIG", "Ollama role policy declares no positive num_predict allowance");
4749
+ if (output === void 0 || !Number.isSafeInteger(output) || output <= 0) throw new _src_core.StudyError("CONFIG", "Ollama role policy declares no positive num_predict allowance");
6362
4750
  const body = JSON.parse(init.body);
6363
- if (!(0, _orkestrel_contract.isRecord)(body) || body.model !== policy.model || !(0, _orkestrel_contract.isRecord)(body.options) || (0, _orkestrel_contract.canonicalStringify)(body.options) !== (0, _orkestrel_contract.canonicalStringify)(policy.options) || body.stream !== (role !== "judge")) throw new StudyError("CONFIG", "Ollama transport model or effective options differ from the declared policy");
6364
- if (role === "judge" ? body.raw !== true || body.logprobs !== true : typeof body.think !== "boolean" || body.think === true && !policy.think) throw new StudyError("CONFIG", "Ollama transport flags differ from the declared policy");
4751
+ if (!(0, _orkestrel_contract.isRecord)(body) || body.model !== policy.model || !(0, _orkestrel_contract.isRecord)(body.options) || (0, _orkestrel_contract.canonicalStringify)(body.options) !== (0, _orkestrel_contract.canonicalStringify)(policy.options) || body.stream !== (role !== "judge")) throw new _src_core.StudyError("CONFIG", "Ollama transport model or effective options differ from the declared policy");
4752
+ if (role === "judge" ? body.raw !== true || body.logprobs !== true : typeof body.think !== "boolean" || body.think === true && !policy.think) throw new _src_core.StudyError("CONFIG", "Ollama transport flags differ from the declared policy");
6365
4753
  const strict = policy.strict && role !== "judge";
6366
- if (strict && (body.truncate !== false || body.shift !== false)) throw new StudyError("CONFIG", "Ollama transport must disable native truncation and shifting");
4754
+ if (strict && (body.truncate !== false || body.shift !== false)) throw new _src_core.StudyError("CONFIG", "Ollama transport must disable native truncation and shifting");
6367
4755
  return {
6368
4756
  role,
6369
4757
  path,
@@ -6383,20 +4771,20 @@ function readStudyOllamaRequest(options, role, input, init) {
6383
4771
  * @throws StudyError - Thrown with code `FAULT` when context metadata is missing, or `CAPACITY` when it is ambiguous or below the requested context
6384
4772
  */
6385
4773
  function readStudyOllamaSupport(value, context) {
6386
- if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.isRecord)(value.model_info) || typeof value.model_info["general.architecture"] !== "string") throw new StudyError("FAULT", "Missing model context metadata");
4774
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.isRecord)(value.model_info) || typeof value.model_info["general.architecture"] !== "string") throw new _src_core.StudyError("FAULT", "Missing model context metadata");
6387
4775
  const architecture = value.model_info["general.architecture"];
6388
4776
  const keys = Object.keys(value.model_info).filter((key) => key.endsWith(".context_length"));
6389
4777
  const ceiling = value.model_info[`${architecture}.context_length`];
6390
4778
  if (keys.length !== 1 || typeof ceiling !== "number" || !Number.isSafeInteger(ceiling) || ceiling < context) {
6391
4779
  const message = "Model context metadata does not support the requested context";
6392
- throw new StudyError("CAPACITY", message, {
4780
+ throw new _src_core.StudyError("CAPACITY", message, {
6393
4781
  cause: "capacity",
6394
4782
  message,
6395
4783
  role: void 0,
6396
4784
  stop: {
6397
4785
  layer: "model",
6398
4786
  id: "context_length",
6399
- max: keys.length === 1 && Number.isSafeInteger(ceiling) ? parseStudyReading(ceiling) : void 0,
4787
+ max: keys.length === 1 && Number.isSafeInteger(ceiling) ? (0, _src_core.parseStudyReading)(ceiling) : void 0,
6400
4788
  consumed: context
6401
4789
  }
6402
4790
  });
@@ -6430,15 +4818,15 @@ function readStudyOllamaFailure(value, role, status) {
6430
4818
  const limit = structured?.n_ctx;
6431
4819
  const capacity = type === "exceed_context_size_error" && (0, _orkestrel_contract.isInteger)(prompt) && prompt > 0 && (0, _orkestrel_contract.isInteger)(limit) && limit > 0 && prompt > limit && (structured?.code === void 0 || structured?.code === 400) && (status === void 0 || status === 400);
6432
4820
  const reason = text.trim() === "" && type === void 0 ? `Malformed ${origin}` : `${origin}${type === void 0 ? "" : ` (${type})`}${text.trim() === "" ? "" : `: ${text}`}`;
6433
- return new StudyError(capacity ? "CAPACITY" : "FAULT", reason, {
4821
+ return new _src_core.StudyError(capacity ? "CAPACITY" : "FAULT", reason, {
6434
4822
  cause: capacity ? "capacity" : "infrastructure",
6435
4823
  message: reason,
6436
4824
  role,
6437
4825
  stop: capacity ? {
6438
4826
  layer: "model",
6439
4827
  id: "num_ctx",
6440
- max: parseStudyReading(limit),
6441
- consumed: parseStudyReading(prompt)
4828
+ max: (0, _src_core.parseStudyReading)(limit),
4829
+ consumed: (0, _src_core.parseStudyReading)(prompt)
6442
4830
  } : void 0
6443
4831
  });
6444
4832
  }
@@ -6452,18 +4840,18 @@ function readStudyOllamaFailure(value, role, status) {
6452
4840
  function readStudyOllamaTerminal(value, request) {
6453
4841
  const failure = readStudyOllamaFailure(value, request.role);
6454
4842
  if (failure !== void 0) throw failure;
6455
- if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.done !== "boolean") throw new StudyError("FAULT", "Malformed Ollama response row");
6456
- if (value.model !== request.model) throw new StudyError("FAULT", "Ollama response model differs from the dispatched model");
4843
+ if (!(0, _orkestrel_contract.isRecord)(value) || typeof value.done !== "boolean") throw new _src_core.StudyError("FAULT", "Malformed Ollama response row");
4844
+ if (value.model !== request.model) throw new _src_core.StudyError("FAULT", "Ollama response model differs from the dispatched model");
6457
4845
  if (request.role !== "judge") {
6458
- if (!(0, _orkestrel_contract.isRecord)(value.message) || typeof value.message.content !== "string" || value.message.thinking !== void 0 && typeof value.message.thinking !== "string") throw new StudyError("FAULT", "Malformed Ollama message");
4846
+ if (!(0, _orkestrel_contract.isRecord)(value.message) || typeof value.message.content !== "string" || value.message.thinking !== void 0 && typeof value.message.thinking !== "string") throw new _src_core.StudyError("FAULT", "Malformed Ollama message");
6459
4847
  if (value.message.tool_calls !== void 0) {
6460
- if (!(0, _orkestrel_contract.isArray)(value.message.tool_calls)) throw new StudyError("FAULT", "Malformed Ollama tool calls");
6461
- for (const call of value.message.tool_calls) if (!(0, _orkestrel_contract.isRecord)(call) || !(0, _orkestrel_contract.isRecord)(call.function) || typeof call.function.name !== "string" || !(0, _orkestrel_contract.isRecord)(call.function.arguments)) throw new StudyError("FAULT", "Malformed Ollama tool call");
4848
+ if (!(0, _orkestrel_contract.isArray)(value.message.tool_calls)) throw new _src_core.StudyError("FAULT", "Malformed Ollama tool calls");
4849
+ for (const call of value.message.tool_calls) if (!(0, _orkestrel_contract.isRecord)(call) || !(0, _orkestrel_contract.isRecord)(call.function) || typeof call.function.name !== "string" || !(0, _orkestrel_contract.isRecord)(call.function.arguments)) throw new _src_core.StudyError("FAULT", "Malformed Ollama tool call");
6462
4850
  }
6463
4851
  }
6464
4852
  if (!value.done) return void 0;
6465
- if (typeof value.done_reason !== "string" || value.done_reason.length === 0) throw new StudyError("FAULT", "Missing Ollama finish reason");
6466
- for (const key of ["prompt_eval_count", "eval_count"]) if (value[key] !== void 0 && (typeof value[key] !== "number" || !Number.isSafeInteger(value[key]) || value[key] < 0)) throw new StudyError("FAULT", "Malformed Ollama usage", {
4853
+ if (typeof value.done_reason !== "string" || value.done_reason.length === 0) throw new _src_core.StudyError("FAULT", "Missing Ollama finish reason");
4854
+ for (const key of ["prompt_eval_count", "eval_count"]) if (value[key] !== void 0 && (typeof value[key] !== "number" || !Number.isSafeInteger(value[key]) || value[key] < 0)) throw new _src_core.StudyError("FAULT", "Malformed Ollama usage", {
6467
4855
  cause: "infrastructure",
6468
4856
  message: "Malformed Ollama usage",
6469
4857
  role: request.role,
@@ -6495,7 +4883,7 @@ function validateStudyOllamaCompletion(terminal, room, request) {
6495
4883
  const ceiling = room.context - request.output - room.safety;
6496
4884
  if (prompt !== void 0 && prompt > ceiling) {
6497
4885
  const message = "Actual prompt exceeded reserved context capacity";
6498
- throw new StudyError("CAPACITY", message, {
4886
+ throw new _src_core.StudyError("CAPACITY", message, {
6499
4887
  cause: "capacity",
6500
4888
  message,
6501
4889
  role,
@@ -6509,7 +4897,7 @@ function validateStudyOllamaCompletion(terminal, room, request) {
6509
4897
  }
6510
4898
  if (completion !== void 0 && completion > request.output) {
6511
4899
  const message = "Actual generation exceeded the output allowance";
6512
- throw new StudyError("CAPACITY", message, {
4900
+ throw new _src_core.StudyError("CAPACITY", message, {
6513
4901
  cause: "capacity",
6514
4902
  message,
6515
4903
  role,
@@ -6518,7 +4906,7 @@ function validateStudyOllamaCompletion(terminal, room, request) {
6518
4906
  }
6519
4907
  if (!request.length && (reason === "length" || completion !== void 0 && completion >= request.output)) {
6520
4908
  const message = "Substantive generation reached its output cap";
6521
- throw new StudyError("CAPACITY", message, {
4909
+ throw new _src_core.StudyError("CAPACITY", message, {
6522
4910
  cause: "capacity",
6523
4911
  message,
6524
4912
  role,
@@ -6527,7 +4915,7 @@ function validateStudyOllamaCompletion(terminal, room, request) {
6527
4915
  }
6528
4916
  if (reason !== "stop" && !(request.length && reason === "length")) {
6529
4917
  const message = "Unsupported Ollama finish reason";
6530
- throw new StudyError("FAULT", message, {
4918
+ throw new _src_core.StudyError("FAULT", message, {
6531
4919
  cause: "infrastructure",
6532
4920
  message,
6533
4921
  role,
@@ -6543,12 +4931,12 @@ function validateStudyOllamaCompletion(terminal, room, request) {
6543
4931
  * @throws StudyError - Thrown with code `FAULT` when the inventory is unsupported or a pinned identity does not match
6544
4932
  */
6545
4933
  function validateStudyOllamaInventory(value, models) {
6546
- if (!(0, _orkestrel_contract.isObject)(value) || !("models" in value) || !(0, _orkestrel_contract.isArray)(value.models)) throw new StudyError("FAULT", "Unsupported Ollama inventory");
4934
+ if (!(0, _orkestrel_contract.isObject)(value) || !("models" in value) || !(0, _orkestrel_contract.isArray)(value.models)) throw new _src_core.StudyError("FAULT", "Unsupported Ollama inventory");
6547
4935
  const served = value.models;
6548
4936
  for (const { name, digest } of Object.values(models)) {
6549
4937
  const matches = served.filter((model) => (0, _orkestrel_contract.isRecord)(model) && model.name === name);
6550
4938
  const model = matches[0];
6551
- if (matches.length !== 1 || !(0, _orkestrel_contract.isRecord)(model) || typeof model.digest !== "string" || model.digest.replace(/^sha256:/, "") !== digest.replace(/^sha256:/, "")) throw new StudyError("FAULT", "Serving inventory does not match the required model digest");
4939
+ if (matches.length !== 1 || !(0, _orkestrel_contract.isRecord)(model) || typeof model.digest !== "string" || model.digest.replace(/^sha256:/, "") !== digest.replace(/^sha256:/, "")) throw new _src_core.StudyError("FAULT", "Serving inventory does not match the required model digest");
6552
4940
  }
6553
4941
  }
6554
4942
  /**
@@ -6676,7 +5064,7 @@ function buildStudyOllamaTransportOptions(options) {
6676
5064
  * @throws StudyError - Thrown with code `CONFIG` when the input is a `Request`
6677
5065
  */
6678
5066
  function buildStudyOllamaSnapshot(input, init, dispatch) {
6679
- if (input instanceof Request) throw new StudyError("CONFIG", "Ollama transport requires explicit URL requests");
5067
+ if (input instanceof Request) throw new _src_core.StudyError("CONFIG", "Ollama transport requires explicit URL requests");
6680
5068
  const copy = init === void 0 ? void 0 : { ...init };
6681
5069
  return {
6682
5070
  url: typeof input === "string" ? input : input.href,
@@ -6756,7 +5144,7 @@ var StudyOllamaExchange = class {
6756
5144
  this.#bytes += length;
6757
5145
  if (this.#bytes <= limit) return;
6758
5146
  const message = "Ollama wire byte allowance exceeded";
6759
- throw new StudyError("CAPACITY", message, {
5147
+ throw new _src_core.StudyError("CAPACITY", message, {
6760
5148
  cause: "capacity",
6761
5149
  message,
6762
5150
  role: this.request.role,
@@ -6855,8 +5243,8 @@ var StudyOllamaTransport = class {
6855
5243
  */
6856
5244
  prepare(activity) {
6857
5245
  this.check();
6858
- if (this.#wires.size !== 0) throw new StudyError("FAULT", "Previous model response is still open");
6859
- if (this.#goal !== void 0) throw new StudyError("FAULT", "Previous goal is still bound");
5246
+ if (this.#wires.size !== 0) throw new _src_core.StudyError("FAULT", "Previous model response is still open");
5247
+ if (this.#goal !== void 0) throw new _src_core.StudyError("FAULT", "Previous goal is still bound");
6860
5248
  this.#goal = new StudyOllamaGoal(activity);
6861
5249
  }
6862
5250
  /** Ends the bound goal so another goal can be prepared; a request already admitted keeps its own goal.
@@ -6887,7 +5275,7 @@ var StudyOllamaTransport = class {
6887
5275
  try {
6888
5276
  validateStudyOllamaInventory(await (await this.fetch("metadata", new URL("/api/tags", this.#options.url), { method: "GET" })).json(), this.#options.models);
6889
5277
  const goal = this.#goal;
6890
- if (goal === void 0) throw new StudyError("FAULT", "Inventory has no goal attribution");
5278
+ if (goal === void 0) throw new _src_core.StudyError("FAULT", "Inventory has no goal attribution");
6891
5279
  const activity = goal.activity;
6892
5280
  const models = Object.values(this.#options.models);
6893
5281
  for (const model of new Set(models.map((entry) => entry.name))) {
@@ -6928,7 +5316,7 @@ var StudyOllamaTransport = class {
6928
5316
  async fetch(role, input, init, original) {
6929
5317
  this.check();
6930
5318
  const goal = this.#goal;
6931
- if (goal === void 0) throw new StudyError("FAULT", "Model dispatch outside an active goal");
5319
+ if (goal === void 0) throw new _src_core.StudyError("FAULT", "Model dispatch outside an active goal");
6932
5320
  const activity = goal.activity;
6933
5321
  try {
6934
5322
  const snapshot = buildStudyOllamaSnapshot(input, init, original);
@@ -6937,7 +5325,7 @@ var StudyOllamaTransport = class {
6937
5325
  const limit = this.#options.limit;
6938
5326
  if (request.bytes >= limit.bytes) {
6939
5327
  const message = "Encoded request consumes the response byte allowance";
6940
- throw new StudyError("CAPACITY", message, {
5328
+ throw new _src_core.StudyError("CAPACITY", message, {
6941
5329
  cause: "capacity",
6942
5330
  message,
6943
5331
  role: void 0,
@@ -6970,7 +5358,7 @@ var StudyOllamaTransport = class {
6970
5358
  if (wire.status < 200 || wire.status > 299) return await this.#refuse(wire);
6971
5359
  return new Response(new ReadableStream({
6972
5360
  pull: (controller) => this.#pull(wire, controller),
6973
- cancel: (reason) => (0, _orkestrel_budget.isBudgetError)(reason) ? this.#cut(wire, reason) : this.#failWire(wire, reason === void 0 ? new StudyError("FAULT", "Ollama response cancelled before its end") : reason)
5361
+ cancel: (reason) => (0, _orkestrel_budget.isBudgetError)(reason) ? this.#cut(wire, reason) : this.#failWire(wire, reason === void 0 ? new _src_core.StudyError("FAULT", "Ollama response cancelled before its end") : reason)
6974
5362
  }), {
6975
5363
  status: wire.status,
6976
5364
  headers: { "content-type": role === "judge" || role === "metadata" ? "application/json" : "application/x-ndjson" }
@@ -6984,7 +5372,7 @@ var StudyOllamaTransport = class {
6984
5372
  * @returns Completion after finite native cancellation.
6985
5373
  */
6986
5374
  async close() {
6987
- this.#abort.abort(new StudyError("FAULT", "Ollama transport closed"));
5375
+ this.#abort.abort(new _src_core.StudyError("FAULT", "Ollama transport closed"));
6988
5376
  await Promise.all([...this.#wires].map((wire) => this.#failWire(wire, this.#abort.signal.reason)));
6989
5377
  this.#goal = void 0;
6990
5378
  }
@@ -7007,7 +5395,7 @@ var StudyOllamaTransport = class {
7007
5395
  }
7008
5396
  const body = (0, _orkestrel_contract.parseJSON)(parts.join("") + decoder.decode());
7009
5397
  const message = `Ollama HTTP response refused: ${wire.status}`;
7010
- return readStudyOllamaFailure(body, wire.request.role, wire.status) ?? new StudyError("FAULT", message, {
5398
+ return readStudyOllamaFailure(body, wire.request.role, wire.status) ?? new _src_core.StudyError("FAULT", message, {
7011
5399
  cause: "infrastructure",
7012
5400
  message,
7013
5401
  role: wire.request.role,
@@ -7036,7 +5424,7 @@ var StudyOllamaTransport = class {
7036
5424
  else readStudyOllamaSupport(value, this.#options.room.context);
7037
5425
  } else if (wire.request.role === "judge") this.#observe(wire, JSON.parse(wire.text));
7038
5426
  else this.#rows(wire, true);
7039
- if (wire.request.role !== "metadata" && wire.terminal === void 0) throw new StudyError("FAULT", "Ollama response has no terminal row");
5427
+ if (wire.request.role !== "metadata" && wire.terminal === void 0) throw new _src_core.StudyError("FAULT", "Ollama response has no terminal row");
7040
5428
  await this.#finish(wire);
7041
5429
  if (wire.terminal !== void 0) await this.#judge(wire, wire.terminal);
7042
5430
  controller.close();
@@ -7054,7 +5442,7 @@ var StudyOllamaTransport = class {
7054
5442
  try {
7055
5443
  validateStudyOllamaCompletion(terminal, this.#options.room, wire.request);
7056
5444
  } catch (error) {
7057
- const stop = readStudyFailure(error).stop;
5445
+ const stop = (0, _src_core.readStudyFailure)(error).stop;
7058
5446
  if (stop !== void 0) await wire.activity.context.evidence.append({
7059
5447
  event: "stop",
7060
5448
  goal: wire.activity.goal,
@@ -7064,7 +5452,7 @@ var StudyOllamaTransport = class {
7064
5452
  finish: terminal.reason,
7065
5453
  prompt: terminal.prompt ?? null,
7066
5454
  completion: terminal.completion ?? null,
7067
- stop: studyStopToJSON(stop),
5455
+ stop: (0, _src_core.studyStopToJSON)(stop),
7068
5456
  ...this.#options.observe ? this.#timeline(wire) : {}
7069
5457
  }
7070
5458
  });
@@ -7074,7 +5462,7 @@ var StudyOllamaTransport = class {
7074
5462
  #rows(wire, final) {
7075
5463
  for (const line of wire.take(final)) {
7076
5464
  if (line.trim() === "") continue;
7077
- if (wire.terminal !== void 0) throw new StudyError("FAULT", "Ollama emitted a row after its terminal");
5465
+ if (wire.terminal !== void 0) throw new _src_core.StudyError("FAULT", "Ollama emitted a row after its terminal");
7078
5466
  this.#observe(wire, JSON.parse(line));
7079
5467
  }
7080
5468
  }
@@ -7108,7 +5496,7 @@ var StudyOllamaTransport = class {
7108
5496
  signal,
7109
5497
  redirect: "error"
7110
5498
  });
7111
- if (response.body === null) throw new StudyError("FAULT", `Ollama HTTP response refused: ${response.status}`);
5499
+ if (response.body === null) throw new _src_core.StudyError("FAULT", `Ollama HTTP response refused: ${response.status}`);
7112
5500
  return new StudyOllamaExchange({
7113
5501
  activity: goal.activity,
7114
5502
  request,
@@ -7133,7 +5521,7 @@ var StudyOllamaTransport = class {
7133
5521
  duration: Math.ceil(performance.now() - started)
7134
5522
  });
7135
5523
  else ticket.fail({
7136
- ...readStudyFailure(error),
5524
+ ...(0, _src_core.readStudyFailure)(error),
7137
5525
  role
7138
5526
  });
7139
5527
  }
@@ -7149,7 +5537,7 @@ var StudyOllamaTransport = class {
7149
5537
  const raw = buildStudyOllamaRaw(init?.body);
7150
5538
  return {
7151
5539
  id,
7152
- ...buildStudyAgentDispatch(dispatch),
5540
+ ...(0, _src_core.buildStudyAgentDispatch)(dispatch),
7153
5541
  ...raw === void 0 ? {} : { raw },
7154
5542
  digest: readStudyOllamaDigest(this.#options.models, request) ?? null,
7155
5543
  previous: previous ?? null,
@@ -7162,14 +5550,14 @@ var StudyOllamaTransport = class {
7162
5550
  #timeline(wire) {
7163
5551
  return {
7164
5552
  id: wire.id,
7165
- ...buildStudyAgentDispatch(wire.dispatch),
5553
+ ...(0, _src_core.buildStudyAgentDispatch)(wire.dispatch),
7166
5554
  started: wire.started,
7167
5555
  sent: wire.sent,
7168
5556
  elapsed: performance.now() - wire.sent
7169
5557
  };
7170
5558
  }
7171
5559
  async #finish(wire) {
7172
- if (wire.settled) throw new StudyError("FAULT", "Duplicate wire accounting");
5560
+ if (wire.settled) throw new _src_core.StudyError("FAULT", "Duplicate wire accounting");
7173
5561
  const terminal = wire.terminal;
7174
5562
  const observation = this.#options.observe ? {
7175
5563
  ...this.#timeline(wire),
@@ -7208,7 +5596,7 @@ var StudyOllamaTransport = class {
7208
5596
  if (wire.settled) return;
7209
5597
  wire.settle();
7210
5598
  const observation = this.#options.observe ? this.#timeline(wire) : {};
7211
- const stop = readStudyFailure(error).stop;
5599
+ const stop = (0, _src_core.readStudyFailure)(error).stop;
7212
5600
  try {
7213
5601
  await wire.activity.context.evidence.append({
7214
5602
  event: "cut",
@@ -7236,7 +5624,7 @@ var StudyOllamaTransport = class {
7236
5624
  } catch (failure) {
7237
5625
  this.fail(failure);
7238
5626
  if (!wire.ticket.settled && !wire.ticket.signal.aborted) wire.ticket.fail({
7239
- ...readStudyFailure(failure),
5627
+ ...(0, _src_core.readStudyFailure)(failure),
7240
5628
  role: wire.request.role
7241
5629
  });
7242
5630
  throw failure;
@@ -7251,7 +5639,7 @@ var StudyOllamaTransport = class {
7251
5639
  this.fail(error);
7252
5640
  wire.settle();
7253
5641
  const observation = this.#options.observe ? this.#timeline(wire) : {};
7254
- const failure = readStudyFailure(this.#fault?.error);
5642
+ const failure = (0, _src_core.readStudyFailure)(this.#fault?.error);
7255
5643
  try {
7256
5644
  if (!wire.ticket.signal.aborted) wire.ticket.fail({
7257
5645
  ...failure,
@@ -7326,6 +5714,8 @@ exports.StudyTaskWorker = StudyTaskWorker;
7326
5714
  exports.StudyWorker = StudyWorker;
7327
5715
  exports.buildStudyAgentReadings = buildStudyAgentReadings;
7328
5716
  exports.buildStudyAgentRevision = buildStudyAgentRevision;
5717
+ exports.buildStudyAgentToolDenial = buildStudyAgentToolDenial;
5718
+ exports.buildStudyAgentToolResult = buildStudyAgentToolResult;
7329
5719
  exports.buildStudyOllamaModels = buildStudyOllamaModels;
7330
5720
  exports.buildStudyOllamaOptions = buildStudyOllamaOptions;
7331
5721
  exports.buildStudyOllamaRaw = buildStudyOllamaRaw;
@@ -7374,13 +5764,17 @@ exports.isStudyIdentity = isStudyIdentity;
7374
5764
  exports.isStudyProcessFailure = isStudyProcessFailure;
7375
5765
  exports.isStudyTrial = isStudyTrial;
7376
5766
  exports.iterateArtifactEntries = iterateArtifactEntries;
5767
+ exports.matchesSystemError = matchesSystemError;
7377
5768
  exports.parseStudyArguments = parseStudyArguments;
7378
5769
  exports.readArtifact = readArtifact;
7379
5770
  exports.readArtifactBytes = readArtifactBytes;
7380
5771
  exports.readStudyAdapter = readStudyAdapter;
7381
5772
  exports.readStudyAgentMessages = readStudyAgentMessages;
7382
5773
  exports.readStudyAgentResult = readStudyAgentResult;
5774
+ exports.readStudyAgentToolCall = readStudyAgentToolCall;
7383
5775
  exports.readStudyBytes = readStudyBytes;
5776
+ exports.readStudyDeclaration = readStudyDeclaration;
5777
+ exports.readStudyDeclarationBytes = readStudyDeclarationBytes;
7384
5778
  exports.readStudyHandle = readStudyHandle;
7385
5779
  exports.readStudyOllamaDigest = readStudyOllamaDigest;
7386
5780
  exports.readStudyOllamaFailure = readStudyOllamaFailure;
@@ -7389,6 +5783,7 @@ exports.readStudyOllamaSupport = readStudyOllamaSupport;
7389
5783
  exports.readStudyOllamaTerminal = readStudyOllamaTerminal;
7390
5784
  exports.readStudyOllamaTiming = readStudyOllamaTiming;
7391
5785
  exports.readStudyResult = readStudyResult;
5786
+ exports.readStudyVersion = readStudyVersion;
7392
5787
  exports.resolveStudyAgentContent = resolveStudyAgentContent;
7393
5788
  exports.resolveStudyInstrument = resolveStudyInstrument;
7394
5789
  exports.resolveStudyPath = resolveStudyPath;