@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.
- package/README.md +63 -1
- package/dist/bin/main.js +2 -5399
- package/dist/bin/main.js.map +1 -1
- package/dist/src/core/index.cjs +47 -6
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +16 -4
- package/dist/src/core/index.d.ts +16 -4
- package/dist/src/core/index.js +48 -8
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +677 -2282
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +154 -446
- package/dist/src/server/index.d.ts +154 -446
- package/dist/src/server/index.js +395 -2007
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -6
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @remarks
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
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 {
|
|
23
|
+
* import { matchesSystemError } from '@orkestrel/study/server'
|
|
114
24
|
*
|
|
115
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
3392
|
-
/**
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
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
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
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
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
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
|
|
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
|
|
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
|
|
4636
|
-
const
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
5971
|
-
|
|
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;
|