@voce-engine/core 0.1.0-rc.1

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/dist/m5.js ADDED
@@ -0,0 +1,2433 @@
1
+ import { ARTIFACT_REPLAY_RESULT_SCHEMA_VERSION, CLEANUP_RECEIPT_SCHEMA_VERSION, COMPENSATION_RECEIPT_SCHEMA_VERSION, EXECUTION_RUN_SCHEMA_VERSION, EXECUTION_TRACE_SCHEMA_VERSION, EVALUATION_SCHEMA_VERSION, HUMAN_ACCEPTANCE_SCHEMA_VERSION, PIPELINE_PLAN_SCHEMA_VERSION, PROMPT_CANDIDATE_IR_SCHEMA_VERSION, PROMPT_COMPILATION_INPUT_SCHEMA_VERSION, PROMPT_CONSTRAINT_COVERAGE_SCHEMA_VERSION, PROMPT_GUARD_FINDING_SCHEMA_VERSION, PROMPT_GUARD_RESULT_SCHEMA_VERSION, PROMPT_IR_SCHEMA_VERSION, PROMPT_OPTIMIZATION_INPUT_SCHEMA_VERSION, PROMPT_PARAMETER_SCHEMA_VERSION, PROMPT_REFERENCE_MAPPING_SCHEMA_VERSION, PROMPT_SECTION_SCHEMA_VERSION, PROMPT_TRANSFORMATION_SCHEMA_VERSION, PROVIDER_RENDER_REQUEST_SCHEMA_VERSION, PROVIDER_RENDER_RESULT_SCHEMA_VERSION, REMOTE_CALL_RUN_SCHEMA_VERSION, STEP_EVENT_SCHEMA_VERSION, STEP_RECEIPT_SCHEMA_VERSION, } from '@voce-engine/contracts';
2
+ import { computeBudgetHash, computeCompilationContextHash, computeConstraintConflictHash, computeConstraintDependencyHash, computeConstraintHash, computeConstraintIRSignature, computeDataTransferHash, computeDegradationHash, computeExecutionAuthorizationHash, computeGoalHash, computeOutputContractHash, computePipelinePlanHash, computePipelineStepHash, computeReferenceDependencyHash, computeReferenceOmissionHash, computeReferencePlanHash, computeRemoteCallAuthorizationHash, computeResourceClaimHash, computeReviewRequirementHash, computeRuleTraceHash, dispatchPreflight, } from './m4.js';
3
+ import { canonicalize, sha256 } from './canonical.js';
4
+ export const PROMPT_COMPILER_VERSION = 'voce.prompt-compiler/v1alpha1';
5
+ export const PROMPT_OPTIMIZER_VERSION = 'voce.deterministic-prompt-optimizer/v1alpha1';
6
+ export const PROMPT_GUARD_VERSION = 'voce.prompt-guard/v1alpha1';
7
+ export const MOCK_PROVIDER_ADAPTER_VERSION = 'voce.mock-provider-adapter/v1alpha1';
8
+ export const EXECUTION_RUNTIME_VERSION = 'voce.offline-execution-runtime/v1alpha1';
9
+ export const FIXED_M5_TIME = '2026-01-01T00:00:00.000Z';
10
+ const HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
11
+ function compareCodeUnits(left, right) {
12
+ const length = Math.min(left.length, right.length);
13
+ for (let index = 0; index < length; index += 1) {
14
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
15
+ if (difference !== 0)
16
+ return difference;
17
+ }
18
+ return left.length - right.length;
19
+ }
20
+ function jsonReady(value) {
21
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
22
+ return value;
23
+ if (typeof value === 'number') {
24
+ if (!Number.isFinite(value))
25
+ throw new Error('JSON_VALUE_INVALID');
26
+ return value;
27
+ }
28
+ if (Array.isArray(value))
29
+ return value.map((item) => jsonReady(item === undefined ? null : item));
30
+ if (value && typeof value === 'object') {
31
+ const object = {};
32
+ for (const [key, item] of Object.entries(value)) {
33
+ if (item !== undefined)
34
+ object[key] = jsonReady(item);
35
+ }
36
+ return object;
37
+ }
38
+ throw new Error('JSON_VALUE_INVALID');
39
+ }
40
+ function clone(value) {
41
+ return JSON.parse(JSON.stringify(jsonReady(value)));
42
+ }
43
+ function sortedStrings(values) {
44
+ return [...new Set(values ?? [])].sort(compareCodeUnits);
45
+ }
46
+ function sortedBy(values, key) {
47
+ return values.map((value) => clone(value)).sort((left, right) => compareCodeUnits(key(left), key(right)) || compareCodeUnits(canonicalize(jsonReady(left)), canonicalize(jsonReady(right))));
48
+ }
49
+ function isHash(value) {
50
+ return typeof value === 'string' && HASH_PATTERN.test(value);
51
+ }
52
+ function hashId(prefix, value) {
53
+ return `${prefix}-${sha256(jsonReady(value)).slice('sha256:'.length, 'sha256:'.length + 24)}`;
54
+ }
55
+ function objectOf(value) {
56
+ const ready = jsonReady(value);
57
+ return ready !== null && typeof ready === 'object' && !Array.isArray(ready) ? ready : {};
58
+ }
59
+ function without(value, field) {
60
+ const result = objectOf(value);
61
+ delete result[field];
62
+ return result;
63
+ }
64
+ function semanticHash(value, field) {
65
+ return sha256(without(value, field));
66
+ }
67
+ function promptSectionProjection(section) {
68
+ return jsonReady({
69
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
70
+ id: section.id,
71
+ kind: section.kind,
72
+ priority: section.priority,
73
+ order: section.order,
74
+ content: section.content,
75
+ ...(section.text === undefined ? {} : { text: section.text }),
76
+ constraintIds: sortedStrings(section.constraintIds),
77
+ sourceIds: sortedStrings(section.sourceIds),
78
+ decisionIds: sortedStrings(section.decisionIds),
79
+ assetIds: sortedStrings(section.assetIds),
80
+ importance: section.importance,
81
+ mutability: section.mutability,
82
+ ...(section.locked === undefined ? {} : { locked: section.locked }),
83
+ ...(section.slotId === undefined ? {} : { slotId: section.slotId }),
84
+ });
85
+ }
86
+ function promptParameterProjection(parameter) {
87
+ return jsonReady({
88
+ schemaVersion: PROMPT_PARAMETER_SCHEMA_VERSION,
89
+ id: parameter.id,
90
+ name: parameter.name,
91
+ value: clone(parameter.value),
92
+ valueType: parameter.valueType,
93
+ required: parameter.required,
94
+ mutability: parameter.mutability,
95
+ ...(parameter.bounds === undefined ? {} : { bounds: clone(parameter.bounds) }),
96
+ constraintIds: sortedStrings(parameter.constraintIds),
97
+ sourceIds: sortedStrings(parameter.sourceIds),
98
+ decisionIds: sortedStrings(parameter.decisionIds),
99
+ ...(parameter.provenance === undefined ? {} : { provenance: clone(parameter.provenance) }),
100
+ ...(parameter.type === undefined ? {} : { type: parameter.type }),
101
+ ...(parameter.minimum === undefined ? {} : { minimum: parameter.minimum }),
102
+ ...(parameter.maximum === undefined ? {} : { maximum: parameter.maximum }),
103
+ ...(parameter.allowedValues === undefined ? {} : { allowedValues: clone(parameter.allowedValues) }),
104
+ });
105
+ }
106
+ function promptReferenceMappingProjection(mapping) {
107
+ return jsonReady({
108
+ schemaVersion: PROMPT_REFERENCE_MAPPING_SCHEMA_VERSION,
109
+ id: mapping.id,
110
+ plannedReferenceId: mapping.plannedReferenceId,
111
+ ...(mapping.referenceId === undefined ? {} : { referenceId: mapping.referenceId }),
112
+ assetId: mapping.assetId,
113
+ contentHash: mapping.contentHash,
114
+ label: mapping.label,
115
+ role: mapping.role,
116
+ order: mapping.order,
117
+ required: mapping.required,
118
+ constraintIds: sortedStrings(mapping.constraintIds),
119
+ sourceBindingIds: sortedStrings(mapping.sourceBindingIds),
120
+ decisionIds: sortedStrings(mapping.decisionIds),
121
+ });
122
+ }
123
+ function promptCoverageProjection(coverage) {
124
+ return jsonReady({
125
+ schemaVersion: PROMPT_CONSTRAINT_COVERAGE_SCHEMA_VERSION,
126
+ constraintId: coverage.constraintId,
127
+ sectionIds: sortedStrings(coverage.sectionIds),
128
+ parameterIds: sortedStrings(coverage.parameterIds),
129
+ referenceMappingIds: sortedStrings(coverage.referenceMappingIds),
130
+ locked: coverage.locked,
131
+ });
132
+ }
133
+ function normalizedPromptIRProjection(prompt) {
134
+ return jsonReady({
135
+ schemaVersion: PROMPT_IR_SCHEMA_VERSION,
136
+ id: prompt.id,
137
+ caseId: prompt.caseId,
138
+ caseRevision: prompt.caseRevision,
139
+ contextHash: prompt.contextHash,
140
+ compilationSignature: prompt.compilationSignature,
141
+ constraintIRHash: prompt.constraintIRHash,
142
+ referencePlanHash: prompt.referencePlanHash,
143
+ pipelinePlanHash: prompt.pipelinePlanHash,
144
+ outputContractHash: prompt.outputContractHash,
145
+ targetAdapter: clone(prompt.targetAdapter),
146
+ targetCapabilityProfile: clone(prompt.targetCapabilityProfile),
147
+ objective: prompt.objective,
148
+ positiveDescription: prompt.positiveDescription,
149
+ sections: [...prompt.sections].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map(promptSectionProjection),
150
+ parameters: sortedBy(prompt.parameters, (item) => item.id).map(promptParameterProjection),
151
+ referenceMappings: [...prompt.referenceMappings].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map(promptReferenceMappingProjection),
152
+ forbidden: sortedBy(prompt.forbidden, (item) => item.id),
153
+ output: clone(prompt.output),
154
+ constraintCoverage: sortedBy(prompt.constraintCoverage, (item) => item.constraintId).map(promptCoverageProjection),
155
+ sourceIds: sortedStrings(prompt.sourceIds),
156
+ constraintIds: sortedStrings(prompt.constraintIds),
157
+ decisionIds: sortedStrings(prompt.decisionIds),
158
+ assetIds: sortedStrings(prompt.assetIds),
159
+ });
160
+ }
161
+ export function computePromptIRHash(prompt) {
162
+ return sha256(normalizedPromptIRProjection(prompt));
163
+ }
164
+ export const computePromptIRSignature = computePromptIRHash;
165
+ function transformationProjection(transformation) {
166
+ const value = clone(transformation);
167
+ if (value.schemaVersion === undefined)
168
+ value.schemaVersion = PROMPT_TRANSFORMATION_SCHEMA_VERSION;
169
+ if (Array.isArray(value.sectionIds))
170
+ value.sectionIds = sortedStrings(value.sectionIds);
171
+ if (Array.isArray(value.constraintIds))
172
+ value.constraintIds = sortedStrings(value.constraintIds);
173
+ if (Array.isArray(value.sourceIds))
174
+ value.sourceIds = sortedStrings(value.sourceIds);
175
+ if (value.proof && typeof value.proof === 'object') {
176
+ const proof = value.proof;
177
+ if (Array.isArray(proof.preservedConstraintIds))
178
+ proof.preservedConstraintIds = sortedStrings(proof.preservedConstraintIds);
179
+ }
180
+ return value;
181
+ }
182
+ export function computePromptTransformationHash(transformation) {
183
+ return sha256(transformationProjection(transformation));
184
+ }
185
+ function candidateSections(candidate) {
186
+ return candidate.sections ?? candidate.candidateSections ?? [];
187
+ }
188
+ function candidateParameters(candidate) {
189
+ if (candidate.parameters)
190
+ return candidate.parameters;
191
+ return Object.entries(candidate.requestParameters ?? {}).map(([name, value], index) => ({
192
+ schemaVersion: PROMPT_PARAMETER_SCHEMA_VERSION,
193
+ id: `parameter-${name || index}`,
194
+ name,
195
+ value,
196
+ valueType: typeof value === 'number' && Number.isInteger(value) ? 'integer' : typeof value,
197
+ required: false,
198
+ mutability: 'rephraseable',
199
+ constraintIds: [],
200
+ sourceIds: [],
201
+ decisionIds: [],
202
+ }));
203
+ }
204
+ function candidateCoverage(candidate) {
205
+ if (candidate.constraintCoverage)
206
+ return candidate.constraintCoverage;
207
+ return (candidate.coverageClaims ?? []).map((claim) => ({
208
+ schemaVersion: PROMPT_CONSTRAINT_COVERAGE_SCHEMA_VERSION,
209
+ constraintId: claim.constraintId,
210
+ sectionIds: claim.sectionIds,
211
+ parameterIds: claim.parameterIds,
212
+ referenceMappingIds: claim.referenceMappingIds,
213
+ locked: false,
214
+ }));
215
+ }
216
+ function normalizedPromptCandidateProjection(candidate) {
217
+ const sections = candidateSections(candidate);
218
+ const parameters = candidateParameters(candidate);
219
+ const coverage = candidateCoverage(candidate);
220
+ return jsonReady({
221
+ schemaVersion: PROMPT_CANDIDATE_IR_SCHEMA_VERSION,
222
+ id: candidate.id,
223
+ basePromptIRHash: candidate.basePromptIRHash,
224
+ ...(candidate.basePromptIRSignature === undefined ? {} : { basePromptIRSignature: candidate.basePromptIRSignature }),
225
+ targetAdapter: clone(candidate.targetAdapter),
226
+ targetCapabilityProfile: clone(candidate.targetCapabilityProfile),
227
+ targetAdapterDigest: candidate.targetAdapterDigest,
228
+ targetProfileDigest: candidate.targetProfileDigest,
229
+ sections: sections.map(promptSectionProjection),
230
+ parameters: sortedBy(parameters, (item) => item.id).map(promptParameterProjection),
231
+ referenceMappings: [...candidate.referenceMappings].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map(promptReferenceMappingProjection),
232
+ constraintCoverage: sortedBy(coverage, (item) => item.constraintId).map(promptCoverageProjection),
233
+ transformations: candidate.transformations.map(transformationProjection),
234
+ optimizer: clone(candidate.optimizer),
235
+ mode: candidate.mode,
236
+ warnings: sortedStrings(candidate.warnings),
237
+ ...(candidate.candidateSections === undefined ? {} : { candidateSections: candidate.candidateSections.map(promptSectionProjection) }),
238
+ ...(candidate.requestParameters === undefined ? {} : { requestParameters: clone(candidate.requestParameters) }),
239
+ ...(candidate.coverageClaims === undefined ? {} : { coverageClaims: clone(candidate.coverageClaims) }),
240
+ });
241
+ }
242
+ export function computePromptCandidateHash(candidate) {
243
+ return sha256(normalizedPromptCandidateProjection(candidate));
244
+ }
245
+ export function computePromptGuardResultHash(result) {
246
+ const value = clone(result);
247
+ delete value.resultHash;
248
+ return sha256(jsonReady(value));
249
+ }
250
+ function promptFinding(value) {
251
+ const normalized = {
252
+ schemaVersion: PROMPT_GUARD_FINDING_SCHEMA_VERSION,
253
+ ...value,
254
+ constraintIds: sortedStrings(value.constraintIds),
255
+ sourceIds: sortedStrings(value.sourceIds),
256
+ sectionIds: sortedStrings(value.sectionIds),
257
+ decisionIds: sortedStrings(value.decisionIds),
258
+ assetIds: sortedStrings(value.assetIds),
259
+ };
260
+ return clone({ ...normalized, id: hashId('prompt-finding', normalized) });
261
+ }
262
+ function promptSectionHash(section) {
263
+ return sha256(promptSectionProjection(section));
264
+ }
265
+ function lockedPromptSectionProjection(section) {
266
+ const value = promptSectionProjection(section);
267
+ delete value.order;
268
+ return value;
269
+ }
270
+ function promptSectionMetadataProjection(section) {
271
+ const value = promptSectionProjection(section);
272
+ delete value.content;
273
+ delete value.text;
274
+ delete value.order;
275
+ return value;
276
+ }
277
+ function promptParameterContractProjection(parameter) {
278
+ const value = promptParameterProjection(parameter);
279
+ delete value.value;
280
+ return value;
281
+ }
282
+ function outputParameter(id, name, value, valueType, constraintIds, bounds) {
283
+ return {
284
+ schemaVersion: PROMPT_PARAMETER_SCHEMA_VERSION,
285
+ id,
286
+ name,
287
+ value: clone(value),
288
+ valueType,
289
+ required: true,
290
+ mutability: 'locked',
291
+ ...(bounds ? { bounds: clone(bounds) } : {}),
292
+ constraintIds: sortedStrings(constraintIds),
293
+ sourceIds: [],
294
+ decisionIds: [],
295
+ type: valueType,
296
+ ...(bounds?.minimum === undefined ? {} : { minimum: bounds.minimum }),
297
+ ...(bounds?.maximum === undefined ? {} : { maximum: bounds.maximum }),
298
+ ...(bounds?.allowedValues === undefined ? {} : { allowedValues: clone(bounds.allowedValues) }),
299
+ };
300
+ }
301
+ function constraintText(constraint) {
302
+ const value = constraint.value === undefined ? '' : ` value=${canonicalize(constraint.value)}`;
303
+ return `${constraint.predicate} ${constraint.targetPath ?? constraint.targetPaths.join(',')}${value}. ${constraint.explanation}`;
304
+ }
305
+ function targetPinFromProfile(profile) {
306
+ return { id: profile.id, version: profile.version, digest: profile.profileHash };
307
+ }
308
+ function profileAdapterPin(profile) {
309
+ return { id: profile.adapterId, version: profile.version, digest: profile.adapterDigest ?? sha256({ adapterId: profile.adapterId, version: profile.version }) };
310
+ }
311
+ function profilePinLike(value, fallback) {
312
+ return clone(value ?? fallback);
313
+ }
314
+ function integrityReasonsForConstraintIR(ir, context, caseId, caseRevision) {
315
+ const reasons = [];
316
+ if (!ir || ir.schemaVersion !== 'voce.constraint-ir/v1alpha1' || ir.status !== 'ok')
317
+ reasons.push('CONSTRAINT_IR_NOT_OK');
318
+ if (ir.caseId !== caseId || ir.caseRevision !== caseRevision || ir.contextHash !== context.contextHash)
319
+ reasons.push('CONSTRAINT_CONTEXT_MISMATCH');
320
+ if (!isHash(ir.deterministicSignature) || computeConstraintIRSignature(ir) !== ir.deterministicSignature)
321
+ reasons.push('CONSTRAINT_IR_SIGNATURE_MISMATCH');
322
+ for (const item of ir.goals)
323
+ if (!isHash(item.goalHash) || computeGoalHash(item) !== item.goalHash)
324
+ reasons.push('GOAL_HASH_MISMATCH');
325
+ for (const item of ir.constraints)
326
+ if (!isHash(item.constraintHash) || computeConstraintHash(item) !== item.constraintHash)
327
+ reasons.push('CONSTRAINT_HASH_MISMATCH');
328
+ for (const item of ir.dependencies)
329
+ if (!isHash(item.dependencyHash) || computeConstraintDependencyHash(item) !== item.dependencyHash)
330
+ reasons.push('CONSTRAINT_DEPENDENCY_HASH_MISMATCH');
331
+ for (const item of ir.resourceClaims)
332
+ if (!isHash(item.resourceHash) || computeResourceClaimHash(item) !== item.resourceHash)
333
+ reasons.push('RESOURCE_CLAIM_HASH_MISMATCH');
334
+ for (const item of ir.conflicts)
335
+ if (!isHash(item.conflictHash) || computeConstraintConflictHash(item) !== item.conflictHash)
336
+ reasons.push('CONSTRAINT_CONFLICT_HASH_MISMATCH');
337
+ for (const item of ir.degradedPreferences)
338
+ if (!isHash(item.degradationHash) || computeDegradationHash(item) !== item.degradationHash)
339
+ reasons.push('DEGRADATION_HASH_MISMATCH');
340
+ for (const item of ir.reviewRequirements)
341
+ if (!isHash(item.reviewHash) || computeReviewRequirementHash(item) !== item.reviewHash)
342
+ reasons.push('REVIEW_REQUIREMENT_HASH_MISMATCH');
343
+ for (const item of ir.ruleTraces)
344
+ if (!isHash(item.traceHash) || computeRuleTraceHash(item) !== item.traceHash)
345
+ reasons.push('RULE_TRACE_HASH_MISMATCH');
346
+ return sortedStrings(reasons);
347
+ }
348
+ function integrityReasonsForReferencePlan(plan, ir, caseId, caseRevision, contextHash) {
349
+ const reasons = [];
350
+ if (!plan || plan.schemaVersion !== 'voce.reference-plan/v1alpha1' || plan.status !== 'ok')
351
+ reasons.push('REFERENCE_PLAN_NOT_OK');
352
+ if (plan.caseId !== caseId || plan.caseRevision !== caseRevision || plan.contextHash !== contextHash)
353
+ reasons.push('REFERENCE_PLAN_CONTEXT_MISMATCH');
354
+ if (plan.constraintSignature !== ir.deterministicSignature)
355
+ reasons.push('REFERENCE_CONSTRAINT_SIGNATURE_MISMATCH');
356
+ if (!isHash(plan.planHash) || computeReferencePlanHash(plan) !== plan.planHash)
357
+ reasons.push('REFERENCE_PLAN_HASH_MISMATCH');
358
+ if (!isHash(plan.profileDigest))
359
+ reasons.push('REFERENCE_PROFILE_DIGEST_MISSING');
360
+ for (const item of plan.dependencies)
361
+ if (!isHash(item.dependencyHash) || computeReferenceDependencyHash(item) !== item.dependencyHash)
362
+ reasons.push('REFERENCE_DEPENDENCY_HASH_MISMATCH');
363
+ for (const item of [...plan.omitted, ...plan.blockedReferences])
364
+ if (!isHash(item.omissionHash) || computeReferenceOmissionHash(item) !== item.omissionHash)
365
+ reasons.push('REFERENCE_OMISSION_HASH_MISMATCH');
366
+ return sortedStrings(reasons);
367
+ }
368
+ function integrityReasonsForPipelinePlan(plan, ir, refs, output, caseId, caseRevision, contextHash) {
369
+ const reasons = [];
370
+ if (!plan || plan.schemaVersion !== PIPELINE_PLAN_SCHEMA_VERSION || plan.status !== 'ok')
371
+ reasons.push('PIPELINE_PLAN_NOT_OK');
372
+ if (plan.caseId !== caseId || plan.caseRevision !== caseRevision || plan.contextHash !== contextHash)
373
+ reasons.push('PIPELINE_PLAN_CONTEXT_MISMATCH');
374
+ if (plan.constraintSignature !== ir.deterministicSignature)
375
+ reasons.push('PIPELINE_CONSTRAINT_SIGNATURE_MISMATCH');
376
+ if (plan.referencePlanHash !== refs.planHash)
377
+ reasons.push('PIPELINE_REFERENCE_PLAN_HASH_MISMATCH');
378
+ if (plan.outputContractHash !== computeOutputContractHash(output))
379
+ reasons.push('PIPELINE_OUTPUT_CONTRACT_HASH_MISMATCH');
380
+ if (!isHash(plan.planHash) || computePipelinePlanHash(plan) !== plan.planHash)
381
+ reasons.push('PIPELINE_PLAN_HASH_MISMATCH');
382
+ for (const step of plan.steps)
383
+ if (!isHash(step.stepHash) || computePipelineStepHash(step) !== step.stepHash)
384
+ reasons.push('PIPELINE_STEP_HASH_MISMATCH');
385
+ return sortedStrings(reasons);
386
+ }
387
+ function outputContractReasons(output) {
388
+ const reasons = [];
389
+ if (!output || !Array.isArray(output.mediaTypes) || output.mediaTypes.length === 0)
390
+ reasons.push('OUTPUT_MEDIA_TYPES_INVALID');
391
+ if (!output?.cardinality || !Number.isInteger(output.cardinality.min) || !Number.isInteger(output.cardinality.max) || output.cardinality.min < 0 || output.cardinality.max < output.cardinality.min)
392
+ reasons.push('OUTPUT_CARDINALITY_INVALID');
393
+ if (output?.dimensions && (!Number.isInteger(output.dimensions.width) || !Number.isInteger(output.dimensions.height) || output.dimensions.width <= 0 || output.dimensions.height <= 0))
394
+ reasons.push('OUTPUT_DIMENSIONS_INVALID');
395
+ if (output?.maxBytes !== undefined && (!Number.isInteger(output.maxBytes) || output.maxBytes < 0))
396
+ reasons.push('OUTPUT_BYTES_INVALID');
397
+ if (output?.background === 'transparent' && output.allowAlpha === false)
398
+ reasons.push('OUTPUT_ALPHA_CONTRACT_CONFLICT');
399
+ return sortedStrings(reasons);
400
+ }
401
+ function promptCompilationInputReasons(input) {
402
+ const reasons = [];
403
+ if (!input || input.schemaVersion !== PROMPT_COMPILATION_INPUT_SCHEMA_VERSION)
404
+ reasons.push('PROMPT_COMPILATION_INPUT_SCHEMA_INVALID');
405
+ if (!input || typeof input.caseId !== 'string' || !Number.isInteger(input.caseRevision))
406
+ reasons.push('PROMPT_CASE_INVALID');
407
+ if (!input.context || input.context.caseSpecId !== input.caseId || input.context.caseSpecRevision !== input.caseRevision)
408
+ reasons.push('PROMPT_CONTEXT_CASE_MISMATCH');
409
+ if (!input.context || !isHash(input.contextHash) || input.context.contextHash !== input.contextHash || computeCompilationContextHash(input.context) !== input.contextHash)
410
+ reasons.push('PROMPT_CONTEXT_HASH_MISMATCH');
411
+ if (!input.constraintIR || integrityReasonsForConstraintIR(input.constraintIR, input.context, input.caseId, input.caseRevision).length)
412
+ reasons.push('CONSTRAINT_IR_INVALID');
413
+ if (!input.referencePlan || integrityReasonsForReferencePlan(input.referencePlan, input.constraintIR, input.caseId, input.caseRevision, input.contextHash).length)
414
+ reasons.push('REFERENCE_PLAN_INVALID');
415
+ if (!input.pipelinePlan || integrityReasonsForPipelinePlan(input.pipelinePlan, input.constraintIR, input.referencePlan, input.outputContract, input.caseId, input.caseRevision, input.contextHash).length)
416
+ reasons.push('PIPELINE_PLAN_INVALID');
417
+ if (!input.outputContract || outputContractReasons(input.outputContract).length || computeOutputContractHash(input.outputContract) !== input.pipelinePlan?.outputContractHash)
418
+ reasons.push('OUTPUT_CONTRACT_INVALID');
419
+ if (!input.targetAdapter || !isHash(input.targetAdapter.digest))
420
+ reasons.push('TARGET_ADAPTER_INVALID');
421
+ if (!input.targetCapabilityProfile || !isHash(input.targetCapabilityProfile.digest))
422
+ reasons.push('TARGET_PROFILE_INVALID');
423
+ if (input.pipelinePlan && input.pipelinePlan.profileDigest !== input.targetCapabilityProfile?.digest)
424
+ reasons.push('TARGET_PROFILE_PLAN_MISMATCH');
425
+ if (input.pipelinePlan && !input.pipelinePlan.adapterDigests.includes(input.targetAdapter?.digest ?? ''))
426
+ reasons.push('TARGET_ADAPTER_PLAN_MISMATCH');
427
+ return sortedStrings(reasons);
428
+ }
429
+ function sectionForConstraint(constraint, order, decisionIds) {
430
+ const locked = constraint.importance === 'hard' || constraint.importance === 'required' || constraint.kind === 'output';
431
+ return {
432
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
433
+ id: hashId('prompt-section', { kind: constraint.importance, constraintId: constraint.id, order }),
434
+ kind: constraint.importance === 'hard' ? 'hard_constraint' : constraint.importance === 'required' ? 'required_constraint' : 'preferred',
435
+ priority: constraint.importance === 'hard' ? 100 : constraint.importance === 'required' ? 80 : 40,
436
+ order,
437
+ content: constraintText(constraint),
438
+ text: constraintText(constraint),
439
+ constraintIds: [constraint.id],
440
+ sourceIds: sortedStrings(constraint.sourceIds),
441
+ decisionIds: sortedStrings(decisionIds),
442
+ assetIds: [],
443
+ importance: constraint.importance,
444
+ mutability: locked ? 'locked' : 'rephraseable',
445
+ locked,
446
+ };
447
+ }
448
+ function referenceMapping(reference, constraints, decisionIds) {
449
+ const required = reference.constraintIds.some((id) => constraints.find((constraint) => constraint.id === id)?.importance !== 'preferred') || reference.sourceBindingIds.length > 0;
450
+ return {
451
+ schemaVersion: PROMPT_REFERENCE_MAPPING_SCHEMA_VERSION,
452
+ id: hashId('prompt-reference-mapping', { plannedReferenceId: reference.id, assetId: reference.assetId, contentHash: reference.contentHash, order: reference.order }),
453
+ plannedReferenceId: reference.id,
454
+ referenceId: reference.id,
455
+ assetId: reference.assetId,
456
+ contentHash: reference.contentHash,
457
+ label: reference.label,
458
+ role: reference.role,
459
+ order: reference.order,
460
+ required,
461
+ constraintIds: sortedStrings(reference.constraintIds),
462
+ sourceBindingIds: sortedStrings(reference.sourceBindingIds),
463
+ decisionIds: sortedStrings(decisionIds),
464
+ };
465
+ }
466
+ function promptParameterValues(output, constraints) {
467
+ const outputIds = constraints.filter((constraint) => constraint.kind === 'output').map((constraint) => constraint.id);
468
+ const parameters = [];
469
+ if (output.dimensions) {
470
+ parameters.push(outputParameter('output-width', 'width', output.dimensions.width, 'integer', outputIds, { type: 'integer', minimum: 1, maximum: output.dimensions.width }));
471
+ parameters.push(outputParameter('output-height', 'height', output.dimensions.height, 'integer', outputIds, { type: 'integer', minimum: 1, maximum: output.dimensions.height }));
472
+ }
473
+ if (output.mediaTypes.length)
474
+ parameters.push(outputParameter('output-media-type', 'mediaType', output.mediaTypes[0], 'enum', outputIds, { type: 'enum', allowedValues: output.mediaTypes.map((item) => item) }));
475
+ if (output.background)
476
+ parameters.push(outputParameter('output-background', 'background', output.background, 'enum', outputIds, { type: 'enum', allowedValues: ['transparent', 'opaque', 'any'] }));
477
+ if (output.allowAlpha !== undefined)
478
+ parameters.push(outputParameter('output-alpha', 'allowAlpha', output.allowAlpha, 'boolean', outputIds, { type: 'boolean', allowedValues: [true, false] }));
479
+ parameters.push(outputParameter('output-count', 'count', output.cardinality.min, 'integer', outputIds, { type: 'integer', minimum: output.cardinality.min, maximum: output.cardinality.max }));
480
+ if (output.maxBytes !== undefined)
481
+ parameters.push(outputParameter('output-max-bytes', 'maxBytes', output.maxBytes, 'integer', outputIds, { type: 'integer', minimum: 0, maximum: output.maxBytes }));
482
+ return parameters;
483
+ }
484
+ function coverageForConstraint(constraint, sections, parameters, mappings) {
485
+ const sectionIds = sections.filter((section) => section.constraintIds.includes(constraint.id)).map((section) => section.id);
486
+ const parameterIds = parameters.filter((parameter) => parameter.constraintIds.includes(constraint.id)).map((parameter) => parameter.id);
487
+ const referenceMappingIds = mappings.filter((mapping) => mapping.constraintIds.includes(constraint.id)).map((mapping) => mapping.id);
488
+ return {
489
+ schemaVersion: PROMPT_CONSTRAINT_COVERAGE_SCHEMA_VERSION,
490
+ constraintId: constraint.id,
491
+ sectionIds: sortedStrings(sectionIds),
492
+ parameterIds: sortedStrings(parameterIds),
493
+ referenceMappingIds: sortedStrings(referenceMappingIds),
494
+ locked: constraint.importance !== 'preferred',
495
+ };
496
+ }
497
+ function promptBaseWithoutSignature(prompt) {
498
+ return { ...clone(prompt), deterministicSignature: '' };
499
+ }
500
+ export class PromptCompiler {
501
+ compile(input) {
502
+ try {
503
+ const safeInput = clone(input);
504
+ const reasons = promptCompilationInputReasons(safeInput);
505
+ if (reasons.length)
506
+ throw new Error(reasons.join('|'));
507
+ const decisionIds = sortedStrings(safeInput.context.decisionHashes);
508
+ const constraints = sortedBy(safeInput.constraintIR.constraints, (item) => item.id);
509
+ const sections = [];
510
+ const objective = safeInput.objective ?? 'Produce the requested visual result using only the approved constraints and references.';
511
+ const positiveDescription = safeInput.positiveDescription ?? 'Express the approved target properties clearly and preserve all locked requirements.';
512
+ sections.push({
513
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
514
+ id: hashId('prompt-section', { kind: 'objective', objective }),
515
+ kind: 'objective', priority: 120, order: 0, content: objective, text: objective,
516
+ constraintIds: [], sourceIds: [], decisionIds, assetIds: [], importance: 'required', mutability: 'rephraseable', locked: false,
517
+ });
518
+ sections.push({
519
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
520
+ id: hashId('prompt-section', { kind: 'positive', positiveDescription }),
521
+ kind: 'positive', priority: 110, order: 1, content: positiveDescription, text: positiveDescription,
522
+ constraintIds: [], sourceIds: [], decisionIds, assetIds: [], importance: 'required', mutability: 'rephraseable', locked: false,
523
+ });
524
+ constraints.forEach((constraint, index) => sections.push(sectionForConstraint(constraint, 10 + index, decisionIds)));
525
+ const mappings = [...safeInput.referencePlan.ordered].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map((reference) => referenceMapping(reference, constraints, decisionIds));
526
+ mappings.forEach((mapping, index) => sections.push({
527
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
528
+ id: hashId('prompt-section', { kind: 'reference', mappingId: mapping.id }),
529
+ kind: 'reference', priority: mapping.required ? 90 : 30, order: 100 + index, content: `${mapping.label}: use approved ${mapping.role} reference ${mapping.assetId}.`,
530
+ text: `${mapping.label}: use approved ${mapping.role} reference ${mapping.assetId}.`, constraintIds: mapping.constraintIds,
531
+ sourceIds: mapping.sourceBindingIds, decisionIds: mapping.decisionIds, assetIds: [mapping.assetId], importance: mapping.required ? 'required' : 'preferred', mutability: 'locked', locked: true,
532
+ }));
533
+ const outputConstraints = constraints.filter((constraint) => constraint.kind === 'output');
534
+ const parameters = promptParameterValues(safeInput.outputContract, outputConstraints);
535
+ sections.push({
536
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
537
+ id: hashId('prompt-section', { kind: 'output', outputContractHash: computeOutputContractHash(safeInput.outputContract) }),
538
+ kind: 'output', priority: 100, order: 1000, content: `Render exactly ${safeInput.outputContract.cardinality.min}-${safeInput.outputContract.cardinality.max} output artifact(s) under the typed output contract.`,
539
+ text: `Render exactly ${safeInput.outputContract.cardinality.min}-${safeInput.outputContract.cardinality.max} output artifact(s) under the typed output contract.`, constraintIds: outputConstraints.map((constraint) => constraint.id), sourceIds: [], decisionIds, assetIds: [], importance: 'hard', mutability: 'locked', locked: true,
540
+ });
541
+ sections.push({
542
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
543
+ id: 'prompt-suggestion-slot-default', kind: 'suggestion', priority: 10, order: 2000, content: '', text: '', constraintIds: [], sourceIds: [], decisionIds: [], assetIds: [], importance: 'preferred', mutability: 'suggestion_slot', locked: false, slotId: 'suggestion.default',
544
+ });
545
+ const forbidden = constraints.filter((constraint) => constraint.predicate === 'absent').map((constraint) => ({
546
+ id: hashId('prompt-prohibition', { constraintId: constraint.id }), text: `Do not include ${constraint.targetPath ?? constraint.targetPaths.join(',')}.`, constraintIds: [constraint.id], sourceIds: sortedStrings(constraint.sourceIds), importance: constraint.importance,
547
+ }));
548
+ const coverage = constraints.map((constraint) => coverageForConstraint(constraint, sections, parameters, mappings));
549
+ const prompt = {
550
+ schemaVersion: PROMPT_IR_SCHEMA_VERSION,
551
+ id: hashId('prompt-ir', { caseId: safeInput.caseId, caseRevision: safeInput.caseRevision, compilationSignature: safeInput.constraintIR.deterministicSignature, referencePlanHash: safeInput.referencePlan.planHash, pipelinePlanHash: safeInput.pipelinePlan.planHash, targetAdapter: safeInput.targetAdapter, targetCapabilityProfile: safeInput.targetCapabilityProfile }),
552
+ caseId: safeInput.caseId,
553
+ caseRevision: safeInput.caseRevision,
554
+ contextHash: safeInput.contextHash,
555
+ compilationSignature: safeInput.constraintIR.deterministicSignature,
556
+ constraintIRHash: safeInput.constraintIR.deterministicSignature,
557
+ referencePlanHash: safeInput.referencePlan.planHash,
558
+ pipelinePlanHash: safeInput.pipelinePlan.planHash,
559
+ outputContractHash: computeOutputContractHash(safeInput.outputContract),
560
+ targetAdapter: clone(safeInput.targetAdapter),
561
+ targetCapabilityProfile: clone(safeInput.targetCapabilityProfile),
562
+ objective, positiveDescription,
563
+ sections: [...sections].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)),
564
+ parameters: sortedBy(parameters, (item) => item.id),
565
+ referenceMappings: mappings,
566
+ forbidden: sortedBy(forbidden, (item) => item.id),
567
+ output: clone(safeInput.outputContract),
568
+ constraintCoverage: sortedBy(coverage, (item) => item.constraintId),
569
+ sourceIds: sortedStrings([...constraints.flatMap((constraint) => constraint.sourceIds), ...mappings.flatMap((mapping) => mapping.sourceBindingIds)]),
570
+ constraintIds: sortedStrings(constraints.map((constraint) => constraint.id)),
571
+ decisionIds,
572
+ assetIds: sortedStrings(mappings.map((mapping) => mapping.assetId)),
573
+ deterministicSignature: '',
574
+ };
575
+ prompt.deterministicSignature = computePromptIRHash(prompt);
576
+ return clone(prompt);
577
+ }
578
+ catch (error) {
579
+ throw error;
580
+ }
581
+ }
582
+ }
583
+ export const DeterministicPromptCompiler = PromptCompiler;
584
+ export function compilePromptIR(input) {
585
+ return new PromptCompiler().compile(input);
586
+ }
587
+ export const compilePrompt = compilePromptIR;
588
+ function candidateRequestParameters(parameters) {
589
+ return Object.fromEntries(parameters.map((parameter) => [parameter.name, clone(parameter.value)]));
590
+ }
591
+ function candidateCoverageClaims(coverage) {
592
+ return coverage.map((item) => ({ constraintId: item.constraintId, transformationIndexes: [], sectionIds: sortedStrings(item.sectionIds), parameterIds: sortedStrings(item.parameterIds), referenceMappingIds: sortedStrings(item.referenceMappingIds) }));
593
+ }
594
+ function applyTransformation(prompt, sections, parameters, transformation) {
595
+ if (transformation.kind === 'reorder') {
596
+ const byId = new Map(sections.map((section) => [section.id, section]));
597
+ if (transformation.sectionIds.length !== sections.length || transformation.sectionIds.some((id) => !byId.has(id)))
598
+ throw new Error('PROMPT_TRANSFORMATION_INVALID');
599
+ sections.splice(0, sections.length, ...transformation.sectionIds.map((id) => byId.get(id)));
600
+ return;
601
+ }
602
+ if (transformation.kind === 'rephrase') {
603
+ const section = sections.find((item) => item.id === transformation.sectionId);
604
+ if (!section)
605
+ throw new Error('PROMPT_SECTION_NOT_FOUND');
606
+ const content = transformation.content ?? transformation.text;
607
+ if (content === undefined)
608
+ throw new Error('PROMPT_TRANSFORMATION_INVALID');
609
+ section.content = content;
610
+ section.text = content;
611
+ return;
612
+ }
613
+ if (transformation.kind === 'parameter_move') {
614
+ const parameter = parameters.find((item) => item.id === transformation.parameterId || item.name === transformation.parameterName);
615
+ if (!parameter)
616
+ throw new Error('PROMPT_PARAMETER_NOT_FOUND');
617
+ if (transformation.value !== undefined)
618
+ parameter.value = clone(transformation.value);
619
+ return;
620
+ }
621
+ if (transformation.kind === 'suggestion' || transformation.kind === 'add_suggestion' || transformation.kind === 'declared_suggestion') {
622
+ const slot = sections.find((item) => item.slotId === transformation.slotId && item.mutability === 'suggestion_slot');
623
+ if (!slot)
624
+ throw new Error('PROMPT_SUGGESTION_SLOT_NOT_FOUND');
625
+ const content = transformation.content ?? transformation.text;
626
+ if (content === undefined)
627
+ throw new Error('PROMPT_TRANSFORMATION_INVALID');
628
+ sections.push({
629
+ schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
630
+ id: hashId('prompt-suggestion', { slotId: transformation.slotId, content, sourceIds: transformation.sourceIds, constraintIds: transformation.constraintIds }),
631
+ kind: 'suggestion', priority: slot.priority, order: Math.max(...sections.map((item) => item.order), 0) + 1,
632
+ content, text: content, constraintIds: sortedStrings(transformation.constraintIds), sourceIds: sortedStrings(transformation.sourceIds), decisionIds: [], assetIds: [], importance: 'preferred', mutability: 'rephraseable', locked: false, slotId: transformation.slotId,
633
+ });
634
+ }
635
+ }
636
+ export function createPromptCandidateIR(prompt, transformations = [], options = {}) {
637
+ const safePrompt = clone(prompt);
638
+ const sections = clone(safePrompt.sections);
639
+ const parameters = clone(safePrompt.parameters);
640
+ const normalizedTransformations = transformations.map((transformation) => ({ schemaVersion: PROMPT_TRANSFORMATION_SCHEMA_VERSION, ...clone(transformation) }));
641
+ for (const transformation of normalizedTransformations)
642
+ applyTransformation(safePrompt, sections, parameters, transformation);
643
+ const candidateBase = {
644
+ schemaVersion: PROMPT_CANDIDATE_IR_SCHEMA_VERSION,
645
+ id: hashId('prompt-candidate', { base: safePrompt.deterministicSignature, transformations: normalizedTransformations, optimizer: options.optimizer ?? { id: PROMPT_OPTIMIZER_VERSION, version: '1.0.0', digest: sha256({ optimizer: PROMPT_OPTIMIZER_VERSION }) }, mode: options.mode ?? 'strict' }),
646
+ candidateHash: '',
647
+ basePromptIRHash: safePrompt.deterministicSignature,
648
+ basePromptIRSignature: safePrompt.deterministicSignature,
649
+ targetAdapter: clone(safePrompt.targetAdapter),
650
+ targetCapabilityProfile: clone(safePrompt.targetCapabilityProfile),
651
+ targetAdapterDigest: safePrompt.targetAdapter.digest,
652
+ targetProfileDigest: safePrompt.targetCapabilityProfile.digest,
653
+ sections: clone(sections),
654
+ parameters: clone(parameters),
655
+ referenceMappings: clone(safePrompt.referenceMappings),
656
+ constraintCoverage: clone(safePrompt.constraintCoverage),
657
+ transformations: normalizedTransformations,
658
+ optimizer: clone(options.optimizer ?? { id: PROMPT_OPTIMIZER_VERSION, version: '1.0.0', digest: sha256({ optimizer: PROMPT_OPTIMIZER_VERSION }) }),
659
+ mode: options.mode ?? 'strict',
660
+ warnings: sortedStrings(options.warnings),
661
+ candidateSections: clone(sections),
662
+ requestParameters: candidateRequestParameters(parameters),
663
+ coverageClaims: candidateCoverageClaims(safePrompt.constraintCoverage),
664
+ };
665
+ candidateBase.candidateHash = computePromptCandidateHash(candidateBase);
666
+ return clone(candidateBase);
667
+ }
668
+ export class DeterministicPromptOptimizer {
669
+ optimize(input) {
670
+ const safeInput = clone(input);
671
+ if (safeInput.schemaVersion !== PROMPT_OPTIMIZATION_INPUT_SCHEMA_VERSION)
672
+ throw new Error('PROMPT_OPTIMIZATION_INPUT_SCHEMA_INVALID');
673
+ const prompt = clone(safeInput.promptIR);
674
+ if (computePromptIRHash(prompt) !== prompt.deterministicSignature)
675
+ throw new Error('PROMPT_IR_SIGNATURE_MISMATCH');
676
+ const mode = safeInput.mode ?? 'strict';
677
+ if (!['strict', 'balanced', 'creative'].includes(mode))
678
+ throw new Error('PROMPT_OPTIMIZATION_MODE_INVALID');
679
+ const transformations = [];
680
+ if (mode !== 'strict') {
681
+ for (const section of prompt.sections) {
682
+ if (section.mutability !== 'rephraseable' || !section.content.trim())
683
+ continue;
684
+ const normalized = section.content.replaceAll(/\s+/g, ' ').trim();
685
+ if (normalized !== section.content)
686
+ transformations.push({ schemaVersion: PROMPT_TRANSFORMATION_SCHEMA_VERSION, kind: 'rephrase', sectionId: section.id, content: normalized, constraintIds: section.constraintIds, sourceIds: section.sourceIds, proof: { kind: 'whitespace_normalization', sourceSectionHash: promptSectionHash(section), preservedConstraintIds: sortedStrings(section.constraintIds), explanation: 'Whitespace-only normalization is mechanically reproducible.' } });
687
+ }
688
+ }
689
+ return createPromptCandidateIR(prompt, transformations, { optimizer: safeInput.optimizer ?? { id: PROMPT_OPTIMIZER_VERSION, version: '1.0.0', digest: sha256({ optimizer: PROMPT_OPTIMIZER_VERSION }) }, mode });
690
+ }
691
+ }
692
+ export const BaselinePromptOptimizer = DeterministicPromptOptimizer;
693
+ export const OfflinePromptOptimizer = DeterministicPromptOptimizer;
694
+ export function optimizePromptIR(input) {
695
+ return new DeterministicPromptOptimizer().optimize(input);
696
+ }
697
+ export function optimizePromptIRWithFallback(input) {
698
+ try {
699
+ return optimizePromptIR(input);
700
+ }
701
+ catch {
702
+ const prompt = clone(input.promptIR);
703
+ if (computePromptIRHash(prompt) !== prompt.deterministicSignature)
704
+ throw new Error('PROMPT_IR_SIGNATURE_MISMATCH');
705
+ return createPromptCandidateIR(prompt, [], { mode: 'strict', warnings: ['OPTIMIZER_FALLBACK_DETERMINISTIC_PROMPT_IR'] });
706
+ }
707
+ }
708
+ export const optimizePromptSafely = optimizePromptIRWithFallback;
709
+ function valueWithinBounds(parameter, value) {
710
+ const bounds = parameter.bounds ?? { type: parameter.valueType, minimum: parameter.minimum, maximum: parameter.maximum, allowedValues: parameter.allowedValues };
711
+ if (bounds.type === 'string' && typeof value !== 'string')
712
+ return false;
713
+ if (bounds.type === 'number' && (typeof value !== 'number' || !Number.isFinite(value)))
714
+ return false;
715
+ if (bounds.type === 'integer' && (typeof value !== 'number' || !Number.isInteger(value)))
716
+ return false;
717
+ if (bounds.type === 'boolean' && typeof value !== 'boolean')
718
+ return false;
719
+ if (bounds.type === 'object' && (value === null || typeof value !== 'object' || Array.isArray(value)))
720
+ return false;
721
+ if (bounds.type === 'array' && !Array.isArray(value))
722
+ return false;
723
+ if (bounds.minimum !== undefined && (typeof value !== 'number' || value < bounds.minimum))
724
+ return false;
725
+ if (bounds.maximum !== undefined && (typeof value !== 'number' || value > bounds.maximum))
726
+ return false;
727
+ if (bounds.allowedValues && !bounds.allowedValues.some((item) => canonicalize(item) === canonicalize(value)))
728
+ return false;
729
+ return true;
730
+ }
731
+ function guardFinding(value) {
732
+ return promptFinding(value);
733
+ }
734
+ function guardInputReasons(input) {
735
+ const reasons = [];
736
+ if (!input || input.schemaVersion !== 'voce.prompt-guard-input/v1alpha1')
737
+ reasons.push('PROMPT_GUARD_INPUT_SCHEMA_INVALID');
738
+ if (!input.promptIR || input.promptIR.schemaVersion !== PROMPT_IR_SCHEMA_VERSION || !isHash(input.promptIR.deterministicSignature) || computePromptIRHash(input.promptIR) !== input.promptIR.deterministicSignature)
739
+ reasons.push('PROMPT_IR_SIGNATURE_MISMATCH');
740
+ if (!input.candidate || input.candidate.schemaVersion !== PROMPT_CANDIDATE_IR_SCHEMA_VERSION || !isHash(input.candidate.candidateHash) || computePromptCandidateHash(input.candidate) !== input.candidate.candidateHash)
741
+ reasons.push('PROMPT_CANDIDATE_HASH_MISMATCH');
742
+ if (input.candidate && input.promptIR && input.candidate.basePromptIRHash !== input.promptIR.deterministicSignature)
743
+ reasons.push('PROMPT_CANDIDATE_BASE_MISMATCH');
744
+ if (input.candidate && input.promptIR && (input.candidate.targetAdapterDigest !== input.promptIR.targetAdapter.digest || input.candidate.targetProfileDigest !== input.promptIR.targetCapabilityProfile.digest))
745
+ reasons.push('PROMPT_CANDIDATE_TARGET_MISMATCH');
746
+ if (input.candidate && input.promptIR && (canonicalize(input.candidate.targetAdapter) !== canonicalize(input.promptIR.targetAdapter) || canonicalize(input.candidate.targetCapabilityProfile) !== canonicalize(input.promptIR.targetCapabilityProfile)))
747
+ reasons.push('PROMPT_CANDIDATE_TARGET_CHANGED');
748
+ if (!input.context || !isHash(input.context.contextHash) || computeCompilationContextHash(input.context) !== input.context.contextHash)
749
+ reasons.push('PROMPT_CONTEXT_HASH_MISMATCH');
750
+ if (input.promptIR && input.context && (input.promptIR.contextHash !== input.context.contextHash || input.promptIR.caseId !== input.context.caseSpecId || input.promptIR.caseRevision !== input.context.caseSpecRevision))
751
+ reasons.push('PROMPT_CONTEXT_MISMATCH');
752
+ if (input.constraintIR && integrityReasonsForConstraintIR(input.constraintIR, input.context, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1).length)
753
+ reasons.push('CONSTRAINT_IR_INVALID');
754
+ if (input.referencePlan && input.constraintIR && integrityReasonsForReferencePlan(input.referencePlan, input.constraintIR, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1, input.context?.contextHash ?? '').length)
755
+ reasons.push('REFERENCE_PLAN_INVALID');
756
+ if (input.pipelinePlan && input.constraintIR && input.referencePlan && integrityReasonsForPipelinePlan(input.pipelinePlan, input.constraintIR, input.referencePlan, input.outputContract, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1, input.context?.contextHash ?? '').length)
757
+ reasons.push('PIPELINE_PLAN_INVALID');
758
+ if (input.promptIR && input.outputContract && input.promptIR.outputContractHash !== computeOutputContractHash(input.outputContract))
759
+ reasons.push('PROMPT_OUTPUT_CONTRACT_MISMATCH');
760
+ if (input.promptIR && input.pipelinePlan && input.promptIR.pipelinePlanHash !== input.pipelinePlan.planHash)
761
+ reasons.push('PROMPT_PIPELINE_PLAN_MISMATCH');
762
+ if (input.promptIR && input.referencePlan && input.promptIR.referencePlanHash !== input.referencePlan.planHash)
763
+ reasons.push('PROMPT_REFERENCE_PLAN_MISMATCH');
764
+ if (input.promptIR && input.constraintIR && input.promptIR.compilationSignature !== input.constraintIR.deterministicSignature)
765
+ reasons.push('PROMPT_CONSTRAINT_SIGNATURE_MISMATCH');
766
+ if (outputContractReasons(input.outputContract).length)
767
+ reasons.push('OUTPUT_CONTRACT_INVALID');
768
+ return sortedStrings(reasons);
769
+ }
770
+ function addGuardFinding(findings, value) {
771
+ const finding = guardFinding(value);
772
+ if (!findings.some((item) => item.id === finding.id))
773
+ findings.push(finding);
774
+ }
775
+ function guardResult(input, status, findings, guardedCandidate) {
776
+ const base = {
777
+ schemaVersion: PROMPT_GUARD_RESULT_SCHEMA_VERSION,
778
+ status,
779
+ accepted: status === 'accepted',
780
+ candidateHash: input.candidate?.candidateHash ?? '',
781
+ basePromptIRHash: input.promptIR?.deterministicSignature ?? '',
782
+ findings: sortedBy(findings, (item) => item.id),
783
+ ...(guardedCandidate ? { guardedCandidate: clone(guardedCandidate) } : {}),
784
+ deterministicFallback: clone(input.promptIR),
785
+ };
786
+ return clone({ ...base, resultHash: sha256(jsonReady(base)) });
787
+ }
788
+ export class PromptGuard {
789
+ guard(input) {
790
+ const safeInput = clone(input);
791
+ const findings = [];
792
+ const structuralReasons = guardInputReasons(safeInput);
793
+ for (const code of structuralReasons)
794
+ addGuardFinding(findings, { code, severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: `Prompt Guard rejected the input because ${code}.` });
795
+ if (structuralReasons.length)
796
+ return guardResult(safeInput, safeInput.policy === 'fallback' ? 'fallback' : 'rejected', findings);
797
+ const prompt = safeInput.promptIR;
798
+ const candidate = safeInput.candidate;
799
+ const baseSections = new Map(prompt.sections.map((section) => [section.id, section]));
800
+ const candidateSectionList = candidateSections(candidate);
801
+ const candidateSectionMap = new Map(candidateSectionList.map((section) => [section.id, section]));
802
+ const baseParameters = new Map(prompt.parameters.map((parameter) => [parameter.id, parameter]));
803
+ const candidateParameterList = candidateParameters(candidate);
804
+ const candidateParameterMap = new Map(candidateParameterList.map((parameter) => [parameter.id, parameter]));
805
+ const baseMappings = new Map(prompt.referenceMappings.map((mapping) => [mapping.id, mapping]));
806
+ const candidateMappings = candidate.referenceMappings;
807
+ const baseCoverage = new Map(prompt.constraintCoverage.map((coverage) => [coverage.constraintId, coverage]));
808
+ const candidateCoverageMap = new Map(candidateCoverage(candidate).map((coverage) => [coverage.constraintId, coverage]));
809
+ for (const section of prompt.sections) {
810
+ const candidateSection = candidateSectionMap.get(section.id);
811
+ const locked = section.locked === true || section.mutability === 'locked' || section.kind === 'hard_constraint' || section.kind === 'required_constraint' || section.kind === 'output' || section.kind === 'reference';
812
+ if (!candidateSection) {
813
+ addGuardFinding(findings, { code: locked ? 'LOCKED_SECTION_REMOVED' : 'PROMPT_SECTION_REMOVED', severity: locked ? 'critical' : 'error', blocking: locked, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: `Candidate removed ${locked ? 'locked' : 'declared'} PromptIR section ${section.id}.` });
814
+ continue;
815
+ }
816
+ if (locked) {
817
+ const same = canonicalize(lockedPromptSectionProjection(section)) === canonicalize(lockedPromptSectionProjection(candidateSection));
818
+ if (!same)
819
+ addGuardFinding(findings, { code: 'LOCKED_SECTION_CHANGED', severity: 'critical', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: `Locked PromptIR section ${section.id} changed in the candidate.` });
820
+ }
821
+ else {
822
+ if (canonicalize(promptSectionMetadataProjection(section)) !== canonicalize(promptSectionMetadataProjection(candidateSection)))
823
+ addGuardFinding(findings, { code: 'PROMPT_SECTION_METADATA_CHANGED', severity: 'critical', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: `Section ${section.id} changed typed provenance or mutability metadata.` });
824
+ if (section.content !== candidateSection.content || section.text !== candidateSection.text) {
825
+ const rephrase = candidate.transformations.some((transformation) => transformation.kind === 'rephrase' && transformation.sectionId === section.id);
826
+ if (!rephrase)
827
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'error', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: `Section ${section.id} changed without a declared rephrase transformation.` });
828
+ }
829
+ }
830
+ }
831
+ for (const section of candidateSectionList)
832
+ if (!baseSections.has(section.id)) {
833
+ const matchingSuggestions = candidate.transformations.filter((transformation) => {
834
+ if (transformation.kind !== 'suggestion' && transformation.kind !== 'add_suggestion' && transformation.kind !== 'declared_suggestion')
835
+ return false;
836
+ const transformationContent = transformation.content ?? transformation.text;
837
+ const transformationText = transformation.text ?? transformation.content;
838
+ const slot = prompt.sections.find((candidateSlot) => candidateSlot.slotId === transformation.slotId && candidateSlot.mutability === 'suggestion_slot');
839
+ return slot !== undefined
840
+ && section.slotId === transformation.slotId
841
+ && section.content === transformationContent
842
+ && section.text === transformationText
843
+ && canonicalize(sortedStrings(section.constraintIds)) === canonicalize(sortedStrings(transformation.constraintIds))
844
+ && canonicalize(sortedStrings(section.sourceIds)) === canonicalize(sortedStrings(transformation.sourceIds))
845
+ && transformation.provenance.source === 'optimizer_suggested'
846
+ && transformation.proof?.kind === 'declared_suggestion';
847
+ });
848
+ const structurallyAllowed = section.kind === 'suggestion' && section.mutability !== 'locked' && section.constraintIds.every((id) => !prompt.constraintIds.includes(id) || prompt.constraintCoverage.find((item) => item.constraintId === id)?.locked !== true);
849
+ if (!structurallyAllowed || matchingSuggestions.length === 0) {
850
+ addGuardFinding(findings, { code: matchingSuggestions.length === 0 && structurallyAllowed ? 'PROMPT_CANDIDATE_UNVERIFIABLE' : 'UNAUTHORIZED_SECTION_ADDED', severity: 'critical', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: `Candidate added suggestion section ${section.id} without a matching declared, proven suggestion transformation.` });
851
+ }
852
+ }
853
+ for (const parameter of prompt.parameters) {
854
+ const candidateParameter = candidateParameterMap.get(parameter.id);
855
+ if (!candidateParameter) {
856
+ addGuardFinding(findings, { code: 'LOCKED_PARAMETER_REMOVED', severity: parameter.mutability === 'locked' ? 'critical' : 'error', blocking: parameter.mutability === 'locked', constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Candidate removed declared parameter ${parameter.name}.` });
857
+ continue;
858
+ }
859
+ if (!valueWithinBounds(parameter, candidateParameter.value))
860
+ addGuardFinding(findings, { code: 'PARAMETER_OUT_OF_BOUNDS', severity: 'critical', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Candidate parameter ${parameter.name} is outside its typed bounds.` });
861
+ if (canonicalize(promptParameterContractProjection(parameter)) !== canonicalize(promptParameterContractProjection(candidateParameter)))
862
+ addGuardFinding(findings, { code: 'PARAMETER_CONTRACT_CHANGED', severity: 'critical', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Candidate changed the typed contract or provenance for parameter ${parameter.name}.` });
863
+ if (parameter.mutability === 'locked' && canonicalize(parameter.value) !== canonicalize(candidateParameter.value))
864
+ addGuardFinding(findings, { code: 'LOCKED_PARAMETER_CHANGED', severity: 'critical', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Locked parameter ${parameter.name} changed.` });
865
+ if (parameter.mutability !== 'locked' && canonicalize(parameter.value) !== canonicalize(candidateParameter.value) && !candidate.transformations.some((transformation) => transformation.kind === 'parameter_move' && (transformation.parameterId === parameter.id || transformation.parameterName === parameter.name)))
866
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'error', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Parameter ${parameter.name} changed without a declared parameter_move transformation.` });
867
+ }
868
+ for (const parameter of candidateParameterList)
869
+ if (!baseParameters.has(parameter.id))
870
+ addGuardFinding(findings, { code: 'UNAUTHORIZED_PARAMETER_ADDED', severity: 'critical', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [], decisionIds: parameter.decisionIds, assetIds: [], explanation: `Candidate added undeclared parameter ${parameter.name}.` });
871
+ if (candidate.candidateSections && canonicalize(candidate.candidateSections.map(promptSectionProjection)) !== canonicalize(candidate.sections.map(promptSectionProjection)))
872
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_ALIAS_MISMATCH', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Candidate section aliases do not match the guarded sections.' });
873
+ if (candidate.requestParameters && canonicalize(candidate.requestParameters) !== canonicalize(Object.fromEntries(candidateParameterList.map((parameter) => [parameter.name, parameter.value]))))
874
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_ALIAS_MISMATCH', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Candidate parameter aliases do not match the guarded typed parameters.' });
875
+ const baseMappingIds = prompt.referenceMappings.map((mapping) => mapping.id).sort(compareCodeUnits);
876
+ const candidateMappingIds = candidateMappings.map((mapping) => mapping.id).sort(compareCodeUnits);
877
+ if (canonicalize(baseMappingIds) !== canonicalize(candidateMappingIds))
878
+ addGuardFinding(findings, { code: 'REFERENCE_MAPPING_SET_CHANGED', severity: 'critical', blocking: true, constraintIds: prompt.referenceMappings.flatMap((mapping) => mapping.constraintIds), sourceIds: prompt.referenceMappings.flatMap((mapping) => mapping.sourceBindingIds), sectionIds: [], decisionIds: prompt.referenceMappings.flatMap((mapping) => mapping.decisionIds), assetIds: prompt.referenceMappings.map((mapping) => mapping.assetId), explanation: 'Candidate added or removed an approved reference mapping.' });
879
+ for (const mapping of prompt.referenceMappings) {
880
+ const candidateMapping = candidateMappings.find((item) => item.id === mapping.id);
881
+ if (!candidateMapping || canonicalize(promptReferenceMappingProjection(mapping)) !== canonicalize(promptReferenceMappingProjection(candidateMapping)))
882
+ addGuardFinding(findings, { code: 'CONFIRMED_REFERENCE_MAPPING_CHANGED', severity: 'critical', blocking: true, constraintIds: mapping.constraintIds, sourceIds: mapping.sourceBindingIds, sectionIds: [], decisionIds: mapping.decisionIds, assetIds: [mapping.assetId], explanation: `Approved reference mapping ${mapping.id} changed.` });
883
+ }
884
+ for (const coverage of prompt.constraintCoverage) {
885
+ const candidateCoverageValue = candidateCoverageMap.get(coverage.constraintId);
886
+ if (!candidateCoverageValue || coverage.locked && canonicalize(promptCoverageProjection(coverage)) !== canonicalize(promptCoverageProjection(candidateCoverageValue)))
887
+ addGuardFinding(findings, { code: 'CONSTRAINT_COVERAGE_LOST', severity: coverage.locked ? 'critical' : 'error', blocking: coverage.locked, constraintIds: [coverage.constraintId], sourceIds: [], sectionIds: coverage.sectionIds, decisionIds: [], assetIds: [], explanation: `Candidate no longer proves coverage for constraint ${coverage.constraintId}.` });
888
+ }
889
+ for (const coverage of candidateCoverage(candidate))
890
+ if (!baseCoverage.has(coverage.constraintId))
891
+ addGuardFinding(findings, { code: 'UNAUTHORIZED_CONSTRAINT_CLAIM', severity: 'critical', blocking: true, constraintIds: [coverage.constraintId], sourceIds: [], sectionIds: coverage.sectionIds, decisionIds: [], assetIds: [], explanation: `Candidate claims coverage for an undeclared constraint ${coverage.constraintId}.` });
892
+ const transformationSectionIds = new Set();
893
+ candidate.transformations.forEach((transformation, index) => {
894
+ if (transformation.schemaVersion !== undefined && transformation.schemaVersion !== PROMPT_TRANSFORMATION_SCHEMA_VERSION)
895
+ addGuardFinding(findings, { code: 'PROMPT_TRANSFORMATION_SCHEMA_INVALID', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: `Transformation ${index} has an unsupported schemaVersion.` });
896
+ if (transformation.kind === 'rephrase') {
897
+ transformationSectionIds.add(transformation.sectionId);
898
+ const section = baseSections.get(transformation.sectionId);
899
+ if (!section)
900
+ addGuardFinding(findings, { code: 'PROMPT_SECTION_NOT_FOUND', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [transformation.sectionId], decisionIds: [], assetIds: [], explanation: 'Rephrase targets a section that is not in PromptIR.' });
901
+ else if (section.mutability === 'locked' || section.locked)
902
+ addGuardFinding(findings, { code: 'LOCKED_SECTION_CHANGED', severity: 'critical', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: 'A rephrase transformation targets a locked section.' });
903
+ else if (!transformation.proof || !['deterministic_rephrase', 'whitespace_normalization'].includes(transformation.proof.kind) || (transformation.proof.sourceSectionHash !== undefined && transformation.proof.sourceSectionHash !== promptSectionHash(section)) || canonicalize(sortedStrings(transformation.proof.preservedConstraintIds)) !== canonicalize(sortedStrings(section.constraintIds)))
904
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'error', blocking: true, constraintIds: section.constraintIds, sourceIds: section.sourceIds, sectionIds: [section.id], decisionIds: section.decisionIds, assetIds: section.assetIds, explanation: 'A free-text rephrase lacks a proof object that preserves the source section constraints.' });
905
+ }
906
+ else if (transformation.kind === 'reorder') {
907
+ const expected = candidateSectionList.map((section) => section.id).sort(compareCodeUnits);
908
+ const actual = [...transformation.sectionIds].sort(compareCodeUnits);
909
+ if (canonicalize(expected) !== canonicalize(actual))
910
+ addGuardFinding(findings, { code: 'PROMPT_REORDER_SET_INVALID', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: transformation.sectionIds, decisionIds: [], assetIds: [], explanation: 'Reorder transformation does not name exactly the candidate section set.' });
911
+ }
912
+ else if (transformation.kind === 'parameter_move') {
913
+ const section = baseSections.get(transformation.sectionId);
914
+ const parameter = [...baseParameters.values()].find((item) => item.id === transformation.parameterId || item.name === transformation.parameterName);
915
+ if (!section || !parameter)
916
+ addGuardFinding(findings, { code: 'PROMPT_PARAMETER_MOVE_TARGET_INVALID', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: section ? [section.id] : [], decisionIds: [], assetIds: [], explanation: 'Parameter move must target an existing section and parameter.' });
917
+ else if (section.mutability === 'locked' || parameter.mutability === 'locked' || !transformation.proof || transformation.proof.kind !== 'typed_parameter_move' || canonicalize(sortedStrings(transformation.proof.preservedConstraintIds)) !== canonicalize(sortedStrings(parameter.constraintIds)) || (transformation.value !== undefined && (!candidateParameterMap.get(parameter.id) || canonicalize(transformation.value) !== canonicalize(candidateParameterMap.get(parameter.id).value))))
918
+ addGuardFinding(findings, { code: parameter.mutability === 'locked' ? 'LOCKED_PARAMETER_CHANGED' : 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'critical', blocking: true, constraintIds: parameter.constraintIds, sourceIds: parameter.sourceIds, sectionIds: [section.id], decisionIds: parameter.decisionIds, assetIds: [], explanation: 'Parameter move is not a declared, typed, provable transformation.' });
919
+ }
920
+ else if (transformation.kind === 'suggestion' || transformation.kind === 'add_suggestion' || transformation.kind === 'declared_suggestion') {
921
+ const slot = prompt.sections.find((section) => section.slotId === transformation.slotId && section.mutability === 'suggestion_slot');
922
+ if (!slot || transformation.provenance.source !== 'optimizer_suggested')
923
+ addGuardFinding(findings, { code: 'SUGGESTION_SLOT_INVALID', severity: 'error', blocking: true, constraintIds: transformation.constraintIds ?? [], sourceIds: transformation.sourceIds ?? [], sectionIds: slot ? [slot.id] : [], decisionIds: [], assetIds: [], explanation: 'Suggestions must use a declared slot and optimizer_suggested provenance.' });
924
+ else if (transformation.proof?.kind !== 'declared_suggestion')
925
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'critical', blocking: true, constraintIds: transformation.constraintIds ?? [], sourceIds: transformation.sourceIds ?? [], sectionIds: [slot.id], decisionIds: [], assetIds: [], explanation: 'Added suggestions require declared_suggestion proof.' });
926
+ else if ((transformation.constraintIds ?? []).some((id) => prompt.constraintCoverage.find((coverage) => coverage.constraintId === id)?.locked))
927
+ addGuardFinding(findings, { code: 'SUGGESTION_WEAKENS_LOCKED_CONSTRAINT', severity: 'critical', blocking: true, constraintIds: transformation.constraintIds ?? [], sourceIds: transformation.sourceIds ?? [], sectionIds: [slot.id], decisionIds: [], assetIds: [], explanation: 'Suggestion references a locked constraint and cannot be used to weaken it.' });
928
+ }
929
+ else if (transformation.kind === 'free_text')
930
+ addGuardFinding(findings, { code: 'PROMPT_CANDIDATE_UNVERIFIABLE', severity: 'error', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Arbitrary free-text transformation cannot be mechanically proven safe.' });
931
+ else
932
+ addGuardFinding(findings, { code: 'PROMPT_TRANSFORMATION_NOT_ALLOWED', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Candidate contains a transformation outside the Prompt Guard AST allowlist.' });
933
+ });
934
+ if (candidate.transformations.some((transformation) => transformation.kind === 'rephrase') && transformationSectionIds.size === 0)
935
+ addGuardFinding(findings, { code: 'PROMPT_TRANSFORMATION_INVALID', severity: 'critical', blocking: true, constraintIds: [], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Rephrase transformation set is malformed.' });
936
+ const blocking = findings.some((finding) => finding.blocking);
937
+ if (blocking)
938
+ return guardResult(safeInput, safeInput.policy === 'fallback' ? 'fallback' : 'rejected', findings);
939
+ return guardResult(safeInput, 'accepted', findings, candidate);
940
+ }
941
+ }
942
+ export const DeterministicPromptGuard = PromptGuard;
943
+ export function guardPromptCandidate(input) {
944
+ return new PromptGuard().guard(input);
945
+ }
946
+ export const guardPrompt = guardPromptCandidate;
947
+ function renderProjection(request) {
948
+ return jsonReady({
949
+ schemaVersion: PROVIDER_RENDER_REQUEST_SCHEMA_VERSION,
950
+ id: request.id,
951
+ caseId: request.caseId,
952
+ caseRevision: request.caseRevision,
953
+ contextHash: request.contextHash,
954
+ promptIRHash: request.promptIRHash,
955
+ ...(request.promptCandidateHash === undefined ? {} : { promptCandidateHash: request.promptCandidateHash }),
956
+ ...(request.guardResultHash === undefined ? {} : { guardResultHash: request.guardResultHash }),
957
+ targetAdapter: clone(request.targetAdapter),
958
+ targetCapabilityProfile: clone(request.targetCapabilityProfile),
959
+ sections: request.sections.map(promptSectionProjection),
960
+ parameters: sortedBy(request.parameters, (item) => item.id).map(promptParameterProjection),
961
+ referenceMappings: [...request.referenceMappings].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map(promptReferenceMappingProjection),
962
+ output: clone(request.output),
963
+ pipelinePlanHash: request.pipelinePlanHash,
964
+ });
965
+ }
966
+ export function computeProviderRenderRequestHash(request) {
967
+ return sha256(renderProjection(request));
968
+ }
969
+ export function createProviderRenderRequest(input) {
970
+ const safePrompt = clone(input.promptIR);
971
+ const candidate = input.candidate ? clone(input.candidate) : undefined;
972
+ if (computePromptIRHash(safePrompt) !== safePrompt.deterministicSignature)
973
+ throw new Error('PROMPT_IR_SIGNATURE_MISMATCH');
974
+ if (candidate) {
975
+ if (computePromptCandidateHash(candidate) !== candidate.candidateHash)
976
+ throw new Error('PROMPT_CANDIDATE_HASH_MISMATCH');
977
+ if (candidate.basePromptIRHash !== safePrompt.deterministicSignature)
978
+ throw new Error('PROMPT_CANDIDATE_BASE_MISMATCH');
979
+ if (input.guardResult?.status !== 'accepted' || input.guardResult.guardedCandidate?.candidateHash !== candidate.candidateHash)
980
+ throw new Error('PROMPT_GUARD_REQUIRED');
981
+ if (input.guardResult && computePromptGuardResultHash(input.guardResult) !== input.guardResult.resultHash)
982
+ throw new Error('PROMPT_GUARD_RESULT_HASH_MISMATCH');
983
+ }
984
+ const sections = candidate ? candidateSections(candidate) : safePrompt.sections;
985
+ const parameters = candidate ? candidateParameters(candidate) : safePrompt.parameters;
986
+ const mappings = candidate ? candidate.referenceMappings : safePrompt.referenceMappings;
987
+ const base = {
988
+ schemaVersion: PROVIDER_RENDER_REQUEST_SCHEMA_VERSION,
989
+ id: hashId('provider-render-request', { prompt: candidate?.candidateHash ?? safePrompt.deterministicSignature, pipelinePlanHash: input.pipelinePlanHash ?? safePrompt.pipelinePlanHash }),
990
+ caseId: input.caseId ?? safePrompt.caseId,
991
+ caseRevision: input.caseRevision ?? safePrompt.caseRevision,
992
+ contextHash: input.contextHash ?? safePrompt.contextHash,
993
+ promptIRHash: safePrompt.deterministicSignature,
994
+ ...(candidate ? { promptCandidateHash: candidate.candidateHash } : {}),
995
+ ...(input.guardResult ? { guardResultHash: input.guardResult.resultHash } : {}),
996
+ targetAdapter: clone(candidate?.targetAdapter ?? safePrompt.targetAdapter),
997
+ targetCapabilityProfile: clone(candidate?.targetCapabilityProfile ?? safePrompt.targetCapabilityProfile),
998
+ sections: clone(sections),
999
+ parameters: clone(parameters),
1000
+ referenceMappings: clone(mappings),
1001
+ output: clone(safePrompt.output),
1002
+ pipelinePlanHash: input.pipelinePlanHash ?? safePrompt.pipelinePlanHash,
1003
+ };
1004
+ return clone({ ...base, requestHash: sha256(renderProjection(base)) });
1005
+ }
1006
+ export const renderProviderRequest = createProviderRenderRequest;
1007
+ export const OFFLINE_EXECUTION_INPUT_SCHEMA_VERSION = 'voce.offline-execution-input/v1alpha1';
1008
+ function promptArtifactHash(promptArtifact) {
1009
+ return 'candidateHash' in promptArtifact ? promptArtifact.candidateHash : promptArtifact.deterministicSignature;
1010
+ }
1011
+ function promptArtifactIntegrityReasons(promptArtifact, guardResult) {
1012
+ const reasons = [];
1013
+ if ('candidateHash' in promptArtifact) {
1014
+ if (promptArtifact.schemaVersion !== PROMPT_CANDIDATE_IR_SCHEMA_VERSION || !isHash(promptArtifact.candidateHash) || computePromptCandidateHash(promptArtifact) !== promptArtifact.candidateHash)
1015
+ reasons.push('PROMPT_CANDIDATE_HASH_MISMATCH');
1016
+ if (!guardResult || guardResult.status !== 'accepted' || guardResult.guardedCandidate?.candidateHash !== promptArtifact.candidateHash)
1017
+ reasons.push('PROMPT_GUARD_REQUIRED');
1018
+ if (guardResult && (!isHash(guardResult.resultHash) || computePromptGuardResultHash(guardResult) !== guardResult.resultHash))
1019
+ reasons.push('PROMPT_GUARD_RESULT_HASH_MISMATCH');
1020
+ }
1021
+ else if (promptArtifact.schemaVersion !== PROMPT_IR_SCHEMA_VERSION || !isHash(promptArtifact.deterministicSignature) || computePromptIRHash(promptArtifact) !== promptArtifact.deterministicSignature)
1022
+ reasons.push('PROMPT_IR_SIGNATURE_MISMATCH');
1023
+ return sortedStrings(reasons);
1024
+ }
1025
+ function executionAdapterProfileDigests(plan) {
1026
+ return sortedStrings([plan.profileDigest, ...plan.adapterDigests, ...plan.steps.map((step) => step.profileVersion.digest)]);
1027
+ }
1028
+ export function computeExecutionDataTransferDigest(plan) {
1029
+ return sha256(jsonReady(sortedBy(plan.dataTransfers, (transfer) => transfer.id).map((transfer) => ({ ...clone(transfer), transferHash: transfer.transferHash ?? computeDataTransferHash(transfer) }))));
1030
+ }
1031
+ export function computeExecutionBudgetDigest(plan) {
1032
+ return sha256(jsonReady(sortedBy(plan.budgets, (budget) => budget.id).map((budget) => ({ ...clone(budget), budgetHash: budget.budgetHash ?? computeBudgetHash(budget) }))));
1033
+ }
1034
+ export function computeExecutionStepInputHash(step, contextHash, pipelinePlanHash, referencePlanHash, promptArtifactHashValue, referenceContentHashes = []) {
1035
+ return sha256({
1036
+ stepId: step.id,
1037
+ stepHash: step.stepHash ?? computePipelineStepHash(step),
1038
+ contextHash,
1039
+ pipelinePlanHash,
1040
+ referencePlanHash,
1041
+ promptArtifactHash: promptArtifactHashValue,
1042
+ referenceContentHashes: sortedStrings(referenceContentHashes),
1043
+ inputArtifactRoles: sortedStrings(step.inputArtifactRoles),
1044
+ outputArtifactRoles: sortedStrings(step.outputArtifactRoles),
1045
+ });
1046
+ }
1047
+ function executionSnapshot(input) {
1048
+ const authorization = input.executionAuthorization;
1049
+ return {
1050
+ kind: 'execution',
1051
+ caseId: input.pipelinePlan.caseId,
1052
+ caseRevision: input.pipelinePlan.caseRevision,
1053
+ contextHash: input.contextHash,
1054
+ constraintIRHash: input.constraintIR.deterministicSignature,
1055
+ compilationSignature: input.constraintIR.deterministicSignature,
1056
+ referencePlanHash: input.referencePlan.planHash,
1057
+ pipelinePlanHash: input.pipelinePlan.planHash,
1058
+ outputContractHash: computeOutputContractHash(input.outputContract),
1059
+ promptArtifactHash: promptArtifactHash(input.promptArtifact),
1060
+ adapterProfileDigests: executionAdapterProfileDigests(input.pipelinePlan),
1061
+ destinations: sortedStrings(input.pipelinePlan.dataTransfers.map((transfer) => transfer.destination)),
1062
+ dataTransferDigest: computeExecutionDataTransferDigest(input.pipelinePlan),
1063
+ budgetDigest: computeExecutionBudgetDigest(input.pipelinePlan),
1064
+ remoteCallAuthorizationIds: sortedStrings(authorization.remoteCallAuthorizationIds),
1065
+ };
1066
+ }
1067
+ function executionInputReasons(input) {
1068
+ const reasons = [];
1069
+ if (!input || input.schemaVersion !== OFFLINE_EXECUTION_INPUT_SCHEMA_VERSION)
1070
+ reasons.push('OFFLINE_EXECUTION_INPUT_SCHEMA_INVALID');
1071
+ if (!input.context || input.contextHash !== input.context.contextHash || computeCompilationContextHash(input.context) !== input.contextHash)
1072
+ reasons.push('PROMPT_CONTEXT_HASH_MISMATCH');
1073
+ if (!input.constraintIR || integrityReasonsForConstraintIR(input.constraintIR, input.context, input.pipelinePlan?.caseId ?? '', input.pipelinePlan?.caseRevision ?? -1).length)
1074
+ reasons.push('CONSTRAINT_IR_INVALID');
1075
+ if (!input.referencePlan || integrityReasonsForReferencePlan(input.referencePlan, input.constraintIR, input.pipelinePlan?.caseId ?? '', input.pipelinePlan?.caseRevision ?? -1, input.contextHash).length)
1076
+ reasons.push('REFERENCE_PLAN_INVALID');
1077
+ if (!input.pipelinePlan || integrityReasonsForPipelinePlan(input.pipelinePlan, input.constraintIR, input.referencePlan, input.outputContract, input.pipelinePlan?.caseId ?? '', input.pipelinePlan?.caseRevision ?? -1, input.contextHash).length)
1078
+ reasons.push('PIPELINE_PLAN_INVALID');
1079
+ if (input.pipelinePlan && (input.pipelinePlan.caseId !== input.context.caseSpecId || input.pipelinePlan.caseRevision !== input.context.caseSpecRevision))
1080
+ reasons.push('EXECUTION_CASE_MISMATCH');
1081
+ if (outputContractReasons(input.outputContract).length)
1082
+ reasons.push('OUTPUT_CONTRACT_INVALID');
1083
+ reasons.push(...promptArtifactIntegrityReasons(input.promptArtifact, input.promptGuardResult));
1084
+ if (!input.executionAuthorization || computeExecutionAuthorizationHash(input.executionAuthorization) !== input.executionAuthorization.authorizationHash)
1085
+ reasons.push('EXECUTION_AUTHORIZATION_INVALID');
1086
+ if (input.executionAuthorization && input.executionAuthorization.caseId !== input.pipelinePlan?.caseId)
1087
+ reasons.push('EXECUTION_AUTHORIZATION_CASE_MISMATCH');
1088
+ if (input.executionAuthorization && input.executionAuthorization.caseRevision !== input.pipelinePlan?.caseRevision)
1089
+ reasons.push('EXECUTION_AUTHORIZATION_REVISION_MISMATCH');
1090
+ if (input.executionAuthorization && input.executionAuthorization.promptArtifactHash !== promptArtifactHash(input.promptArtifact))
1091
+ reasons.push('EXECUTION_AUTHORIZATION_PROMPT_MISMATCH');
1092
+ for (const step of input.pipelinePlan?.steps ?? []) {
1093
+ const budget = step.budget;
1094
+ if (!Number.isInteger(budget.maximumCalls) || budget.maximumCalls < 1)
1095
+ reasons.push('STEP_BUDGET_CALL_LIMIT_INVALID');
1096
+ if (!Number.isInteger(budget.maximumRetries) || budget.maximumRetries < 0 || budget.maximumRetries >= Math.max(budget.maximumCalls, 1))
1097
+ reasons.push('STEP_BUDGET_RETRY_LIMIT_INVALID');
1098
+ if (!Number.isInteger(budget.timeoutMs) || budget.timeoutMs <= 0)
1099
+ reasons.push('STEP_BUDGET_TIMEOUT_INVALID');
1100
+ if (budget.maximumCost !== undefined && (!Number.isFinite(budget.maximumCost) || budget.maximumCost < 0))
1101
+ reasons.push('STEP_BUDGET_COST_INVALID');
1102
+ if (budget.maximumBytes !== undefined && (!Number.isInteger(budget.maximumBytes) || budget.maximumBytes < 0))
1103
+ reasons.push('STEP_BUDGET_BYTES_INVALID');
1104
+ if (budget.budgetHash !== undefined && (!isHash(budget.budgetHash) || computeBudgetHash(budget) !== budget.budgetHash))
1105
+ reasons.push('STEP_BUDGET_HASH_INVALID');
1106
+ if (step.dataTransfer.maximumBytes !== undefined && (!Number.isInteger(step.dataTransfer.maximumBytes) || step.dataTransfer.maximumBytes < 0))
1107
+ reasons.push('STEP_TRANSFER_BYTES_INVALID');
1108
+ if (step.dataTransfer.transferHash !== undefined && (!isHash(step.dataTransfer.transferHash) || computeDataTransferHash(step.dataTransfer) !== step.dataTransfer.transferHash))
1109
+ reasons.push('STEP_TRANSFER_HASH_INVALID');
1110
+ }
1111
+ return sortedStrings(reasons);
1112
+ }
1113
+ function requiredRemoteStep(step) {
1114
+ return step.mayCreateChargedSubmission || step.destination !== 'local';
1115
+ }
1116
+ function remoteSnapshot(authorization) {
1117
+ return {
1118
+ kind: 'remote_call',
1119
+ caseId: authorization.caseId,
1120
+ caseRevision: authorization.caseRevision,
1121
+ contextHash: authorization.contextHash,
1122
+ stepId: authorization.stepId,
1123
+ purpose: authorization.purpose,
1124
+ inputHash: authorization.inputHash,
1125
+ inputManifestHash: authorization.inputManifestHash,
1126
+ modelId: authorization.modelId,
1127
+ modelVersion: authorization.modelVersion,
1128
+ permittedArtifactHashes: sortedStrings(authorization.permittedArtifactHashes),
1129
+ permittedScopeIds: sortedStrings(authorization.permittedScopeIds),
1130
+ constraintIds: sortedStrings(authorization.constraintIds),
1131
+ adapterId: authorization.adapterId,
1132
+ adapterDigest: authorization.adapterDigest,
1133
+ profileDigest: authorization.profileDigest,
1134
+ destination: authorization.destination,
1135
+ region: authorization.region,
1136
+ dataCategories: sortedStrings(authorization.dataCategories),
1137
+ maximumCalls: authorization.maximumCalls,
1138
+ maximumRetries: authorization.maximumRetries,
1139
+ maximumBytes: authorization.maximumBytes,
1140
+ timeoutMs: authorization.timeoutMs,
1141
+ maximumCost: authorization.maximumCost,
1142
+ currency: authorization.currency,
1143
+ idempotencyKey: authorization.idempotencyKey,
1144
+ };
1145
+ }
1146
+ function remoteAuthorizationReasons(input) {
1147
+ const reasons = [];
1148
+ const byStep = new Map();
1149
+ const seenAuthorizationIds = new Set();
1150
+ const authorizationIds = new Set(input.executionAuthorization.remoteCallAuthorizationIds);
1151
+ const referenceHashes = input.referencePlan.ordered.map((reference) => reference.contentHash);
1152
+ for (const authorization of sortedBy(input.remoteCallAuthorizations, (item) => item.id)) {
1153
+ if (seenAuthorizationIds.has(authorization.id))
1154
+ reasons.push('REMOTE_AUTHORIZATION_ID_DUPLICATE');
1155
+ seenAuthorizationIds.add(authorization.id);
1156
+ if (!authorizationIds.has(authorization.id))
1157
+ reasons.push('REMOTE_AUTHORIZATION_NOT_BOUND');
1158
+ if (computeRemoteCallAuthorizationHash(authorization) !== authorization.authorizationHash)
1159
+ reasons.push('REMOTE_AUTHORIZATION_HASH_MISMATCH');
1160
+ const step = input.pipelinePlan.steps.find((item) => item.id === authorization.stepId);
1161
+ if (!step || !requiredRemoteStep(step))
1162
+ reasons.push('REMOTE_AUTHORIZATION_STEP_INVALID');
1163
+ if (step) {
1164
+ const expectedInputHash = computeExecutionStepInputHash(step, input.contextHash, input.pipelinePlan.planHash, input.referencePlan.planHash, promptArtifactHash(input.promptArtifact), referenceHashes);
1165
+ if (authorization.inputHash !== expectedInputHash)
1166
+ reasons.push('REMOTE_AUTHORIZATION_INPUT_MISMATCH');
1167
+ if (authorization.caseId !== input.pipelinePlan.caseId || authorization.caseRevision !== input.pipelinePlan.caseRevision || authorization.contextHash !== input.contextHash)
1168
+ reasons.push('REMOTE_AUTHORIZATION_CONTEXT_MISMATCH');
1169
+ if (authorization.adapterId !== step.adapterId || authorization.adapterDigest !== step.adapterVersion.digest || authorization.destination !== step.destination)
1170
+ reasons.push('REMOTE_AUTHORIZATION_ADAPTER_MISMATCH');
1171
+ if (authorization.profileDigest !== undefined && authorization.profileDigest !== step.profileVersion.digest)
1172
+ reasons.push('REMOTE_AUTHORIZATION_PROFILE_MISMATCH');
1173
+ if (authorization.maximumCalls > step.budget.maximumCalls || authorization.maximumRetries > step.budget.maximumRetries || authorization.timeoutMs > step.budget.timeoutMs)
1174
+ reasons.push('REMOTE_AUTHORIZATION_BUDGET_EXCEEDED');
1175
+ if (byStep.has(step.id))
1176
+ reasons.push('REMOTE_AUTHORIZATION_STEP_DUPLICATE');
1177
+ else
1178
+ byStep.set(step.id, authorization);
1179
+ const preflight = dispatchPreflight(authorization, remoteSnapshot(authorization), input.options?.now ?? FIXED_M5_TIME);
1180
+ if (preflight.status !== 'authorized')
1181
+ reasons.push(...preflight.reasons.map((reason) => `REMOTE_${reason}`));
1182
+ }
1183
+ }
1184
+ for (const step of input.pipelinePlan.steps) {
1185
+ if (!requiredRemoteStep(step))
1186
+ continue;
1187
+ const authorization = byStep.get(step.id);
1188
+ if (!authorization)
1189
+ reasons.push('REMOTE_AUTHORIZATION_MISSING');
1190
+ else if (!authorizationIds.has(authorization.id))
1191
+ reasons.push('REMOTE_AUTHORIZATION_ID_NOT_BOUND');
1192
+ }
1193
+ for (const id of authorizationIds)
1194
+ if (!input.remoteCallAuthorizations.some((authorization) => authorization.id === id))
1195
+ reasons.push('REMOTE_AUTHORIZATION_RECORD_MISSING');
1196
+ return sortedStrings(reasons);
1197
+ }
1198
+ function virtualArtifact(runId, step, role, mediaType) {
1199
+ const contentHash = sha256({ fixture: 'voce-offline-mock-artifact', runId, stepId: step.id, role, mediaType });
1200
+ return {
1201
+ id: `mock-artifact-${contentHash.slice('sha256:'.length, 'sha256:'.length + 24)}`,
1202
+ storeId: 'voce-mock-store',
1203
+ contentHash,
1204
+ mediaType,
1205
+ role,
1206
+ resolverId: 'voce.mock.offline',
1207
+ availability: 'available',
1208
+ retentionClass: 'fixture',
1209
+ redactionPolicy: 'hash-only',
1210
+ };
1211
+ }
1212
+ function mockMediaType(step) {
1213
+ if (step.adapterId === 'mock.jpeg-generator')
1214
+ return 'image/jpeg';
1215
+ if (step.type === 'postprocess' || step.type === 'normalize')
1216
+ return 'image/png';
1217
+ return step.type === 'generate' ? 'image/png' : 'application/json';
1218
+ }
1219
+ export class MockProviderAdapter {
1220
+ id;
1221
+ version;
1222
+ digest;
1223
+ profileDigest;
1224
+ offline = true;
1225
+ options;
1226
+ constructor(options = {}, id = 'voce.mock.offline') {
1227
+ this.id = id;
1228
+ this.version = clone(options.version ?? { id, version: '1.0.0', digest: options.digest ?? sha256({ adapter: id, version: '1.0.0', fixture: 'offline' }) });
1229
+ this.digest = options.digest ?? this.version.digest;
1230
+ this.profileDigest = options.profileDigest;
1231
+ this.options = { failStepIds: sortedStrings(options.failStepIds), unknownStepIds: sortedStrings(options.unknownStepIds), retryableFailureStepIds: sortedStrings(options.retryableFailureStepIds) };
1232
+ }
1233
+ render(request) {
1234
+ const safeRequest = clone(request);
1235
+ const requestHash = computeProviderRenderRequestHash(safeRequest);
1236
+ if (requestHash !== safeRequest.requestHash) {
1237
+ const failed = { schemaVersion: PROVIDER_RENDER_RESULT_SCHEMA_VERSION, status: 'failed', requestHash: safeRequest.requestHash, adapterId: this.id, adapterVersion: clone(this.version), outputArtifacts: [], metadata: { offline: true, provider: this.id }, failureCode: 'PROVIDER_RENDER_REQUEST_HASH_MISMATCH' };
1238
+ return clone({ ...failed, resultHash: sha256(jsonReady(failed)) });
1239
+ }
1240
+ const base = { schemaVersion: PROVIDER_RENDER_RESULT_SCHEMA_VERSION, status: 'ok', requestHash, adapterId: this.id, adapterVersion: clone(this.version), providerRequestId: `mock-request-${hashId('request', { requestHash, adapter: this.id }).slice('request-'.length)}`, outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: this.id } };
1241
+ return clone({ ...base, resultHash: sha256(jsonReady(base)) });
1242
+ }
1243
+ executeStep(context) {
1244
+ const stepId = context.step.id;
1245
+ const unknown = this.options.unknownStepIds?.includes(stepId) || context.options.unknownStepIds?.includes(stepId);
1246
+ if (unknown)
1247
+ return { status: 'submission_unknown', outputArtifacts: [], metadata: { offline: true, virtual: true, provider: this.id }, providerRequestId: `mock-unknown-${hashId('request', { stepId, attempt: context.attempt }).slice('request-'.length)}`, failureCode: 'REMOTE_SUBMISSION_UNKNOWN', actualCost: 0 };
1248
+ const retryable = this.options.retryableFailureStepIds?.includes(stepId) || context.options.retryableFailureStepIds?.includes(stepId);
1249
+ const shouldFail = this.options.failStepIds?.includes(stepId) || context.options.failStepIds?.includes(stepId);
1250
+ if (shouldFail && (!retryable || context.attempt === 1))
1251
+ return { status: 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, provider: this.id }, failureCode: 'MOCK_STEP_FAILED', actualCost: 0 };
1252
+ const mediaType = mockMediaType(context.step);
1253
+ const artifacts = [];
1254
+ if (context.step.type === 'resolve_asset')
1255
+ artifacts.push(virtualArtifact(context.runId, context.step, 'provider-readable-reference', 'image/png'));
1256
+ if (context.step.type === 'publish_asset')
1257
+ artifacts.push(virtualArtifact(context.runId, context.step, 'published_reference', 'image/png'));
1258
+ if (context.step.type === 'generate')
1259
+ artifacts.push(virtualArtifact(context.runId, context.step, 'generated-image', mediaType));
1260
+ if (context.step.type === 'postprocess')
1261
+ artifacts.push(...context.step.outputArtifactRoles.map((role) => virtualArtifact(context.runId, context.step, role, mediaType)));
1262
+ if (context.step.type === 'normalize')
1263
+ artifacts.push(virtualArtifact(context.runId, context.step, 'normalized-image', mediaType === 'application/json' ? 'image/png' : mediaType));
1264
+ const remote = requiredRemoteStep(context.step);
1265
+ return { status: 'succeeded', outputArtifacts: artifacts, metadata: { offline: true, virtual: true, provider: this.id, stepType: context.step.type, destination: context.step.destination, budgetId: context.step.budget.id }, ...(remote ? { providerRequestId: `mock-request-${hashId('request', { stepId, attempt: context.attempt }).slice('request-'.length)}` } : {}), actualCost: 0 };
1266
+ }
1267
+ reconcileStep(context) {
1268
+ const mediaType = mockMediaType(context.step);
1269
+ const outputArtifacts = context.step.outputArtifactRoles.map((role) => virtualArtifact(context.runId, context.step, role, mediaType));
1270
+ return {
1271
+ status: 'succeeded',
1272
+ outputArtifacts,
1273
+ metadata: { offline: true, virtual: true, reconciled: true, provider: this.id, stepType: context.step.type, destination: context.step.destination, budgetId: context.step.budget.id },
1274
+ providerRequestId: `mock-reconciled-${hashId('request', { runId: context.runId, stepId: context.step.id }).slice('request-'.length)}`,
1275
+ actualCost: 0,
1276
+ };
1277
+ }
1278
+ }
1279
+ export class MockGeneratorAdapter extends MockProviderAdapter {
1280
+ constructor(options = {}) { super(options, 'mock.image-generator'); }
1281
+ }
1282
+ export class MockPostprocessorAdapter extends MockProviderAdapter {
1283
+ constructor(options = {}) { super(options, 'voce.postprocessor'); }
1284
+ }
1285
+ export class MockNormalizerAdapter extends MockProviderAdapter {
1286
+ constructor(options = {}) { super(options, 'voce.image-normalizer'); }
1287
+ }
1288
+ export class MockStructuralValidatorAdapter extends MockProviderAdapter {
1289
+ constructor(options = {}) { super(options, 'voce.structural-validator'); }
1290
+ }
1291
+ function adapterCanRender(adapter) {
1292
+ return typeof adapter.render === 'function';
1293
+ }
1294
+ function eventProjection(event) {
1295
+ const value = clone(event);
1296
+ delete value.eventHash;
1297
+ delete value.id;
1298
+ delete value.runId;
1299
+ delete value.sequence;
1300
+ delete value.at;
1301
+ return value;
1302
+ }
1303
+ function receiptProjection(receipt) {
1304
+ const value = clone(receipt);
1305
+ delete value.receiptHash;
1306
+ delete value.id;
1307
+ delete value.runId;
1308
+ delete value.eventIds;
1309
+ delete value.firstSequence;
1310
+ delete value.lastSequence;
1311
+ return value;
1312
+ }
1313
+ function cleanupReceiptProjection(receipt) {
1314
+ const value = clone(receipt);
1315
+ delete value.receiptHash;
1316
+ delete value.id;
1317
+ delete value.runId;
1318
+ delete value.eventIds;
1319
+ return value;
1320
+ }
1321
+ function compensationReceiptProjection(receipt) {
1322
+ const value = clone(receipt);
1323
+ delete value.receiptHash;
1324
+ delete value.id;
1325
+ delete value.runId;
1326
+ delete value.eventIds;
1327
+ return value;
1328
+ }
1329
+ function executionRunProjection(run) {
1330
+ const value = clone(run);
1331
+ delete value.runHash;
1332
+ delete value.createdAt;
1333
+ delete value.updatedAt;
1334
+ delete value.eventCount;
1335
+ return value;
1336
+ }
1337
+ function makeEvent(runId, sequence, step, state, now, promptHash, authorizationId, inputHash, outputHashes, attempt, retriesUsed, providerRequestId, failureCode, cost, bytes) {
1338
+ const base = {
1339
+ schemaVersion: STEP_EVENT_SCHEMA_VERSION,
1340
+ id: hashId('step-event', { runId, sequence, stepId: step.id, state, inputHash, outputHashes }),
1341
+ runId,
1342
+ sequence,
1343
+ stepId: step.id,
1344
+ state,
1345
+ at: now,
1346
+ contextHash: '',
1347
+ pipelinePlanHash: '',
1348
+ promptArtifactHash: promptHash,
1349
+ ...(authorizationId ? { authorizationId } : {}),
1350
+ ...(inputHash ? { inputHash } : {}),
1351
+ outputHashes: sortedStrings(outputHashes),
1352
+ adapterId: step.adapterId,
1353
+ adapterVersion: clone(step.adapterVersion),
1354
+ profileDigest: step.profileVersion.digest,
1355
+ ...(providerRequestId ? { providerRequestId } : {}),
1356
+ destination: step.destination,
1357
+ dataCategories: sortedStrings(step.dataTransfer.dataCategories),
1358
+ budgetId: step.budget.id,
1359
+ attempt,
1360
+ retriesUsed,
1361
+ ...(cost === undefined ? {} : { cost }),
1362
+ ...(bytes === undefined ? {} : { bytes }),
1363
+ ...(failureCode ? { failureCode } : {}),
1364
+ safeReferences: [],
1365
+ };
1366
+ return { ...base, eventHash: sha256(eventProjection(base)) };
1367
+ }
1368
+ function bindEvent(event, contextHash, pipelinePlanHash) {
1369
+ const base = { ...clone(event), contextHash, pipelinePlanHash, eventHash: '' };
1370
+ return clone({ ...base, eventHash: sha256(eventProjection(base)) });
1371
+ }
1372
+ function makeCleanupEvent(runId, sequence, cleanup, state, now, failureCode) {
1373
+ const step = {
1374
+ schemaVersion: 'voce.pipeline-step/v1alpha1',
1375
+ id: cleanup.id,
1376
+ type: 'cleanup',
1377
+ adapterId: 'voce.cleanup',
1378
+ adapterVersion: { id: 'voce.cleanup', version: '1.0.0', digest: sha256({ adapter: 'voce.cleanup' }) },
1379
+ profileVersion: { id: 'voce.cleanup', version: '1.0.0', digest: sha256({ profile: 'voce.cleanup' }) },
1380
+ inputArtifactRoles: cleanup.artifactRoles,
1381
+ outputArtifactRoles: [],
1382
+ dependsOn: [],
1383
+ budget: { schemaVersion: 'voce.budget/v1alpha1', id: `cleanup-${cleanup.id}`, maximumCalls: 1, maximumRetries: 2, timeoutMs: 30_000 },
1384
+ dataTransfer: { schemaVersion: 'voce.data-transfer/v1alpha1', id: `cleanup-transfer-${cleanup.id}`, adapterId: 'voce.cleanup', destination: cleanup.destination, dataCategories: cleanup.dataCategories, purpose: 'cleanup' },
1385
+ destination: cleanup.destination,
1386
+ cancellation: { cancellable: false, onCancel: 'continue' },
1387
+ cleanupObligationIds: [], compensationIds: [], mayCreateChargedSubmission: false, capability: 'cleanup',
1388
+ };
1389
+ return makeEvent(runId, sequence, step, state, now, '', undefined, undefined, [], 1, 0, undefined, failureCode);
1390
+ }
1391
+ function makeStepReceipt(runId, step, events, cleanupStatus) {
1392
+ const terminal = events[events.length - 1];
1393
+ const base = {
1394
+ schemaVersion: STEP_RECEIPT_SCHEMA_VERSION,
1395
+ id: hashId('step-receipt', { runId, stepId: step.id, eventIds: events.map((event) => event.id), state: terminal?.state }),
1396
+ runId,
1397
+ stepId: step.id,
1398
+ state: terminal?.state ?? 'skipped',
1399
+ eventIds: events.map((event) => event.id),
1400
+ firstSequence: events[0]?.sequence ?? 0,
1401
+ lastSequence: terminal?.sequence ?? 0,
1402
+ ...(terminal?.authorizationId ? { authorizationId: terminal.authorizationId } : {}),
1403
+ ...(terminal?.inputHash ? { inputHash: terminal.inputHash } : {}),
1404
+ outputHashes: sortedStrings(events.flatMap((event) => event.outputHashes)),
1405
+ adapterId: step.adapterId,
1406
+ adapterVersion: clone(step.adapterVersion),
1407
+ profileDigest: step.profileVersion.digest,
1408
+ ...(terminal?.providerRequestId ? { providerRequestId: terminal.providerRequestId } : {}),
1409
+ destination: step.destination,
1410
+ dataCategories: sortedStrings(step.dataTransfer.dataCategories),
1411
+ budgetId: step.budget.id,
1412
+ maximumCalls: step.budget.maximumCalls,
1413
+ maximumRetries: step.budget.maximumRetries,
1414
+ timeoutMs: step.budget.timeoutMs,
1415
+ attempts: Math.max(...events.map((event) => event.attempt), 1),
1416
+ retriesUsed: Math.max(...events.map((event) => event.retriesUsed), 0),
1417
+ ...(terminal?.cost === undefined ? {} : { actualCost: terminal.cost }),
1418
+ ...(terminal?.bytes === undefined ? {} : { actualBytes: terminal.bytes }),
1419
+ ...(terminal?.failureCode ? { failureCode: terminal.failureCode } : {}),
1420
+ cleanupStatus,
1421
+ };
1422
+ return clone({ ...base, receiptHash: sha256(receiptProjection(base)) });
1423
+ }
1424
+ function makeCleanupReceipt(runId, cleanup, events, failed, maximumRetries) {
1425
+ const base = {
1426
+ schemaVersion: CLEANUP_RECEIPT_SCHEMA_VERSION,
1427
+ id: hashId('cleanup-receipt', { runId, cleanupId: cleanup.id }),
1428
+ runId,
1429
+ cleanupId: cleanup.id,
1430
+ status: failed ? 'cleanup_failed' : 'succeeded',
1431
+ attempts: failed ? maximumRetries + 1 : 1,
1432
+ maximumRetries,
1433
+ artifactRoles: sortedStrings(cleanup.artifactRoles),
1434
+ destination: cleanup.destination,
1435
+ dataCategories: sortedStrings(cleanup.dataCategories),
1436
+ eventIds: events.map((event) => event.id),
1437
+ ...(failed ? { failureCode: 'CLEANUP_FAILED' } : {}),
1438
+ };
1439
+ return clone({ ...base, receiptHash: sha256(cleanupReceiptProjection(base)) });
1440
+ }
1441
+ function makeCompensationReceipt(runId, compensation, events, failed, maximumRetries) {
1442
+ const base = {
1443
+ schemaVersion: COMPENSATION_RECEIPT_SCHEMA_VERSION,
1444
+ id: hashId('compensation-receipt', { runId, compensationId: compensation.id }),
1445
+ runId,
1446
+ compensationId: compensation.id,
1447
+ trigger: compensation.trigger,
1448
+ cleanupId: compensation.cleanupId,
1449
+ status: failed ? 'cleanup_failed' : 'succeeded',
1450
+ attempts: failed ? maximumRetries + 1 : 1,
1451
+ maximumRetries,
1452
+ eventIds: events.map((event) => event.id),
1453
+ ...(failed ? { failureCode: 'CLEANUP_FAILED' } : {}),
1454
+ };
1455
+ return clone({ ...base, receiptHash: sha256(compensationReceiptProjection(base)) });
1456
+ }
1457
+ function normalizedExecutionOptions(options = {}) {
1458
+ const maximumCleanupRetries = Number.isInteger(options.maximumCleanupRetries) && (options.maximumCleanupRetries ?? 0) >= 0
1459
+ ? Math.min(options.maximumCleanupRetries ?? 0, 3)
1460
+ : 1;
1461
+ return {
1462
+ now: options.now ?? FIXED_M5_TIME,
1463
+ failStepIds: sortedStrings(options.failStepIds),
1464
+ unknownStepIds: sortedStrings(options.unknownStepIds),
1465
+ retryableFailureStepIds: sortedStrings(options.retryableFailureStepIds),
1466
+ cancelBeforeStepId: options.cancelBeforeStepId ?? '',
1467
+ workerRestartAfterStepId: options.workerRestartAfterStepId ?? '',
1468
+ cleanupFailureIds: sortedStrings(options.cleanupFailureIds),
1469
+ compensationFailureIds: sortedStrings(options.compensationFailureIds),
1470
+ maximumCleanupRetries,
1471
+ };
1472
+ }
1473
+ function executionRunHash(run) {
1474
+ return sha256(executionRunProjection(run));
1475
+ }
1476
+ export function computeExecutionRunHash(run) {
1477
+ return executionRunHash(run);
1478
+ }
1479
+ function traceProjection(trace) {
1480
+ const value = clone(trace);
1481
+ delete value.traceHash;
1482
+ value.events = sortedBy(value.events ?? [], (event) => `${event.sequence}|${event.id}`);
1483
+ value.receipts = sortedBy(value.receipts ?? [], (receipt) => receipt.stepId);
1484
+ value.remoteCallRuns = sortedBy(value.remoteCallRuns ?? [], (item) => item.stepId);
1485
+ value.cleanupReceipts = sortedBy(value.cleanupReceipts ?? [], (item) => item.cleanupId);
1486
+ value.compensationReceipts = sortedBy(value.compensationReceipts ?? [], (item) => item.compensationId);
1487
+ return value;
1488
+ }
1489
+ export function computeExecutionTraceHash(trace) {
1490
+ return sha256(traceProjection(trace));
1491
+ }
1492
+ export function projectExecutionTrace(trace) {
1493
+ const base = traceProjection(trace);
1494
+ return clone({ ...base, traceHash: sha256(base) });
1495
+ }
1496
+ export const deterministicTraceProjection = projectExecutionTrace;
1497
+ export function serializeExecutionTrace(trace) {
1498
+ return canonicalize(jsonReady(projectExecutionTrace(trace)));
1499
+ }
1500
+ export const executionTraceJson = serializeExecutionTrace;
1501
+ function evaluationForRun(runId, artifacts, needsReview, outcome) {
1502
+ const artifactIds = sortedStrings(artifacts.map((artifact) => artifact.id));
1503
+ const failed = outcome === 'failure';
1504
+ const uncertain = outcome === 'cancel' || outcome === 'unknown';
1505
+ const finding = {
1506
+ id: hashId('evaluation-finding', { runId, status: failed ? 'fail' : needsReview || uncertain ? 'needs_review' : 'pass' }),
1507
+ code: failed ? 'TECHNICAL_EXECUTION_FAILED' : outcome === 'unknown' ? 'REMOTE_SUBMISSION_UNKNOWN' : outcome === 'cancel' ? 'EXECUTION_CANCELLED' : needsReview ? 'HUMAN_ACCEPTANCE_REQUIRED' : 'STRUCTURAL_VALIDATION_PASSED',
1508
+ status: failed ? 'fail' : needsReview || uncertain ? 'needs_review' : 'pass',
1509
+ severity: failed ? 'error' : needsReview || uncertain ? 'warning' : 'info',
1510
+ explanation: failed ? 'The offline execution did not complete all pipeline steps.' : outcome === 'unknown' ? 'The provider-semantic submission remains uncertain and requires explicit reconciliation.' : outcome === 'cancel' ? 'The execution was cancelled without changing its technical execution record into a provider failure.' : needsReview ? 'Technical execution completed and awaits separate human acceptance.' : 'The offline structural validation path completed.',
1511
+ sourceIds: [],
1512
+ artifactIds,
1513
+ };
1514
+ const base = {
1515
+ schemaVersion: EVALUATION_SCHEMA_VERSION,
1516
+ id: hashId('evaluation', { runId, finding: finding.code }),
1517
+ runId,
1518
+ technicalStatus: failed ? 'failed' : needsReview || uncertain ? 'needs_review' : 'passed',
1519
+ findings: [finding],
1520
+ artifactIds,
1521
+ };
1522
+ return clone({ ...base, evaluationHash: sha256(base) });
1523
+ }
1524
+ function humanAcceptanceForRun(runId, artifacts) {
1525
+ const base = {
1526
+ schemaVersion: HUMAN_ACCEPTANCE_SCHEMA_VERSION,
1527
+ id: hashId('human-acceptance', { runId }),
1528
+ runId,
1529
+ status: 'pending',
1530
+ artifactIds: sortedStrings(artifacts.map((artifact) => artifact.id)),
1531
+ };
1532
+ return clone({ ...base, acceptanceHash: sha256(base) });
1533
+ }
1534
+ function updateHumanAcceptanceHash(acceptance) {
1535
+ const base = clone(acceptance);
1536
+ delete base.acceptanceHash;
1537
+ return clone({ ...base, acceptanceHash: sha256(base) });
1538
+ }
1539
+ function updateStepReceiptHash(receipt) {
1540
+ const base = clone(receipt);
1541
+ delete base.receiptHash;
1542
+ return clone({ ...base, receiptHash: sha256(receiptProjection(base)) });
1543
+ }
1544
+ function updateRemoteCallRunHash(run) {
1545
+ const base = clone(run);
1546
+ delete base.runHash;
1547
+ delete base.id;
1548
+ delete base.runId;
1549
+ return clone({ ...run, runHash: sha256(base) });
1550
+ }
1551
+ function stepPurpose(step) {
1552
+ if (step.type === 'generate')
1553
+ return 'generation';
1554
+ if (step.type === 'semantic_review')
1555
+ return 'semantic_review';
1556
+ if (step.type === 'publish_asset')
1557
+ return 'asset_publication';
1558
+ if (step.type === 'resolve_asset')
1559
+ return 'reference_interpretation';
1560
+ return 'postprocessing';
1561
+ }
1562
+ function expectedRemoteArtifacts(input) {
1563
+ return sortedStrings(input.referencePlan.ordered.map((reference) => reference.contentHash));
1564
+ }
1565
+ function expectedRemoteScopes(input) {
1566
+ return sortedStrings(input.referencePlan.ordered.flatMap((reference) => reference.ontologyScopes));
1567
+ }
1568
+ function expectedRemoteConstraints(input) {
1569
+ return sortedStrings(input.constraintIR.constraints.map((constraint) => constraint.id));
1570
+ }
1571
+ function remoteAuthorizationExactReasons(input) {
1572
+ const reasons = [];
1573
+ const artifacts = expectedRemoteArtifacts(input);
1574
+ const scopes = expectedRemoteScopes(input);
1575
+ const constraints = expectedRemoteConstraints(input);
1576
+ for (const authorization of input.remoteCallAuthorizations) {
1577
+ const step = input.pipelinePlan.steps.find((candidate) => candidate.id === authorization.stepId);
1578
+ if (!step)
1579
+ continue;
1580
+ if (authorization.purpose !== stepPurpose(step))
1581
+ reasons.push('REMOTE_AUTHORIZATION_PURPOSE_MISMATCH');
1582
+ if (canonicalize(sortedStrings(authorization.permittedArtifactHashes)) !== canonicalize(artifacts))
1583
+ reasons.push('REMOTE_AUTHORIZATION_ARTIFACT_SCOPE_MISMATCH');
1584
+ if (canonicalize(sortedStrings(authorization.permittedScopeIds)) !== canonicalize(scopes))
1585
+ reasons.push('REMOTE_AUTHORIZATION_SCOPE_MISMATCH');
1586
+ if (canonicalize(sortedStrings(authorization.constraintIds)) !== canonicalize(constraints))
1587
+ reasons.push('REMOTE_AUTHORIZATION_CONSTRAINT_SCOPE_MISMATCH');
1588
+ if (authorization.profileDigest !== step.profileVersion.digest)
1589
+ reasons.push('REMOTE_AUTHORIZATION_PROFILE_REQUIRED');
1590
+ if (authorization.region !== step.dataTransfer.region)
1591
+ reasons.push('REMOTE_AUTHORIZATION_REGION_MISMATCH');
1592
+ if (canonicalize(sortedStrings(authorization.dataCategories)) !== canonicalize(sortedStrings(step.dataTransfer.dataCategories)))
1593
+ reasons.push('REMOTE_AUTHORIZATION_DATA_CATEGORIES_MISMATCH');
1594
+ const expectedMaximumBytes = step.budget.maximumBytes ?? step.dataTransfer.maximumBytes;
1595
+ const expectedMaximumCost = step.budget.maximumCost;
1596
+ if (authorization.maximumCalls !== step.budget.maximumCalls || authorization.maximumRetries !== step.budget.maximumRetries || authorization.timeoutMs !== step.budget.timeoutMs)
1597
+ reasons.push('REMOTE_AUTHORIZATION_BUDGET_MISMATCH');
1598
+ if (authorization.maximumBytes !== expectedMaximumBytes || authorization.maximumCost !== expectedMaximumCost || authorization.currency !== step.budget.currency)
1599
+ reasons.push('REMOTE_AUTHORIZATION_BUDGET_BOUND_MISMATCH');
1600
+ }
1601
+ return sortedStrings(reasons);
1602
+ }
1603
+ function safeExecutionInputReasons(input) {
1604
+ try {
1605
+ return executionInputReasons(input);
1606
+ }
1607
+ catch {
1608
+ return ['OFFLINE_EXECUTION_INPUT_INVALID'];
1609
+ }
1610
+ }
1611
+ function safeRemoteAuthorizationReasons(input) {
1612
+ try {
1613
+ return [...remoteAuthorizationReasons(input), ...remoteAuthorizationExactReasons(input)];
1614
+ }
1615
+ catch {
1616
+ return ['REMOTE_AUTHORIZATION_INPUT_INVALID'];
1617
+ }
1618
+ }
1619
+ function blockedExecutionResult(code, reasons) {
1620
+ return { status: 'blocked', code, reasons: sortedStrings(reasons), events: [], receipts: [], remoteCallRuns: [], cleanupReceipts: [], compensationReceipts: [] };
1621
+ }
1622
+ function runtimeStepEvent(record, step, state, promptHash, authorizationId, inputHash, outputHashes, attempt, retriesUsed, providerRequestId, failureCode, cost, bytes) {
1623
+ const event = makeEvent(record.run.id, record.events.length + 1, step, state, record.options.now, promptHash, authorizationId, inputHash, outputHashes, attempt, retriesUsed, providerRequestId, failureCode, cost, bytes);
1624
+ return bindEvent(event, record.input.contextHash, record.input.pipelinePlan.planHash);
1625
+ }
1626
+ function appendRuntimeEvent(record, event) {
1627
+ record.events.push(clone(event));
1628
+ record.run.eventCount = record.events.length;
1629
+ }
1630
+ function runtimeCleanupEvent(record, cleanup, state, failureCode) {
1631
+ return bindEvent(makeCleanupEvent(record.run.id, record.events.length + 1, cleanup, state, record.options.now, failureCode), record.input.contextHash, record.input.pipelinePlan.planHash);
1632
+ }
1633
+ function outputArtifactsForResult(result) {
1634
+ return result.outputArtifacts.filter((artifact) => artifact.availability === 'available').map((artifact) => clone(artifact));
1635
+ }
1636
+ function cleanupConditionMatches(cleanup, outcome, workerRestarted) {
1637
+ if (workerRestarted && cleanup.conditions.includes('on_worker_restart'))
1638
+ return true;
1639
+ if (cleanup.conditions.includes('always'))
1640
+ return true;
1641
+ if (outcome === 'success' && cleanup.conditions.includes('on_success'))
1642
+ return true;
1643
+ if ((outcome === 'failure' || outcome === 'cancel') && cleanup.conditions.includes('on_failure_or_cancel'))
1644
+ return true;
1645
+ if (outcome === 'unknown' && cleanup.conditions.includes('on_submission_unknown'))
1646
+ return true;
1647
+ return false;
1648
+ }
1649
+ function compensationTrigger(outcome, workerRestarted) {
1650
+ if (workerRestarted)
1651
+ return 'worker_restart';
1652
+ if (outcome === 'failure')
1653
+ return 'failure';
1654
+ if (outcome === 'cancel')
1655
+ return 'cancel';
1656
+ if (outcome === 'unknown')
1657
+ return 'submission_unknown';
1658
+ return undefined;
1659
+ }
1660
+ function terminalOutcomeFromStep(state) {
1661
+ if (state === 'succeeded')
1662
+ return 'success';
1663
+ if (state === 'failed')
1664
+ return 'failure';
1665
+ if (state === 'cancelled')
1666
+ return 'cancel';
1667
+ if (state === 'submission_unknown')
1668
+ return 'unknown';
1669
+ return undefined;
1670
+ }
1671
+ function traceForRecord(record) {
1672
+ const base = {
1673
+ schemaVersion: EXECUTION_TRACE_SCHEMA_VERSION,
1674
+ runId: record.run.id,
1675
+ state: record.run.state,
1676
+ executionAuthorizationHash: record.input.executionAuthorization.authorizationHash,
1677
+ pipelinePlanHash: record.input.pipelinePlan.planHash,
1678
+ ...(record.run.promptArtifactHash ? { promptArtifactHash: record.run.promptArtifactHash } : {}),
1679
+ events: sortedBy(record.events, (event) => `${String(event.sequence).padStart(12, '0')}|${event.id}`),
1680
+ receipts: sortedBy(record.receipts, (receipt) => receipt.stepId),
1681
+ remoteCallRuns: sortedBy(record.remoteCallRuns, (item) => item.stepId),
1682
+ cleanupReceipts: sortedBy(record.cleanupReceipts, (item) => item.cleanupId),
1683
+ compensationReceipts: sortedBy(record.compensationReceipts, (item) => item.compensationId),
1684
+ ...(record.evaluation ? { evaluation: clone(record.evaluation) } : {}),
1685
+ ...(record.humanAcceptance ? { humanAcceptance: clone(record.humanAcceptance) } : {}),
1686
+ };
1687
+ return clone({ ...base, traceHash: sha256(traceProjection(base)) });
1688
+ }
1689
+ function resultForRecord(record, code = 'EXECUTION_COMPLETED', reasons = []) {
1690
+ record.run.eventCount = record.events.length;
1691
+ record.run.runHash = executionRunHash(record.run);
1692
+ record.trace = traceForRecord(record);
1693
+ return clone({
1694
+ status: record.run.state,
1695
+ code,
1696
+ reasons: sortedStrings(reasons),
1697
+ executionRun: record.run,
1698
+ run: record.run,
1699
+ events: record.events,
1700
+ receipts: record.receipts,
1701
+ remoteCallRuns: record.remoteCallRuns,
1702
+ cleanupReceipts: record.cleanupReceipts,
1703
+ compensationReceipts: record.compensationReceipts,
1704
+ ...(record.evaluation ? { evaluation: record.evaluation } : {}),
1705
+ ...(record.humanAcceptance ? { humanAcceptance: record.humanAcceptance } : {}),
1706
+ trace: record.trace,
1707
+ });
1708
+ }
1709
+ function refreshReconciledReceipts(record, steps) {
1710
+ for (const step of steps) {
1711
+ const events = record.events.filter((event) => event.stepId === step.id);
1712
+ const receipt = makeStepReceipt(record.run.id, step, events, stepCleanupStatus(record, step));
1713
+ const index = record.receipts.findIndex((candidate) => candidate.stepId === step.id);
1714
+ if (index >= 0)
1715
+ record.receipts[index] = receipt;
1716
+ else
1717
+ record.receipts.push(receipt);
1718
+ }
1719
+ for (const remote of record.remoteCallRuns) {
1720
+ const stepEvents = record.events.filter((event) => event.stepId === remote.stepId);
1721
+ const terminal = stepEvents.at(-1);
1722
+ const receipt = record.receipts.find((candidate) => candidate.stepId === remote.stepId);
1723
+ if (terminal)
1724
+ remote.state = terminal.state;
1725
+ if (terminal?.providerRequestId)
1726
+ remote.providerRequestId = terminal.providerRequestId;
1727
+ if (receipt)
1728
+ remote.receiptId = receipt.id;
1729
+ record.remoteCallRuns[record.remoteCallRuns.indexOf(remote)] = updateRemoteCallRunHash(remote);
1730
+ }
1731
+ }
1732
+ function appendReconciliationCleanup(record, outcome, workerRestarted) {
1733
+ for (const cleanup of sortedBy(record.input.pipelinePlan.cleanup, (item) => item.id)) {
1734
+ if (!cleanupConditionMatches(cleanup, outcome, workerRestarted))
1735
+ continue;
1736
+ const cleanupEvents = [];
1737
+ const shouldFail = record.options.cleanupFailureIds.includes(cleanup.id);
1738
+ let failedCleanup = false;
1739
+ for (let attempt = 0; attempt <= record.options.maximumCleanupRetries; attempt += 1) {
1740
+ const pending = runtimeCleanupEvent(record, cleanup, 'cleanup_pending');
1741
+ appendRuntimeEvent(record, pending);
1742
+ cleanupEvents.push(pending);
1743
+ const terminalState = shouldFail ? 'cleanup_failed' : 'cleaned';
1744
+ const terminalEvent = runtimeCleanupEvent(record, cleanup, terminalState, shouldFail ? 'CLEANUP_FAILED' : undefined);
1745
+ appendRuntimeEvent(record, terminalEvent);
1746
+ cleanupEvents.push(terminalEvent);
1747
+ if (!shouldFail)
1748
+ break;
1749
+ failedCleanup = true;
1750
+ }
1751
+ const receipt = makeCleanupReceipt(record.run.id, cleanup, cleanupEvents, failedCleanup, record.options.maximumCleanupRetries);
1752
+ const existingIndex = record.cleanupReceipts.findIndex((candidate) => candidate.cleanupId === cleanup.id);
1753
+ if (existingIndex >= 0)
1754
+ record.cleanupReceipts[existingIndex] = receipt;
1755
+ else
1756
+ record.cleanupReceipts.push(receipt);
1757
+ }
1758
+ }
1759
+ function appendReconciliationCompensation(record, outcome, workerRestarted, completed, failed) {
1760
+ const trigger = compensationTrigger(outcome, workerRestarted);
1761
+ if (!trigger)
1762
+ return;
1763
+ for (const compensation of sortedBy(record.input.pipelinePlan.compensation.filter((item) => item.trigger === trigger && (workerRestarted || item.appliesToStepIds.some((id) => failed.has(id) || !completed.has(id)))), (item) => item.id)) {
1764
+ if (record.compensationReceipts.some((receipt) => receipt.compensationId === compensation.id))
1765
+ continue;
1766
+ const cleanup = record.input.pipelinePlan.cleanup.find((item) => item.id === compensation.cleanupId);
1767
+ if (!cleanup)
1768
+ continue;
1769
+ const compensationCleanup = { ...cleanup, id: compensation.id, artifactRoles: cleanup.artifactRoles, appliesToStepIds: compensation.appliesToStepIds };
1770
+ const compensationEvents = [];
1771
+ const shouldFail = record.options.compensationFailureIds.includes(compensation.id);
1772
+ let failedCompensation = false;
1773
+ for (let attempt = 0; attempt <= record.options.maximumCleanupRetries; attempt += 1) {
1774
+ const pending = runtimeCleanupEvent(record, compensationCleanup, 'cleanup_pending');
1775
+ appendRuntimeEvent(record, pending);
1776
+ compensationEvents.push(pending);
1777
+ const terminalState = shouldFail ? 'cleanup_failed' : 'cleaned';
1778
+ const terminalEvent = runtimeCleanupEvent(record, compensationCleanup, terminalState, shouldFail ? 'CLEANUP_FAILED' : undefined);
1779
+ appendRuntimeEvent(record, terminalEvent);
1780
+ compensationEvents.push(terminalEvent);
1781
+ if (!shouldFail)
1782
+ break;
1783
+ failedCompensation = true;
1784
+ }
1785
+ record.compensationReceipts.push(makeCompensationReceipt(record.run.id, compensation, compensationEvents, failedCompensation, record.options.maximumCleanupRetries));
1786
+ }
1787
+ }
1788
+ function makeExecutionRun(input, options) {
1789
+ const promptHash = promptArtifactHash(input.promptArtifact);
1790
+ const base = {
1791
+ schemaVersion: EXECUTION_RUN_SCHEMA_VERSION,
1792
+ id: hashId('execution-run', { authorizationHash: input.executionAuthorization.authorizationHash, pipelinePlanHash: input.pipelinePlan.planHash, promptHash, options }),
1793
+ caseId: input.pipelinePlan.caseId,
1794
+ caseRevision: input.pipelinePlan.caseRevision,
1795
+ contextHash: input.contextHash,
1796
+ constraintIRHash: input.constraintIR.deterministicSignature,
1797
+ referencePlanHash: input.referencePlan.planHash,
1798
+ pipelinePlanHash: input.pipelinePlan.planHash,
1799
+ promptArtifactHash: promptHash,
1800
+ executionAuthorizationId: input.executionAuthorization.id,
1801
+ state: 'queued',
1802
+ technicalOutcome: 'pending',
1803
+ createdAt: options.now,
1804
+ updatedAt: options.now,
1805
+ eventCount: 0,
1806
+ stepIds: sortedStrings(input.pipelinePlan.steps.map((step) => step.id)),
1807
+ outputArtifacts: [],
1808
+ cleanupStatus: 'pending',
1809
+ };
1810
+ return clone({ ...base, runHash: executionRunHash(base) });
1811
+ }
1812
+ function makeRemoteCallRun(record, step, authorization, receipt) {
1813
+ const terminal = record.events.filter((event) => event.stepId === step.id).at(-1);
1814
+ const base = {
1815
+ schemaVersion: REMOTE_CALL_RUN_SCHEMA_VERSION,
1816
+ id: hashId('remote-call-run', { runId: record.run.id, stepId: step.id, authorizationId: authorization.id }),
1817
+ runId: record.run.id,
1818
+ stepId: step.id,
1819
+ authorizationId: authorization.id,
1820
+ inputHash: authorization.inputHash,
1821
+ state: terminal?.state ?? 'failed',
1822
+ provider: step.adapterId,
1823
+ adapterId: step.adapterId,
1824
+ profileDigest: step.profileVersion.digest,
1825
+ destination: step.destination,
1826
+ budgetId: step.budget.id,
1827
+ maximumCalls: authorization.maximumCalls,
1828
+ maximumRetries: authorization.maximumRetries,
1829
+ timeoutMs: authorization.timeoutMs,
1830
+ receiptId: receipt.id,
1831
+ ...(terminal?.providerRequestId ? { providerRequestId: terminal.providerRequestId } : {}),
1832
+ };
1833
+ const hashBase = objectOf(base);
1834
+ delete hashBase.id;
1835
+ delete hashBase.runId;
1836
+ return clone({ ...base, runHash: sha256(hashBase) });
1837
+ }
1838
+ function stepCleanupStatus(record, step) {
1839
+ const applicable = record.cleanupReceipts.filter((receipt) => {
1840
+ const cleanup = record.input.pipelinePlan.cleanup.find((candidate) => candidate.id === receipt.cleanupId);
1841
+ return cleanup?.appliesToStepIds.includes(step.id);
1842
+ });
1843
+ if (applicable.some((receipt) => receipt.status === 'cleanup_failed'))
1844
+ return 'cleanup_failed';
1845
+ if (applicable.length)
1846
+ return 'cleaned';
1847
+ return 'not_required';
1848
+ }
1849
+ function executionOutcomeStatus(outcome, needsReview) {
1850
+ if (outcome === 'unknown')
1851
+ return { state: 'submission_unknown', technicalOutcome: 'unknown' };
1852
+ if (outcome === 'cancel')
1853
+ return { state: 'cancelled', technicalOutcome: 'cancelled' };
1854
+ if (outcome === 'failure')
1855
+ return { state: 'failed', technicalOutcome: 'failed' };
1856
+ return { state: needsReview ? 'needs_review' : 'completed', technicalOutcome: 'succeeded' };
1857
+ }
1858
+ function pipelineExecutionOrder(steps) {
1859
+ const remaining = new Map(sortedBy(steps, (step) => step.id).map((step) => [step.id, step]));
1860
+ const completed = new Set();
1861
+ const ordered = [];
1862
+ while (remaining.size) {
1863
+ const ready = [...remaining.values()].filter((step) => step.dependsOn.every((dependency) => completed.has(dependency) || !remaining.has(dependency))).sort((left, right) => compareCodeUnits(left.id, right.id));
1864
+ if (!ready.length)
1865
+ return [...ordered, ...sortedBy([...remaining.values()], (step) => step.id)];
1866
+ const next = ready[0];
1867
+ remaining.delete(next.id);
1868
+ completed.add(next.id);
1869
+ ordered.push(next);
1870
+ }
1871
+ return ordered;
1872
+ }
1873
+ function adapterRegistrationReasons(steps, adapters) {
1874
+ const reasons = [];
1875
+ for (const step of steps) {
1876
+ const adapter = adapters.get(step.adapterId);
1877
+ if (!adapter) {
1878
+ reasons.push(`ADAPTER_NOT_REGISTERED:${step.adapterId}`);
1879
+ continue;
1880
+ }
1881
+ const versionMatches = adapter.id === step.adapterId
1882
+ && adapter.version.id === step.adapterVersion.id
1883
+ && adapter.version.version === step.adapterVersion.version
1884
+ && adapter.version.digest === step.adapterVersion.digest
1885
+ && adapter.digest === step.adapterVersion.digest;
1886
+ if (!versionMatches)
1887
+ reasons.push(`ADAPTER_BINDING_MISMATCH:${step.id}`);
1888
+ if (adapter.profileDigest !== undefined && adapter.profileDigest !== step.profileVersion.digest)
1889
+ reasons.push(`ADAPTER_PROFILE_BINDING_MISMATCH:${step.id}`);
1890
+ }
1891
+ return sortedStrings(reasons);
1892
+ }
1893
+ export class OfflineExecutionRuntime {
1894
+ adapters;
1895
+ records = new Map();
1896
+ constructor(adapter, adapters = []) {
1897
+ this.adapters = new Map(adapters.map((candidate) => [candidate.id, candidate]));
1898
+ if (adapter)
1899
+ this.adapters.set(adapter.id, adapter);
1900
+ }
1901
+ executeRegisteredStep(record, step, authorization) {
1902
+ const input = record.input;
1903
+ const options = record.options;
1904
+ const promptHash = promptArtifactHash(input.promptArtifact);
1905
+ const inputHash = computeExecutionStepInputHash(step, input.contextHash, input.pipelinePlan.planHash, input.referencePlan.planHash, promptHash, input.referencePlan.ordered.map((reference) => reference.contentHash));
1906
+ const append = (event) => { appendRuntimeEvent(record, event); };
1907
+ append(runtimeStepEvent(record, step, 'authorized', promptHash, authorization?.id, inputHash, [], 0, 0));
1908
+ const adapter = this.adapters.get(step.adapterId);
1909
+ if (!adapter) {
1910
+ append(runtimeStepEvent(record, step, 'failed', promptHash, authorization?.id, inputHash, [], 0, 0, undefined, 'ADAPTER_NOT_REGISTERED'));
1911
+ return { terminal: 'failed', outcome: 'failure' };
1912
+ }
1913
+ const remote = requiredRemoteStep(step);
1914
+ let attempts = 0;
1915
+ let retries = 0;
1916
+ let spentCost = 0;
1917
+ let spentBytes = 0;
1918
+ let terminal = 'failed';
1919
+ let terminalResult;
1920
+ while (attempts < step.budget.maximumCalls) {
1921
+ attempts += 1;
1922
+ if (remote)
1923
+ append(runtimeStepEvent(record, step, 'submitted', promptHash, authorization?.id, inputHash, [], attempts, retries));
1924
+ let result;
1925
+ try {
1926
+ let renderedRequestId;
1927
+ if (attempts === 1 && step.type === 'generate' && adapterCanRender(adapter)) {
1928
+ const promptIR = 'candidateHash' in input.promptArtifact ? input.promptGuardResult?.deterministicFallback : input.promptArtifact;
1929
+ if (!promptIR)
1930
+ result = { status: 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, failureCode: 'PROMPT_RENDER_INPUT_MISSING', actualCost: 0 };
1931
+ else {
1932
+ const rendered = adapter.render(createProviderRenderRequest({ promptIR, candidate: 'candidateHash' in input.promptArtifact ? input.promptArtifact : undefined, guardResult: input.promptGuardResult, caseId: input.pipelinePlan.caseId, caseRevision: input.pipelinePlan.caseRevision, contextHash: input.contextHash, pipelinePlanHash: input.pipelinePlan.planHash }));
1933
+ if (rendered.status !== 'ok')
1934
+ result = { status: rendered.status === 'submission_unknown' ? 'submission_unknown' : 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, providerRequestId: rendered.providerRequestId, failureCode: rendered.failureCode ?? 'PROMPT_RENDER_FAILED', actualCost: 0 };
1935
+ else {
1936
+ renderedRequestId = rendered.providerRequestId;
1937
+ result = adapter.executeStep({ runId: record.run.id, step, promptArtifactHash: promptHash, referencePlanHash: input.referencePlan.planHash, outputContract: input.outputContract, attempt: attempts, options });
1938
+ if (!result.providerRequestId && renderedRequestId)
1939
+ result = { ...result, providerRequestId: renderedRequestId };
1940
+ }
1941
+ }
1942
+ }
1943
+ else
1944
+ result = adapter.executeStep({ runId: record.run.id, step, promptArtifactHash: promptHash, referencePlanHash: input.referencePlan.planHash, outputContract: input.outputContract, attempt: attempts, options });
1945
+ }
1946
+ catch {
1947
+ result = { status: 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, failureCode: 'MOCK_ADAPTER_EXCEPTION', actualCost: 0 };
1948
+ }
1949
+ terminalResult = result;
1950
+ const rawCost = result.actualCost ?? 0;
1951
+ const rawBytes = result.actualBytes ?? 0;
1952
+ const invalidUsage = !Number.isFinite(rawCost) || rawCost < 0 || !Number.isInteger(rawBytes) || rawBytes < 0;
1953
+ const cost = Number.isFinite(rawCost) && rawCost >= 0 ? rawCost : 0;
1954
+ const bytes = Number.isInteger(rawBytes) && rawBytes >= 0 ? rawBytes : 0;
1955
+ spentCost += cost;
1956
+ spentBytes += bytes;
1957
+ const budgetViolation = invalidUsage || (step.budget.maximumCost !== undefined && spentCost > step.budget.maximumCost) || (step.budget.maximumBytes !== undefined && spentBytes > step.budget.maximumBytes) || (step.dataTransfer.maximumBytes !== undefined && spentBytes > step.dataTransfer.maximumBytes);
1958
+ if (budgetViolation) {
1959
+ result = { ...result, status: 'failed', outputArtifacts: [], failureCode: invalidUsage ? 'USAGE_ACCOUNTING_INVALID' : (step.budget.maximumBytes !== undefined || step.dataTransfer.maximumBytes !== undefined) && spentBytes > (step.budget.maximumBytes ?? step.dataTransfer.maximumBytes ?? Number.MAX_SAFE_INTEGER) ? 'BYTES_BUDGET_EXCEEDED' : 'COST_BUDGET_EXCEEDED' };
1960
+ terminalResult = result;
1961
+ }
1962
+ if (result.status === 'submission_unknown') {
1963
+ terminal = 'submission_unknown';
1964
+ append(runtimeStepEvent(record, step, terminal, promptHash, authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, result.failureCode ?? 'REMOTE_SUBMISSION_UNKNOWN', spentCost, spentBytes));
1965
+ return { terminal, terminalResult, outcome: 'unknown' };
1966
+ }
1967
+ if (result.status === 'cancelled') {
1968
+ terminal = 'cancelled';
1969
+ append(runtimeStepEvent(record, step, terminal, promptHash, authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, result.failureCode ?? 'CANCELLED', spentCost, spentBytes));
1970
+ return { terminal, terminalResult, outcome: 'cancel' };
1971
+ }
1972
+ if (result.status === 'succeeded' && !budgetViolation) {
1973
+ const artifacts = outputArtifactsForResult(result);
1974
+ const outputHashes = artifacts.map((artifact) => artifact.contentHash);
1975
+ if (remote)
1976
+ append(runtimeStepEvent(record, step, 'acknowledged', promptHash, authorization?.id, inputHash, outputHashes, attempts, retries, result.providerRequestId, undefined, spentCost, spentBytes));
1977
+ append(runtimeStepEvent(record, step, 'succeeded', promptHash, authorization?.id, inputHash, outputHashes, attempts, retries, result.providerRequestId, undefined, spentCost, spentBytes));
1978
+ record.run.outputArtifacts.push(...artifacts);
1979
+ return { terminal: 'succeeded', terminalResult, outcome: 'success' };
1980
+ }
1981
+ const failureCode = result.failureCode ?? 'STEP_FAILED';
1982
+ append(runtimeStepEvent(record, step, 'failed', promptHash, authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, failureCode, spentCost, spentBytes));
1983
+ terminal = 'failed';
1984
+ const retryAllowed = options.retryableFailureStepIds.includes(step.id) && retries < step.budget.maximumRetries && attempts < step.budget.maximumCalls;
1985
+ if (retryAllowed) {
1986
+ retries += 1;
1987
+ continue;
1988
+ }
1989
+ return { terminal, terminalResult, outcome: 'failure' };
1990
+ }
1991
+ return { terminal, terminalResult, outcome: 'failure' };
1992
+ }
1993
+ execute(input) {
1994
+ let safeInput;
1995
+ try {
1996
+ safeInput = clone(input);
1997
+ }
1998
+ catch {
1999
+ return blockedExecutionResult('OFFLINE_EXECUTION_INPUT_INVALID', ['OFFLINE_EXECUTION_INPUT_INVALID']);
2000
+ }
2001
+ const options = normalizedExecutionOptions(safeInput.options);
2002
+ const reasons = [...safeExecutionInputReasons(safeInput), ...safeRemoteAuthorizationReasons(safeInput)];
2003
+ let preflight;
2004
+ try {
2005
+ const snapshot = safeInput.pipelinePlan ? executionSnapshot(safeInput) : { kind: 'execution', caseId: safeInput.executionAuthorization?.caseId ?? '', caseRevision: safeInput.executionAuthorization?.caseRevision ?? 0, contextHash: safeInput.executionAuthorization?.contextHash ?? '' };
2006
+ preflight = dispatchPreflight(safeInput.executionAuthorization, snapshot, options.now);
2007
+ if (preflight.status !== 'authorized')
2008
+ reasons.push('DISPATCH_PREFLIGHT_BLOCKED', preflight.code, ...preflight.reasons.map((reason) => `DISPATCH_${reason}`));
2009
+ }
2010
+ catch {
2011
+ reasons.push('DISPATCH_PREFLIGHT_INPUT_INVALID');
2012
+ }
2013
+ reasons.push(...adapterRegistrationReasons(safeInput.pipelinePlan?.steps ?? [], this.adapters));
2014
+ const uniqueReasons = sortedStrings(reasons);
2015
+ if (uniqueReasons.length || preflight?.status !== 'authorized')
2016
+ return blockedExecutionResult('EXECUTION_NOT_AUTHORIZED', uniqueReasons.length ? uniqueReasons : ['EXECUTION_NOT_AUTHORIZED']);
2017
+ const priorRun = [...this.records.values()].find((record) => record.input.executionAuthorization.authorizationHash === safeInput.executionAuthorization.authorizationHash && record.input.pipelinePlan.planHash === safeInput.pipelinePlan.planHash && promptArtifactHash(record.input.promptArtifact) === promptArtifactHash(safeInput.promptArtifact));
2018
+ if (priorRun && ['completed', 'failed', 'cancelled', 'needs_review', 'submission_unknown'].includes(priorRun.run.state))
2019
+ return blockedExecutionResult('NEW_AUTHORIZATION_REQUIRED', ['TERMINAL_RUN_REQUIRES_NEW_AUTHORIZATION']);
2020
+ const run = makeExecutionRun(safeInput, options);
2021
+ const record = { input: safeInput, options, run, events: [], receipts: [], remoteCallRuns: [], cleanupReceipts: [], compensationReceipts: [] };
2022
+ this.records.set(run.id, record);
2023
+ run.state = 'running';
2024
+ run.updatedAt = options.now;
2025
+ const steps = pipelineExecutionOrder(safeInput.pipelinePlan.steps);
2026
+ const authorizations = new Map(safeInput.remoteCallAuthorizations.map((authorization) => [authorization.stepId, authorization]));
2027
+ const groups = new Map();
2028
+ const completed = new Set();
2029
+ const failed = new Set();
2030
+ let outcome = 'success';
2031
+ let workerRestarted = false;
2032
+ let stop = false;
2033
+ for (const step of steps) {
2034
+ const group = [];
2035
+ const append = (event) => { group.push(event); appendRuntimeEvent(record, event); };
2036
+ const inputHash = computeExecutionStepInputHash(step, safeInput.contextHash, safeInput.pipelinePlan.planHash, safeInput.referencePlan.planHash, promptArtifactHash(safeInput.promptArtifact), safeInput.referencePlan.ordered.map((reference) => reference.contentHash));
2037
+ append(runtimeStepEvent(record, step, 'pending', promptArtifactHash(safeInput.promptArtifact), authorizations.get(step.id)?.id, inputHash, [], 0, 0));
2038
+ if (stop) {
2039
+ append(runtimeStepEvent(record, step, 'skipped', promptArtifactHash(safeInput.promptArtifact), authorizations.get(step.id)?.id, inputHash, [], 0, 0, undefined, outcome === 'unknown' ? 'UPSTREAM_SUBMISSION_UNKNOWN' : 'UPSTREAM_STEP_FAILED'));
2040
+ groups.set(step.id, group);
2041
+ continue;
2042
+ }
2043
+ if (options.cancelBeforeStepId && step.id === options.cancelBeforeStepId) {
2044
+ append(runtimeStepEvent(record, step, 'cancel_requested', promptArtifactHash(safeInput.promptArtifact), authorizations.get(step.id)?.id, inputHash, [], 0, 0, undefined, 'CANCEL_REQUESTED'));
2045
+ append(runtimeStepEvent(record, step, 'cancelled', promptArtifactHash(safeInput.promptArtifact), authorizations.get(step.id)?.id, inputHash, [], 0, 0, undefined, 'CANCELLED_BEFORE_STEP'));
2046
+ outcome = 'cancel';
2047
+ stop = true;
2048
+ groups.set(step.id, group);
2049
+ continue;
2050
+ }
2051
+ if (step.dependsOn.some((dependency) => !completed.has(dependency))) {
2052
+ append(runtimeStepEvent(record, step, 'skipped', promptArtifactHash(safeInput.promptArtifact), authorizations.get(step.id)?.id, inputHash, [], 0, 0, undefined, 'DEPENDENCY_NOT_COMPLETED'));
2053
+ outcome = outcome === 'success' ? 'failure' : outcome;
2054
+ stop = true;
2055
+ failed.add(step.id);
2056
+ groups.set(step.id, group);
2057
+ continue;
2058
+ }
2059
+ const authorization = authorizations.get(step.id);
2060
+ append(runtimeStepEvent(record, step, 'authorized', promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, [], 0, 0));
2061
+ const adapter = this.adapters.get(step.adapterId);
2062
+ let attempts = 0;
2063
+ let retries = 0;
2064
+ let spentCost = 0;
2065
+ let spentBytes = 0;
2066
+ let terminal = 'failed';
2067
+ let terminalResult;
2068
+ while (attempts < step.budget.maximumCalls) {
2069
+ attempts += 1;
2070
+ const remote = requiredRemoteStep(step);
2071
+ if (remote)
2072
+ append(runtimeStepEvent(record, step, 'submitted', promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, [], attempts, retries));
2073
+ let result;
2074
+ try {
2075
+ let renderedRequestId;
2076
+ if (attempts === 1 && step.type === 'generate' && adapterCanRender(adapter)) {
2077
+ const promptIR = 'candidateHash' in safeInput.promptArtifact ? safeInput.promptGuardResult?.deterministicFallback : safeInput.promptArtifact;
2078
+ if (!promptIR)
2079
+ result = { status: 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, failureCode: 'PROMPT_RENDER_INPUT_MISSING', actualCost: 0 };
2080
+ else {
2081
+ const rendered = adapter.render(createProviderRenderRequest({ promptIR, candidate: 'candidateHash' in safeInput.promptArtifact ? safeInput.promptArtifact : undefined, guardResult: safeInput.promptGuardResult, caseId: safeInput.pipelinePlan.caseId, caseRevision: safeInput.pipelinePlan.caseRevision, contextHash: safeInput.contextHash, pipelinePlanHash: safeInput.pipelinePlan.planHash }));
2082
+ if (rendered.status !== 'ok')
2083
+ result = { status: rendered.status === 'submission_unknown' ? 'submission_unknown' : 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, providerRequestId: rendered.providerRequestId, failureCode: rendered.failureCode ?? 'PROMPT_RENDER_FAILED', actualCost: 0 };
2084
+ else {
2085
+ renderedRequestId = rendered.providerRequestId;
2086
+ result = adapter.executeStep({ runId: record.run.id, step, promptArtifactHash: promptArtifactHash(safeInput.promptArtifact), referencePlanHash: safeInput.referencePlan.planHash, outputContract: safeInput.outputContract, attempt: attempts, options });
2087
+ if (!result.providerRequestId && renderedRequestId)
2088
+ result = { ...result, providerRequestId: renderedRequestId };
2089
+ }
2090
+ }
2091
+ }
2092
+ else
2093
+ result = adapter.executeStep({ runId: record.run.id, step, promptArtifactHash: promptArtifactHash(safeInput.promptArtifact), referencePlanHash: safeInput.referencePlan.planHash, outputContract: safeInput.outputContract, attempt: attempts, options });
2094
+ }
2095
+ catch {
2096
+ result = { status: 'failed', outputArtifacts: [], metadata: { offline: true, virtual: true, adapterId: adapter.id }, failureCode: 'MOCK_ADAPTER_EXCEPTION', actualCost: 0 };
2097
+ }
2098
+ terminalResult = result;
2099
+ const rawCost = result.actualCost ?? 0;
2100
+ const rawBytes = result.actualBytes ?? 0;
2101
+ const invalidUsage = !Number.isFinite(rawCost) || rawCost < 0 || !Number.isInteger(rawBytes) || rawBytes < 0;
2102
+ const cost = Number.isFinite(rawCost) && rawCost >= 0 ? rawCost : 0;
2103
+ const bytes = Number.isInteger(rawBytes) && rawBytes >= 0 ? rawBytes : 0;
2104
+ spentCost += cost;
2105
+ spentBytes += bytes;
2106
+ const budgetViolation = invalidUsage || (step.budget.maximumCost !== undefined && spentCost > step.budget.maximumCost) || (step.budget.maximumBytes !== undefined && spentBytes > step.budget.maximumBytes) || (step.dataTransfer.maximumBytes !== undefined && spentBytes > step.dataTransfer.maximumBytes);
2107
+ if (budgetViolation) {
2108
+ result = { ...result, status: 'failed', outputArtifacts: [], failureCode: invalidUsage ? 'USAGE_ACCOUNTING_INVALID' : (step.budget.maximumBytes !== undefined || step.dataTransfer.maximumBytes !== undefined) && spentBytes > (step.budget.maximumBytes ?? step.dataTransfer.maximumBytes ?? Number.MAX_SAFE_INTEGER) ? 'BYTES_BUDGET_EXCEEDED' : 'COST_BUDGET_EXCEEDED' };
2109
+ terminalResult = result;
2110
+ }
2111
+ if (result.status === 'submission_unknown') {
2112
+ terminal = 'submission_unknown';
2113
+ append(runtimeStepEvent(record, step, terminal, promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, result.failureCode ?? 'REMOTE_SUBMISSION_UNKNOWN', spentCost, spentBytes));
2114
+ outcome = 'unknown';
2115
+ stop = true;
2116
+ break;
2117
+ }
2118
+ if (result.status === 'cancelled') {
2119
+ terminal = 'cancelled';
2120
+ append(runtimeStepEvent(record, step, terminal, promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, result.failureCode ?? 'CANCELLED', spentCost, spentBytes));
2121
+ outcome = 'cancel';
2122
+ stop = true;
2123
+ break;
2124
+ }
2125
+ if (result.status === 'succeeded' && !budgetViolation) {
2126
+ const artifacts = outputArtifactsForResult(result);
2127
+ const outputHashes = artifacts.map((artifact) => artifact.contentHash);
2128
+ if (remote)
2129
+ append(runtimeStepEvent(record, step, 'acknowledged', promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, outputHashes, attempts, retries, result.providerRequestId, undefined, spentCost, spentBytes));
2130
+ append(runtimeStepEvent(record, step, 'succeeded', promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, outputHashes, attempts, retries, result.providerRequestId, undefined, spentCost, spentBytes));
2131
+ record.run.outputArtifacts.push(...artifacts);
2132
+ terminal = 'succeeded';
2133
+ completed.add(step.id);
2134
+ break;
2135
+ }
2136
+ const failureCode = result.failureCode ?? 'STEP_FAILED';
2137
+ append(runtimeStepEvent(record, step, 'failed', promptArtifactHash(safeInput.promptArtifact), authorization?.id, inputHash, [], attempts, retries, result.providerRequestId, failureCode, spentCost, spentBytes));
2138
+ terminal = 'failed';
2139
+ const retryAllowed = options.retryableFailureStepIds.includes(step.id) && retries < step.budget.maximumRetries && attempts < step.budget.maximumCalls;
2140
+ if (retryAllowed) {
2141
+ retries += 1;
2142
+ continue;
2143
+ }
2144
+ outcome = 'failure';
2145
+ stop = true;
2146
+ failed.add(step.id);
2147
+ break;
2148
+ }
2149
+ if (terminal === 'failed' && outcome === 'success') {
2150
+ outcome = 'failure';
2151
+ stop = true;
2152
+ failed.add(step.id);
2153
+ }
2154
+ if (options.workerRestartAfterStepId && step.id === options.workerRestartAfterStepId)
2155
+ workerRestarted = true;
2156
+ groups.set(step.id, group);
2157
+ if (terminalResult && requiredRemoteStep(step) && authorization) {
2158
+ const placeholder = makeStepReceipt(record.run.id, step, group, 'pending');
2159
+ record.remoteCallRuns.push(makeRemoteCallRun(record, step, authorization, placeholder));
2160
+ }
2161
+ }
2162
+ const needsReview = outcome === 'success' && steps.some((step) => step.type === 'semantic_review' && completed.has(step.id));
2163
+ const finalStatus = executionOutcomeStatus(outcome, needsReview);
2164
+ record.run.state = finalStatus.state;
2165
+ record.run.technicalOutcome = finalStatus.technicalOutcome;
2166
+ record.run.outputArtifacts = [...new Map(record.run.outputArtifacts.map((artifact) => [artifact.id, artifact])).values()].sort((left, right) => compareCodeUnits(left.id, right.id));
2167
+ record.evaluation = evaluationForRun(record.run.id, record.run.outputArtifacts, needsReview, outcome);
2168
+ if (needsReview)
2169
+ record.humanAcceptance = humanAcceptanceForRun(record.run.id, record.run.outputArtifacts);
2170
+ const cleanupOutcome = outcome;
2171
+ for (const cleanup of sortedBy(safeInput.pipelinePlan.cleanup, (item) => item.id)) {
2172
+ if (!cleanupConditionMatches(cleanup, cleanupOutcome, workerRestarted))
2173
+ continue;
2174
+ const cleanupEvents = [];
2175
+ const shouldFail = options.cleanupFailureIds.includes(cleanup.id);
2176
+ let failedCleanup = false;
2177
+ for (let attempt = 0; attempt <= options.maximumCleanupRetries; attempt += 1) {
2178
+ const pending = runtimeCleanupEvent(record, cleanup, 'cleanup_pending');
2179
+ appendRuntimeEvent(record, pending);
2180
+ cleanupEvents.push(pending);
2181
+ const terminalState = shouldFail ? 'cleanup_failed' : 'cleaned';
2182
+ const terminalEvent = runtimeCleanupEvent(record, cleanup, terminalState, shouldFail ? 'CLEANUP_FAILED' : undefined);
2183
+ appendRuntimeEvent(record, terminalEvent);
2184
+ cleanupEvents.push(terminalEvent);
2185
+ if (!shouldFail)
2186
+ break;
2187
+ failedCleanup = true;
2188
+ }
2189
+ record.cleanupReceipts.push(makeCleanupReceipt(record.run.id, cleanup, cleanupEvents, failedCleanup, options.maximumCleanupRetries));
2190
+ }
2191
+ const trigger = compensationTrigger(outcome, workerRestarted);
2192
+ if (trigger) {
2193
+ for (const compensation of sortedBy(safeInput.pipelinePlan.compensation.filter((item) => item.trigger === trigger && (workerRestarted || item.appliesToStepIds.some((id) => failed.has(id) || !completed.has(id)))), (item) => item.id)) {
2194
+ const cleanup = safeInput.pipelinePlan.cleanup.find((item) => item.id === compensation.cleanupId);
2195
+ if (!cleanup)
2196
+ continue;
2197
+ const compensationCleanup = { ...cleanup, id: compensation.id, artifactRoles: cleanup.artifactRoles, appliesToStepIds: compensation.appliesToStepIds };
2198
+ const compensationEvents = [];
2199
+ const shouldFail = options.compensationFailureIds.includes(compensation.id);
2200
+ let failedCompensation = false;
2201
+ for (let attempt = 0; attempt <= options.maximumCleanupRetries; attempt += 1) {
2202
+ const pending = runtimeCleanupEvent(record, compensationCleanup, 'cleanup_pending');
2203
+ appendRuntimeEvent(record, pending);
2204
+ compensationEvents.push(pending);
2205
+ const terminalState = shouldFail ? 'cleanup_failed' : 'cleaned';
2206
+ const terminalEvent = runtimeCleanupEvent(record, compensationCleanup, terminalState, shouldFail ? 'CLEANUP_FAILED' : undefined);
2207
+ appendRuntimeEvent(record, terminalEvent);
2208
+ compensationEvents.push(terminalEvent);
2209
+ if (!shouldFail)
2210
+ break;
2211
+ failedCompensation = true;
2212
+ }
2213
+ record.compensationReceipts.push(makeCompensationReceipt(record.run.id, compensation, compensationEvents, failedCompensation, options.maximumCleanupRetries));
2214
+ }
2215
+ }
2216
+ const cleanupFailed = record.cleanupReceipts.some((receipt) => receipt.status === 'cleanup_failed') || record.compensationReceipts.some((receipt) => receipt.status === 'cleanup_failed');
2217
+ for (const step of steps) {
2218
+ const group = groups.get(step.id) ?? [];
2219
+ record.receipts.push(makeStepReceipt(record.run.id, step, group, stepCleanupStatus(record, step)));
2220
+ }
2221
+ for (const remote of record.remoteCallRuns) {
2222
+ const receipt = record.receipts.find((candidate) => candidate.stepId === remote.stepId);
2223
+ if (receipt) {
2224
+ remote.receiptId = receipt.id;
2225
+ record.remoteCallRuns[record.remoteCallRuns.indexOf(remote)] = updateRemoteCallRunHash(remote);
2226
+ }
2227
+ }
2228
+ record.run.cleanupStatus = cleanupFailed ? 'cleanup_failed' : 'completed';
2229
+ record.run.updatedAt = options.now;
2230
+ const code = cleanupFailed ? 'CLEANUP_FAILED' : outcome === 'unknown' ? 'SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED' : outcome === 'cancel' ? 'EXECUTION_CANCELLED' : outcome === 'failure' ? 'EXECUTION_FAILED' : needsReview ? 'HUMAN_ACCEPTANCE_REQUIRED' : 'EXECUTION_COMPLETED';
2231
+ return resultForRecord(record, code, cleanupFailed ? ['CLEANUP_FAILED'] : outcome === 'unknown' ? ['SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED'] : []);
2232
+ }
2233
+ get(runId) {
2234
+ const record = this.records.get(runId);
2235
+ return record ? resultForRecord(record, record.run.state === 'submission_unknown' ? 'SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED' : 'EXECUTION_COMPLETED') : undefined;
2236
+ }
2237
+ run(input) {
2238
+ return this.execute(input);
2239
+ }
2240
+ dispatch(input) {
2241
+ return this.execute(input);
2242
+ }
2243
+ reconcile(runId, state) {
2244
+ const record = this.records.get(runId);
2245
+ if (!record)
2246
+ return blockedExecutionResult('RUN_NOT_FOUND', ['RUN_NOT_FOUND']);
2247
+ if (!['submission_unknown', 'reconciling', 'running', 'validating'].includes(record.run.state))
2248
+ return resultForRecord(record, 'RECONCILIATION_NOT_REQUIRED');
2249
+ const unknown = [...record.events].reverse().find((event) => event.state === 'submission_unknown');
2250
+ const step = record.input.pipelinePlan.steps.find((candidate) => candidate.id === unknown?.stepId);
2251
+ if (!unknown || !step)
2252
+ return resultForRecord(record, 'RECONCILIATION_INPUT_INVALID', ['RECONCILIATION_INPUT_INVALID']);
2253
+ const authorizationId = unknown.authorizationId;
2254
+ const promptHash = promptArtifactHash(record.input.promptArtifact);
2255
+ const inputHash = unknown.inputHash ?? computeExecutionStepInputHash(step, record.input.contextHash, record.input.pipelinePlan.planHash, record.input.referencePlan.planHash, promptHash, record.input.referencePlan.ordered.map((reference) => reference.contentHash));
2256
+ appendRuntimeEvent(record, runtimeStepEvent(record, step, 'reconciling', promptHash, authorizationId, inputHash, [], unknown.attempt, unknown.retriesUsed, unknown.providerRequestId, 'EXPLICIT_RECONCILIATION'));
2257
+ if (state === 'running' || state === 'validating') {
2258
+ record.run.state = state;
2259
+ record.run.technicalOutcome = 'pending';
2260
+ record.run.updatedAt = record.options.now;
2261
+ refreshReconciledReceipts(record, pipelineExecutionOrder(record.input.pipelinePlan.steps));
2262
+ return resultForRecord(record, `RECONCILED_${state.toUpperCase()}`, ['RECONCILIATION_IN_PROGRESS']);
2263
+ }
2264
+ const completed = new Set(record.input.pipelinePlan.steps.filter((candidate) => record.events.filter((event) => event.stepId === candidate.id).at(-1)?.state === 'succeeded').map((candidate) => candidate.id));
2265
+ const failed = new Set(record.input.pipelinePlan.steps.filter((candidate) => ['failed', 'cancelled', 'skipped'].includes(record.events.filter((event) => event.stepId === candidate.id).at(-1)?.state ?? '')).map((candidate) => candidate.id));
2266
+ let outcome;
2267
+ if (state === 'failed' || state === 'cancelled') {
2268
+ const terminalState = state === 'cancelled' ? 'cancelled' : 'failed';
2269
+ appendRuntimeEvent(record, runtimeStepEvent(record, step, terminalState, promptHash, authorizationId, inputHash, [], unknown.attempt, unknown.retriesUsed, unknown.providerRequestId, `RECONCILED_${state.toUpperCase()}`));
2270
+ outcome = state === 'cancelled' ? 'cancel' : 'failure';
2271
+ failed.add(step.id);
2272
+ }
2273
+ else {
2274
+ const adapter = this.adapters.get(step.adapterId);
2275
+ let recovery;
2276
+ try {
2277
+ recovery = adapter?.reconcileStep?.({ runId: record.run.id, step, promptArtifactHash: promptHash, referencePlanHash: record.input.referencePlan.planHash, outputContract: record.input.outputContract, attempt: unknown.attempt, options: record.options });
2278
+ }
2279
+ catch {
2280
+ recovery = undefined;
2281
+ }
2282
+ const recoveredArtifacts = recovery?.status === 'succeeded' ? outputArtifactsForResult(recovery) : [];
2283
+ const missingRoles = step.outputArtifactRoles.filter((role) => !recoveredArtifacts.some((artifact) => artifact.role === role));
2284
+ if (!recovery || recovery.status !== 'succeeded' || missingRoles.length > 0) {
2285
+ record.run.state = 'needs_review';
2286
+ record.run.technicalOutcome = 'unknown';
2287
+ record.evaluation = evaluationForRun(record.run.id, record.run.outputArtifacts, true, 'unknown');
2288
+ record.run.updatedAt = record.options.now;
2289
+ refreshReconciledReceipts(record, pipelineExecutionOrder(record.input.pipelinePlan.steps));
2290
+ return resultForRecord(record, 'RECONCILIATION_ARTIFACTS_UNAVAILABLE', ['RECONCILIATION_ARTIFACTS_UNAVAILABLE']);
2291
+ }
2292
+ const recoveredHashes = recoveredArtifacts.map((artifact) => artifact.contentHash);
2293
+ const providerRequestId = recovery.providerRequestId ?? unknown.providerRequestId;
2294
+ if (requiredRemoteStep(step))
2295
+ appendRuntimeEvent(record, runtimeStepEvent(record, step, 'acknowledged', promptHash, authorizationId, inputHash, recoveredHashes, unknown.attempt, unknown.retriesUsed, providerRequestId, undefined, recovery.actualCost ?? 0, recovery.actualBytes ?? 0));
2296
+ appendRuntimeEvent(record, runtimeStepEvent(record, step, 'succeeded', promptHash, authorizationId, inputHash, recoveredHashes, unknown.attempt, unknown.retriesUsed, providerRequestId, 'RECONCILED_COMPLETED', recovery.actualCost ?? 0, recovery.actualBytes ?? 0));
2297
+ record.run.outputArtifacts.push(...recoveredArtifacts);
2298
+ completed.add(step.id);
2299
+ outcome = 'success';
2300
+ const steps = pipelineExecutionOrder(record.input.pipelinePlan.steps);
2301
+ const unknownIndex = steps.findIndex((candidate) => candidate.id === step.id);
2302
+ const authorizations = new Map(record.input.remoteCallAuthorizations.map((authorization) => [authorization.stepId, authorization]));
2303
+ for (const downstream of steps.slice(unknownIndex + 1)) {
2304
+ const latest = record.events.filter((event) => event.stepId === downstream.id).at(-1);
2305
+ if (latest?.state === 'succeeded') {
2306
+ completed.add(downstream.id);
2307
+ continue;
2308
+ }
2309
+ if (downstream.dependsOn.some((dependency) => !completed.has(dependency))) {
2310
+ failed.add(downstream.id);
2311
+ outcome = 'failure';
2312
+ break;
2313
+ }
2314
+ appendRuntimeEvent(record, runtimeStepEvent(record, downstream, 'reconciling', promptHash, authorizations.get(downstream.id)?.id, computeExecutionStepInputHash(downstream, record.input.contextHash, record.input.pipelinePlan.planHash, record.input.referencePlan.planHash, promptHash, record.input.referencePlan.ordered.map((reference) => reference.contentHash)), [], 0, 0, undefined, 'RESUME_AFTER_RECONCILIATION'));
2315
+ const stepResult = this.executeRegisteredStep(record, downstream, authorizations.get(downstream.id));
2316
+ if (stepResult.terminalResult && requiredRemoteStep(downstream) && authorizations.get(downstream.id)) {
2317
+ const stepEvents = record.events.filter((event) => event.stepId === downstream.id);
2318
+ const placeholder = makeStepReceipt(record.run.id, downstream, stepEvents, 'pending');
2319
+ record.remoteCallRuns.push(makeRemoteCallRun(record, downstream, authorizations.get(downstream.id), placeholder));
2320
+ }
2321
+ if (stepResult.outcome === 'success')
2322
+ completed.add(downstream.id);
2323
+ else {
2324
+ if (stepResult.outcome === 'unknown')
2325
+ outcome = 'unknown';
2326
+ else if (stepResult.outcome === 'cancel')
2327
+ outcome = 'cancel';
2328
+ else
2329
+ outcome = 'failure';
2330
+ failed.add(downstream.id);
2331
+ break;
2332
+ }
2333
+ }
2334
+ }
2335
+ const steps = pipelineExecutionOrder(record.input.pipelinePlan.steps);
2336
+ const needsReview = outcome === 'success' && steps.some((candidate) => candidate.type === 'semantic_review' && completed.has(candidate.id));
2337
+ const finalStatus = executionOutcomeStatus(outcome, needsReview);
2338
+ record.run.state = finalStatus.state;
2339
+ record.run.technicalOutcome = finalStatus.technicalOutcome;
2340
+ record.run.outputArtifacts = [...new Map(record.run.outputArtifacts.map((artifact) => [artifact.id, artifact])).values()].sort((left, right) => compareCodeUnits(left.id, right.id));
2341
+ record.evaluation = evaluationForRun(record.run.id, record.run.outputArtifacts, needsReview, outcome);
2342
+ if (needsReview && !record.humanAcceptance)
2343
+ record.humanAcceptance = humanAcceptanceForRun(record.run.id, record.run.outputArtifacts);
2344
+ appendReconciliationCleanup(record, outcome, false);
2345
+ appendReconciliationCompensation(record, outcome, false, completed, failed);
2346
+ const cleanupFailed = record.cleanupReceipts.some((receipt) => receipt.status === 'cleanup_failed') || record.compensationReceipts.some((receipt) => receipt.status === 'cleanup_failed');
2347
+ refreshReconciledReceipts(record, steps);
2348
+ record.run.cleanupStatus = cleanupFailed ? 'cleanup_failed' : 'completed';
2349
+ record.run.updatedAt = record.options.now;
2350
+ const code = cleanupFailed ? 'CLEANUP_FAILED' : outcome === 'unknown' ? 'SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED' : outcome === 'cancel' ? 'EXECUTION_CANCELLED' : outcome === 'failure' ? 'EXECUTION_FAILED' : needsReview ? 'HUMAN_ACCEPTANCE_REQUIRED' : 'EXECUTION_COMPLETED';
2351
+ const reasons = cleanupFailed ? ['CLEANUP_FAILED'] : outcome === 'unknown' ? ['SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED'] : [];
2352
+ return resultForRecord(record, code, reasons);
2353
+ }
2354
+ cancel(runId) {
2355
+ const record = this.records.get(runId);
2356
+ if (!record)
2357
+ return blockedExecutionResult('RUN_NOT_FOUND', ['RUN_NOT_FOUND']);
2358
+ if (record.run.state === 'submission_unknown')
2359
+ return resultForRecord(record, 'SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED', ['SUBMISSION_UNKNOWN_RECONCILIATION_REQUIRED']);
2360
+ if (['completed', 'failed', 'cancelled', 'needs_review'].includes(record.run.state))
2361
+ return resultForRecord(record, 'RUN_TERMINAL', ['RUN_TERMINAL']);
2362
+ record.run.state = 'cancel_requested';
2363
+ record.run.updatedAt = record.options.now;
2364
+ record.run.state = 'cancelled';
2365
+ record.run.technicalOutcome = 'cancelled';
2366
+ return resultForRecord(record, 'EXECUTION_CANCELLED');
2367
+ }
2368
+ cancelRun(runId) {
2369
+ return this.cancel(runId);
2370
+ }
2371
+ acceptHumanAcceptance(runId, reviewerId = 'offline-reviewer') {
2372
+ const record = this.records.get(runId);
2373
+ if (!record || !record.humanAcceptance)
2374
+ return blockedExecutionResult('HUMAN_ACCEPTANCE_NOT_REQUIRED', ['HUMAN_ACCEPTANCE_NOT_REQUIRED']);
2375
+ record.humanAcceptance = updateHumanAcceptanceHash({ ...record.humanAcceptance, status: 'accepted', reviewerId, decidedAt: record.options.now, reasonCode: 'HUMAN_ACCEPTED' });
2376
+ return resultForRecord(record, 'HUMAN_ACCEPTED');
2377
+ }
2378
+ declineHumanAcceptance(runId, reasonCode = 'HUMAN_DECLINED', reviewerId = 'offline-reviewer') {
2379
+ const record = this.records.get(runId);
2380
+ if (!record || !record.humanAcceptance)
2381
+ return blockedExecutionResult('HUMAN_ACCEPTANCE_NOT_REQUIRED', ['HUMAN_ACCEPTANCE_NOT_REQUIRED']);
2382
+ record.humanAcceptance = updateHumanAcceptanceHash({ ...record.humanAcceptance, status: 'declined', reviewerId, decidedAt: record.options.now, reasonCode });
2383
+ return resultForRecord(record, 'HUMAN_ACCEPTANCE_DECLINED', ['HUMAN_ACCEPTANCE_DECLINED']);
2384
+ }
2385
+ getTrace(runId) {
2386
+ const record = this.records.get(runId);
2387
+ return record?.trace ? clone(record.trace) : record ? traceForRecord(record) : undefined;
2388
+ }
2389
+ }
2390
+ export function createOfflineExecutionRuntime(adapter = new MockProviderAdapter(), adapters = []) {
2391
+ return new OfflineExecutionRuntime(adapter, adapters);
2392
+ }
2393
+ export function createMockRuntimeForPlan(plan, options = {}) {
2394
+ const adapters = new Map();
2395
+ for (const step of plan.steps) {
2396
+ const existing = adapters.get(step.adapterId);
2397
+ if (existing && existing.version.digest === step.adapterVersion.digest && existing.profileDigest === step.profileVersion.digest)
2398
+ continue;
2399
+ adapters.set(step.adapterId, new MockProviderAdapter({
2400
+ ...options,
2401
+ version: clone(step.adapterVersion),
2402
+ digest: step.adapterVersion.digest,
2403
+ profileDigest: step.profileVersion.digest,
2404
+ }, step.adapterId));
2405
+ }
2406
+ return new OfflineExecutionRuntime(undefined, [...adapters.values()]);
2407
+ }
2408
+ export const OfflineExecutionEngine = OfflineExecutionRuntime;
2409
+ export function executeOffline(input, runtime) {
2410
+ return (runtime ?? createMockRuntimeForPlan(input.pipelinePlan)).execute(input);
2411
+ }
2412
+ export const executePipeline = executeOffline;
2413
+ export const runOfflineExecution = executeOffline;
2414
+ function replayResult(artifacts, traceHash) {
2415
+ const artifactIds = sortedStrings(artifacts.map((artifact) => artifact.id));
2416
+ const missingArtifactIds = sortedStrings(artifacts.filter((artifact) => artifact.availability !== 'available').map((artifact) => artifact.id));
2417
+ const base = {
2418
+ schemaVersion: ARTIFACT_REPLAY_RESULT_SCHEMA_VERSION,
2419
+ status: missingArtifactIds.length ? 'unavailable' : 'available',
2420
+ code: missingArtifactIds.length ? 'ARTIFACT_UNAVAILABLE' : 'REPLAY_AVAILABLE',
2421
+ artifactIds,
2422
+ missingArtifactIds,
2423
+ ...(traceHash ? { traceHash } : {}),
2424
+ };
2425
+ return clone({ ...base, resultHash: sha256(base) });
2426
+ }
2427
+ export function replayArtifacts(artifacts, traceHash) {
2428
+ return replayResult(clone(artifacts), traceHash);
2429
+ }
2430
+ export function replayArtifact(artifact, traceHash) {
2431
+ return replayArtifacts([artifact], traceHash);
2432
+ }
2433
+ export const replayArtifactHandles = replayArtifacts;