@orkestrel/study 0.0.1 → 0.0.3

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.
@@ -84,8 +84,8 @@ export declare function computeStudyOutputStop(bytes: number, limit: number, out
84
84
  export declare function computeStudyScores(manifest: StudyManifest, rubric: StudyRubric, grading: StudyGrading): readonly StudyScore[];
85
85
 
86
86
  /**
87
- * Records an error that escaped admitted work as the failure of its ticket.
88
- * @param ticket - The admitted request whose work escaped
87
+ * Records an error thrown by admitted work as the failure of its ticket.
88
+ * @param ticket - The admitted request whose work threw
89
89
  * @param error - The caught value, read through `readStudyFailure`
90
90
  * @param role - The responsibility that replaces any role the reading carries; Default: the carried role, else the ticket's admitted role
91
91
  * @remarks
@@ -94,7 +94,7 @@ export declare function computeStudyScores(manifest: StudyManifest, rubric: Stud
94
94
  * so the parent's ownership is respected rather than inferred as local settlement. A refusal
95
95
  * thrown by the ticket's own settlement propagates.
96
96
  * @throws Thrown when the ticket's settlement refuses the failure.
97
- * @example Fail an escaped request
97
+ * @example Fail an admitted request
98
98
  * ```ts
99
99
  * import type { StudyTicket } from '@orkestrel/study'
100
100
  * import { failStudyTicket } from '@orkestrel/study'
@@ -112,6 +112,17 @@ export declare function failStudyTicket(ticket: StudyTicket, error: unknown, rol
112
112
  */
113
113
  export declare function failureToStudyError(failure: StudyFailure): StudyError;
114
114
 
115
+ /**
116
+ * Checks whether a value is a gauge with a finite positive scale and a finite nonnegative overhead.
117
+ * @param value - The candidate gauge; additional members are admitted
118
+ * @returns True if the gauge readings are finite and within their bounds; false otherwise
119
+ * @example
120
+ * ```ts
121
+ * isStudyAgentGauge({ scale: 1, overhead: 0 }) // true
122
+ * ```
123
+ */
124
+ export declare function isStudyAgentGauge(value: unknown): value is LedgerGauge;
125
+
115
126
  /**
116
127
  * Checks whether a namespace exposes the Agent capabilities the instrumentation invokes.
117
128
  * @remarks
@@ -147,6 +158,16 @@ export declare const isStudyCause: Guard<StudyCause>;
147
158
  */
148
159
  export declare function isStudyCriterion(value: unknown): value is StudyCriterion;
149
160
 
161
+ /** Checks whether an owned daemon declaration pins an absolute executable and distinct models.
162
+ * @param value - The candidate declaration.
163
+ * @returns True if executable, version, environment, and model identities are valid; false otherwise.
164
+ * @example
165
+ * ```ts
166
+ * isStudyDaemon({ executable: 'ollama' }) // false
167
+ * ```
168
+ */
169
+ export declare function isStudyDaemon(value: unknown): value is StudyDaemon;
170
+
150
171
  /**
151
172
  * Checks whether a study freezes a complete arm order for every repetition.
152
173
  * @param value - The candidate definition
@@ -170,14 +191,15 @@ export declare function isStudyDefinition(value: unknown): value is StudyDefinit
170
191
  export declare function isStudyDigest(value: unknown): value is string;
171
192
 
172
193
  /**
173
- * Narrows a caught value to an attributable study error.
194
+ * Narrows a caught value to a branded study error with valid failure attribution.
174
195
  * @remarks
175
- * Recognition combines the native `Error` base, a subclass prototype, the fixed name, a declared
176
- * study code, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is the error itself.
196
+ * Recognition combines the native `Error` base, a subclass prototype, a declared study code,
197
+ * valid optional failure members, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is
198
+ * the error itself. Subclasses can replace the name.
177
199
  * The global key is recognized across package, ESM, CommonJS and captured module copies; it is
178
- * not unforgeable provenance. A transparent proxy is refused because its forwarded descriptor
200
+ * not proof of origin. A transparent proxy is refused because its forwarded descriptor
179
201
  * stores the target, and an unbranded copy, an inherited brand, or a property-only lookalike
180
- * stays outside the type. Hostile property access returns false instead of throwing.
202
+ * stays outside the type. Throwing property access returns false instead of throwing.
181
203
  * @param value - The caught value
182
204
  * @returns True if the value is a study error; false otherwise
183
205
  * @example
@@ -300,6 +322,12 @@ export declare function isStudyProfile(value: unknown): value is StudyProfile;
300
322
  */
301
323
  export declare function isStudyProvider(value: unknown): value is StudyProvider;
302
324
 
325
+ /** Checks whether a recording declaration bounds every serialized sidecar byte.
326
+ * @param value - The candidate declaration.
327
+ * @returns True if its byte allowance is a positive safe integer; false otherwise.
328
+ */
329
+ export declare function isStudyRecord(value: unknown): value is StudyRecord;
330
+
303
331
  /** Checks whether a value names a measured request role. */
304
332
  export declare const isStudyRole: Guard<StudyRole>;
305
333
 
@@ -521,8 +549,8 @@ export declare const STUDY_CAUSES: readonly StudyCause[];
521
549
  * Keys the own self-identity every study error stamps on itself.
522
550
  * @remarks
523
551
  * The global symbol registry lets every package, ESM, CommonJS and captured copy read the same
524
- * key. The registry makes the key discoverable; it is a recognition mechanism, not an
525
- * unforgeable provenance marker.
552
+ * key. The registry makes the key discoverable; it recognizes the contract without proving
553
+ * its origin.
526
554
  */
527
555
  export declare const STUDY_ERROR_BRAND: unique symbol;
528
556
 
@@ -743,6 +771,32 @@ export declare interface StudyCriterion {
743
771
  readonly assertion: JSONValue;
744
772
  }
745
773
 
774
+ /** Pins an executable and the model population an execution owns until cleanup.
775
+ * @remarks
776
+ * Study owns this declaration independently of Ollama's server API. The executable is an absolute
777
+ * canonical path; digest is its SHA-256 and version is the exact expected version reply. The
778
+ * provider URL must be a dedicated loopback origin outside port 11434. Environment entries are
779
+ * explicit non-secret overrides, merged by case-folded name; the host owns OLLAMA_HOST. Models
780
+ * have distinct names and SHA-256 digests equal to the provider's model and judge pins; shared
781
+ * pins require one entry. Execution
782
+ * verifies identity before use, unloads this population before each trial, and stops and joins
783
+ * on every ending. Offline replay never creates this daemon.
784
+ */
785
+ export declare interface StudyDaemon {
786
+ readonly executable: string;
787
+ readonly digest: string;
788
+ readonly version: string;
789
+ /** Holds explicit overrides with distinct case-folded names and no NUL characters. */
790
+ readonly environment?: Readonly<Record<string, string>> | undefined;
791
+ readonly models: readonly StudyDaemonModel[];
792
+ }
793
+
794
+ /** Pins one model the declared daemon verifies and unloads before each trial. */
795
+ export declare interface StudyDaemonModel {
796
+ readonly name: string;
797
+ readonly digest: string;
798
+ }
799
+
746
800
  /** Bounds each independent operation with a fresh failure owner and deadline. */
747
801
  export declare class StudyDeadline implements StudyDeadlineInterface {
748
802
  #private;
@@ -922,6 +976,8 @@ export declare interface StudyManifest {
922
976
  readonly bounds: StudyBounds;
923
977
  readonly qualification: string | undefined;
924
978
  readonly host: Readonly<Record<string, string>>;
979
+ /** Copies the profile's recording declaration before any trial starts. */
980
+ readonly record?: StudyRecord | undefined;
925
981
  }
926
982
 
927
983
  /** Admits request ceilings across every enclosing scope before dispatch. */
@@ -989,6 +1045,8 @@ export declare interface StudyProfile {
989
1045
  readonly qualification: string | undefined;
990
1046
  /** Holds driver-owned JSON; the driver owns its interpretation and Study only validates and owns the object. */
991
1047
  readonly settings: Readonly<Record<string, JSONValue>> | undefined;
1048
+ /** Requires one bounded exchange sidecar for every trial when present. */
1049
+ readonly record?: StudyRecord | undefined;
992
1050
  }
993
1051
 
994
1052
  /** Holds caller-selected provider artifacts and model identities. */
@@ -1004,11 +1062,24 @@ export declare interface StudyProvider {
1004
1062
  /** Pins the judge model; declared together with its name or both absent. */
1005
1063
  readonly judge: string | undefined;
1006
1064
  };
1065
+ /** Declares an execution-owned daemon at this provider's dedicated URL. */
1066
+ readonly daemon?: StudyDaemon;
1007
1067
  }
1008
1068
 
1009
1069
  /** Names the evidence a study can establish. */
1010
1070
  export declare type StudyPurpose = 'mechanism' | 'quality' | 'capacity';
1011
1071
 
1072
+ /** Bounds the serialized exchange sidecar retained for each scheduled trial.
1073
+ * @remarks
1074
+ * Bytes is a positive safe integer counting all UTF-8 JSONL bytes, including declarations,
1075
+ * base64 expansion, terminal records, and LF separators. Recording is independent of passive
1076
+ * observation. Sidecars have their own trial allowance and also consume series custody capacity;
1077
+ * they do not consume the goal evidence allowance or the model's request quota.
1078
+ */
1079
+ export declare interface StudyRecord {
1080
+ readonly bytes: number;
1081
+ }
1082
+
1012
1083
  /** Settles an admitted request once and propagates operational failure to its owner. */
1013
1084
  export declare class StudyRequest implements StudyTicket {
1014
1085
  #private;
@@ -1039,7 +1110,7 @@ export declare interface StudyRequestOptions {
1039
1110
  }
1040
1111
 
1041
1112
  /** Names a request's measured responsibility. */
1042
- export declare type StudyRole = 'agent' | 'judge' | 'summary' | 'metadata' | 'tool';
1113
+ export declare type StudyRole = 'agent' | 'judge' | 'summary' | 'metadata' | 'maintenance' | 'tool';
1043
1114
 
1044
1115
  /** Holds the model context and generation allowance as separate measurements. */
1045
1116
  export declare interface StudyRoom {
@@ -1308,7 +1379,7 @@ export declare function validateStudyAgentUsage(usage: ProviderResult['usage'],
1308
1379
  * @param rubric - The independently held grading declaration
1309
1380
  * @param profile - The execution bounds and model room
1310
1381
  * @returns Nothing when every cross-input invariant holds
1311
- * @throws StudyError - Thrown when any input or cross-reference is invalid
1382
+ * @throws StudyError - Thrown when any input or cross-reference is invalid, or a quality study has no capacity qualification
1312
1383
  * @example
1313
1384
  * ```ts
1314
1385
  * validateStudyInputs(definition, scenarios, rubric, profile)
@@ -84,8 +84,8 @@ export declare function computeStudyOutputStop(bytes: number, limit: number, out
84
84
  export declare function computeStudyScores(manifest: StudyManifest, rubric: StudyRubric, grading: StudyGrading): readonly StudyScore[];
85
85
 
86
86
  /**
87
- * Records an error that escaped admitted work as the failure of its ticket.
88
- * @param ticket - The admitted request whose work escaped
87
+ * Records an error thrown by admitted work as the failure of its ticket.
88
+ * @param ticket - The admitted request whose work threw
89
89
  * @param error - The caught value, read through `readStudyFailure`
90
90
  * @param role - The responsibility that replaces any role the reading carries; Default: the carried role, else the ticket's admitted role
91
91
  * @remarks
@@ -94,7 +94,7 @@ export declare function computeStudyScores(manifest: StudyManifest, rubric: Stud
94
94
  * so the parent's ownership is respected rather than inferred as local settlement. A refusal
95
95
  * thrown by the ticket's own settlement propagates.
96
96
  * @throws Thrown when the ticket's settlement refuses the failure.
97
- * @example Fail an escaped request
97
+ * @example Fail an admitted request
98
98
  * ```ts
99
99
  * import type { StudyTicket } from '@orkestrel/study'
100
100
  * import { failStudyTicket } from '@orkestrel/study'
@@ -112,6 +112,17 @@ export declare function failStudyTicket(ticket: StudyTicket, error: unknown, rol
112
112
  */
113
113
  export declare function failureToStudyError(failure: StudyFailure): StudyError;
114
114
 
115
+ /**
116
+ * Checks whether a value is a gauge with a finite positive scale and a finite nonnegative overhead.
117
+ * @param value - The candidate gauge; additional members are admitted
118
+ * @returns True if the gauge readings are finite and within their bounds; false otherwise
119
+ * @example
120
+ * ```ts
121
+ * isStudyAgentGauge({ scale: 1, overhead: 0 }) // true
122
+ * ```
123
+ */
124
+ export declare function isStudyAgentGauge(value: unknown): value is LedgerGauge;
125
+
115
126
  /**
116
127
  * Checks whether a namespace exposes the Agent capabilities the instrumentation invokes.
117
128
  * @remarks
@@ -147,6 +158,16 @@ export declare const isStudyCause: Guard<StudyCause>;
147
158
  */
148
159
  export declare function isStudyCriterion(value: unknown): value is StudyCriterion;
149
160
 
161
+ /** Checks whether an owned daemon declaration pins an absolute executable and distinct models.
162
+ * @param value - The candidate declaration.
163
+ * @returns True if executable, version, environment, and model identities are valid; false otherwise.
164
+ * @example
165
+ * ```ts
166
+ * isStudyDaemon({ executable: 'ollama' }) // false
167
+ * ```
168
+ */
169
+ export declare function isStudyDaemon(value: unknown): value is StudyDaemon;
170
+
150
171
  /**
151
172
  * Checks whether a study freezes a complete arm order for every repetition.
152
173
  * @param value - The candidate definition
@@ -170,14 +191,15 @@ export declare function isStudyDefinition(value: unknown): value is StudyDefinit
170
191
  export declare function isStudyDigest(value: unknown): value is string;
171
192
 
172
193
  /**
173
- * Narrows a caught value to an attributable study error.
194
+ * Narrows a caught value to a branded study error with valid failure attribution.
174
195
  * @remarks
175
- * Recognition combines the native `Error` base, a subclass prototype, the fixed name, a declared
176
- * study code, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is the error itself.
196
+ * Recognition combines the native `Error` base, a subclass prototype, a declared study code,
197
+ * valid optional failure members, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is
198
+ * the error itself. Subclasses can replace the name.
177
199
  * The global key is recognized across package, ESM, CommonJS and captured module copies; it is
178
- * not unforgeable provenance. A transparent proxy is refused because its forwarded descriptor
200
+ * not proof of origin. A transparent proxy is refused because its forwarded descriptor
179
201
  * stores the target, and an unbranded copy, an inherited brand, or a property-only lookalike
180
- * stays outside the type. Hostile property access returns false instead of throwing.
202
+ * stays outside the type. Throwing property access returns false instead of throwing.
181
203
  * @param value - The caught value
182
204
  * @returns True if the value is a study error; false otherwise
183
205
  * @example
@@ -300,6 +322,12 @@ export declare function isStudyProfile(value: unknown): value is StudyProfile;
300
322
  */
301
323
  export declare function isStudyProvider(value: unknown): value is StudyProvider;
302
324
 
325
+ /** Checks whether a recording declaration bounds every serialized sidecar byte.
326
+ * @param value - The candidate declaration.
327
+ * @returns True if its byte allowance is a positive safe integer; false otherwise.
328
+ */
329
+ export declare function isStudyRecord(value: unknown): value is StudyRecord;
330
+
303
331
  /** Checks whether a value names a measured request role. */
304
332
  export declare const isStudyRole: Guard<StudyRole>;
305
333
 
@@ -521,8 +549,8 @@ export declare const STUDY_CAUSES: readonly StudyCause[];
521
549
  * Keys the own self-identity every study error stamps on itself.
522
550
  * @remarks
523
551
  * The global symbol registry lets every package, ESM, CommonJS and captured copy read the same
524
- * key. The registry makes the key discoverable; it is a recognition mechanism, not an
525
- * unforgeable provenance marker.
552
+ * key. The registry makes the key discoverable; it recognizes the contract without proving
553
+ * its origin.
526
554
  */
527
555
  export declare const STUDY_ERROR_BRAND: unique symbol;
528
556
 
@@ -743,6 +771,32 @@ export declare interface StudyCriterion {
743
771
  readonly assertion: JSONValue;
744
772
  }
745
773
 
774
+ /** Pins an executable and the model population an execution owns until cleanup.
775
+ * @remarks
776
+ * Study owns this declaration independently of Ollama's server API. The executable is an absolute
777
+ * canonical path; digest is its SHA-256 and version is the exact expected version reply. The
778
+ * provider URL must be a dedicated loopback origin outside port 11434. Environment entries are
779
+ * explicit non-secret overrides, merged by case-folded name; the host owns OLLAMA_HOST. Models
780
+ * have distinct names and SHA-256 digests equal to the provider's model and judge pins; shared
781
+ * pins require one entry. Execution
782
+ * verifies identity before use, unloads this population before each trial, and stops and joins
783
+ * on every ending. Offline replay never creates this daemon.
784
+ */
785
+ export declare interface StudyDaemon {
786
+ readonly executable: string;
787
+ readonly digest: string;
788
+ readonly version: string;
789
+ /** Holds explicit overrides with distinct case-folded names and no NUL characters. */
790
+ readonly environment?: Readonly<Record<string, string>> | undefined;
791
+ readonly models: readonly StudyDaemonModel[];
792
+ }
793
+
794
+ /** Pins one model the declared daemon verifies and unloads before each trial. */
795
+ export declare interface StudyDaemonModel {
796
+ readonly name: string;
797
+ readonly digest: string;
798
+ }
799
+
746
800
  /** Bounds each independent operation with a fresh failure owner and deadline. */
747
801
  export declare class StudyDeadline implements StudyDeadlineInterface {
748
802
  #private;
@@ -922,6 +976,8 @@ export declare interface StudyManifest {
922
976
  readonly bounds: StudyBounds;
923
977
  readonly qualification: string | undefined;
924
978
  readonly host: Readonly<Record<string, string>>;
979
+ /** Copies the profile's recording declaration before any trial starts. */
980
+ readonly record?: StudyRecord | undefined;
925
981
  }
926
982
 
927
983
  /** Admits request ceilings across every enclosing scope before dispatch. */
@@ -989,6 +1045,8 @@ export declare interface StudyProfile {
989
1045
  readonly qualification: string | undefined;
990
1046
  /** Holds driver-owned JSON; the driver owns its interpretation and Study only validates and owns the object. */
991
1047
  readonly settings: Readonly<Record<string, JSONValue>> | undefined;
1048
+ /** Requires one bounded exchange sidecar for every trial when present. */
1049
+ readonly record?: StudyRecord | undefined;
992
1050
  }
993
1051
 
994
1052
  /** Holds caller-selected provider artifacts and model identities. */
@@ -1004,11 +1062,24 @@ export declare interface StudyProvider {
1004
1062
  /** Pins the judge model; declared together with its name or both absent. */
1005
1063
  readonly judge: string | undefined;
1006
1064
  };
1065
+ /** Declares an execution-owned daemon at this provider's dedicated URL. */
1066
+ readonly daemon?: StudyDaemon;
1007
1067
  }
1008
1068
 
1009
1069
  /** Names the evidence a study can establish. */
1010
1070
  export declare type StudyPurpose = 'mechanism' | 'quality' | 'capacity';
1011
1071
 
1072
+ /** Bounds the serialized exchange sidecar retained for each scheduled trial.
1073
+ * @remarks
1074
+ * Bytes is a positive safe integer counting all UTF-8 JSONL bytes, including declarations,
1075
+ * base64 expansion, terminal records, and LF separators. Recording is independent of passive
1076
+ * observation. Sidecars have their own trial allowance and also consume series custody capacity;
1077
+ * they do not consume the goal evidence allowance or the model's request quota.
1078
+ */
1079
+ export declare interface StudyRecord {
1080
+ readonly bytes: number;
1081
+ }
1082
+
1012
1083
  /** Settles an admitted request once and propagates operational failure to its owner. */
1013
1084
  export declare class StudyRequest implements StudyTicket {
1014
1085
  #private;
@@ -1039,7 +1110,7 @@ export declare interface StudyRequestOptions {
1039
1110
  }
1040
1111
 
1041
1112
  /** Names a request's measured responsibility. */
1042
- export declare type StudyRole = 'agent' | 'judge' | 'summary' | 'metadata' | 'tool';
1113
+ export declare type StudyRole = 'agent' | 'judge' | 'summary' | 'metadata' | 'maintenance' | 'tool';
1043
1114
 
1044
1115
  /** Holds the model context and generation allowance as separate measurements. */
1045
1116
  export declare interface StudyRoom {
@@ -1308,7 +1379,7 @@ export declare function validateStudyAgentUsage(usage: ProviderResult['usage'],
1308
1379
  * @param rubric - The independently held grading declaration
1309
1380
  * @param profile - The execution bounds and model room
1310
1381
  * @returns Nothing when every cross-input invariant holds
1311
- * @throws StudyError - Thrown when any input or cross-reference is invalid
1382
+ * @throws StudyError - Thrown when any input or cross-reference is invalid, or a quality study has no capacity qualification
1312
1383
  * @example
1313
1384
  * ```ts
1314
1385
  * validateStudyInputs(definition, scenarios, rubric, profile)
@@ -1,4 +1,4 @@
1
- import { attempt, canonicalStringify, cloneJSONValue, holds, isArray, isError, isFiniteNumber, isJSONValue, isNonEmptyString, isObject, isRecord, literalOf } from "@orkestrel/contract";
1
+ import { INTRINSICS, attempt, canonicalStringify, cloneJSONValue, holds, isArray, isError, isFiniteNumber, isJSONValue, isNonEmptyString, isNumber, isObject, isRecord, literalOf } from "@orkestrel/contract";
2
2
  import { MAX_TIMEOUT_MS, Timeout } from "@orkestrel/timeout";
3
3
  import { Budget, isBudgetError } from "@orkestrel/budget";
4
4
  import { Abort, linkSignal } from "@orkestrel/abort";
@@ -7,8 +7,8 @@ import { Abort, linkSignal } from "@orkestrel/abort";
7
7
  * Keys the own self-identity every study error stamps on itself.
8
8
  * @remarks
9
9
  * The global symbol registry lets every package, ESM, CommonJS and captured copy read the same
10
- * key. The registry makes the key discoverable; it is a recognition mechanism, not an
11
- * unforgeable provenance marker.
10
+ * key. The registry makes the key discoverable; it recognizes the contract without proving
11
+ * its origin.
12
12
  */
13
13
  var STUDY_ERROR_BRAND = Symbol.for("@orkestrel/study.error");
14
14
  /** Caps a declared trial population before schedule allocation. */
@@ -23,6 +23,7 @@ var STUDY_ROLES = Object.freeze([
23
23
  "judge",
24
24
  "summary",
25
25
  "metadata",
26
+ "maintenance",
26
27
  "tool"
27
28
  ]);
28
29
  /** Lists every ceiling a run can stop at in report order. */
@@ -66,16 +67,18 @@ function omitStudyAbsent(value) {
66
67
  * ```
67
68
  */
68
69
  function normalizeStudyProfile(profile) {
69
- const { provider, qualification, settings, room, ...required } = profile;
70
+ const { provider, qualification, settings, record, room, ...required } = profile;
70
71
  return {
71
72
  ...required,
72
73
  room: omitStudyAbsent(room),
73
74
  ...provider === void 0 ? {} : { provider: {
74
75
  ...omitStudyAbsent(provider),
75
- digest: omitStudyAbsent(provider.digest)
76
+ digest: omitStudyAbsent(provider.digest),
77
+ ...provider.daemon === void 0 ? {} : { daemon: omitStudyAbsent(provider.daemon) }
76
78
  } },
77
79
  ...qualification === void 0 ? {} : { qualification },
78
- ...settings === void 0 ? {} : { settings }
80
+ ...settings === void 0 ? {} : { settings },
81
+ ...record === void 0 ? {} : { record }
79
82
  };
80
83
  }
81
84
  /**
@@ -149,7 +152,7 @@ function buildStudySchedule(definition, scenarios) {
149
152
  * @param rubric - The independently held grading declaration
150
153
  * @param profile - The execution bounds and model room
151
154
  * @returns Nothing when every cross-input invariant holds
152
- * @throws StudyError - Thrown when any input or cross-reference is invalid
155
+ * @throws StudyError - Thrown when any input or cross-reference is invalid, or a quality study has no capacity qualification
153
156
  * @example
154
157
  * ```ts
155
158
  * validateStudyInputs(definition, scenarios, rubric, profile)
@@ -166,6 +169,7 @@ function validateStudyInputs(definition, scenarios, rubric, profile) {
166
169
  }
167
170
  const covered = new Set(rubric.criteria.map((criterion) => canonicalStringify([criterion.scenario, criterion.goal]) ?? ""));
168
171
  for (const scenario of scenarios) for (const goal of scenario.goals) if (!covered.has(canonicalStringify([scenario.id, goal.id]) ?? "")) throw new StudyError("CONFIG", "Every declared goal needs a criterion");
172
+ if (definition.purpose === "quality" && profile.qualification === void 0) throw new StudyError("CONFIG", "Quality studies require a capacity qualification");
169
173
  }
170
174
  /**
171
175
  * Computes arm intervals over the complete declared criterion horizon.
@@ -416,15 +420,46 @@ function isStudyProvider(value) {
416
420
  "url",
417
421
  "model",
418
422
  "digest"
419
- ], ["judge"]) || ![
423
+ ], ["judge", "daemon"]) || ![
420
424
  "entry",
421
425
  "url",
422
426
  "model"
423
427
  ].every((key) => isNonEmptyString(value[key])) || typeof value.url !== "string" || !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 && (!isNonEmptyString(value.judge) || !isStudyDigest(value.digest.judge))) return false;
424
428
  const url = new URL(value.url);
429
+ if (value.daemon !== void 0) {
430
+ if (!isStudyDaemon(value.daemon) || !/^http:\/\/127\.0\.0\.1:[0-9]+\/?$/.test(value.url) || Number(url.port) <= 0 || Number(url.port) === 11434) return false;
431
+ const daemon = value.daemon;
432
+ const digest = value.digest;
433
+ if (!daemon.models.every((model) => model.name === value.model && model.digest === digest.model || value.judge !== void 0 && model.name === value.judge && model.digest === digest.judge) || !daemon.models.some((model) => model.name === value.model && model.digest === digest.model) || value.judge !== void 0 && !daemon.models.some((model) => model.name === value.judge && model.digest === digest.judge)) return false;
434
+ }
425
435
  return ["http:", "https:"].includes(url.protocol) && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && url.pathname === "/";
426
436
  });
427
437
  }
438
+ /** Checks whether an owned daemon declaration pins an absolute executable and distinct models.
439
+ * @param value - The candidate declaration.
440
+ * @returns True if executable, version, environment, and model identities are valid; false otherwise.
441
+ * @example
442
+ * ```ts
443
+ * isStudyDaemon({ executable: 'ollama' }) // false
444
+ * ```
445
+ */
446
+ function isStudyDaemon(value) {
447
+ return holds(() => {
448
+ if (!isRecord(value) || !matchesStudyKeys(value, [
449
+ "executable",
450
+ "digest",
451
+ "version",
452
+ "models"
453
+ ], ["environment"]) || typeof value.executable !== "string" || !/^(?:[A-Za-z]:[\\/]|\/(?!\/)|\\\\[^\\]+\\[^\\]+\\)/.test(value.executable) || value.executable.includes("\0") || value.executable.split(/[\\/]/).some((part) => part === "." || part === "..") || !isStudyDigest(value.digest) || !isNonEmptyString(value.version) || value.version.trim().length === 0 || value.version.includes("\0") || !isArray(value.models) || value.models.length === 0 || !value.models.every((model) => isRecord(model) && matchesStudyKeys(model, ["name", "digest"]) && isNonEmptyString(model.name) && model.name.trim().length > 0 && !model.name.includes("\0") && isStudyDigest(model.digest))) return false;
454
+ if (new Set(value.models.map((model) => isRecord(model) ? model.name : void 0)).size !== value.models.length) return false;
455
+ if (value.environment !== void 0) {
456
+ if (!isRecord(value.environment) || !matchesStudyKeys(value.environment, [], Object.keys(value.environment))) return false;
457
+ const entries = Object.entries(value.environment);
458
+ if (new Set(entries.map(([name]) => name.toLowerCase())).size !== entries.length || entries.some(([name, entry]) => name.length === 0 || /[=\0]/.test(name) || typeof entry !== "string" || entry.includes("\0"))) return false;
459
+ }
460
+ return true;
461
+ });
462
+ }
428
463
  /**
429
464
  * Checks whether a profile carries finite bounds and an optional explicit provider.
430
465
  * @param value - The candidate profile
@@ -446,10 +481,18 @@ function isStudyProfile(value) {
446
481
  ], [
447
482
  "provider",
448
483
  "qualification",
449
- "settings"
450
- ]) && isStudyName(value.name) && isNonEmptyString(value.agent) && isArray(value.roots) && 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 && isNonEmptyString(value.subject) && isStudyBounds(value.bounds) && isStudyRoom(value.room) && (value.provider === void 0 || isStudyProvider(value.provider)) && (value.qualification === void 0 || isNonEmptyString(value.qualification)) && (value.settings === void 0 || isRecord(value.settings) && isJSONValue(value.settings));
484
+ "settings",
485
+ "record"
486
+ ]) && isStudyName(value.name) && isNonEmptyString(value.agent) && isArray(value.roots) && 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 && isNonEmptyString(value.subject) && isStudyBounds(value.bounds) && isStudyRoom(value.room) && (value.provider === void 0 || isStudyProvider(value.provider)) && (value.record === void 0 || isStudyRecord(value.record)) && (value.qualification === void 0 || isNonEmptyString(value.qualification)) && (value.settings === void 0 || isRecord(value.settings) && isJSONValue(value.settings));
451
487
  });
452
488
  }
489
+ /** Checks whether a recording declaration bounds every serialized sidecar byte.
490
+ * @param value - The candidate declaration.
491
+ * @returns True if its byte allowance is a positive safe integer; false otherwise.
492
+ */
493
+ function isStudyRecord(value) {
494
+ return holds(() => isRecord(value) && matchesStudyKeys(value, ["bytes"]) && typeof value.bytes === "number" && Number.isSafeInteger(value.bytes) && value.bytes > 0);
495
+ }
453
496
  /**
454
497
  * Checks whether a byte identity is a lowercase SHA-256 digest.
455
498
  * @param value - The candidate identity
@@ -630,8 +673,10 @@ var StudyError = class extends Error {
630
673
  */
631
674
  constructor(code, message, failure) {
632
675
  super(message);
633
- Object.defineProperty(this, STUDY_ERROR_BRAND, {
676
+ INTRINSICS.define(this, STUDY_ERROR_BRAND, {
634
677
  value: this,
678
+ enumerable: false,
679
+ writable: false,
635
680
  configurable: true
636
681
  });
637
682
  this.name = "StudyError";
@@ -640,14 +685,15 @@ var StudyError = class extends Error {
640
685
  }
641
686
  };
642
687
  /**
643
- * Narrows a caught value to an attributable study error.
688
+ * Narrows a caught value to a branded study error with valid failure attribution.
644
689
  * @remarks
645
- * Recognition combines the native `Error` base, a subclass prototype, the fixed name, a declared
646
- * study code, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is the error itself.
690
+ * Recognition combines the native `Error` base, a subclass prototype, a declared study code,
691
+ * valid optional failure members, and an own {@link STUDY_ERROR_BRAND} descriptor whose value is
692
+ * the error itself. Subclasses can replace the name.
647
693
  * The global key is recognized across package, ESM, CommonJS and captured module copies; it is
648
- * not unforgeable provenance. A transparent proxy is refused because its forwarded descriptor
694
+ * not proof of origin. A transparent proxy is refused because its forwarded descriptor
649
695
  * stores the target, and an unbranded copy, an inherited brand, or a property-only lookalike
650
- * stays outside the type. Hostile property access returns false instead of throwing.
696
+ * stays outside the type. Throwing property access returns false instead of throwing.
651
697
  * @param value - The caught value
652
698
  * @returns True if the value is a study error; false otherwise
653
699
  * @example
@@ -656,7 +702,28 @@ var StudyError = class extends Error {
656
702
  * ```
657
703
  */
658
704
  function isStudyError(value) {
659
- return isError(value) && holds(() => Object.getPrototypeOf(value) !== Error.prototype && value.name === "StudyError" && Object.getOwnPropertyDescriptor(value, STUDY_ERROR_BRAND)?.value === value && "code" in value && isStudyErrorCode(value.code));
705
+ return holds(() => {
706
+ if (!(value instanceof INTRINSICS.error) || INTRINSICS.prototype(value) === INTRINSICS.error.prototype) return false;
707
+ const brand = INTRINSICS.describe(value, STUDY_ERROR_BRAND)?.value;
708
+ const code = INTRINSICS.reflect.read(value, "code");
709
+ const failure = INTRINSICS.reflect.read(value, "failure");
710
+ const stack = INTRINSICS.reflect.read(value, "stack");
711
+ if (brand !== value || !isStudyErrorCode(code) || typeof INTRINSICS.reflect.read(value, "name") !== "string" || typeof INTRINSICS.reflect.read(value, "message") !== "string" || stack !== void 0 && typeof stack !== "string") return false;
712
+ if (failure === void 0) return true;
713
+ if (!isObject(failure)) return false;
714
+ const cause = INTRINSICS.reflect.read(failure, "cause");
715
+ const message = INTRINSICS.reflect.read(failure, "message");
716
+ const role = INTRINSICS.reflect.read(failure, "role");
717
+ const stop = INTRINSICS.reflect.read(failure, "stop");
718
+ if (!isStudyCause(cause) || typeof message !== "string" || role !== void 0 && !isStudyRole(role)) return false;
719
+ if (stop === void 0) return true;
720
+ if (!isObject(stop)) return false;
721
+ const layer = INTRINSICS.reflect.read(stop, "layer");
722
+ const id = INTRINSICS.reflect.read(stop, "id");
723
+ const max = INTRINSICS.reflect.read(stop, "max");
724
+ const consumed = INTRINSICS.reflect.read(stop, "consumed");
725
+ return isStudyLayer(layer) && typeof id === "string" && (max === void 0 || isNumber(max)) && (consumed === void 0 || isNumber(consumed));
726
+ });
660
727
  }
661
728
  //#endregion
662
729
  //#region src/core/parsers.ts
@@ -920,8 +987,8 @@ function computeStudyOutputStop(bytes, limit, outputs) {
920
987
  };
921
988
  }
922
989
  /**
923
- * Records an error that escaped admitted work as the failure of its ticket.
924
- * @param ticket - The admitted request whose work escaped
990
+ * Records an error thrown by admitted work as the failure of its ticket.
991
+ * @param ticket - The admitted request whose work threw
925
992
  * @param error - The caught value, read through `readStudyFailure`
926
993
  * @param role - The responsibility that replaces any role the reading carries; Default: the carried role, else the ticket's admitted role
927
994
  * @remarks
@@ -930,7 +997,7 @@ function computeStudyOutputStop(bytes, limit, outputs) {
930
997
  * so the parent's ownership is respected rather than inferred as local settlement. A refusal
931
998
  * thrown by the ticket's own settlement propagates.
932
999
  * @throws Thrown when the ticket's settlement refuses the failure.
933
- * @example Fail an escaped request
1000
+ * @example Fail an admitted request
934
1001
  * ```ts
935
1002
  * import type { StudyTicket } from '@orkestrel/study'
936
1003
  * import { failStudyTicket } from '@orkestrel/study'
@@ -1876,6 +1943,22 @@ function buildStudyAgentDispatch(dispatch) {
1876
1943
  //#endregion
1877
1944
  //#region src/core/agents/validators.ts
1878
1945
  /**
1946
+ * Checks whether a value is a gauge with a finite positive scale and a finite nonnegative overhead.
1947
+ * @param value - The candidate gauge; additional members are admitted
1948
+ * @returns True if the gauge readings are finite and within their bounds; false otherwise
1949
+ * @example
1950
+ * ```ts
1951
+ * isStudyAgentGauge({ scale: 1, overhead: 0 }) // true
1952
+ * ```
1953
+ */
1954
+ function isStudyAgentGauge(value) {
1955
+ return holds(() => {
1956
+ if (!isObject(value) || !("scale" in value) || !("overhead" in value)) return false;
1957
+ const { scale, overhead } = value;
1958
+ return isFiniteNumber(scale) && scale > 0 && isFiniteNumber(overhead) && overhead >= 0;
1959
+ });
1960
+ }
1961
+ /**
1879
1962
  * Checks whether a namespace exposes the Agent capabilities the instrumentation invokes.
1880
1963
  * @remarks
1881
1964
  * The guard reads only `estimateRequest`, `measureRequest`, and `isAgentError`, so a namespace
@@ -1995,6 +2078,6 @@ var StudyAgentJudge = class {
1995
2078
  }
1996
2079
  };
1997
2080
  //#endregion
1998
- export { STUDY_CAUSES, STUDY_ERROR_BRAND, STUDY_GOALS, STUDY_LAYERS, STUDY_NAME, STUDY_ROLES, STUDY_TRIALS, StudyAgentJudge, StudyAgentObserver, StudyControl, StudyDeadline, StudyError, StudyMeter, StudyRequest, StudyScope, StudySeries, buildStudyAgentDispatch, buildStudyAgentParts, buildStudySchedule, computeStudyAdmittedStop, computeStudyOutputStop, computeStudyScores, failStudyTicket, failureToStudyError, isStudyAgentRuntime, isStudyBounds, isStudyCause, isStudyCriterion, isStudyDefinition, isStudyDigest, isStudyError, isStudyErrorCode, isStudyGoal, isStudyGrading, isStudyJudgment, isStudyLabeling, isStudyLayer, isStudyLimit, isStudyName, isStudyProduct, isStudyProfile, isStudyProvider, isStudyRole, isStudyRoom, isStudyRubric, isStudyScenario, isStudyStop, isStudyTimestamp, isStudyUsage, matchesStudyKeys, normalizeStudyProfile, omitStudyAbsent, parseStudyDefinition, parseStudyGrading, parseStudyLabeling, parseStudyProfile, parseStudyReading, parseStudyRubric, parseStudyScenario, projectStudyAgentRequest, readStudyFailure, studyStopToJSON, translateStudyAgentError, translateStudyAgentStop, validateStudyAgentUsage, validateStudyInputs };
2081
+ export { STUDY_CAUSES, STUDY_ERROR_BRAND, STUDY_GOALS, STUDY_LAYERS, STUDY_NAME, STUDY_ROLES, STUDY_TRIALS, StudyAgentJudge, StudyAgentObserver, StudyControl, StudyDeadline, StudyError, StudyMeter, StudyRequest, StudyScope, StudySeries, buildStudyAgentDispatch, buildStudyAgentParts, buildStudySchedule, computeStudyAdmittedStop, computeStudyOutputStop, computeStudyScores, failStudyTicket, failureToStudyError, isStudyAgentGauge, isStudyAgentRuntime, isStudyBounds, isStudyCause, isStudyCriterion, isStudyDaemon, isStudyDefinition, isStudyDigest, isStudyError, isStudyErrorCode, isStudyGoal, isStudyGrading, isStudyJudgment, isStudyLabeling, isStudyLayer, isStudyLimit, isStudyName, isStudyProduct, isStudyProfile, isStudyProvider, isStudyRecord, isStudyRole, isStudyRoom, isStudyRubric, isStudyScenario, isStudyStop, isStudyTimestamp, isStudyUsage, matchesStudyKeys, normalizeStudyProfile, omitStudyAbsent, parseStudyDefinition, parseStudyGrading, parseStudyLabeling, parseStudyProfile, parseStudyReading, parseStudyRubric, parseStudyScenario, projectStudyAgentRequest, readStudyFailure, studyStopToJSON, translateStudyAgentError, translateStudyAgentStop, validateStudyAgentUsage, validateStudyInputs };
1999
2082
 
2000
2083
  //# sourceMappingURL=index.js.map