@unson/brainbase-mcp 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,424 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { executeJudgmentDAG } from './judgment-dag-runner.js';
3
+ import { computeJudgmentDAGRunArtifactId } from './judgment-dag-artifact-store.js';
4
+ export const JUDGMENT_DAG_OUTCOME_ATTACHMENT_VERSION = 'judgment-dag-outcome.v1';
5
+ export const JUDGMENT_DAG_EVALUATION_EVENT_SET_VERSION = 'judgment-dag-evaluation-event-set.v1';
6
+ const ARTIFACT_ID_PATTERN = /^sha256:[0-9a-f]{64}$/u;
7
+ export class JudgmentDAGReplayEvaluationError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.name = 'JudgmentDAGReplayEvaluationError';
12
+ this.code = code;
13
+ }
14
+ }
15
+ function fail(code, message) {
16
+ throw new JudgmentDAGReplayEvaluationError(code, message);
17
+ }
18
+ function isPlainObject(value) {
19
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
20
+ return false;
21
+ const prototype = Object.getPrototypeOf(value);
22
+ return prototype === Object.prototype || prototype === null;
23
+ }
24
+ function snapshotJSON(value, label, seen = new WeakSet()) {
25
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
26
+ return value;
27
+ if (typeof value === 'number') {
28
+ if (!Number.isFinite(value))
29
+ fail('invalid_request', `${label} must contain only finite numbers`);
30
+ return Object.is(value, -0) ? 0 : value;
31
+ }
32
+ if (typeof value !== 'object')
33
+ fail('invalid_request', `${label} must be JSON-compatible`);
34
+ if (seen.has(value))
35
+ fail('invalid_request', `${label} must not contain cycles`);
36
+ seen.add(value);
37
+ try {
38
+ if (Array.isArray(value)) {
39
+ const result = [];
40
+ for (let index = 0; index < value.length; index += 1) {
41
+ if (!Object.hasOwn(value, index))
42
+ fail('invalid_request', `${label} must not contain sparse arrays`);
43
+ result.push(snapshotJSON(value[index], `${label}[${index}]`, seen));
44
+ }
45
+ return result;
46
+ }
47
+ if (!isPlainObject(value))
48
+ fail('invalid_request', `${label} must contain only plain objects`);
49
+ const result = {};
50
+ for (const key of Object.keys(value).sort()) {
51
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
52
+ if (descriptor === undefined || !('value' in descriptor)) {
53
+ fail('invalid_request', `${label}.${key} must be a data property`);
54
+ }
55
+ result[key] = snapshotJSON(descriptor.value, `${label}.${key}`, seen);
56
+ }
57
+ return result;
58
+ }
59
+ finally {
60
+ seen.delete(value);
61
+ }
62
+ }
63
+ function canonicalJSON(value, label) {
64
+ return JSON.stringify(snapshotJSON(value, label));
65
+ }
66
+ function detached(value, label) {
67
+ return JSON.parse(canonicalJSON(value, label));
68
+ }
69
+ function deepFreeze(value) {
70
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
71
+ Object.freeze(value);
72
+ for (const child of Object.values(value))
73
+ deepFreeze(child);
74
+ }
75
+ return value;
76
+ }
77
+ function requireNonEmptyString(value, label) {
78
+ if (typeof value !== 'string' || value.trim().length === 0) {
79
+ fail('invalid_request', `${label} must be a non-empty string`);
80
+ }
81
+ return value;
82
+ }
83
+ function requireArtifactId(value, label) {
84
+ if (typeof value !== 'string' || !ARTIFACT_ID_PATTERN.test(value)) {
85
+ fail('invalid_request', `${label} must be a sha256 artifact ID`);
86
+ }
87
+ return value;
88
+ }
89
+ function contentId(value, label) {
90
+ return `sha256:${createHash('sha256').update(canonicalJSON(value, label), 'utf8').digest('hex')}`;
91
+ }
92
+ function detachedRecord(record, label) {
93
+ const snapshot = detached(record, label);
94
+ requireNonEmptyString(snapshot.run_id, `${label}.run_id`);
95
+ return deepFreeze(snapshot);
96
+ }
97
+ function verifiedArtifactRecord(artifactIdValue, recordValue, label) {
98
+ const artifactId = requireArtifactId(artifactIdValue, `${label}.artifact_id`);
99
+ const record = detachedRecord(recordValue, `${label}.record`);
100
+ let expectedArtifactId;
101
+ try {
102
+ expectedArtifactId = computeJudgmentDAGRunArtifactId(record);
103
+ }
104
+ catch {
105
+ fail('invalid_request', `${label}.record is not a valid run artifact record`);
106
+ }
107
+ if (artifactId !== expectedArtifactId) {
108
+ fail('artifact_mismatch', `${label}.artifact_id does not match the run record content`);
109
+ }
110
+ return { artifactId, record };
111
+ }
112
+ function requiredHistoricalVersions(record) {
113
+ const versions = new Map();
114
+ for (const entry of record.runner_versions) {
115
+ if (versions.has(entry.runner_type))
116
+ fail('invalid_request', 'historical runner versions must be unique');
117
+ versions.set(entry.runner_type, requireNonEmptyString(entry.version, 'historical runner version'));
118
+ }
119
+ return versions;
120
+ }
121
+ function captureRunnerRegistrations(request, dag, expectedVersions) {
122
+ try {
123
+ const runnersDescriptor = Object.getOwnPropertyDescriptor(request, 'runners');
124
+ if (runnersDescriptor === undefined || !('value' in runnersDescriptor) ||
125
+ !isPlainObject(runnersDescriptor.value)) {
126
+ fail('invalid_request', 'runners must be an own data property containing a plain object');
127
+ }
128
+ const runners = runnersDescriptor.value;
129
+ const captured = {};
130
+ const runnerTypes = [...new Set(dag.nodes.map((node) => node.runner_type))]
131
+ .sort((left, right) => left.localeCompare(right));
132
+ for (const runnerType of runnerTypes) {
133
+ const registrationDescriptor = Object.getOwnPropertyDescriptor(runners, runnerType);
134
+ if (registrationDescriptor === undefined || !('value' in registrationDescriptor) ||
135
+ !isPlainObject(registrationDescriptor.value)) {
136
+ fail('invalid_request', `runner ${runnerType} must be an own data property`);
137
+ }
138
+ const versionDescriptor = Object.getOwnPropertyDescriptor(registrationDescriptor.value, 'version');
139
+ const runDescriptor = Object.getOwnPropertyDescriptor(registrationDescriptor.value, 'run');
140
+ if (versionDescriptor === undefined || !('value' in versionDescriptor) ||
141
+ runDescriptor === undefined || !('value' in runDescriptor) ||
142
+ typeof runDescriptor.value !== 'function') {
143
+ fail('invalid_request', `runner ${runnerType} must expose version and run as data properties`);
144
+ }
145
+ const version = requireNonEmptyString(versionDescriptor.value, `runner ${runnerType} version`);
146
+ const expectedVersion = expectedVersions?.get(runnerType);
147
+ if (expectedVersion !== undefined && version !== expectedVersion) {
148
+ fail('runner_version_mismatch', `historical runner ${runnerType} must use recorded version ${expectedVersion}`);
149
+ }
150
+ captured[runnerType] = { version, run: runDescriptor.value };
151
+ }
152
+ return Object.freeze(captured);
153
+ }
154
+ catch (error) {
155
+ if (error instanceof JudgmentDAGReplayEvaluationError)
156
+ throw error;
157
+ fail('invalid_request', 'runner registrations could not be captured safely');
158
+ }
159
+ }
160
+ export async function replayJudgmentDAGRun(request) {
161
+ if (!isPlainObject(request) || !isPlainObject(request.source)) {
162
+ fail('invalid_request', 'replay request and source must be plain objects');
163
+ }
164
+ const { artifactId, record: sourceRecord } = verifiedArtifactRecord(request.source.artifact_id, request.source.record, 'source');
165
+ const replayRunId = requireNonEmptyString(request.replay_run_id, 'replay_run_id');
166
+ if (replayRunId === sourceRecord.run_id)
167
+ fail('invalid_request', 'replay_run_id must differ from source run_id');
168
+ if (request.mode !== 'historical' && request.mode !== 'candidate')
169
+ fail('invalid_request', 'mode is invalid');
170
+ let replayDAG;
171
+ let expectedVersions;
172
+ if (request.mode === 'historical') {
173
+ if (request.candidate_dag !== undefined)
174
+ fail('invalid_request', 'historical replay cannot provide candidate_dag');
175
+ expectedVersions = requiredHistoricalVersions(sourceRecord);
176
+ replayDAG = sourceRecord.dag;
177
+ }
178
+ else {
179
+ if (request.candidate_dag === undefined)
180
+ fail('invalid_request', 'candidate replay requires candidate_dag');
181
+ replayDAG = detached(request.candidate_dag, 'candidate_dag');
182
+ }
183
+ const capturedRunners = captureRunnerRegistrations(request, replayDAG, expectedVersions);
184
+ const record = await executeJudgmentDAG({
185
+ run_id: replayRunId,
186
+ dag: replayDAG,
187
+ input: sourceRecord.input,
188
+ runners: capturedRunners
189
+ });
190
+ return deepFreeze({
191
+ source_artifact_id: artifactId,
192
+ source_run_id: sourceRecord.run_id,
193
+ mode: request.mode,
194
+ record
195
+ });
196
+ }
197
+ function normalizedObservations(record, observations) {
198
+ if (!Array.isArray(observations) || observations.length === 0) {
199
+ fail('invalid_request', 'observations must be a non-empty array');
200
+ }
201
+ const nodeIds = new Set(record.nodes.map((node) => node.node_id));
202
+ const identities = new Set();
203
+ return observations.map((raw, index) => {
204
+ if (!isPlainObject(raw))
205
+ fail('invalid_request', `observations[${index}] must be a plain object`);
206
+ const metricId = requireNonEmptyString(raw.metric_id, `observations[${index}].metric_id`);
207
+ if (raw.scope !== 'run' && raw.scope !== 'node')
208
+ fail('invalid_request', 'observation scope is invalid');
209
+ if (typeof raw.value !== 'boolean' && (typeof raw.value !== 'number' || !Number.isFinite(raw.value))) {
210
+ fail('invalid_request', 'observation value must be boolean or a finite number');
211
+ }
212
+ let nodeId;
213
+ if (raw.scope === 'node') {
214
+ nodeId = requireNonEmptyString(raw.node_id, `observations[${index}].node_id`);
215
+ if (!nodeIds.has(nodeId))
216
+ fail('invalid_request', `observation references unknown node ${nodeId}`);
217
+ }
218
+ else if (raw.node_id !== undefined) {
219
+ fail('invalid_request', 'run observation cannot have node_id');
220
+ }
221
+ const identity = `${metricId}\u0000${raw.scope}\u0000${nodeId ?? ''}`;
222
+ if (identities.has(identity))
223
+ fail('invalid_request', 'observations must be unique by metric, scope, and node');
224
+ identities.add(identity);
225
+ return nodeId === undefined
226
+ ? { metric_id: metricId, scope: raw.scope, value: raw.value }
227
+ : { metric_id: metricId, scope: raw.scope, node_id: nodeId, value: raw.value };
228
+ });
229
+ }
230
+ export function createJudgmentDAGOutcomeAttachment(request) {
231
+ if (!isPlainObject(request))
232
+ fail('invalid_request', 'outcome request must be a plain object');
233
+ const { artifactId, record } = verifiedArtifactRecord(request.run_artifact_id, request.record, 'run_artifact');
234
+ const observations = normalizedObservations(record, request.observations);
235
+ const payload = {
236
+ attachment_version: JUDGMENT_DAG_OUTCOME_ATTACHMENT_VERSION,
237
+ run_artifact_id: artifactId,
238
+ run_id: record.run_id,
239
+ observations
240
+ };
241
+ return deepFreeze({ ...payload, attachment_id: contentId(payload, 'outcome attachment') });
242
+ }
243
+ function validateOutcome(outcome, artifactId, record, label) {
244
+ const snapshot = detached(outcome, label);
245
+ if (snapshot.attachment_version !== JUDGMENT_DAG_OUTCOME_ATTACHMENT_VERSION ||
246
+ snapshot.run_artifact_id !== artifactId || snapshot.run_id !== record.run_id) {
247
+ fail('outcome_mismatch', `${label} does not bind to its run artifact`);
248
+ }
249
+ const normalized = createJudgmentDAGOutcomeAttachment({
250
+ run_artifact_id: artifactId,
251
+ record,
252
+ observations: snapshot.observations
253
+ });
254
+ if (normalized.attachment_id !== snapshot.attachment_id) {
255
+ fail('outcome_mismatch', `${label} content identity is invalid`);
256
+ }
257
+ return normalized;
258
+ }
259
+ function normalizedEvaluationRun(value, label) {
260
+ if (!isPlainObject(value))
261
+ fail('invalid_request', `${label} must be a plain object`);
262
+ const { artifactId, record } = verifiedArtifactRecord(value.artifact_id, value.record, label);
263
+ const outcome = validateOutcome(value.outcome, artifactId, record, `${label}.outcome`);
264
+ return { artifact_id: artifactId, record, outcome };
265
+ }
266
+ export function createJudgmentDAGEvaluationEventSet(request) {
267
+ if (!isPlainObject(request) || !Array.isArray(request.events) || request.events.length === 0) {
268
+ fail('invalid_request', 'evaluation event set requires a non-empty events array');
269
+ }
270
+ const eventIds = new Set();
271
+ const events = request.events.map((event, index) => {
272
+ if (!isPlainObject(event))
273
+ fail('invalid_request', `events[${index}] must be a plain object`);
274
+ const typedEvent = event;
275
+ const eventId = requireNonEmptyString(typedEvent.event_id, `events[${index}].event_id`);
276
+ if (eventIds.has(eventId))
277
+ fail('invalid_request', `duplicate event_id ${eventId}`);
278
+ eventIds.add(eventId);
279
+ const baseline = normalizedEvaluationRun(typedEvent.baseline, `events[${index}].baseline`);
280
+ const candidate = normalizedEvaluationRun(typedEvent.candidate, `events[${index}].candidate`);
281
+ if (canonicalJSON(baseline.record.input, 'baseline input') !==
282
+ canonicalJSON(candidate.record.input, 'candidate input')) {
283
+ fail('context_mismatch', `event ${eventId} does not compare the same recorded context`);
284
+ }
285
+ return { event_id: eventId, baseline, candidate };
286
+ });
287
+ const payload = {
288
+ event_set_version: JUDGMENT_DAG_EVALUATION_EVENT_SET_VERSION,
289
+ events
290
+ };
291
+ return deepFreeze({
292
+ ...payload,
293
+ event_set_id: contentId(payload, 'evaluation event set'),
294
+ event_ids: events.map((event) => event.event_id)
295
+ });
296
+ }
297
+ function validateEventSet(eventSet) {
298
+ const rebuilt = createJudgmentDAGEvaluationEventSet({ events: eventSet.events });
299
+ if (eventSet.event_set_version !== JUDGMENT_DAG_EVALUATION_EVENT_SET_VERSION ||
300
+ eventSet.event_set_id !== rebuilt.event_set_id ||
301
+ canonicalJSON(eventSet.event_ids, 'event_ids') !== canonicalJSON(rebuilt.event_ids, 'event_ids')) {
302
+ fail('invalid_request', 'evaluation event set identity is invalid');
303
+ }
304
+ return rebuilt;
305
+ }
306
+ function validateCriterion(value) {
307
+ if (!isPlainObject(value) || !isPlainObject(value.scoring)) {
308
+ fail('invalid_request', 'criterion and scoring must be plain objects');
309
+ }
310
+ const criterionId = requireNonEmptyString(value.criterion_id, 'criterion_id');
311
+ const goal = requireNonEmptyString(value.goal, 'goal');
312
+ const metricId = requireNonEmptyString(value.metric_id, 'metric_id');
313
+ let scoring;
314
+ if (value.scoring.kind === 'numeric') {
315
+ if (value.scoring.direction !== 'higher_is_better' && value.scoring.direction !== 'lower_is_better') {
316
+ fail('invalid_request', 'numeric direction is invalid');
317
+ }
318
+ scoring = { kind: 'numeric', direction: value.scoring.direction };
319
+ }
320
+ else if (value.scoring.kind === 'pass_fail') {
321
+ if (!['eq', 'gte', 'lte'].includes(value.scoring.operator))
322
+ fail('invalid_request', 'pass_fail operator is invalid');
323
+ if (typeof value.scoring.target !== 'boolean' &&
324
+ (typeof value.scoring.target !== 'number' || !Number.isFinite(value.scoring.target))) {
325
+ fail('invalid_request', 'pass_fail target must be boolean or a finite number');
326
+ }
327
+ if (typeof value.scoring.target === 'boolean' && value.scoring.operator !== 'eq') {
328
+ fail('invalid_request', 'boolean pass_fail target only supports eq');
329
+ }
330
+ scoring = { kind: 'pass_fail', operator: value.scoring.operator, target: value.scoring.target };
331
+ }
332
+ else {
333
+ fail('invalid_request', 'scoring kind is invalid');
334
+ }
335
+ return deepFreeze({ criterion_id: criterionId, goal, metric_id: metricId, scoring });
336
+ }
337
+ function scoreValue(value, scoring) {
338
+ if (scoring.kind === 'numeric') {
339
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
340
+ fail('invalid_score', 'numeric scoring requires finite number observations');
341
+ }
342
+ return value;
343
+ }
344
+ if (scoring.operator === 'eq')
345
+ return value === scoring.target ? 1 : 0;
346
+ if (typeof value !== 'number' || typeof scoring.target !== 'number') {
347
+ fail('invalid_score', 'ordered pass_fail scoring requires number observations and target');
348
+ }
349
+ return scoring.operator === 'gte'
350
+ ? (value >= scoring.target ? 1 : 0)
351
+ : (value <= scoring.target ? 1 : 0);
352
+ }
353
+ function observation(attachment, metricId, scope, nodeId) {
354
+ return attachment.observations.find((item) => item.metric_id === metricId && item.scope === scope && item.node_id === nodeId);
355
+ }
356
+ function average(values) {
357
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
358
+ }
359
+ export function evaluateJudgmentDAGVersions(request) {
360
+ if (!isPlainObject(request))
361
+ fail('invalid_request', 'evaluation request must be a plain object');
362
+ const eventSet = validateEventSet(request.event_set);
363
+ const criterion = validateCriterion(request.criterion);
364
+ const baselineOverall = [];
365
+ const candidateOverall = [];
366
+ const allNodeIds = new Set();
367
+ for (const event of eventSet.events) {
368
+ const baselineRun = observation(event.baseline.outcome, criterion.metric_id, 'run');
369
+ const candidateRun = observation(event.candidate.outcome, criterion.metric_id, 'run');
370
+ if (baselineRun === undefined || candidateRun === undefined) {
371
+ fail('outcome_mismatch', `event ${event.event_id} is missing the run observation for ${criterion.metric_id}`);
372
+ }
373
+ baselineOverall.push(scoreValue(baselineRun.value, criterion.scoring));
374
+ candidateOverall.push(scoreValue(candidateRun.value, criterion.scoring));
375
+ for (const node of event.baseline.record.nodes)
376
+ allNodeIds.add(node.node_id);
377
+ for (const node of event.candidate.record.nodes)
378
+ allNodeIds.add(node.node_id);
379
+ }
380
+ const nodeCalibration = [...allNodeIds].sort().map((nodeId) => {
381
+ const baselineScores = [];
382
+ const candidateScores = [];
383
+ let baselinePresent = false;
384
+ let candidatePresent = false;
385
+ for (const event of eventSet.events) {
386
+ baselinePresent ||= event.baseline.record.nodes.some((node) => node.node_id === nodeId);
387
+ candidatePresent ||= event.candidate.record.nodes.some((node) => node.node_id === nodeId);
388
+ const baselineNode = observation(event.baseline.outcome, criterion.metric_id, 'node', nodeId);
389
+ const candidateNode = observation(event.candidate.outcome, criterion.metric_id, 'node', nodeId);
390
+ if (baselineNode !== undefined && candidateNode !== undefined) {
391
+ baselineScores.push(scoreValue(baselineNode.value, criterion.scoring));
392
+ candidateScores.push(scoreValue(candidateNode.value, criterion.scoring));
393
+ }
394
+ }
395
+ if (!baselinePresent) {
396
+ return { node_id: nodeId, status: 'candidate_only', baseline: null, candidate: null, delta: null, event_count: 0 };
397
+ }
398
+ if (!candidatePresent) {
399
+ return { node_id: nodeId, status: 'baseline_only', baseline: null, candidate: null, delta: null, event_count: 0 };
400
+ }
401
+ if (baselineScores.length === 0) {
402
+ return { node_id: nodeId, status: 'no_observation', baseline: null, candidate: null, delta: null, event_count: 0 };
403
+ }
404
+ const baseline = average(baselineScores);
405
+ const candidate = average(candidateScores);
406
+ return {
407
+ node_id: nodeId,
408
+ status: 'comparable',
409
+ baseline,
410
+ candidate,
411
+ delta: candidate - baseline,
412
+ event_count: baselineScores.length
413
+ };
414
+ });
415
+ const baseline = average(baselineOverall);
416
+ const candidate = average(candidateOverall);
417
+ return deepFreeze({
418
+ event_set_id: eventSet.event_set_id,
419
+ event_ids: eventSet.event_ids.slice(),
420
+ criterion,
421
+ overall: { baseline, candidate, delta: candidate - baseline, event_count: eventSet.events.length },
422
+ node_calibration: nodeCalibration
423
+ });
424
+ }
@@ -0,0 +1,70 @@
1
+ import { type JudgmentDAG, type JudgmentDAGMetadata, type JudgmentDAGNode, type JudgmentDAGRunnerType } from './judgment-dag-core.js';
2
+ /** A JSON-compatible value accepted at every local runner boundary. */
3
+ export type JudgmentDAGJSONValue = JudgmentDAGMetadata;
4
+ export interface JudgmentDAGRunnerInput {
5
+ readonly run_id: string;
6
+ readonly dag: JudgmentDAG;
7
+ readonly node: JudgmentDAGNode;
8
+ readonly input: JudgmentDAGJSONValue;
9
+ readonly dependency_outputs: readonly JudgmentDAGDependencyOutput[];
10
+ }
11
+ export interface JudgmentDAGDependencyOutput {
12
+ readonly node_id: string;
13
+ readonly output: JudgmentDAGJSONValue;
14
+ }
15
+ export interface JudgmentDAGRunnerRegistration {
16
+ readonly version: string;
17
+ readonly run: (input: JudgmentDAGRunnerInput) => JudgmentDAGJSONValue | Promise<JudgmentDAGJSONValue>;
18
+ }
19
+ export interface JudgmentDAGRunRequest {
20
+ readonly run_id: string;
21
+ readonly dag: JudgmentDAG;
22
+ readonly input: JudgmentDAGJSONValue;
23
+ readonly runners: Partial<Record<JudgmentDAGRunnerType, JudgmentDAGRunnerRegistration>>;
24
+ }
25
+ export interface JudgmentDAGRunnerVersion {
26
+ readonly runner_type: JudgmentDAGRunnerType;
27
+ readonly version: string;
28
+ }
29
+ export interface JudgmentDAGNodeRunRecord {
30
+ readonly node_id: string;
31
+ readonly runner_type: JudgmentDAGRunnerType;
32
+ readonly runner_version: string;
33
+ readonly input_contract: string;
34
+ readonly output_contract: string;
35
+ readonly input: JudgmentDAGJSONValue;
36
+ readonly dependency_outputs: readonly JudgmentDAGDependencyOutput[];
37
+ readonly output: JudgmentDAGJSONValue;
38
+ }
39
+ export interface JudgmentDAGRunRecord {
40
+ readonly run_id: string;
41
+ readonly dag: JudgmentDAG;
42
+ readonly input: JudgmentDAGJSONValue;
43
+ readonly execution_order: readonly string[];
44
+ readonly runner_versions: readonly JudgmentDAGRunnerVersion[];
45
+ readonly nodes: readonly JudgmentDAGNodeRunRecord[];
46
+ }
47
+ export type JudgmentDAGExecutionCode = 'invalid_request' | 'missing_runner' | 'invalid_runner' | 'invalid_json' | 'runner_failed';
48
+ export type JudgmentDAGRunnerFailureKind = 'sync_throw' | 'async_reject';
49
+ export interface JudgmentDAGExecutionErrorDetails {
50
+ readonly node_id?: string;
51
+ readonly node_type?: JudgmentDAGNode['node_type'];
52
+ readonly runner_type?: JudgmentDAGRunnerType;
53
+ readonly failure_kind?: JudgmentDAGRunnerFailureKind;
54
+ readonly cause?: unknown;
55
+ }
56
+ export declare class JudgmentDAGExecutionError extends Error {
57
+ readonly code: JudgmentDAGExecutionCode;
58
+ readonly node_id?: string;
59
+ readonly node_type?: JudgmentDAGNode['node_type'];
60
+ readonly runner_type?: JudgmentDAGRunnerType;
61
+ readonly failure_kind?: JudgmentDAGRunnerFailureKind;
62
+ readonly cause?: unknown;
63
+ constructor(code: JudgmentDAGExecutionCode, message: string, details?: JudgmentDAGExecutionErrorDetails);
64
+ }
65
+ /**
66
+ * Execute a validated local DAG sequentially using only explicit runner
67
+ * registrations. No clock, randomness, filesystem, network, or persistence
68
+ * is consulted by this function.
69
+ */
70
+ export declare function executeJudgmentDAG(request: JudgmentDAGRunRequest): Promise<JudgmentDAGRunRecord>;