@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/m4.js ADDED
@@ -0,0 +1,2250 @@
1
+ import { BINDING_DECISION_SCHEMA_VERSION, BUDGET_SCHEMA_VERSION, CLEANUP_SCHEMA_VERSION, COMPENSATION_SCHEMA_VERSION, CONSTRAINT_CONFLICT_SCHEMA_VERSION, CONSTRAINT_DEPENDENCY_SCHEMA_VERSION, CONSTRAINT_IR_SCHEMA_VERSION, CONSTRAINT_SCHEMA_VERSION, DATA_TRANSFER_SCHEMA_VERSION, DEGRADATION_SCHEMA_VERSION, EXECUTION_AUTHORIZATION_SCHEMA_VERSION, EXPLAIN_RESULT_SCHEMA_VERSION, GOAL_SCHEMA_VERSION, PIPELINE_PLAN_SCHEMA_VERSION, PIPELINE_PLANNING_RESULT_SCHEMA_VERSION, PIPELINE_STEP_SCHEMA_VERSION, PLANNED_REFERENCE_SCHEMA_VERSION, PROVIDER_CAPABILITY_PROFILE_SCHEMA_VERSION, REFERENCE_CANDIDATE_SCHEMA_VERSION, REFERENCE_DEPENDENCY_SCHEMA_VERSION, REFERENCE_OMISSION_SCHEMA_VERSION, REFERENCE_PLAN_SCHEMA_VERSION, REMOTE_CALL_AUTHORIZATION_SCHEMA_VERSION, REVIEW_REQUIREMENT_SCHEMA_VERSION, RESOURCE_CLAIM_SCHEMA_VERSION, RULE_TRACE_SCHEMA_VERSION, SEMANTIC_DIFF_SCHEMA_VERSION, STEP_DEPENDENCY_SCHEMA_VERSION, } from '@voce-engine/contracts';
2
+ import { computeBindingDecisionHash, computeSourceBindingContentHash, } from './evidence.js';
3
+ import { canonicalize, sha256 } from './canonical.js';
4
+ export const CONSTRAINT_COMPILER_VERSION = 'voce.constraint-compiler/v1alpha1';
5
+ export const REFERENCE_OPTIMIZER_VERSION = 'voce.reference-budget-optimizer/v1alpha1';
6
+ export const PIPELINE_PLANNER_VERSION = 'voce.pipeline-planner/v1alpha1';
7
+ export const REMOTE_AUTHORIZATION_VERSION = 'voce.authorization-preflight/v1alpha1';
8
+ export const FIXED_M4_TIME = '2026-01-01T00:00:00.000Z';
9
+ const HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
10
+ const IMPORTANCE_RANK = { preferred: 1, required: 2, hard: 3 };
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 objectOf(value) {
44
+ const ready = jsonReady(value);
45
+ return ready !== null && typeof ready === 'object' && !Array.isArray(ready) ? ready : {};
46
+ }
47
+ function sortedStrings(values) {
48
+ return [...new Set(values ?? [])].sort(compareCodeUnits);
49
+ }
50
+ function sortedBy(values, key) {
51
+ return values.map((value) => clone(value)).sort((left, right) => compareCodeUnits(key(left), key(right)) || compareCodeUnits(canonicalize(jsonReady(left)), canonicalize(jsonReady(right))));
52
+ }
53
+ function cleanWithout(value, field) {
54
+ const object = objectOf(value);
55
+ delete object[field];
56
+ return object;
57
+ }
58
+ function hashId(prefix, value) {
59
+ return `${prefix}-${sha256(jsonReady(value)).slice('sha256:'.length, 'sha256:'.length + 24)}`;
60
+ }
61
+ function stableImportance(left, right) {
62
+ return IMPORTANCE_RANK[left ?? 'preferred'] >= IMPORTANCE_RANK[right ?? 'preferred'] ? (left ?? 'preferred') : (right ?? 'preferred');
63
+ }
64
+ function importanceFromValues(values, fallback = 'required') {
65
+ return values.reduce((current, value) => stableImportance(current, value), fallback);
66
+ }
67
+ function uniqueSortedObjects(values, key) {
68
+ const result = [];
69
+ const seen = new Set();
70
+ for (const value of sortedBy(values, key)) {
71
+ const identity = key(value);
72
+ if (seen.has(identity))
73
+ continue;
74
+ seen.add(identity);
75
+ result.push(value);
76
+ }
77
+ return result;
78
+ }
79
+ function isHash(value) {
80
+ return typeof value === 'string' && HASH_PATTERN.test(value);
81
+ }
82
+ function semanticHash(value, field) {
83
+ return sha256(cleanWithout(value, field));
84
+ }
85
+ function normalizeImportance(value) {
86
+ return value === 'hard' || value === 'required' || value === 'preferred' ? value : 'required';
87
+ }
88
+ function pathMatches(candidate, expected) {
89
+ return candidate === expected || candidate.startsWith(`${expected}.`) || expected.startsWith(`${candidate}.`);
90
+ }
91
+ function pathPresent(paths, candidates) {
92
+ return paths.some((path) => candidates.some((candidate) => pathMatches(path, candidate)));
93
+ }
94
+ function valueStrings(value) {
95
+ const values = [];
96
+ const visit = (current) => {
97
+ if (typeof current === 'string')
98
+ values.push(current.toLowerCase().replaceAll(' ', '_'));
99
+ else if (Array.isArray(current))
100
+ current.forEach(visit);
101
+ else if (current && typeof current === 'object')
102
+ Object.values(current).forEach(visit);
103
+ };
104
+ visit(value);
105
+ return values;
106
+ }
107
+ function valueHasToken(value, tokens) {
108
+ const normalized = valueStrings(value);
109
+ return tokens.some((token) => normalized.includes(token.toLowerCase().replaceAll(' ', '_')));
110
+ }
111
+ function normalizeArray(values, key) {
112
+ return sortedBy(values ?? [], key);
113
+ }
114
+ function sourceBindingProjection(binding) {
115
+ return {
116
+ schemaVersion: binding.schemaVersion,
117
+ id: binding.id,
118
+ targetPath: binding.targetPath,
119
+ observationIds: sortedStrings(binding.observationIds),
120
+ relation: binding.relation,
121
+ priority: binding.priority,
122
+ };
123
+ }
124
+ function bindingDecisionProjection(decision) {
125
+ const result = {
126
+ schemaVersion: decision.schemaVersion,
127
+ decisionId: decision.decisionId,
128
+ bindingId: decision.bindingId,
129
+ bindingHash: decision.bindingHash,
130
+ contextHash: decision.contextHash,
131
+ status: decision.status,
132
+ authority: decision.authority,
133
+ decidedBy: decision.decidedBy,
134
+ reasonCode: decision.reasonCode,
135
+ };
136
+ if (decision.policyVersion !== undefined)
137
+ result.policyVersion = decision.policyVersion;
138
+ if (decision.decidedAt !== undefined)
139
+ result.decidedAt = decision.decidedAt;
140
+ return result;
141
+ }
142
+ function normalizedOntologyProjection(instance) {
143
+ return {
144
+ schemaVersion: instance.schemaVersion,
145
+ id: instance.id,
146
+ caseId: instance.caseId,
147
+ caseRevision: instance.caseRevision,
148
+ contextHash: instance.contextHash,
149
+ requestedScopePlanHash: instance.requestedScopePlanHash,
150
+ facts: sortedBy(instance.facts, (item) => `${item.path}|${item.id}`),
151
+ unknownPaths: sortedStrings(instance.unknownPaths),
152
+ unspecifiedPaths: sortedStrings(instance.unspecifiedPaths),
153
+ unresolvedItems: sortedBy(instance.unresolvedItems, (item) => item.id),
154
+ conflicts: sortedBy(instance.conflicts, (item) => item.id),
155
+ decisionTrace: sortedBy(instance.decisionTrace, (item) => item.id),
156
+ };
157
+ }
158
+ export function computeOntologyInstanceHash(instance) {
159
+ return sha256(normalizedOntologyProjection(instance));
160
+ }
161
+ export function computeCompilationContextHash(context) {
162
+ const projection = cleanWithout(context, 'contextHash');
163
+ if (Array.isArray(projection.artifactHashes))
164
+ projection.artifactHashes = sortedStrings(projection.artifactHashes);
165
+ if (Array.isArray(projection.decisionHashes))
166
+ projection.decisionHashes = sortedStrings(projection.decisionHashes);
167
+ if (Array.isArray(projection.rulePackPlugins))
168
+ projection.rulePackPlugins = sortedBy(projection.rulePackPlugins, (item) => canonicalize(jsonReady(item)));
169
+ if (Array.isArray(projection.adapters))
170
+ projection.adapters = sortedBy(projection.adapters, (item) => canonicalize(jsonReady(item)));
171
+ if (Array.isArray(projection.capabilityProfiles))
172
+ projection.capabilityProfiles = sortedBy(projection.capabilityProfiles, (item) => canonicalize(jsonReady(item)));
173
+ if (Array.isArray(projection.budgets))
174
+ projection.budgets = sortedBy(projection.budgets, (item) => String(item.id));
175
+ if (Array.isArray(projection.dataTransfers))
176
+ projection.dataTransfers = sortedBy(projection.dataTransfers, (item) => String(item.id));
177
+ return sha256(projection);
178
+ }
179
+ export function computeOutputContractHash(contract) {
180
+ const projection = cleanWithout(contract, 'outputContractHash');
181
+ if (Array.isArray(projection.mediaTypes))
182
+ projection.mediaTypes = sortedStrings(projection.mediaTypes);
183
+ return sha256(projection);
184
+ }
185
+ export function computeConstraintHash(constraint) {
186
+ const projection = cleanWithout(constraint, 'constraintHash');
187
+ if (Array.isArray(projection.targetPaths))
188
+ projection.targetPaths = sortedStrings(projection.targetPaths);
189
+ if (Array.isArray(projection.goalIds))
190
+ projection.goalIds = sortedStrings(projection.goalIds);
191
+ if (Array.isArray(projection.dependsOn))
192
+ projection.dependsOn = sortedStrings(projection.dependsOn);
193
+ if (Array.isArray(projection.resourceClaimIds))
194
+ projection.resourceClaimIds = sortedStrings(projection.resourceClaimIds);
195
+ if (Array.isArray(projection.sourceIds))
196
+ projection.sourceIds = sortedStrings(projection.sourceIds);
197
+ return sha256(projection);
198
+ }
199
+ export function computeGoalHash(goal) { return semanticHash({ ...goal, sourceIds: sortedStrings(goal.sourceIds), constraintIds: sortedStrings(goal.constraintIds) }, 'goalHash'); }
200
+ export function computeConstraintDependencyHash(dependency) { return semanticHash(dependency, 'dependencyHash'); }
201
+ export function computeResourceClaimHash(claim) { return semanticHash({ ...claim, claimantIds: sortedStrings(claim.claimantIds), constraintIds: sortedStrings(claim.constraintIds) }, 'resourceHash'); }
202
+ export function computeConstraintConflictHash(conflict) { return semanticHash({ ...conflict, constraintIds: sortedStrings(conflict.constraintIds), dependencyIds: sortedStrings(conflict.dependencyIds), resourceClaimIds: sortedStrings(conflict.resourceClaimIds) }, 'conflictHash'); }
203
+ export function computeDegradationHash(degradation) { return semanticHash({ ...degradation, affectedIds: sortedStrings(degradation.affectedIds) }, 'degradationHash'); }
204
+ export function computeRuleTraceHash(trace) { return semanticHash({ ...trace, inputIds: sortedStrings(trace.inputIds), outputIds: sortedStrings(trace.outputIds) }, 'traceHash'); }
205
+ export function computeReviewRequirementHash(requirement) { return semanticHash({ ...requirement, constraintIds: sortedStrings(requirement.constraintIds), sourceIds: sortedStrings(requirement.sourceIds) }, 'reviewHash'); }
206
+ function constraintIRProjection(ir) {
207
+ return {
208
+ schemaVersion: ir.schemaVersion,
209
+ id: ir.id,
210
+ caseId: ir.caseId,
211
+ caseRevision: ir.caseRevision,
212
+ contextHash: ir.contextHash,
213
+ requestedScopePlanHash: ir.requestedScopePlanHash,
214
+ instanceHash: ir.instanceHash,
215
+ decisionHashes: sortedStrings(ir.decisionHashes),
216
+ goals: sortedBy(ir.goals, (item) => item.id),
217
+ constraints: sortedBy(ir.constraints, (item) => item.id),
218
+ dependencies: sortedBy(ir.dependencies, (item) => item.id),
219
+ resourceClaims: sortedBy(ir.resourceClaims, (item) => item.id),
220
+ conflicts: sortedBy(ir.conflicts, (item) => item.id),
221
+ degradedPreferences: sortedBy(ir.degradedPreferences, (item) => item.id),
222
+ reviewRequirements: sortedBy(ir.reviewRequirements, (item) => item.id),
223
+ explanations: sortedBy(ir.explanations, (item) => item.id),
224
+ ruleTraces: sortedBy(ir.ruleTraces, (item) => item.id),
225
+ warnings: sortedStrings(ir.warnings),
226
+ status: ir.status,
227
+ };
228
+ }
229
+ export function computeConstraintIRSignature(ir) { return sha256(constraintIRProjection(ir)); }
230
+ function referenceCandidateProjection(candidate) {
231
+ const artifact = candidate.artifact ?? candidate.artifactHandle;
232
+ return jsonReady({
233
+ schemaVersion: candidate.schemaVersion,
234
+ id: candidate.id,
235
+ assetId: candidate.assetId,
236
+ contentHash: candidate.contentHash,
237
+ ...(artifact ? { artifact: { id: artifact.id, contentHash: artifact.contentHash, mediaType: artifact.mediaType, byteLength: artifact.byteLength, role: artifact.role, availability: artifact.availability } } : {}),
238
+ mediaType: candidate.mediaType,
239
+ byteLength: candidate.byteLength,
240
+ role: candidate.role,
241
+ ontologyScopes: sortedStrings(candidate.ontologyScopes),
242
+ importance: candidate.importance,
243
+ constraintIds: sortedStrings(candidate.constraintIds),
244
+ sourceBindingIds: sortedStrings(candidate.sourceBindingIds),
245
+ goalIds: sortedStrings(candidate.goalIds),
246
+ orderKey: candidate.orderKey,
247
+ });
248
+ }
249
+ export function computeReferenceCandidateHash(candidate) { return semanticHash(referenceCandidateProjection(candidate), 'candidateHash'); }
250
+ export function computeReferenceDependencyHash(dependency) { return semanticHash(dependency, 'dependencyHash'); }
251
+ export function computeReferenceOmissionHash(omission) { return semanticHash({ ...omission, constraintIds: sortedStrings(omission.constraintIds), dependencyIds: sortedStrings(omission.dependencyIds) }, 'omissionHash'); }
252
+ export function createReferenceCandidate(input) {
253
+ const base = clone({ ...input, candidateHash: '' });
254
+ return clone({ ...base, candidateHash: computeReferenceCandidateHash(base) });
255
+ }
256
+ export function createReferenceDependency(input) {
257
+ const base = clone({ ...input, dependencyHash: '' });
258
+ return clone({ ...base, dependencyHash: computeReferenceDependencyHash(base) });
259
+ }
260
+ function profileProjection(profile) {
261
+ const reference = profile.referenceLimits ?? {};
262
+ const output = profile.outputCapabilities ?? {};
263
+ return jsonReady({
264
+ schemaVersion: profile.schemaVersion,
265
+ id: profile.id,
266
+ version: profile.version,
267
+ versionSummary: profile.versionSummary,
268
+ adapterId: profile.adapterId,
269
+ adapterDigest: profile.adapterDigest,
270
+ verificationStatus: profile.verificationStatus,
271
+ referenceLimits: {
272
+ maximumReferenceCount: profile.maximumReferenceCount ?? reference.maximumReferenceCount,
273
+ maximumTotalBytes: profile.maximumTotalReferenceBytes ?? reference.maximumTotalBytes,
274
+ maximumBytesPerReference: profile.maximumBytesPerReference ?? reference.maximumBytesPerReference,
275
+ allowedMediaTypes: sortedStrings(profile.allowedReferenceMediaTypes ?? reference.allowedMediaTypes),
276
+ allowedRoles: sortedStrings(profile.allowedReferenceRoles ?? reference.allowedRoles),
277
+ ordering: profile.referenceOrdering ?? reference.ordering,
278
+ roleOrder: profile.referenceRoleOrder ?? reference.roleOrder,
279
+ supportsMultipleReferences: profile.supportsMultipleReferences ?? reference.supportsMultipleReferences,
280
+ requiresPublishedReferences: profile.requiresPublishedReferences ?? reference.requiresPublishedReferences,
281
+ },
282
+ outputCapabilities: {
283
+ mediaTypes: sortedStrings(profile.outputMediaTypes ?? output.mediaTypes),
284
+ formats: sortedStrings(output.formats),
285
+ supportsTransparentOutput: profile.supportsTransparentOutput ?? output.supportsTransparentOutput,
286
+ supportsAlpha: profile.supportsAlpha ?? output.supportsAlpha,
287
+ maximumWidth: output.maximumWidth,
288
+ maximumHeight: output.maximumHeight,
289
+ minimumWidth: output.minimumWidth,
290
+ minimumHeight: output.minimumHeight,
291
+ },
292
+ supportsEditing: profile.supportsEditing,
293
+ supportsBatchOutput: profile.supportsBatchOutput,
294
+ knownIncompatibilities: sortedStrings(profile.knownIncompatibilities),
295
+ timeoutMs: profile.timeoutMs,
296
+ streaming: profile.streaming,
297
+ destination: profile.destination,
298
+ dataCategories: sortedStrings(profile.dataCategories),
299
+ });
300
+ }
301
+ export function computeProviderCapabilityProfileHash(profile) { return sha256(profileProjection(profile)); }
302
+ export function computeBudgetHash(budget) { return semanticHash(budget, 'budgetHash'); }
303
+ export function computeDataTransferHash(transfer) { return semanticHash({ ...transfer, dataCategories: sortedStrings(transfer.dataCategories) }, 'transferHash'); }
304
+ export function computeCleanupHash(cleanup) { return semanticHash({ ...cleanup, appliesToStepIds: sortedStrings(cleanup.appliesToStepIds), conditions: sortedStrings(cleanup.conditions), artifactRoles: sortedStrings(cleanup.artifactRoles), dataCategories: sortedStrings(cleanup.dataCategories) }, 'cleanupHash'); }
305
+ export function computeCompensationHash(compensation) { return semanticHash({ ...compensation, appliesToStepIds: sortedStrings(compensation.appliesToStepIds) }, 'compensationHash'); }
306
+ export function computePipelineStepHash(step) { return semanticHash({ ...step, inputArtifactRoles: sortedStrings(step.inputArtifactRoles), outputArtifactRoles: sortedStrings(step.outputArtifactRoles), dependsOn: sortedStrings(step.dependsOn), cleanupObligationIds: sortedStrings(step.cleanupObligationIds), compensationIds: sortedStrings(step.compensationIds) }, 'stepHash'); }
307
+ export function computePipelinePlanHash(plan) {
308
+ const projection = cleanWithout(plan, 'planHash');
309
+ if (Array.isArray(projection.adapterDigests))
310
+ projection.adapterDigests = sortedStrings(projection.adapterDigests);
311
+ if (Array.isArray(projection.steps))
312
+ projection.steps = sortedBy(projection.steps, (item) => String(item.id));
313
+ if (Array.isArray(projection.dependencies))
314
+ projection.dependencies = sortedBy(projection.dependencies, (item) => String(item.id));
315
+ if (Array.isArray(projection.budgets))
316
+ projection.budgets = sortedBy(projection.budgets, (item) => String(item.id));
317
+ if (Array.isArray(projection.dataTransfers))
318
+ projection.dataTransfers = sortedBy(projection.dataTransfers, (item) => String(item.id));
319
+ if (Array.isArray(projection.cleanup))
320
+ projection.cleanup = sortedBy(projection.cleanup, (item) => String(item.id));
321
+ if (Array.isArray(projection.compensation))
322
+ projection.compensation = sortedBy(projection.compensation, (item) => String(item.id));
323
+ return sha256(projection);
324
+ }
325
+ function authorizationProjection(value) {
326
+ const projection = cleanWithout(value, 'authorizationHash');
327
+ delete projection.authorizedAt;
328
+ if (Array.isArray(projection.permittedArtifactHashes))
329
+ projection.permittedArtifactHashes = sortedStrings(projection.permittedArtifactHashes);
330
+ if (Array.isArray(projection.permittedScopeIds))
331
+ projection.permittedScopeIds = sortedStrings(projection.permittedScopeIds);
332
+ if (Array.isArray(projection.constraintIds))
333
+ projection.constraintIds = sortedStrings(projection.constraintIds);
334
+ if (Array.isArray(projection.adapterProfileDigests))
335
+ projection.adapterProfileDigests = sortedStrings(projection.adapterProfileDigests);
336
+ if (Array.isArray(projection.destinations))
337
+ projection.destinations = sortedStrings(projection.destinations);
338
+ if (Array.isArray(projection.remoteCallAuthorizationIds))
339
+ projection.remoteCallAuthorizationIds = sortedStrings(projection.remoteCallAuthorizationIds);
340
+ return projection;
341
+ }
342
+ export function computeRemoteCallAuthorizationHash(authorization) { return sha256(authorizationProjection(authorization)); }
343
+ export function computeExecutionAuthorizationHash(authorization) { return sha256(authorizationProjection(authorization)); }
344
+ export function computeConstraintWaiverHash(waiver) {
345
+ const projection = cleanWithout(waiver, 'waiverHash');
346
+ delete projection.decidedAt;
347
+ return sha256(projection);
348
+ }
349
+ function normalizeConstraint(constraint) {
350
+ return clone({ ...constraint, targetPaths: sortedStrings(constraint.targetPaths), goalIds: sortedStrings(constraint.goalIds), dependsOn: sortedStrings(constraint.dependsOn), resourceClaimIds: sortedStrings(constraint.resourceClaimIds), sourceIds: sortedStrings(constraint.sourceIds) });
351
+ }
352
+ export function createConstraint(input) {
353
+ const base = normalizeConstraint({ ...input, constraintHash: '' });
354
+ return clone({ ...base, constraintHash: computeConstraintHash(base) });
355
+ }
356
+ export function createGoal(input) {
357
+ const base = clone({ ...input, sourceIds: sortedStrings(input.sourceIds), constraintIds: sortedStrings(input.constraintIds), goalHash: '' });
358
+ return clone({ ...base, goalHash: computeGoalHash(base) });
359
+ }
360
+ export function createConstraintDependency(input) {
361
+ const base = clone({ ...input, dependencyHash: '' });
362
+ return clone({ ...base, dependencyHash: computeConstraintDependencyHash(base) });
363
+ }
364
+ export function createResourceClaim(input) {
365
+ const base = clone({ ...input, claimantIds: sortedStrings(input.claimantIds), constraintIds: sortedStrings(input.constraintIds), resourceHash: '' });
366
+ return clone({ ...base, resourceHash: computeResourceClaimHash(base) });
367
+ }
368
+ export function createConstraintConflict(input) {
369
+ const base = clone({ ...input, constraintIds: sortedStrings(input.constraintIds), dependencyIds: sortedStrings(input.dependencyIds), resourceClaimIds: sortedStrings(input.resourceClaimIds), conflictHash: '' });
370
+ return clone({ ...base, conflictHash: computeConstraintConflictHash(base) });
371
+ }
372
+ export function createDegradation(input) {
373
+ const base = clone({ ...input, affectedIds: sortedStrings(input.affectedIds), degradationHash: '' });
374
+ return clone({ ...base, degradationHash: computeDegradationHash(base) });
375
+ }
376
+ export function createRuleTrace(input) {
377
+ const base = clone({ ...input, inputIds: sortedStrings(input.inputIds), outputIds: sortedStrings(input.outputIds), traceHash: '' });
378
+ return clone({ ...base, traceHash: computeRuleTraceHash(base) });
379
+ }
380
+ export function createReviewRequirement(input) {
381
+ const base = clone({ ...input, constraintIds: sortedStrings(input.constraintIds), sourceIds: sortedStrings(input.sourceIds), reviewHash: '' });
382
+ return clone({ ...base, reviewHash: computeReviewRequirementHash(base) });
383
+ }
384
+ export function createBudget(input) {
385
+ const base = clone({ ...input, budgetHash: '' });
386
+ return clone({ ...base, budgetHash: computeBudgetHash(base) });
387
+ }
388
+ export function createDataTransfer(input) {
389
+ const base = clone({ ...input, dataCategories: sortedStrings(input.dataCategories), transferHash: '' });
390
+ return clone({ ...base, transferHash: computeDataTransferHash(base) });
391
+ }
392
+ export function createRemoteCallAuthorization(input) {
393
+ const base = clone({ ...input, permittedArtifactHashes: sortedStrings(input.permittedArtifactHashes), permittedScopeIds: sortedStrings(input.permittedScopeIds), constraintIds: sortedStrings(input.constraintIds), dataCategories: sortedStrings(input.dataCategories), authorizationHash: '' });
394
+ return clone({ ...base, authorizationHash: computeRemoteCallAuthorizationHash(base) });
395
+ }
396
+ export function createExecutionAuthorization(input) {
397
+ const base = clone({ ...input, adapterProfileDigests: sortedStrings(input.adapterProfileDigests), destinations: sortedStrings(input.destinations), remoteCallAuthorizationIds: sortedStrings(input.remoteCallAuthorizationIds), authorizationHash: '' });
398
+ return clone({ ...base, authorizationHash: computeExecutionAuthorizationHash(base) });
399
+ }
400
+ export function createConstraintWaiver(input) {
401
+ const base = clone({ ...input, waiverHash: '' });
402
+ return clone({ ...base, waiverHash: computeConstraintWaiverHash(base) });
403
+ }
404
+ /**
405
+ * These are plain declarative records. They are deliberately not keyed to a
406
+ * scenario name; a ScenarioPack may copy, extend, or replace them through its
407
+ * `rulePacks` contribution.
408
+ */
409
+ export const M4_RULE_FIXTURES = {
410
+ maskIdentity: {
411
+ id: 'rule.mask-identity-visibility',
412
+ ruleType: 'occlusion',
413
+ leftPaths: ['person.identity', 'person.face'],
414
+ rightPaths: ['accessories.mask', 'mask', 'person.faceMask'],
415
+ rightTokens: ['full_face', 'full-face', 'fullface', 'opaque_face'],
416
+ importance: 'preferred',
417
+ code: 'MASK_IDENTITY_VISIBILITY_CONFLICT',
418
+ reasonCode: 'MASK_OCCLUDES_REQUIRED_IDENTITY',
419
+ message: 'A full-face mask occludes a required identity-visibility constraint.',
420
+ },
421
+ sleeveBracelet: {
422
+ id: 'rule.sleeve-bracelet-occlusion',
423
+ ruleType: 'occlusion',
424
+ leftPaths: ['accessories.bracelet', 'jewelry.bracelet', 'wrist.accessory'],
425
+ rightPaths: ['wardrobe.sleeve', 'wardrobe.sleeves', 'garment.sleeve'],
426
+ rightTokens: ['wrist_cover', 'full_length', 'long', 'long_sleeve', 'covers_wrist'],
427
+ importance: 'required',
428
+ code: 'SLEEVE_BRACELET_OCCLUSION',
429
+ reasonCode: 'SLEEVE_COVERS_REQUIRED_BRACELET',
430
+ message: 'A sleeve coverage requirement occludes a required bracelet detail.',
431
+ },
432
+ handProp: {
433
+ id: 'rule.hand-prop-resource',
434
+ ruleType: 'resource',
435
+ leftPaths: ['accessories.bracelet', 'jewelry', 'accessories.hand'],
436
+ rightPaths: ['prop', 'props', 'pose.hand', 'hand'],
437
+ rightTokens: ['held', 'left', 'right', 'two_hands', 'both_hands'],
438
+ importance: 'required',
439
+ code: 'HAND_PROP_RESOURCE_CONFLICT',
440
+ reasonCode: 'HAND_RESOURCE_OVERLAP',
441
+ message: 'A hand-worn or hand-held requirement claims the same exclusive hand resource as a prop.',
442
+ resourceId: 'hand',
443
+ },
444
+ };
445
+ export const M4_DECLARATIVE_RULE_FIXTURES = Object.values(M4_RULE_FIXTURES);
446
+ function internalRule(value, contributionId) {
447
+ if (!value || typeof value !== 'object' || Array.isArray(value))
448
+ return undefined;
449
+ const object = value;
450
+ const id = typeof object.id === 'string' ? object.id : typeof object.ruleId === 'string' ? object.ruleId : undefined;
451
+ if (!id)
452
+ return undefined;
453
+ const type = object.ruleType ?? object.type ?? object.kind;
454
+ const kind = type === 'resource' ? 'resource' : type === 'dependency' ? 'dependency' : type === 'occlusion' ? 'occlusion' : 'incompatibility';
455
+ const leftPaths = Array.isArray(object.leftPaths) ? object.leftPaths.filter((item) => typeof item === 'string') : Array.isArray(object.whenPaths) ? object.whenPaths.filter((item) => typeof item === 'string') : [];
456
+ const rightPaths = Array.isArray(object.rightPaths) ? object.rightPaths.filter((item) => typeof item === 'string') : Array.isArray(object.conflictingPaths) ? object.conflictingPaths.filter((item) => typeof item === 'string') : [];
457
+ if (leftPaths.length === 0 || rightPaths.length === 0)
458
+ return undefined;
459
+ const tokenList = (field) => Array.isArray(object[field]) ? object[field].filter((item) => typeof item === 'string') : [];
460
+ const severity = normalizeImportance(object.importance ?? object.severity);
461
+ const code = typeof object.code === 'string' ? object.code : `RULE_${id.toUpperCase().replaceAll(/[^A-Z0-9]+/g, '_')}`;
462
+ const reasonCode = typeof object.reasonCode === 'string' ? object.reasonCode : code;
463
+ const message = typeof object.message === 'string' ? object.message : `Declarative rule ${id} found incompatible constraints.`;
464
+ const dependencyKind = object.dependencyKind === 'parent_detail' || object.dependencyKind === 'identity_garment' || object.dependencyKind === 'source_isolation' || object.dependencyKind === 'visibility' || object.dependencyKind === 'occludes' || object.dependencyKind === 'ordered_before' || object.dependencyKind === 'supports' || object.dependencyKind === 'excludes' || object.dependencyKind === 'requires' ? object.dependencyKind : undefined;
465
+ return {
466
+ id,
467
+ contributionId,
468
+ code,
469
+ kind,
470
+ leftPaths: sortedStrings(leftPaths),
471
+ rightPaths: sortedStrings(rightPaths),
472
+ leftTokens: sortedStrings(tokenList('leftTokens')),
473
+ rightTokens: sortedStrings(tokenList('rightTokens')),
474
+ importance: severity,
475
+ reasonCode,
476
+ message,
477
+ ...(typeof object.resourceId === 'string' ? { resourceId: object.resourceId } : {}),
478
+ ...(dependencyKind ? { dependencyKind } : {}),
479
+ };
480
+ }
481
+ function allRules(effectiveScenario, collisionIds = []) {
482
+ const values = M4_DECLARATIVE_RULE_FIXTURES.map((value) => ({ value }));
483
+ for (const contribution of effectiveScenario?.rulePacks ?? []) {
484
+ const object = contribution;
485
+ const rules = Array.isArray(object.rules) ? object.rules : [];
486
+ for (const rule of rules)
487
+ values.push({ value: rule, contributionId: typeof object.contributionId === 'string' ? object.contributionId : undefined });
488
+ if (rules.length === 0)
489
+ values.push({ value: contribution, contributionId: typeof object.contributionId === 'string' ? object.contributionId : undefined });
490
+ }
491
+ const byId = new Map();
492
+ for (const entry of values) {
493
+ const rule = internalRule(entry.value, entry.contributionId);
494
+ if (!rule)
495
+ continue;
496
+ const prior = byId.get(rule.id);
497
+ if (prior && canonicalize(jsonReady(rule)) !== canonicalize(jsonReady(prior)))
498
+ collisionIds.push(rule.id);
499
+ if (!prior || canonicalize(jsonReady(rule)) < canonicalize(jsonReady(prior)))
500
+ byId.set(rule.id, rule);
501
+ }
502
+ return sortedBy([...byId.values()], (item) => item.id);
503
+ }
504
+ function itemImportance(path, intents, bindingPriority) {
505
+ const matching = intents.filter((intent) => pathMatches(path, intent.targetPath) || pathMatches(intent.targetPath, path)).map((intent) => intent.importance);
506
+ return importanceFromValues([...matching, bindingPriority], 'required');
507
+ }
508
+ function semanticItems(input) {
509
+ const intentItems = input.changeIntents.map((intent) => ({
510
+ id: intent.id,
511
+ path: intent.targetPath,
512
+ value: intent.requestedValue ?? null,
513
+ importance: intent.importance,
514
+ sourceIds: sortedStrings([intent.id, ...(intent.sourceHintIds ?? [])]),
515
+ }));
516
+ const factItems = input.ontologyInstance.facts.map((fact) => ({
517
+ id: fact.id,
518
+ path: fact.path,
519
+ value: fact.value,
520
+ importance: itemImportance(fact.path, input.changeIntents),
521
+ sourceIds: sortedStrings([...fact.acceptedByIds, ...fact.acceptedByDecisionIds, ...fact.sourceBindingIds]),
522
+ }));
523
+ return sortedBy([...intentItems, ...factItems], (item) => `${item.path}|${item.id}`);
524
+ }
525
+ function ruleMatches(rule, items) {
526
+ const left = items.filter((item) => pathPresent([item.path], rule.leftPaths) && (rule.leftTokens.length === 0 || valueHasToken(item.value, rule.leftTokens)));
527
+ const right = items.filter((item) => pathPresent([item.path], rule.rightPaths) && (rule.rightTokens.length === 0 || valueHasToken(item.value, rule.rightTokens)));
528
+ if (left.length === 0 || right.length === 0)
529
+ return undefined;
530
+ return { left, right };
531
+ }
532
+ function goalForIntent(intent) {
533
+ const base = {
534
+ schemaVersion: GOAL_SCHEMA_VERSION,
535
+ id: hashId('goal', { id: intent.id, operation: intent.operation, targetPath: intent.targetPath, requestedValue: intent.requestedValue, importance: intent.importance }),
536
+ operation: intent.operation,
537
+ importance: intent.importance,
538
+ targetPath: intent.targetPath,
539
+ ...(intent.requestedValue === undefined ? {} : { requestedValue: clone(intent.requestedValue) }),
540
+ sourceIds: sortedStrings([intent.id, ...(intent.sourceHintIds ?? [])]),
541
+ constraintIds: [],
542
+ explanation: `Target ${intent.operation} goal for ${intent.targetPath}.`,
543
+ };
544
+ return createGoal(base);
545
+ }
546
+ function constraintForIntent(intent, goal) {
547
+ const kind = intent.operation === 'preserve' ? 'preservation' : intent.operation === 'remove' ? 'visibility' : intent.operation === 'create' ? 'output' : 'transformation';
548
+ const base = {
549
+ schemaVersion: CONSTRAINT_SCHEMA_VERSION,
550
+ id: hashId('constraint', { goalId: goal.id, kind, targetPath: intent.targetPath, value: intent.requestedValue, importance: intent.importance }),
551
+ kind,
552
+ importance: intent.importance,
553
+ status: 'active',
554
+ targetPath: intent.targetPath,
555
+ targetPaths: [intent.targetPath],
556
+ predicate: intent.operation === 'remove' ? 'absent' : intent.operation,
557
+ ...(intent.requestedValue === undefined ? {} : { value: clone(intent.requestedValue) }),
558
+ goalIds: [goal.id],
559
+ dependsOn: [],
560
+ resourceClaimIds: [],
561
+ sourceIds: sortedStrings([intent.id, ...(intent.sourceHintIds ?? [])]),
562
+ reasonCode: `TARGET_${intent.operation.toUpperCase()}`,
563
+ explanation: `Constraint derived from ${intent.operation} intent at ${intent.targetPath}.`,
564
+ };
565
+ return createConstraint(base);
566
+ }
567
+ function factConstraint(item) {
568
+ return createConstraint({
569
+ schemaVersion: CONSTRAINT_SCHEMA_VERSION,
570
+ id: hashId('fact-constraint', { id: item.id, path: item.path, value: item.value }),
571
+ kind: 'preservation',
572
+ importance: item.importance,
573
+ status: 'satisfied',
574
+ targetPath: item.path,
575
+ targetPaths: [item.path],
576
+ predicate: 'fact_present',
577
+ value: clone(item.value),
578
+ goalIds: [],
579
+ dependsOn: [],
580
+ resourceClaimIds: [],
581
+ sourceIds: item.sourceIds,
582
+ reasonCode: 'ONTOLOGY_FACT_ACCEPTED',
583
+ explanation: `Accepted sparse ontology fact at ${item.path}.`,
584
+ });
585
+ }
586
+ function outputConstraints(contract) {
587
+ const constraints = [];
588
+ const outputValue = { artifactKind: contract.artifactKind, dataType: contract.dataType, mediaTypes: sortedStrings(contract.mediaTypes), cardinality: contract.cardinality, dimensions: contract.dimensions, background: contract.background, maxBytes: contract.maxBytes, allowAlpha: contract.allowAlpha, downstreamUse: contract.downstreamUse };
589
+ constraints.push(createConstraint({
590
+ schemaVersion: CONSTRAINT_SCHEMA_VERSION,
591
+ id: hashId('output-constraint', outputValue),
592
+ kind: 'output',
593
+ importance: 'hard',
594
+ status: 'active',
595
+ targetPath: 'output',
596
+ targetPaths: ['output'],
597
+ predicate: 'output_contract',
598
+ value: outputValue,
599
+ goalIds: [],
600
+ dependsOn: [],
601
+ resourceClaimIds: [],
602
+ sourceIds: [],
603
+ reasonCode: 'OUTPUT_CONTRACT_REQUIRED',
604
+ explanation: 'OutputContract requirements are immutable compilation constraints.',
605
+ }));
606
+ return constraints;
607
+ }
608
+ function makeRuleTrace(rule, inputIds, outputIds, outcome, reasonCode = rule.reasonCode, message = rule.message) {
609
+ return createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { ruleId: rule.id, inputIds, outputIds, outcome, reasonCode }), ruleId: rule.id, ...(rule.contributionId ? { contributionId: rule.contributionId } : {}), inputIds, outputIds, outcome, reasonCode, message });
610
+ }
611
+ function conflictForRule(rule, left, right) {
612
+ const constraintIds = sortedStrings([...left, ...right].map((item) => item.id));
613
+ const severity = importanceFromValues([...left, ...right].map((item) => item.importance), rule.importance);
614
+ return createConstraintConflict({
615
+ schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION,
616
+ id: hashId('constraint-conflict', { code: rule.code, ruleId: rule.id, constraintIds }),
617
+ code: rule.code,
618
+ severity,
619
+ targetPath: [...left, ...right].map((item) => item.targetPath).find((item) => typeof item === 'string'),
620
+ constraintIds,
621
+ dependencyIds: [],
622
+ resourceClaimIds: rule.resourceId ? [hashId('resource', { resourceId: rule.resourceId, constraintIds })] : [],
623
+ message: rule.message,
624
+ blocking: severity !== 'preferred',
625
+ waiverAllowed: severity === 'required',
626
+ });
627
+ }
628
+ function validWaiver(waiver, input) {
629
+ if (waiver.schemaVersion !== 'voce.constraint-waiver/v1alpha1' || waiver.caseId !== input.caseId || waiver.caseRevision !== input.caseRevision || waiver.contextHash !== input.contextHash || !isHash(waiver.waiverHash))
630
+ return false;
631
+ return computeConstraintWaiverHash(waiver) === waiver.waiverHash;
632
+ }
633
+ function waiverTargets(waivers, input) {
634
+ const result = new Set();
635
+ for (const waiver of waivers)
636
+ if (validWaiver(waiver, input))
637
+ result.add(waiver.targetId);
638
+ return result;
639
+ }
640
+ function dependencyCycles(dependencies, ids) {
641
+ const edges = new Map();
642
+ for (const dependency of dependencies) {
643
+ if (!ids.has(dependency.parentId) || !ids.has(dependency.childId))
644
+ continue;
645
+ edges.set(dependency.parentId, [...(edges.get(dependency.parentId) ?? []), dependency.childId]);
646
+ }
647
+ for (const [key, values] of edges)
648
+ edges.set(key, sortedStrings(values));
649
+ const visited = new Set();
650
+ const active = new Set();
651
+ const cycles = [];
652
+ const walk = (id, stack) => {
653
+ if (active.has(id)) {
654
+ const index = stack.indexOf(id);
655
+ cycles.push(stack.slice(index));
656
+ return;
657
+ }
658
+ if (visited.has(id))
659
+ return;
660
+ active.add(id);
661
+ for (const child of edges.get(id) ?? [])
662
+ walk(child, [...stack, child]);
663
+ active.delete(id);
664
+ visited.add(id);
665
+ };
666
+ for (const id of [...ids].sort(compareCodeUnits))
667
+ walk(id, [id]);
668
+ return cycles;
669
+ }
670
+ function blockedConstraintIR(input, reasons, conflicts = []) {
671
+ const caseId = typeof input.caseId === 'string' ? input.caseId : 'unknown-case';
672
+ const caseRevision = typeof input.caseRevision === 'number' ? input.caseRevision : 0;
673
+ const contextHash = typeof input.contextHash === 'string' ? input.contextHash : 'sha256:' + '0'.repeat(64);
674
+ const instance = input.ontologyInstance;
675
+ const requestedScopePlanHash = typeof input.requestedScopePlanHash === 'string' ? input.requestedScopePlanHash : instance?.requestedScopePlanHash ?? 'sha256:' + '0'.repeat(64);
676
+ const instanceHash = instance?.instanceHash ?? 'sha256:' + '0'.repeat(64);
677
+ const base = {
678
+ schemaVersion: CONSTRAINT_IR_SCHEMA_VERSION,
679
+ id: hashId('constraint-ir', { caseId, caseRevision, contextHash, requestedScopePlanHash, instanceHash, reasons: sortedStrings(reasons) }),
680
+ caseId,
681
+ caseRevision,
682
+ contextHash,
683
+ requestedScopePlanHash,
684
+ instanceHash,
685
+ decisionHashes: [],
686
+ goals: [],
687
+ constraints: [],
688
+ dependencies: [],
689
+ resourceClaims: [],
690
+ conflicts: sortedBy(conflicts, (item) => item.id),
691
+ degradedPreferences: [],
692
+ reviewRequirements: [],
693
+ explanations: [],
694
+ ruleTraces: [],
695
+ warnings: sortedStrings(reasons),
696
+ status: 'blocked',
697
+ deterministicSignature: '',
698
+ };
699
+ base.deterministicSignature = computeConstraintIRSignature(base);
700
+ return clone(base);
701
+ }
702
+ function validateBindingInputs(input) {
703
+ const reasons = [];
704
+ const bindingsById = new Map();
705
+ for (const binding of sortedBy(input.sourceBindings ?? [], (item) => item.id)) {
706
+ const prior = bindingsById.get(binding.id);
707
+ const hashValid = binding.schemaVersion === 'voce.source-binding/v1alpha1' && isHash(binding.contentHash) && computeSourceBindingContentHash(binding) === binding.contentHash;
708
+ if (!hashValid) {
709
+ reasons.push('SOURCE_BINDING_HASH_MISMATCH');
710
+ continue;
711
+ }
712
+ if (prior && canonicalize(jsonReady(sourceBindingProjection(prior))) !== canonicalize(jsonReady(sourceBindingProjection(binding))))
713
+ reasons.push('SOURCE_BINDING_ID_COLLISION');
714
+ else
715
+ bindingsById.set(binding.id, clone(binding));
716
+ }
717
+ const decisionsById = new Map();
718
+ for (const decision of sortedBy(input.bindingDecisions ?? [], (item) => item.decisionId)) {
719
+ const prior = decisionsById.get(decision.decisionId);
720
+ const hashValid = decision.schemaVersion === BINDING_DECISION_SCHEMA_VERSION && isHash(decision.decisionHash) && computeBindingDecisionHash(decision) === decision.decisionHash;
721
+ if (!hashValid) {
722
+ reasons.push('BINDING_DECISION_HASH_MISMATCH');
723
+ continue;
724
+ }
725
+ if (prior && canonicalize(jsonReady(bindingDecisionProjection(prior))) !== canonicalize(jsonReady(bindingDecisionProjection(decision))))
726
+ reasons.push('BINDING_DECISION_ID_COLLISION');
727
+ else
728
+ decisionsById.set(decision.decisionId, clone(decision));
729
+ }
730
+ for (const decision of decisionsById.values()) {
731
+ const binding = bindingsById.get(decision.bindingId);
732
+ if (!binding)
733
+ reasons.push('BINDING_NOT_FOUND');
734
+ else if (decision.bindingHash !== binding.contentHash)
735
+ reasons.push('BINDING_HASH_MISMATCH');
736
+ if (decision.contextHash !== input.contextHash)
737
+ reasons.push('BINDING_CONTEXT_MISMATCH');
738
+ if (decision.status !== 'confirmed')
739
+ reasons.push('BINDING_NOT_CONFIRMED');
740
+ if (input.context.decisionHashes.length > 0 && !input.context.decisionHashes.includes(decision.decisionHash))
741
+ reasons.push('DECISION_HASH_NOT_PINNED');
742
+ }
743
+ return sortedStrings(reasons);
744
+ }
745
+ function constraintIRIntegrityReasons(ir) {
746
+ const reasons = [];
747
+ if (ir.schemaVersion !== CONSTRAINT_IR_SCHEMA_VERSION)
748
+ reasons.push('CONSTRAINT_IR_SCHEMA_INVALID');
749
+ if (!isHash(ir.deterministicSignature) || computeConstraintIRSignature(ir) !== ir.deterministicSignature)
750
+ reasons.push('CONSTRAINT_IR_SIGNATURE_MISMATCH');
751
+ for (const item of ir.goals)
752
+ if (!isHash(item.goalHash) || computeGoalHash(item) !== item.goalHash)
753
+ reasons.push('GOAL_HASH_MISMATCH');
754
+ for (const item of ir.constraints)
755
+ if (!isHash(item.constraintHash) || computeConstraintHash(item) !== item.constraintHash)
756
+ reasons.push('CONSTRAINT_HASH_MISMATCH');
757
+ for (const item of ir.dependencies)
758
+ if (!isHash(item.dependencyHash) || computeConstraintDependencyHash(item) !== item.dependencyHash)
759
+ reasons.push('CONSTRAINT_DEPENDENCY_HASH_MISMATCH');
760
+ for (const item of ir.resourceClaims)
761
+ if (!isHash(item.resourceHash) || computeResourceClaimHash(item) !== item.resourceHash)
762
+ reasons.push('RESOURCE_CLAIM_HASH_MISMATCH');
763
+ for (const item of ir.conflicts)
764
+ if (!isHash(item.conflictHash) || computeConstraintConflictHash(item) !== item.conflictHash)
765
+ reasons.push('CONSTRAINT_CONFLICT_HASH_MISMATCH');
766
+ for (const item of ir.degradedPreferences)
767
+ if (!isHash(item.degradationHash) || computeDegradationHash(item) !== item.degradationHash)
768
+ reasons.push('DEGRADATION_HASH_MISMATCH');
769
+ for (const item of ir.reviewRequirements)
770
+ if (!isHash(item.reviewHash) || computeReviewRequirementHash(item) !== item.reviewHash)
771
+ reasons.push('REVIEW_REQUIREMENT_HASH_MISMATCH');
772
+ for (const item of ir.ruleTraces)
773
+ if (!isHash(item.traceHash) || computeRuleTraceHash(item) !== item.traceHash)
774
+ reasons.push('RULE_TRACE_HASH_MISMATCH');
775
+ return sortedStrings(reasons);
776
+ }
777
+ export class ConstraintGraphCompiler {
778
+ compile(input) {
779
+ try {
780
+ return this.compileSafe(clone(input));
781
+ }
782
+ catch (error) {
783
+ const code = error instanceof Error && error.message === 'JSON_VALUE_INVALID' ? 'INPUT_INVALID' : 'INPUT_INVALID';
784
+ return blockedConstraintIR(input ?? {}, [code]);
785
+ }
786
+ }
787
+ compileSafe(input) {
788
+ const basicReasons = [];
789
+ if (!input || typeof input !== 'object' || input.schemaVersion !== 'voce.constraint-compilation-input/v1alpha1')
790
+ basicReasons.push('CONSTRAINT_INPUT_SCHEMA_INVALID');
791
+ if (typeof input.caseId !== 'string' || typeof input.caseRevision !== 'number')
792
+ basicReasons.push('CASE_REVISION_INVALID');
793
+ if (!input.context || typeof input.context !== 'object')
794
+ basicReasons.push('COMPILATION_CONTEXT_INVALID');
795
+ if (input.context && (input.context.caseSpecId !== input.caseId || input.context.caseSpecRevision !== input.caseRevision))
796
+ basicReasons.push('CONTEXT_CASE_MISMATCH');
797
+ if (!isHash(input.contextHash) || input.context.contextHash !== input.contextHash || computeCompilationContextHash(input.context) !== input.contextHash)
798
+ basicReasons.push('CONTEXT_HASH_MISMATCH');
799
+ if (!input.ontologyInstance || typeof input.ontologyInstance !== 'object')
800
+ basicReasons.push('ONTOLOGY_INSTANCE_INVALID');
801
+ if (input.ontologyInstance && (!isHash(input.ontologyInstance.instanceHash) || computeOntologyInstanceHash(input.ontologyInstance) !== input.ontologyInstance.instanceHash))
802
+ basicReasons.push('INSTANCE_HASH_MISMATCH');
803
+ if (input.ontologyInstance && input.ontologyInstance.contextHash !== input.contextHash)
804
+ basicReasons.push('ONTOLOGY_CONTEXT_MISMATCH');
805
+ if (input.ontologyInstance && input.ontologyInstance.requestedScopePlanHash !== input.requestedScopePlanHash)
806
+ basicReasons.push('REQUESTED_SCOPE_PLAN_HASH_MISMATCH');
807
+ const status = input.status ?? input.ontologyStatus ?? input.ontologyInstance.status ?? 'ok';
808
+ if (status !== 'ok')
809
+ basicReasons.push('M3_STATUS_BLOCKED');
810
+ if (input.ontologyInstance?.conflicts.some((conflict) => conflict.blocking))
811
+ basicReasons.push('M3_BLOCKING_CONFLICT');
812
+ if (input.effectiveScenario) {
813
+ const scenarioProjection = cleanWithout(input.effectiveScenario, 'effectiveScenarioHash');
814
+ if (!isHash(input.effectiveScenario.effectiveScenarioHash) || sha256(scenarioProjection) !== input.effectiveScenario.effectiveScenarioHash)
815
+ basicReasons.push('EFFECTIVE_SCENARIO_HASH_MISMATCH');
816
+ }
817
+ const bindingReasons = validateBindingInputs(input);
818
+ basicReasons.push(...bindingReasons);
819
+ if (basicReasons.length)
820
+ return blockedConstraintIR(input, basicReasons);
821
+ const ruleCollisionIds = [];
822
+ allRules(input.effectiveScenario, ruleCollisionIds);
823
+ if (ruleCollisionIds.length)
824
+ return blockedConstraintIR(input, ['DECLARATIVE_RULE_ID_COLLISION']);
825
+ const goals = [];
826
+ const constraints = [];
827
+ const dependencies = [];
828
+ const resourceClaims = [];
829
+ const conflicts = [];
830
+ const degradations = [];
831
+ const reviewRequirements = [];
832
+ const traces = [];
833
+ const warnings = [];
834
+ const items = semanticItems(input);
835
+ for (const item of sortedBy(input.ontologyInstance.unresolvedItems, (value) => value.id)) {
836
+ reviewRequirements.push(createReviewRequirement({
837
+ schemaVersion: REVIEW_REQUIREMENT_SCHEMA_VERSION,
838
+ id: hashId('review-requirement', { kind: 'unresolved', id: item.id }),
839
+ reasonCode: `ONTOLOGY_${item.code}`,
840
+ ...(item.targetPath ? { targetPath: item.targetPath } : {}),
841
+ constraintIds: [],
842
+ sourceIds: item.relatedIds,
843
+ blocking: false,
844
+ explanation: item.message,
845
+ }));
846
+ }
847
+ for (const path of sortedStrings([...input.ontologyInstance.unknownPaths, ...input.ontologyInstance.unspecifiedPaths])) {
848
+ const isUnknown = input.ontologyInstance.unknownPaths.includes(path);
849
+ reviewRequirements.push(createReviewRequirement({
850
+ schemaVersion: REVIEW_REQUIREMENT_SCHEMA_VERSION,
851
+ id: hashId('review-requirement', { kind: isUnknown ? 'unknown' : 'unspecified', path }),
852
+ reasonCode: isUnknown ? 'ONTOLOGY_PATH_UNKNOWN' : 'ONTOLOGY_PATH_UNSPECIFIED',
853
+ targetPath: path,
854
+ constraintIds: [],
855
+ sourceIds: [],
856
+ blocking: false,
857
+ explanation: isUnknown ? `Ontology path ${path} is unknown and needs review.` : `Ontology path ${path} is unspecified and needs review.`,
858
+ }));
859
+ }
860
+ const constraintByPath = new Map();
861
+ for (const intent of sortedBy(input.changeIntents ?? [], (item) => item.id)) {
862
+ const goal = goalForIntent(intent);
863
+ const constraint = constraintForIntent(intent, goal);
864
+ goal.constraintIds = [constraint.id];
865
+ goal.goalHash = computeGoalHash(goal);
866
+ goals.push(goal);
867
+ constraints.push(constraint);
868
+ const group = constraintByPath.get(intent.targetPath) ?? [];
869
+ group.push(constraint);
870
+ constraintByPath.set(intent.targetPath, group);
871
+ traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { kind: 'intent', id: intent.id }), ruleId: 'rule.intent-to-constraint', inputIds: [intent.id], outputIds: [goal.id, constraint.id], outcome: 'applied', reasonCode: constraint.reasonCode, message: constraint.explanation }));
872
+ }
873
+ for (const item of items.filter((candidate) => input.ontologyInstance.facts.some((fact) => fact.id === candidate.id))) {
874
+ const constraint = factConstraint(item);
875
+ constraints.push(constraint);
876
+ const group = constraintByPath.get(item.path) ?? [];
877
+ group.push(constraint);
878
+ constraintByPath.set(item.path, group);
879
+ traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { kind: 'fact', id: item.id }), ruleId: 'rule.accepted-fact-to-constraint', inputIds: item.sourceIds, outputIds: [constraint.id], outcome: 'applied', reasonCode: 'ONTOLOGY_FACT_TO_CONSTRAINT', message: constraint.explanation }));
880
+ }
881
+ const output = outputConstraints(input.outputContract);
882
+ constraints.push(...output);
883
+ traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { kind: 'output', id: output[0].id }), ruleId: 'rule.output-contract', inputIds: [], outputIds: output.map((item) => item.id), outcome: 'applied', reasonCode: 'OUTPUT_CONTRACT_REQUIRED', message: 'OutputContract was compiled into provider-neutral output constraints.' }));
884
+ const ruleItems = items;
885
+ for (const rule of allRules(input.effectiveScenario)) {
886
+ const match = ruleMatches(rule, ruleItems);
887
+ if (!match) {
888
+ traces.push(makeRuleTrace(rule, [], [], 'skipped', 'RULE_PRECONDITION_NOT_MET', 'Declarative rule preconditions were not met.'));
889
+ continue;
890
+ }
891
+ const leftConstraints = match.left.flatMap((item) => constraintByPath.get(item.path) ?? []);
892
+ const rightConstraints = match.right.flatMap((item) => constraintByPath.get(item.path) ?? []);
893
+ if (rule.kind === 'dependency') {
894
+ const parent = leftConstraints[0];
895
+ const child = rightConstraints[0];
896
+ if (parent && child) {
897
+ const dependency = createConstraintDependency({ schemaVersion: CONSTRAINT_DEPENDENCY_SCHEMA_VERSION, id: hashId('constraint-dependency', { ruleId: rule.id, parent: parent.id, child: child.id }), parentId: parent.id, childId: child.id, kind: rule.dependencyKind ?? 'requires', importance: rule.importance, explanation: rule.message });
898
+ dependencies.push(dependency);
899
+ traces.push(makeRuleTrace(rule, [...match.left, ...match.right].map((item) => item.id), [dependency.id], 'applied', rule.reasonCode));
900
+ }
901
+ continue;
902
+ }
903
+ const conflict = conflictForRule(rule, leftConstraints, rightConstraints);
904
+ conflicts.push(conflict);
905
+ if (rule.resourceId) {
906
+ const claim = createResourceClaim({ schemaVersion: RESOURCE_CLAIM_SCHEMA_VERSION, id: conflict.resourceClaimIds[0] ?? hashId('resource', { resourceId: rule.resourceId, constraintIds: conflict.constraintIds }), resourceId: rule.resourceId, mode: 'exclusive', claimantIds: [...match.left, ...match.right].map((item) => item.id), constraintIds: conflict.constraintIds, quantity: 1, explanation: rule.message });
907
+ resourceClaims.push(claim);
908
+ }
909
+ traces.push(makeRuleTrace(rule, [...match.left, ...match.right].map((item) => item.id), conflict.constraintIds, conflict.blocking ? 'blocked' : 'degraded', conflict.code));
910
+ }
911
+ const allConstraintIds = new Set(constraints.map((constraint) => constraint.id));
912
+ for (const dependency of dependencies) {
913
+ if (!allConstraintIds.has(dependency.parentId) || !allConstraintIds.has(dependency.childId)) {
914
+ conflicts.push(createConstraintConflict({ schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION, id: hashId('constraint-conflict', { code: 'CONSTRAINT_DEPENDENCY_MISSING', dependencyId: dependency.id }), code: 'CONSTRAINT_DEPENDENCY_MISSING', severity: dependency.importance, constraintIds: [], dependencyIds: [dependency.id], resourceClaimIds: [], message: 'A declarative constraint dependency refers to a missing node.', blocking: dependency.importance !== 'preferred', waiverAllowed: dependency.importance === 'required' }));
915
+ }
916
+ }
917
+ for (const cycle of dependencyCycles(dependencies, allConstraintIds)) {
918
+ conflicts.push(createConstraintConflict({ schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION, id: hashId('constraint-conflict', { code: 'CONSTRAINT_DEPENDENCY_CYCLE', cycle }), code: 'CONSTRAINT_DEPENDENCY_CYCLE', severity: 'hard', constraintIds: cycle, dependencyIds: dependencies.filter((dependency) => cycle.includes(dependency.parentId) && cycle.includes(dependency.childId)).map((dependency) => dependency.id), resourceClaimIds: [], message: 'Constraint dependency graph contains a directed cycle.', blocking: true, waiverAllowed: false }));
919
+ }
920
+ for (const claim of resourceClaims) {
921
+ const exclusiveClaimants = sortedStrings(claim.claimantIds);
922
+ if (claim.mode === 'exclusive' && exclusiveClaimants.length > 1 && !conflicts.some((conflict) => conflict.resourceClaimIds.includes(claim.id))) {
923
+ conflicts.push(createConstraintConflict({ schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION, id: hashId('constraint-conflict', { code: 'RESOURCE_CONFLICT', claim: claim.id }), code: 'RESOURCE_CONFLICT', severity: 'required', constraintIds: claim.constraintIds, dependencyIds: [], resourceClaimIds: [claim.id], message: `Exclusive resource ${claim.resourceId} is claimed by multiple constraints.`, blocking: true, waiverAllowed: true }));
924
+ }
925
+ }
926
+ const validWaivers = waiverTargets(input.waivers ?? [], input);
927
+ const adjustedConflicts = [];
928
+ const rehashConflict = (conflict) => clone({ ...conflict, conflictHash: computeConstraintConflictHash(conflict) });
929
+ for (const conflict of uniqueSortedObjects(conflicts, (item) => item.id)) {
930
+ const covered = [conflict.id, conflict.code, ...conflict.constraintIds, ...conflict.dependencyIds, ...conflict.resourceClaimIds].some((id) => validWaivers.has(id));
931
+ if (conflict.severity === 'hard' && covered) {
932
+ adjustedConflicts.push(rehashConflict({ ...conflict, blocking: true, waiverAllowed: false, message: `${conflict.message} Hard conflicts cannot be waived.` }));
933
+ warnings.push('HARD_CONFLICT_CANNOT_WAIVE');
934
+ }
935
+ else if (conflict.severity === 'required' && covered) {
936
+ adjustedConflicts.push(rehashConflict({ ...conflict, blocking: false, message: `${conflict.message} Proceeding only under an explicit scoped waiver.` }));
937
+ warnings.push('REQUIRED_CONFLICT_WAIVED');
938
+ }
939
+ else if (conflict.severity === 'preferred') {
940
+ adjustedConflicts.push(rehashConflict({ ...conflict, blocking: false }));
941
+ const degradation = createDegradation({ schemaVersion: DEGRADATION_SCHEMA_VERSION, id: hashId('degradation', { conflictId: conflict.id }), preferenceId: conflict.id, constraintId: conflict.constraintIds[0], reasonCode: conflict.code, impact: conflict.message, affectedIds: conflict.constraintIds, explanation: `Preferred constraint was degraded deterministically because ${conflict.message}` });
942
+ degradations.push(degradation);
943
+ traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { degradation: degradation.id }), ruleId: 'rule.preferred-degradation', inputIds: conflict.constraintIds, outputIds: [degradation.id], outcome: 'degraded', reasonCode: conflict.code, message: degradation.explanation }));
944
+ }
945
+ else
946
+ adjustedConflicts.push(conflict);
947
+ }
948
+ const blocked = adjustedConflicts.some((conflict) => conflict.blocking);
949
+ const orderedGoals = sortedBy(goals, (item) => item.id);
950
+ const orderedConstraints = sortedBy(constraints.map(normalizeConstraint), (item) => item.id);
951
+ const orderedDependencies = sortedBy(dependencies, (item) => item.id);
952
+ const orderedClaims = sortedBy(resourceClaims, (item) => item.id);
953
+ const orderedConflicts = sortedBy(adjustedConflicts, (item) => item.id);
954
+ const orderedDegradations = sortedBy(degradations, (item) => item.id);
955
+ const orderedTraces = sortedBy(traces, (item) => item.id);
956
+ const base = {
957
+ schemaVersion: CONSTRAINT_IR_SCHEMA_VERSION,
958
+ id: hashId('constraint-ir', { caseId: input.caseId, caseRevision: input.caseRevision, contextHash: input.contextHash, requestedScopePlanHash: input.requestedScopePlanHash, instanceHash: input.ontologyInstance.instanceHash }),
959
+ caseId: input.caseId,
960
+ caseRevision: input.caseRevision,
961
+ contextHash: input.contextHash,
962
+ requestedScopePlanHash: input.requestedScopePlanHash,
963
+ instanceHash: input.ontologyInstance.instanceHash,
964
+ decisionHashes: sortedStrings(input.bindingDecisions.map((decision) => decision.decisionHash)),
965
+ goals: orderedGoals,
966
+ constraints: orderedConstraints,
967
+ dependencies: orderedDependencies,
968
+ resourceClaims: orderedClaims,
969
+ conflicts: orderedConflicts,
970
+ degradedPreferences: orderedDegradations,
971
+ reviewRequirements,
972
+ explanations: orderedTraces,
973
+ ruleTraces: orderedTraces,
974
+ warnings: sortedStrings(warnings),
975
+ status: blocked ? 'blocked' : 'ok',
976
+ deterministicSignature: '',
977
+ };
978
+ base.deterministicSignature = computeConstraintIRSignature(base);
979
+ return clone(base);
980
+ }
981
+ }
982
+ export const DeterministicConstraintGraphCompiler = ConstraintGraphCompiler;
983
+ export function compileConstraints(input) {
984
+ return new ConstraintGraphCompiler().compile(input);
985
+ }
986
+ export function compileConstraintIR(input) {
987
+ return compileConstraints(input);
988
+ }
989
+ function referenceProfileLimits(profile) {
990
+ const nested = profile.referenceLimits ?? {};
991
+ return {
992
+ maximumReferenceCount: profile.maximumReferenceCount ?? nested.maximumReferenceCount,
993
+ maximumTotalBytes: profile.maximumTotalReferenceBytes ?? nested.maximumTotalBytes,
994
+ maximumBytesPerReference: profile.maximumBytesPerReference ?? nested.maximumBytesPerReference,
995
+ allowedMediaTypes: sortedStrings(profile.allowedReferenceMediaTypes ?? nested.allowedMediaTypes),
996
+ allowedRoles: sortedStrings(profile.allowedReferenceRoles ?? nested.allowedRoles),
997
+ ordering: profile.referenceOrdering ?? nested.ordering ?? 'stable',
998
+ roleOrder: sortedStrings(profile.referenceRoleOrder ?? nested.roleOrder),
999
+ supportsMultipleReferences: profile.supportsMultipleReferences ?? nested.supportsMultipleReferences ?? true,
1000
+ requiresPublishedReferences: profile.requiresPublishedReferences ?? nested.requiresPublishedReferences ?? false,
1001
+ };
1002
+ }
1003
+ function validProfile(profile) {
1004
+ const reasons = [];
1005
+ if (!profile || typeof profile !== 'object' || profile.schemaVersion !== PROVIDER_CAPABILITY_PROFILE_SCHEMA_VERSION)
1006
+ return ['PROFILE_SCHEMA_INVALID'];
1007
+ if (typeof profile.id !== 'string' || !profile.id || typeof profile.version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(profile.version))
1008
+ reasons.push('PROFILE_VERSION_UNKNOWN');
1009
+ if (profile.verificationStatus === 'unknown' || profile.verificationStatus === 'stale')
1010
+ reasons.push('PROFILE_VERIFICATION_INVALID');
1011
+ if (typeof profile.adapterId !== 'string' || !profile.adapterId)
1012
+ reasons.push('ADAPTER_ID_MISSING');
1013
+ if (!isHash(profile.adapterDigest))
1014
+ reasons.push('ADAPTER_DIGEST_MISSING');
1015
+ if (!isHash(profile.profileHash))
1016
+ reasons.push('PROFILE_HASH_MISSING');
1017
+ else if (profile.profileHash !== computeProviderCapabilityProfileHash(profile))
1018
+ reasons.push('PROFILE_HASH_MISMATCH');
1019
+ if (typeof profile.timeoutMs !== 'number' || profile.timeoutMs <= 0)
1020
+ reasons.push('PROFILE_TIMEOUT_INVALID');
1021
+ return sortedStrings(reasons);
1022
+ }
1023
+ function candidateArtifact(candidate) {
1024
+ return candidate.artifact ?? candidate.artifactHandle;
1025
+ }
1026
+ function normalizedCandidate(candidate, constraintIR) {
1027
+ const artifact = candidateArtifact(candidate);
1028
+ const constraints = sortedStrings(candidate.constraintIds);
1029
+ const inferredImportance = importanceFromValues(constraints.map((id) => constraintIR.constraints.find((item) => item.id === id)?.importance), candidate.importance ?? 'preferred');
1030
+ return clone({
1031
+ ...candidate,
1032
+ candidateHash: candidate.candidateHash ?? computeReferenceCandidateHash(candidate),
1033
+ contentHash: candidate.contentHash ?? artifact?.contentHash ?? '',
1034
+ mediaType: candidate.mediaType ?? artifact?.mediaType,
1035
+ byteLength: candidate.byteLength ?? artifact?.byteLength,
1036
+ role: candidate.role ?? artifact?.role ?? 'reference',
1037
+ ontologyScopes: sortedStrings(candidate.ontologyScopes),
1038
+ importance: inferredImportance,
1039
+ constraintIds: constraints,
1040
+ sourceBindingIds: sortedStrings(candidate.sourceBindingIds),
1041
+ goalIds: sortedStrings(candidate.goalIds),
1042
+ orderKey: candidate.orderKey ?? candidate.id,
1043
+ });
1044
+ }
1045
+ function candidateValidation(candidate) {
1046
+ const reasons = [];
1047
+ const artifact = candidateArtifact(candidate);
1048
+ if (candidate.schemaVersion !== REFERENCE_CANDIDATE_SCHEMA_VERSION)
1049
+ reasons.push('REFERENCE_CANDIDATE_SCHEMA_INVALID');
1050
+ if (!candidate.id || !candidate.assetId || !isHash(candidate.contentHash))
1051
+ reasons.push('REFERENCE_CANDIDATE_INVALID');
1052
+ if (!isHash(candidate.candidateHash))
1053
+ reasons.push('REFERENCE_CANDIDATE_HASH_MISSING');
1054
+ else if (computeReferenceCandidateHash(candidate) !== candidate.candidateHash)
1055
+ reasons.push('REFERENCE_CANDIDATE_HASH_MISMATCH');
1056
+ if (artifact) {
1057
+ if (artifact.contentHash !== candidate.contentHash)
1058
+ reasons.push('REFERENCE_ARTIFACT_HASH_MISMATCH');
1059
+ if (artifact.availability !== 'available')
1060
+ reasons.push('REFERENCE_ARTIFACT_UNAVAILABLE');
1061
+ }
1062
+ if (candidate.byteLength !== undefined && (!Number.isInteger(candidate.byteLength) || candidate.byteLength < 0))
1063
+ reasons.push('REFERENCE_BYTE_LENGTH_INVALID');
1064
+ return sortedStrings(reasons);
1065
+ }
1066
+ function referenceBudgetValidationReasons(budget) {
1067
+ if (!budget)
1068
+ return [];
1069
+ const reasons = [];
1070
+ if (budget.maximumReferenceCount !== undefined && (!Number.isInteger(budget.maximumReferenceCount) || budget.maximumReferenceCount < 0))
1071
+ reasons.push('REFERENCE_COUNT_BUDGET_INVALID');
1072
+ if (budget.maximumTotalBytes !== undefined && (!Number.isInteger(budget.maximumTotalBytes) || budget.maximumTotalBytes < 0))
1073
+ reasons.push('REFERENCE_BYTES_BUDGET_INVALID');
1074
+ if (!Number.isInteger(budget.usedReferenceCount) || budget.usedReferenceCount < 0)
1075
+ reasons.push('REFERENCE_USED_COUNT_INVALID');
1076
+ if (budget.usedTotalBytes !== undefined && (!Number.isInteger(budget.usedTotalBytes) || budget.usedTotalBytes < 0))
1077
+ reasons.push('REFERENCE_USED_BYTES_INVALID');
1078
+ return sortedStrings(reasons);
1079
+ }
1080
+ function makeReferenceOmission(candidate, dependencyIds, reasonCode, impact) {
1081
+ const base = {
1082
+ schemaVersion: REFERENCE_OMISSION_SCHEMA_VERSION,
1083
+ id: hashId('reference-omission', { candidateId: candidate.id, assetId: candidate.assetId, reasonCode, dependencyIds }),
1084
+ candidateId: candidate.id,
1085
+ assetId: candidate.assetId,
1086
+ importance: candidate.importance ?? 'preferred',
1087
+ constraintIds: sortedStrings(candidate.constraintIds),
1088
+ dependencyIds: sortedStrings(dependencyIds),
1089
+ reasonCode,
1090
+ impact,
1091
+ };
1092
+ return clone({ ...base, omissionHash: computeReferenceOmissionHash({ ...base, omissionHash: '' }) });
1093
+ }
1094
+ function planProjection(plan) {
1095
+ const projection = cleanWithout(plan, 'planHash');
1096
+ if (Array.isArray(projection.selected))
1097
+ projection.selected = sortedBy(projection.selected, (item) => String(item.id));
1098
+ if (Array.isArray(projection.ordered))
1099
+ projection.ordered = sortedBy(projection.ordered, (item) => String(item.id));
1100
+ if (Array.isArray(projection.omitted))
1101
+ projection.omitted = sortedBy(projection.omitted, (item) => String(item.id));
1102
+ if (Array.isArray(projection.blockedReferences))
1103
+ projection.blockedReferences = sortedBy(projection.blockedReferences, (item) => String(item.id));
1104
+ if (Array.isArray(projection.dependencies))
1105
+ projection.dependencies = sortedBy(projection.dependencies, (item) => String(item.id));
1106
+ if (Array.isArray(projection.budget?.unknownByteLengthAssetIds)) {
1107
+ const budget = projection.budget;
1108
+ budget.unknownByteLengthAssetIds = sortedStrings(budget.unknownByteLengthAssetIds);
1109
+ }
1110
+ return projection;
1111
+ }
1112
+ export function computeReferencePlanHash(plan) { return sha256(planProjection(plan)); }
1113
+ function referencePlanIntegrityReasons(plan) {
1114
+ const reasons = [];
1115
+ if (plan.schemaVersion !== REFERENCE_PLAN_SCHEMA_VERSION)
1116
+ reasons.push('REFERENCE_PLAN_SCHEMA_INVALID');
1117
+ if (!isHash(plan.planHash) || computeReferencePlanHash(plan) !== plan.planHash)
1118
+ reasons.push('REFERENCE_PLAN_HASH_MISMATCH');
1119
+ if (!isHash(plan.profileDigest))
1120
+ reasons.push('REFERENCE_PROFILE_DIGEST_MISSING');
1121
+ for (const dependency of plan.dependencies) {
1122
+ if (!isHash(dependency.dependencyHash) || computeReferenceDependencyHash(dependency) !== dependency.dependencyHash)
1123
+ reasons.push('REFERENCE_DEPENDENCY_HASH_MISMATCH');
1124
+ }
1125
+ for (const omission of [...plan.omitted, ...plan.blockedReferences]) {
1126
+ if (!isHash(omission.omissionHash) || computeReferenceOmissionHash(omission) !== omission.omissionHash)
1127
+ reasons.push('REFERENCE_OMISSION_HASH_MISMATCH');
1128
+ }
1129
+ return sortedStrings(reasons);
1130
+ }
1131
+ function makePlannedReference(candidate, dependencyIds, order) {
1132
+ const base = {
1133
+ schemaVersion: PLANNED_REFERENCE_SCHEMA_VERSION,
1134
+ id: hashId('planned-reference', { candidateId: candidate.id, contentHash: candidate.contentHash }),
1135
+ candidateId: candidate.id,
1136
+ assetId: candidate.assetId,
1137
+ contentHash: candidate.contentHash,
1138
+ mediaType: candidate.mediaType ?? 'application/octet-stream',
1139
+ ...(candidate.byteLength === undefined ? {} : { byteLength: candidate.byteLength }),
1140
+ role: candidate.role ?? 'reference',
1141
+ ontologyScopes: sortedStrings(candidate.ontologyScopes),
1142
+ constraintIds: sortedStrings(candidate.constraintIds),
1143
+ sourceBindingIds: sortedStrings(candidate.sourceBindingIds),
1144
+ dependencyIds: sortedStrings(dependencyIds),
1145
+ order,
1146
+ label: `ref-${String(order + 1).padStart(2, '0')}`,
1147
+ };
1148
+ return clone(base);
1149
+ }
1150
+ function blockedReferencePlan(input, reasons, warnings = [], blockedReferences = []) {
1151
+ const profile = input.profile;
1152
+ const base = {
1153
+ schemaVersion: REFERENCE_PLAN_SCHEMA_VERSION,
1154
+ id: hashId('reference-plan', { caseId: input.caseId ?? 'unknown-case', caseRevision: input.caseRevision ?? 0, contextHash: input.contextHash ?? '', constraintSignature: input.constraintIR?.deterministicSignature ?? '', profileId: profile?.id ?? 'unknown-profile', reasons: sortedStrings(reasons) }),
1155
+ caseId: input.caseId ?? 'unknown-case',
1156
+ caseRevision: input.caseRevision ?? 0,
1157
+ contextHash: input.contextHash ?? 'sha256:' + '0'.repeat(64),
1158
+ constraintSignature: input.constraintIR?.deterministicSignature ?? 'sha256:' + '0'.repeat(64),
1159
+ profileId: profile?.id ?? 'unknown-profile',
1160
+ profileVersion: profile?.version ?? 'unknown',
1161
+ profileDigest: profile && isHash(profile.profileHash) ? profile.profileHash : 'sha256:' + '0'.repeat(64),
1162
+ selected: [],
1163
+ ordered: [],
1164
+ omitted: [],
1165
+ blockedReferences,
1166
+ dependencies: sortedBy(input.dependencies ?? [], (item) => item.id),
1167
+ budget: { maximumReferenceCount: referenceProfileLimits(profile ?? { referenceLimits: {}, knownIncompatibilities: [], timeoutMs: 1, streaming: false }).maximumReferenceCount, maximumTotalBytes: referenceProfileLimits(profile ?? { referenceLimits: {}, knownIncompatibilities: [], timeoutMs: 1, streaming: false }).maximumTotalBytes, usedReferenceCount: 0, byteLengthKnown: false, unknownByteLengthAssetIds: [] },
1168
+ warnings: sortedStrings(warnings),
1169
+ status: 'blocked',
1170
+ planHash: '',
1171
+ };
1172
+ base.planHash = computeReferencePlanHash(base);
1173
+ return clone(base);
1174
+ }
1175
+ function groupCandidates(candidates) {
1176
+ const groups = new Map();
1177
+ for (const candidate of candidates) {
1178
+ const key = candidate.contentHash;
1179
+ const existing = groups.get(key);
1180
+ if (!existing) {
1181
+ groups.set(key, { key, representative: clone(candidate), aliases: [candidate.id], bytes: candidate.byteLength, required: candidate.importance !== 'preferred', members: [clone(candidate)] });
1182
+ continue;
1183
+ }
1184
+ existing.aliases.push(candidate.id);
1185
+ existing.members.push(clone(candidate));
1186
+ existing.required = existing.required || candidate.importance !== 'preferred';
1187
+ existing.representative = clone({
1188
+ ...existing.representative,
1189
+ id: [existing.representative.id, candidate.id].sort(compareCodeUnits)[0],
1190
+ importance: stableImportance(existing.representative.importance, candidate.importance),
1191
+ role: [existing.representative.role ?? 'reference', candidate.role ?? 'reference'].sort(compareCodeUnits)[0],
1192
+ ontologyScopes: sortedStrings([...(existing.representative.ontologyScopes ?? []), ...(candidate.ontologyScopes ?? [])]),
1193
+ constraintIds: sortedStrings([...(existing.representative.constraintIds ?? []), ...(candidate.constraintIds ?? [])]),
1194
+ sourceBindingIds: sortedStrings([...(existing.representative.sourceBindingIds ?? []), ...(candidate.sourceBindingIds ?? [])]),
1195
+ goalIds: sortedStrings([...(existing.representative.goalIds ?? []), ...(candidate.goalIds ?? [])]),
1196
+ byteLength: existing.representative.byteLength ?? candidate.byteLength,
1197
+ });
1198
+ if (existing.bytes === undefined)
1199
+ existing.bytes = candidate.byteLength;
1200
+ }
1201
+ return sortedBy([...groups.values()], (group) => `${group.key}|${group.representative.id}`);
1202
+ }
1203
+ function representativeFor(id, groups) {
1204
+ return groups.find((group) => group.aliases.includes(id));
1205
+ }
1206
+ function dependencyGroupEdges(dependencies, groups) {
1207
+ const edges = new Map();
1208
+ for (const group of groups)
1209
+ edges.set(group.key, new Set([group.key]));
1210
+ const union = (left, right) => {
1211
+ const a = edges.get(left);
1212
+ const b = edges.get(right);
1213
+ if (!a || !b || a === b)
1214
+ return;
1215
+ const merged = new Set([...a, ...b]);
1216
+ for (const key of merged)
1217
+ edges.set(key, merged);
1218
+ };
1219
+ for (const dependency of dependencies) {
1220
+ if (dependency.importance === 'preferred')
1221
+ continue;
1222
+ const parent = representativeFor(dependency.parentCandidateId, groups);
1223
+ const child = representativeFor(dependency.childCandidateId, groups);
1224
+ if (parent && child)
1225
+ union(parent.key, child.key);
1226
+ }
1227
+ return edges;
1228
+ }
1229
+ function candidateDependencyIds(candidate, dependencies) {
1230
+ return dependencies.filter((dependency) => dependency.parentCandidateId === candidate.id || dependency.childCandidateId === candidate.id).map((dependency) => dependency.id);
1231
+ }
1232
+ function profileOutput(profile) {
1233
+ const output = profile.outputCapabilities ?? {};
1234
+ return {
1235
+ mediaTypes: sortedStrings(profile.outputMediaTypes ?? output.mediaTypes),
1236
+ transparent: profile.supportsTransparentOutput ?? output.supportsTransparentOutput ?? false,
1237
+ alpha: profile.supportsAlpha ?? output.supportsAlpha ?? false,
1238
+ minimumWidth: output.minimumWidth,
1239
+ minimumHeight: output.minimumHeight,
1240
+ maximumWidth: output.maximumWidth,
1241
+ maximumHeight: output.maximumHeight,
1242
+ };
1243
+ }
1244
+ export class ReferenceBudgetOptimizer {
1245
+ plan(input) {
1246
+ try {
1247
+ return this.planSafe(clone(input));
1248
+ }
1249
+ catch {
1250
+ return blockedReferencePlan(input ?? {}, ['REFERENCE_PLANNING_INPUT_INVALID']);
1251
+ }
1252
+ }
1253
+ planSafe(input) {
1254
+ const profileReasons = validProfile(input.profile);
1255
+ if (profileReasons.length)
1256
+ return blockedReferencePlan(input, profileReasons);
1257
+ if (!input.constraintIR || input.constraintIR.status !== 'ok' || constraintIRIntegrityReasons(input.constraintIR).length)
1258
+ return blockedReferencePlan(input, ['CONSTRAINT_IR_INVALID']);
1259
+ if (input.constraintIR.contextHash !== input.contextHash || input.caseId !== input.constraintIR.caseId || input.caseRevision !== input.constraintIR.caseRevision)
1260
+ return blockedReferencePlan(input, ['CONSTRAINT_CONTEXT_MISMATCH']);
1261
+ const referenceBudgetReasons = referenceBudgetValidationReasons(input.budget);
1262
+ if (referenceBudgetReasons.length)
1263
+ return blockedReferencePlan(input, referenceBudgetReasons);
1264
+ const limits = referenceProfileLimits(input.profile);
1265
+ const candidates = (input.candidates ?? []).map((candidate) => ({ raw: clone(candidate), normalized: normalizedCandidate(candidate, input.constraintIR) }));
1266
+ const reasons = [];
1267
+ const seenIds = new Map();
1268
+ const validCandidates = [];
1269
+ for (const entry of sortedBy(candidates, (item) => item.raw.id)) {
1270
+ const candidate = entry.normalized;
1271
+ const rawCandidate = entry.raw;
1272
+ const prior = seenIds.get(candidate.id);
1273
+ if (prior && canonicalize(jsonReady(referenceCandidateProjection(prior))) !== canonicalize(jsonReady(referenceCandidateProjection(candidate)))) {
1274
+ reasons.push('REFERENCE_CANDIDATE_ID_COLLISION');
1275
+ continue;
1276
+ }
1277
+ seenIds.set(candidate.id, candidate);
1278
+ // Validate the externally supplied candidate hash before deterministic
1279
+ // defaults (role/order/media metadata) are derived for planning.
1280
+ const validation = candidateValidation(rawCandidate);
1281
+ if (validation.length) {
1282
+ reasons.push(...validation);
1283
+ continue;
1284
+ }
1285
+ if (candidate.constraintIds?.some((id) => !input.constraintIR.constraints.some((constraint) => constraint.id === id))) {
1286
+ reasons.push('REFERENCE_CONSTRAINT_NOT_FOUND');
1287
+ continue;
1288
+ }
1289
+ if (candidate.goalIds?.some((id) => !input.constraintIR.goals.some((goal) => goal.id === id))) {
1290
+ reasons.push('REFERENCE_GOAL_NOT_FOUND');
1291
+ continue;
1292
+ }
1293
+ validCandidates.push(candidate);
1294
+ }
1295
+ if (reasons.length)
1296
+ return blockedReferencePlan(input, reasons);
1297
+ const dependencies = uniqueSortedObjects((input.dependencies ?? []).map((dependency) => clone(dependency)), (item) => item.id);
1298
+ const dependencyReasons = [];
1299
+ for (const dependency of dependencies) {
1300
+ if (dependency.schemaVersion !== REFERENCE_DEPENDENCY_SCHEMA_VERSION)
1301
+ dependencyReasons.push('REFERENCE_DEPENDENCY_SCHEMA_INVALID');
1302
+ if (!isHash(dependency.dependencyHash))
1303
+ dependencyReasons.push('REFERENCE_DEPENDENCY_HASH_MISSING');
1304
+ else if (computeReferenceDependencyHash(dependency) !== dependency.dependencyHash)
1305
+ dependencyReasons.push('REFERENCE_DEPENDENCY_HASH_MISMATCH');
1306
+ }
1307
+ if (dependencyReasons.length)
1308
+ return blockedReferencePlan(input, dependencyReasons);
1309
+ const groups = groupCandidates(validCandidates);
1310
+ const omissions = [];
1311
+ const blockedReferences = [];
1312
+ const groupEdges = dependencyGroupEdges(dependencies, groups);
1313
+ for (const dependency of dependencies) {
1314
+ if (!representativeFor(dependency.parentCandidateId, groups) || !representativeFor(dependency.childCandidateId, groups)) {
1315
+ if (dependency.importance !== 'preferred')
1316
+ dependencyReasons.push('REFERENCE_DEPENDENCY_MISSING');
1317
+ }
1318
+ }
1319
+ if (dependencyReasons.length)
1320
+ return blockedReferencePlan(input, dependencyReasons);
1321
+ const componentMembers = new Map();
1322
+ for (const group of groups) {
1323
+ const component = [...(groupEdges.get(group.key) ?? new Set([group.key]))].sort(compareCodeUnits);
1324
+ const key = component[0];
1325
+ componentMembers.set(key, component.map((item) => groups.find((candidateGroup) => candidateGroup.key === item)).filter(Boolean));
1326
+ }
1327
+ const allComponents = sortedBy([...componentMembers.entries()], (entry) => entry[0]);
1328
+ const selectedGroups = [];
1329
+ let usedBytes = 0;
1330
+ let bytesKnown = true;
1331
+ const unknownByteLengthAssetIds = [];
1332
+ const maximumCount = input.budget?.maximumReferenceCount === undefined ? limits.maximumReferenceCount : Math.min(input.budget.maximumReferenceCount, limits.maximumReferenceCount ?? Number.MAX_SAFE_INTEGER);
1333
+ const maximumBytes = input.budget?.maximumTotalBytes === undefined ? limits.maximumTotalBytes : Math.min(input.budget.maximumTotalBytes, limits.maximumTotalBytes ?? Number.MAX_SAFE_INTEGER);
1334
+ const countFor = (component) => component.length;
1335
+ const bytesFor = (component) => {
1336
+ let total = 0;
1337
+ for (const group of component) {
1338
+ if (group.bytes === undefined)
1339
+ return undefined;
1340
+ total += group.bytes;
1341
+ }
1342
+ return total;
1343
+ };
1344
+ const fits = (component) => {
1345
+ const count = selectedGroups.length + countFor(component);
1346
+ if (maximumCount !== undefined && count > maximumCount)
1347
+ return { ok: false, reason: 'REFERENCE_COUNT_EXCEEDED' };
1348
+ if (!limits.supportsMultipleReferences && count > 1)
1349
+ return { ok: false, reason: 'MULTI_REFERENCE_UNSUPPORTED' };
1350
+ for (const group of component) {
1351
+ const candidate = group.representative;
1352
+ if (limits.allowedMediaTypes.length && (!candidate.mediaType || !limits.allowedMediaTypes.includes(candidate.mediaType)))
1353
+ return { ok: false, reason: 'REFERENCE_MEDIA_TYPE_UNSUPPORTED' };
1354
+ if (limits.allowedRoles.length && (!candidate.role || !limits.allowedRoles.includes(candidate.role)))
1355
+ return { ok: false, reason: 'REFERENCE_ROLE_UNSUPPORTED' };
1356
+ if (limits.maximumBytesPerReference !== undefined) {
1357
+ if (candidate.byteLength === undefined)
1358
+ return { ok: false, reason: 'REFERENCE_BYTE_LENGTH_REQUIRED' };
1359
+ if (candidate.byteLength > limits.maximumBytesPerReference)
1360
+ return { ok: false, reason: 'REFERENCE_BYTES_PER_ASSET_EXCEEDED' };
1361
+ }
1362
+ }
1363
+ const componentBytes = bytesFor(component);
1364
+ if (maximumBytes !== undefined) {
1365
+ if (componentBytes === undefined)
1366
+ return { ok: false, reason: 'REFERENCE_TOTAL_BYTES_UNKNOWN' };
1367
+ if (usedBytes + componentBytes > maximumBytes)
1368
+ return { ok: false, reason: 'REFERENCE_TOTAL_BYTES_EXCEEDED', componentBytes };
1369
+ }
1370
+ return { ok: true, componentBytes };
1371
+ };
1372
+ const sortedComponents = allComponents.sort((left, right) => {
1373
+ const leftGroups = left[1];
1374
+ const rightGroups = right[1];
1375
+ const leftImportance = Math.max(...leftGroups.map((group) => IMPORTANCE_RANK[group.representative.importance ?? 'preferred']));
1376
+ const rightImportance = Math.max(...rightGroups.map((group) => IMPORTANCE_RANK[group.representative.importance ?? 'preferred']));
1377
+ return rightImportance - leftImportance || compareCodeUnits(left[0], right[0]);
1378
+ });
1379
+ for (const [, component] of sortedComponents) {
1380
+ if (component.some((group) => group.bytes === undefined)) {
1381
+ bytesKnown = false;
1382
+ unknownByteLengthAssetIds.push(...component.map((group) => group.representative.assetId));
1383
+ }
1384
+ const required = component.some((group) => group.required) || component.some((group) => dependencies.some((dependency) => dependency.importance !== 'preferred' && (dependency.parentCandidateId === group.representative.id || dependency.childCandidateId === group.representative.id)));
1385
+ const fit = fits(component);
1386
+ if (!fit.ok) {
1387
+ const reason = fit.reason ?? 'REFERENCE_BUDGET_UNSATISFIABLE';
1388
+ for (const group of component) {
1389
+ const candidate = group.representative;
1390
+ const omission = makeReferenceOmission(candidate, candidateDependencyIds(candidate, dependencies), reason, required ? 'A hard or required reference dependency cannot fit the provider budget.' : 'Preferred reference was omitted to remain within the provider budget.');
1391
+ if (required)
1392
+ blockedReferences.push(omission);
1393
+ else
1394
+ omissions.push(omission);
1395
+ }
1396
+ if (required)
1397
+ reasons.push(reason);
1398
+ continue;
1399
+ }
1400
+ selectedGroups.push(...component);
1401
+ if (fit.componentBytes === undefined) {
1402
+ bytesKnown = false;
1403
+ for (const group of component)
1404
+ unknownByteLengthAssetIds.push(group.representative.assetId);
1405
+ }
1406
+ else
1407
+ usedBytes += fit.componentBytes;
1408
+ }
1409
+ for (const group of selectedGroups)
1410
+ if (group.representative.byteLength === undefined) {
1411
+ bytesKnown = false;
1412
+ unknownByteLengthAssetIds.push(group.representative.assetId);
1413
+ }
1414
+ const selectedUnique = selectedGroups.filter((group, index) => selectedGroups.findIndex((candidate) => candidate.key === group.key) === index);
1415
+ const order = (left, right) => {
1416
+ const roleOrder = limits.roleOrder;
1417
+ const roleDifference = roleOrder.length ? (roleOrder.indexOf(left.representative.role ?? 'reference') + 1 || Number.MAX_SAFE_INTEGER) - (roleOrder.indexOf(right.representative.role ?? 'reference') + 1 || Number.MAX_SAFE_INTEGER) : 0;
1418
+ return (limits.ordering === 'role' ? roleDifference : 0) || compareCodeUnits(left.representative.orderKey ?? left.representative.id, right.representative.orderKey ?? right.representative.id) || compareCodeUnits(left.key, right.key);
1419
+ };
1420
+ const orderedGroups = [...selectedUnique].sort(order);
1421
+ const planned = orderedGroups.map((group, index) => makePlannedReference(group.representative, dependencies.filter((dependency) => group.aliases.includes(dependency.parentCandidateId) || group.aliases.includes(dependency.childCandidateId)).map((dependency) => dependency.id), index));
1422
+ const warnings = [...(bytesKnown ? [] : ['REFERENCE_BYTE_LENGTH_UNKNOWN']), ...(limits.requiresPublishedReferences ? ['REFERENCE_PUBLICATION_REQUIRED'] : [])];
1423
+ const planBase = {
1424
+ schemaVersion: REFERENCE_PLAN_SCHEMA_VERSION,
1425
+ id: hashId('reference-plan', { caseId: input.caseId, caseRevision: input.caseRevision, contextHash: input.contextHash, constraintSignature: input.constraintIR.deterministicSignature, profileId: input.profile.id, profileVersion: input.profile.version }),
1426
+ caseId: input.caseId,
1427
+ caseRevision: input.caseRevision,
1428
+ contextHash: input.contextHash,
1429
+ constraintSignature: input.constraintIR.deterministicSignature,
1430
+ profileId: input.profile.id,
1431
+ profileVersion: input.profile.version,
1432
+ profileDigest: profileDigest(input.profile),
1433
+ selected: planned,
1434
+ ordered: planned,
1435
+ omitted: sortedBy(omissions, (item) => item.id),
1436
+ blockedReferences: sortedBy(blockedReferences, (item) => item.id),
1437
+ dependencies: sortedBy(dependencies, (item) => item.id),
1438
+ budget: { maximumReferenceCount: maximumCount, maximumTotalBytes: maximumBytes, usedReferenceCount: planned.length, ...(bytesKnown ? { usedTotalBytes: usedBytes } : {}), byteLengthKnown: bytesKnown, unknownByteLengthAssetIds: sortedStrings(unknownByteLengthAssetIds) },
1439
+ warnings: sortedStrings(warnings),
1440
+ status: reasons.length || blockedReferences.length ? 'blocked' : 'ok',
1441
+ planHash: '',
1442
+ };
1443
+ planBase.planHash = computeReferencePlanHash(planBase);
1444
+ return clone(planBase);
1445
+ }
1446
+ }
1447
+ export const DeterministicReferenceBudgetOptimizer = ReferenceBudgetOptimizer;
1448
+ export function planReferences(input) {
1449
+ return new ReferenceBudgetOptimizer().plan(input);
1450
+ }
1451
+ export function optimizeReferenceBudget(input) {
1452
+ return planReferences(input);
1453
+ }
1454
+ function profileDigest(profile) {
1455
+ return profile.profileHash ?? computeProviderCapabilityProfileHash(profile);
1456
+ }
1457
+ function profileOutputMediaTypes(profile) {
1458
+ return profileOutput(profile).mediaTypes;
1459
+ }
1460
+ function targetMediaTypes(contract) {
1461
+ return sortedStrings(contract.mediaTypes);
1462
+ }
1463
+ function capabilityVersionPin(capability, profile) {
1464
+ return {
1465
+ adapterVersion: clone(capability.adapterVersion),
1466
+ profileVersion: clone(capability.profileVersion ?? { id: profile.id, version: profile.version, digest: profileDigest(profile) }),
1467
+ };
1468
+ }
1469
+ function defaultCapabilities(profile) {
1470
+ const generatorDigest = profile.adapterDigest ?? '';
1471
+ const generatorPin = { id: profile.adapterId, version: profile.version, digest: generatorDigest };
1472
+ const profilePin = { id: profile.id, version: profile.version, digest: profileDigest(profile) };
1473
+ const localDigest = sha256({ fixture: 'voce-local-step-adapter', version: '1.0.0' });
1474
+ const normalizeDigest = sha256({ fixture: 'voce-image-normalization-adapter', version: '1.0.0' });
1475
+ const validateDigest = sha256({ fixture: 'voce-structural-validation-adapter', version: '1.0.0' });
1476
+ const localPin = { id: 'voce.local', version: '1.0.0', digest: localDigest };
1477
+ return [
1478
+ { id: 'resolve-provider-asset', type: 'resolve_asset', capability: 'resolve_provider_readable_asset', adapterId: 'voce.local', adapterVersion: localPin, adapterDigest: localDigest, outputMediaTypes: ['image/png', 'image/jpeg', 'image/webp'], destination: 'local', dataCategories: ['asset_metadata'], mayCreateChargedSubmission: false },
1479
+ { id: 'publish-provider-asset', type: 'publish_asset', capability: 'publish_provider_readable_asset', adapterId: 'voce.asset-publisher', adapterVersion: { id: 'voce.asset-publisher', version: '1.0.0', digest: localDigest }, adapterDigest: localDigest, destination: profile.destination, dataCategories: ['reference_image'], mayCreateChargedSubmission: true },
1480
+ { id: 'generate-image', type: 'generate', capability: 'image_generation', adapterId: profile.adapterId, adapterVersion: generatorPin, adapterDigest: generatorDigest, profileVersion: profilePin, outputMediaTypes: profileOutputMediaTypes(profile), supportsAlpha: profileOutput(profile).alpha, destination: profile.destination, dataCategories: profile.dataCategories ?? ['reference_image', 'prompt'], budget: createBudget({ schemaVersion: BUDGET_SCHEMA_VERSION, id: profile.adapterId, maximumCalls: 1, maximumRetries: 0, timeoutMs: profile.timeoutMs }), cancellation: { cancellable: true, onCancel: 'submission_unknown' }, mayCreateChargedSubmission: true },
1481
+ { id: 'normalize-image', type: 'normalize', capability: 'image_normalization', adapterId: 'voce.image-normalizer', adapterVersion: { id: 'voce.image-normalizer', version: '1.0.0', digest: normalizeDigest }, adapterDigest: normalizeDigest, inputMediaTypes: ['image/jpeg', 'image/png', 'image/webp'], outputMediaTypes: ['image/png', 'image/jpeg', 'image/webp'], supportsAlpha: true, destination: 'local', dataCategories: ['generated_image'], budget: createBudget({ schemaVersion: BUDGET_SCHEMA_VERSION, id: 'voce.image-normalizer', maximumCalls: 1, maximumRetries: 0, timeoutMs: 60_000 }), cancellation: { cancellable: false, onCancel: 'continue' }, mayCreateChargedSubmission: false },
1482
+ { id: 'structural-validate', type: 'structural_validate', capability: 'structural_validation', adapterId: 'voce.structural-validator', adapterVersion: { id: 'voce.structural-validator', version: '1.0.0', digest: validateDigest }, adapterDigest: validateDigest, inputMediaTypes: ['image/png', 'image/jpeg', 'image/webp'], destination: 'local', dataCategories: ['generated_image', 'output_metadata'], budget: createBudget({ schemaVersion: BUDGET_SCHEMA_VERSION, id: 'voce.structural-validator', maximumCalls: 1, maximumRetries: 0, timeoutMs: 30_000 }), cancellation: { cancellable: false, onCancel: 'continue' }, mayCreateChargedSubmission: false },
1483
+ ];
1484
+ }
1485
+ function capabilityFor(type, capabilities) {
1486
+ return sortedBy(capabilities.filter((capability) => capability.type === type), (item) => `${item.id}|${item.adapterId}`)[0];
1487
+ }
1488
+ function conflictingObjectIds(values, key, reasonCode) {
1489
+ const seen = new Map();
1490
+ const reasons = [];
1491
+ for (const value of sortedBy(values, key)) {
1492
+ const id = key(value);
1493
+ const projection = canonicalize(jsonReady(value));
1494
+ const prior = seen.get(id);
1495
+ if (prior !== undefined && prior !== projection)
1496
+ reasons.push(reasonCode);
1497
+ else if (prior === undefined)
1498
+ seen.set(id, projection);
1499
+ }
1500
+ return sortedStrings(reasons);
1501
+ }
1502
+ function pipelineDataTransfer(capability, input, purpose) {
1503
+ const explicit = sortedBy(input.dataTransfers ?? [], (transfer) => `${transfer.adapterId}|${transfer.id}`).find((transfer) => transfer.adapterId === capability.adapterId);
1504
+ const destination = capability.destination ?? explicit?.destination;
1505
+ if (!destination)
1506
+ return undefined;
1507
+ return createDataTransfer({ schemaVersion: DATA_TRANSFER_SCHEMA_VERSION, id: explicit?.id ?? hashId('transfer', { adapterId: capability.adapterId, destination, purpose }), adapterId: capability.adapterId, destination, ...(explicit?.region ? { region: explicit.region } : {}), dataCategories: capability.dataCategories ?? explicit?.dataCategories ?? ['generated_image'], purpose, ...(explicit?.maximumBytes === undefined ? {} : { maximumBytes: explicit.maximumBytes }) });
1508
+ }
1509
+ function pipelineBudget(capability, input) {
1510
+ const explicit = sortedBy(input.budgets ?? [], (budget) => budget.id).find((budget) => budget.id === capability.adapterId || budget.id === capability.id);
1511
+ const source = explicit ?? capability.budget ?? { schemaVersion: BUDGET_SCHEMA_VERSION, id: capability.adapterId, maximumCalls: capability.mayCreateChargedSubmission ? 1 : 1, maximumRetries: 0, timeoutMs: 60_000 };
1512
+ return createBudget({ ...source, schemaVersion: BUDGET_SCHEMA_VERSION });
1513
+ }
1514
+ function makePipelineStep(capability, profile, input, dependsOn, cleanupIds, compensationIds, inputRoles, outputRoles, purpose) {
1515
+ const transfer = pipelineDataTransfer(capability, input, purpose);
1516
+ if (!transfer)
1517
+ return undefined;
1518
+ const versions = capabilityVersionPin(capability, profile);
1519
+ const budget = pipelineBudget(capability, input);
1520
+ const cancellation = capability.cancellation ?? { cancellable: false, onCancel: 'continue' };
1521
+ const base = {
1522
+ schemaVersion: PIPELINE_STEP_SCHEMA_VERSION,
1523
+ id: hashId('pipeline-step', { capability: capability.id, type: capability.type, adapterId: capability.adapterId, dependsOn, inputRoles, outputRoles }),
1524
+ type: capability.type,
1525
+ adapterId: capability.adapterId,
1526
+ adapterVersion: versions.adapterVersion,
1527
+ profileVersion: versions.profileVersion,
1528
+ inputArtifactRoles: sortedStrings(inputRoles),
1529
+ outputArtifactRoles: sortedStrings(outputRoles),
1530
+ dependsOn: sortedStrings(dependsOn),
1531
+ budget,
1532
+ dataTransfer: transfer,
1533
+ destination: transfer.destination,
1534
+ cancellation,
1535
+ cleanupObligationIds: sortedStrings(cleanupIds),
1536
+ compensationIds: sortedStrings(compensationIds),
1537
+ mayCreateChargedSubmission: capability.mayCreateChargedSubmission ?? false,
1538
+ capability: capability.capability,
1539
+ };
1540
+ return clone({ ...base, stepHash: computePipelineStepHash(base) });
1541
+ }
1542
+ function pipelineDependencies(steps) {
1543
+ const dependencies = [];
1544
+ for (const step of steps)
1545
+ for (const parent of step.dependsOn)
1546
+ dependencies.push({ schemaVersion: STEP_DEPENDENCY_SCHEMA_VERSION, id: hashId('step-dependency', { fromStepId: parent, toStepId: step.id }), fromStepId: parent, toStepId: step.id, relation: 'depends_on' });
1547
+ return dependencies.map((dependency) => ({ ...dependency, dependencyHash: semanticHash(dependency, 'dependencyHash') })).sort((left, right) => compareCodeUnits(left.id, right.id));
1548
+ }
1549
+ function budgetValidationReasons(budget, requireHash = false) {
1550
+ const reasons = [];
1551
+ if (budget.schemaVersion !== BUDGET_SCHEMA_VERSION)
1552
+ reasons.push('BUDGET_SCHEMA_INVALID');
1553
+ if (!Number.isInteger(budget.maximumCalls) || budget.maximumCalls < 0)
1554
+ reasons.push('BUDGET_CALL_LIMIT_INVALID');
1555
+ if (!Number.isInteger(budget.maximumRetries) || budget.maximumRetries < 0)
1556
+ reasons.push('BUDGET_RETRY_LIMIT_INVALID');
1557
+ if (!Number.isInteger(budget.timeoutMs) || budget.timeoutMs <= 0)
1558
+ reasons.push('BUDGET_TIMEOUT_INVALID');
1559
+ if (budget.maximumCost !== undefined && (!Number.isFinite(budget.maximumCost) || budget.maximumCost < 0))
1560
+ reasons.push('BUDGET_COST_INVALID');
1561
+ if (budget.maximumBytes !== undefined && (!Number.isInteger(budget.maximumBytes) || budget.maximumBytes < 0))
1562
+ reasons.push('BUDGET_BYTES_INVALID');
1563
+ if (requireHash && !isHash(budget.budgetHash))
1564
+ reasons.push('BUDGET_HASH_MISSING');
1565
+ else if (budget.budgetHash !== undefined && (!isHash(budget.budgetHash) || computeBudgetHash(budget) !== budget.budgetHash))
1566
+ reasons.push('BUDGET_HASH_MISMATCH');
1567
+ return sortedStrings(reasons);
1568
+ }
1569
+ function dataTransferValidationReasons(transfer, requireHash = false) {
1570
+ const reasons = [];
1571
+ if (transfer.schemaVersion !== DATA_TRANSFER_SCHEMA_VERSION)
1572
+ reasons.push('DATA_TRANSFER_SCHEMA_INVALID');
1573
+ if (!transfer.id || !transfer.adapterId || !transfer.destination || !transfer.purpose)
1574
+ reasons.push('DATA_TRANSFER_BINDING_INVALID');
1575
+ if (requireHash && !isHash(transfer.transferHash))
1576
+ reasons.push('DATA_TRANSFER_HASH_MISSING');
1577
+ else if (transfer.transferHash !== undefined && (!isHash(transfer.transferHash) || computeDataTransferHash(transfer) !== transfer.transferHash))
1578
+ reasons.push('DATA_TRANSFER_HASH_MISMATCH');
1579
+ if (transfer.maximumBytes !== undefined && (!Number.isInteger(transfer.maximumBytes) || transfer.maximumBytes < 0))
1580
+ reasons.push('DATA_TRANSFER_BYTES_INVALID');
1581
+ return sortedStrings(reasons);
1582
+ }
1583
+ function capabilityValidationReasons(capability) {
1584
+ const reasons = [];
1585
+ if (!isHash(capability.adapterDigest) || !isHash(capability.adapterVersion.digest))
1586
+ reasons.push('ADAPTER_DIGEST_MISSING');
1587
+ if (capability.profileVersion && !isHash(capability.profileVersion.digest))
1588
+ reasons.push('PROFILE_DIGEST_MISSING');
1589
+ if (capability.budget)
1590
+ reasons.push(...budgetValidationReasons(capability.budget));
1591
+ return sortedStrings(reasons);
1592
+ }
1593
+ function pipelineHasCycle(steps, dependencies) {
1594
+ const ids = new Set(steps.map((step) => step.id));
1595
+ const edges = new Map();
1596
+ for (const dependency of dependencies) {
1597
+ if (!ids.has(dependency.fromStepId) || !ids.has(dependency.toStepId))
1598
+ return true;
1599
+ edges.set(dependency.fromStepId, [...(edges.get(dependency.fromStepId) ?? []), dependency.toStepId]);
1600
+ }
1601
+ const active = new Set();
1602
+ const visited = new Set();
1603
+ const walk = (id) => {
1604
+ if (active.has(id))
1605
+ return true;
1606
+ if (visited.has(id))
1607
+ return false;
1608
+ active.add(id);
1609
+ for (const child of edges.get(id) ?? [])
1610
+ if (walk(child))
1611
+ return true;
1612
+ active.delete(id);
1613
+ visited.add(id);
1614
+ return false;
1615
+ };
1616
+ return [...ids].sort(compareCodeUnits).some(walk);
1617
+ }
1618
+ function pipelineResult(status, plan, blockedReasons, warnings) {
1619
+ const base = { schemaVersion: PIPELINE_PLANNING_RESULT_SCHEMA_VERSION, status, ...(plan ? { pipelinePlan: plan } : {}), blockedReasons: sortedStrings(blockedReasons), warnings: sortedStrings(warnings) };
1620
+ return clone({ ...base, resultHash: sha256(jsonReady(base)) });
1621
+ }
1622
+ function blockedPipelinePlan(input, reasons, warnings = []) {
1623
+ const profile = input.profile;
1624
+ const base = {
1625
+ schemaVersion: PIPELINE_PLAN_SCHEMA_VERSION,
1626
+ id: hashId('pipeline-plan', { caseId: input.caseId ?? 'unknown-case', caseRevision: input.caseRevision ?? 0, contextHash: input.contextHash ?? '', reasons: sortedStrings(reasons) }),
1627
+ caseId: input.caseId ?? 'unknown-case',
1628
+ caseRevision: input.caseRevision ?? 0,
1629
+ contextHash: input.contextHash ?? 'sha256:' + '0'.repeat(64),
1630
+ constraintSignature: input.constraintIR?.deterministicSignature ?? 'sha256:' + '0'.repeat(64),
1631
+ referencePlanHash: input.referencePlan?.planHash ?? 'sha256:' + '0'.repeat(64),
1632
+ outputContractHash: input.outputContract ? computeOutputContractHash(input.outputContract) : 'sha256:' + '0'.repeat(64),
1633
+ profileDigest: profile ? profileDigest(profile) : 'sha256:' + '0'.repeat(64),
1634
+ adapterDigests: [],
1635
+ steps: [],
1636
+ dependencies: [],
1637
+ budgets: [],
1638
+ dataTransfers: [],
1639
+ cleanup: [],
1640
+ compensation: [],
1641
+ warnings: sortedStrings(warnings),
1642
+ blockedReasons: sortedStrings(reasons),
1643
+ status: 'blocked',
1644
+ planHash: '',
1645
+ };
1646
+ base.planHash = computePipelinePlanHash(base);
1647
+ return clone(base);
1648
+ }
1649
+ export const MOCK_IMAGE_PROFILE = (() => {
1650
+ const base = {
1651
+ schemaVersion: PROVIDER_CAPABILITY_PROFILE_SCHEMA_VERSION,
1652
+ id: 'mock-image',
1653
+ version: '1.0.0',
1654
+ versionSummary: 'Offline mock image generator with standard opaque PNG and JPEG output.',
1655
+ adapterId: 'mock.image-generator',
1656
+ adapterDigest: sha256({ fixture: 'mock-image-generator', version: '1.0.0' }),
1657
+ verificationStatus: 'verified',
1658
+ maximumReferenceCount: 8,
1659
+ maximumTotalReferenceBytes: 8_000_000,
1660
+ allowedReferenceMediaTypes: ['image/jpeg', 'image/png', 'image/webp'],
1661
+ referenceOrdering: 'role',
1662
+ referenceRoleOrder: ['identity', 'primary', 'detail', 'context'],
1663
+ supportsMultipleReferences: true,
1664
+ supportsEditing: true,
1665
+ supportsBatchOutput: false,
1666
+ outputMediaTypes: ['image/png', 'image/jpeg'],
1667
+ supportsTransparentOutput: false,
1668
+ supportsAlpha: false,
1669
+ knownIncompatibilities: [],
1670
+ timeoutMs: 120_000,
1671
+ streaming: true,
1672
+ destination: 'mock://generator',
1673
+ dataCategories: ['reference_image', 'prompt', 'generated_image'],
1674
+ };
1675
+ return { ...base, profileHash: computeProviderCapabilityProfileHash(base) };
1676
+ })();
1677
+ export const MOCK_JPEG_PROFILE = (() => {
1678
+ const base = {
1679
+ schemaVersion: PROVIDER_CAPABILITY_PROFILE_SCHEMA_VERSION,
1680
+ id: 'mock-jpeg',
1681
+ version: '1.0.0',
1682
+ versionSummary: 'Offline mock generator that emits opaque JPEG.',
1683
+ adapterId: 'mock.jpeg-generator',
1684
+ adapterDigest: sha256({ fixture: 'mock-jpeg-generator', version: '1.0.0' }),
1685
+ verificationStatus: 'verified',
1686
+ maximumReferenceCount: 8,
1687
+ maximumTotalReferenceBytes: 8_000_000,
1688
+ allowedReferenceMediaTypes: ['image/jpeg', 'image/png'],
1689
+ referenceOrdering: 'stable',
1690
+ supportsMultipleReferences: true,
1691
+ supportsEditing: false,
1692
+ supportsBatchOutput: false,
1693
+ outputMediaTypes: ['image/jpeg'],
1694
+ supportsTransparentOutput: false,
1695
+ supportsAlpha: false,
1696
+ knownIncompatibilities: [],
1697
+ timeoutMs: 120_000,
1698
+ streaming: false,
1699
+ destination: 'mock://jpeg-generator',
1700
+ dataCategories: ['reference_image', 'prompt', 'generated_image'],
1701
+ };
1702
+ return { ...base, profileHash: computeProviderCapabilityProfileHash(base) };
1703
+ })();
1704
+ export const MOCK_LIMITED_REFERENCE_PROFILE = (() => {
1705
+ const base = {
1706
+ schemaVersion: PROVIDER_CAPABILITY_PROFILE_SCHEMA_VERSION,
1707
+ id: 'mock-limited-reference',
1708
+ version: '1.0.0',
1709
+ versionSummary: 'Offline mock generator with a deliberately small reference count and byte budget.',
1710
+ adapterId: 'mock.limited-generator',
1711
+ adapterDigest: sha256({ fixture: 'mock-limited-generator', version: '1.0.0' }),
1712
+ verificationStatus: 'verified',
1713
+ maximumReferenceCount: 2,
1714
+ maximumTotalReferenceBytes: 1_000_000,
1715
+ maximumBytesPerReference: 700_000,
1716
+ allowedReferenceMediaTypes: ['image/png'],
1717
+ referenceOrdering: 'role',
1718
+ referenceRoleOrder: ['identity', 'primary', 'detail'],
1719
+ supportsMultipleReferences: true,
1720
+ supportsEditing: false,
1721
+ supportsBatchOutput: false,
1722
+ outputMediaTypes: ['image/png'],
1723
+ supportsTransparentOutput: false,
1724
+ supportsAlpha: false,
1725
+ knownIncompatibilities: [],
1726
+ timeoutMs: 90_000,
1727
+ streaming: false,
1728
+ destination: 'mock://limited-generator',
1729
+ dataCategories: ['reference_image', 'prompt'],
1730
+ };
1731
+ return { ...base, profileHash: computeProviderCapabilityProfileHash(base) };
1732
+ })();
1733
+ export const MOCK_PROVIDER_CAPABILITY_PROFILES = [MOCK_IMAGE_PROFILE, MOCK_JPEG_PROFILE, MOCK_LIMITED_REFERENCE_PROFILE];
1734
+ export class CapabilityAwarePipelinePlanner {
1735
+ plan(input) {
1736
+ try {
1737
+ return this.planSafe(clone(input));
1738
+ }
1739
+ catch {
1740
+ return pipelineResult('blocked', blockedPipelinePlan(input ?? {}, ['PIPELINE_PLANNING_INPUT_INVALID']), ['PIPELINE_PLANNING_INPUT_INVALID'], []);
1741
+ }
1742
+ }
1743
+ planSafe(input) {
1744
+ const profileReasons = validProfile(input.profile);
1745
+ if (profileReasons.length)
1746
+ return pipelineResult('blocked', blockedPipelinePlan(input, profileReasons), profileReasons, []);
1747
+ if (!input.constraintIR || input.constraintIR.status !== 'ok' || constraintIRIntegrityReasons(input.constraintIR).length)
1748
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['CONSTRAINT_IR_INVALID']), ['CONSTRAINT_IR_INVALID'], []);
1749
+ if (!input.referencePlan || input.referencePlan.status !== 'ok' || referencePlanIntegrityReasons(input.referencePlan).length)
1750
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['REFERENCE_PLAN_INVALID']), ['REFERENCE_PLAN_INVALID'], []);
1751
+ if (input.referencePlan && input.referencePlan.profileDigest !== profileDigest(input.profile))
1752
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['REFERENCE_PROFILE_MISMATCH']), ['REFERENCE_PROFILE_MISMATCH'], []);
1753
+ if (input.contextHash !== input.constraintIR.contextHash || input.contextHash !== input.referencePlan.contextHash || input.caseId !== input.constraintIR.caseId || input.caseRevision !== input.constraintIR.caseRevision)
1754
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['PLANNING_CONTEXT_MISMATCH']), ['PLANNING_CONTEXT_MISMATCH'], []);
1755
+ const output = profileOutput(input.profile);
1756
+ const targetTypes = targetMediaTypes(input.outputContract);
1757
+ const targetTransparent = input.outputContract.background === 'transparent';
1758
+ const capabilities = input.registeredCapabilities ? sortedBy(input.registeredCapabilities, (item) => `${item.type}|${item.id}`) : defaultCapabilities(input.profile);
1759
+ const generator = capabilityFor('generate', capabilities);
1760
+ const validator = capabilityFor('structural_validate', capabilities);
1761
+ const normalizer = capabilityFor('normalize', capabilities);
1762
+ const publisher = capabilityFor('publish_asset', capabilities);
1763
+ const resolver = capabilityFor('resolve_asset', capabilities);
1764
+ const reasons = [];
1765
+ reasons.push(...conflictingObjectIds(capabilities, (item) => `${item.type}|${item.id}`, 'CAPABILITY_ID_COLLISION'));
1766
+ reasons.push(...conflictingObjectIds(input.budgets ?? [], (item) => item.id, 'BUDGET_ID_COLLISION'));
1767
+ reasons.push(...conflictingObjectIds(input.dataTransfers ?? [], (item) => item.id, 'DATA_TRANSFER_ID_COLLISION'));
1768
+ for (const budget of input.budgets ?? [])
1769
+ reasons.push(...budgetValidationReasons(budget, true));
1770
+ for (const transfer of input.dataTransfers ?? [])
1771
+ reasons.push(...dataTransferValidationReasons(transfer, true));
1772
+ const selectedCapabilities = [resolver, publisher, generator, normalizer, validator].filter((value) => Boolean(value));
1773
+ for (const capability of selectedCapabilities)
1774
+ reasons.push(...capabilityValidationReasons(capability));
1775
+ if (!generator)
1776
+ reasons.push('GENERATION_CAPABILITY_MISSING');
1777
+ if (!validator)
1778
+ reasons.push('STRUCTURAL_VALIDATOR_MISSING');
1779
+ if (!resolver)
1780
+ reasons.push('ASSET_RESOLUTION_CAPABILITY_MISSING');
1781
+ if (!input.outputContract.mediaTypes.length)
1782
+ reasons.push('OUTPUT_MEDIA_TYPE_MISSING');
1783
+ const native = targetTransparent && output.transparent && output.alpha && targetTypes.some((type) => output.mediaTypes.includes(type));
1784
+ const generatorMedia = generator?.outputMediaTypes ?? output.mediaTypes;
1785
+ const directMedia = targetTypes.some((type) => generatorMedia.includes(type));
1786
+ const dimensions = input.outputContract.dimensions;
1787
+ const dimensionsNeedNormalization = Boolean(dimensions && ((output.minimumWidth !== undefined && dimensions.width < output.minimumWidth) || (output.minimumHeight !== undefined && dimensions.height < output.minimumHeight) || (output.maximumWidth !== undefined && dimensions.width > output.maximumWidth) || (output.maximumHeight !== undefined && dimensions.height > output.maximumHeight)));
1788
+ const postprocessNeeded = dimensionsNeedNormalization || (!targetTransparent && !directMedia);
1789
+ if (targetTransparent && input.outputContract.allowAlpha === false)
1790
+ reasons.push('OUTPUT_ALPHA_CONTRACT_CONFLICT');
1791
+ if (targetTransparent && !native)
1792
+ reasons.push('TRANSPARENT_OUTPUT_UNSATISFIABLE');
1793
+ if (!targetTransparent && !directMedia && !normalizer)
1794
+ reasons.push('OUTPUT_FORMAT_UNSATISFIABLE');
1795
+ if (dimensionsNeedNormalization && !normalizer)
1796
+ reasons.push('OUTPUT_DIMENSION_UNSATISFIABLE');
1797
+ if (normalizer && postprocessNeeded && targetTransparent && (!normalizer.supportsAlpha || !(normalizer.outputMediaTypes ?? []).some((type) => targetTypes.includes(type))))
1798
+ reasons.push('NORMALIZATION_ALPHA_OR_FORMAT_UNSUPPORTED');
1799
+ if (normalizer && dimensionsNeedNormalization && !(normalizer.outputMediaTypes ?? []).length)
1800
+ reasons.push('NORMALIZATION_CAPABILITY_INCOMPLETE');
1801
+ if (input.profile.knownIncompatibilities.some((code) => targetTransparent && (code === 'TRANSPARENT_OUTPUT_NOT_NATIVE' || code === 'TRANSPARENT_OUTPUT_UNSUPPORTED')))
1802
+ reasons.push('KNOWN_CAPABILITY_GAP');
1803
+ if (reasons.length)
1804
+ return pipelineResult('blocked', blockedPipelinePlan(input, reasons), reasons, []);
1805
+ const temporaryPublication = referenceProfileLimits(input.profile).requiresPublishedReferences;
1806
+ if (temporaryPublication && !publisher)
1807
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['ASSET_PUBLICATION_CAPABILITY_MISSING']), ['ASSET_PUBLICATION_CAPABILITY_MISSING'], []);
1808
+ const cleanupBase = {
1809
+ schemaVersion: CLEANUP_SCHEMA_VERSION,
1810
+ id: hashId('cleanup', { caseId: input.caseId, profileId: input.profile.id, role: 'temporary-assets' }),
1811
+ cleanupHash: '',
1812
+ phase: 'finally',
1813
+ appliesToStepIds: [],
1814
+ conditions: ['always', 'on_failure_or_cancel', 'on_submission_unknown', 'on_worker_restart'],
1815
+ artifactRoles: temporaryPublication ? ['published_reference', 'temporary_intermediate'] : ['temporary_intermediate'],
1816
+ destination: temporaryPublication ? (publisher?.destination ?? 'local') : 'local',
1817
+ dataCategories: ['temporary_asset'],
1818
+ explanation: 'Cleanup remains a finally obligation after success, failure, cancellation, uncertain submission, or worker restart.',
1819
+ };
1820
+ const cleanup = { ...cleanupBase, cleanupHash: computeCleanupHash(cleanupBase) };
1821
+ const compensation = [];
1822
+ const steps = [];
1823
+ const addCompensation = (stepId, trigger) => {
1824
+ const base = { schemaVersion: COMPENSATION_SCHEMA_VERSION, id: hashId('compensation', { stepId, trigger, cleanupId: cleanup.id }), compensationHash: '', appliesToStepIds: [stepId], trigger, cleanupId: cleanup.id, explanation: 'Retain cleanup obligation; do not resubmit the uncertain or failed call.' };
1825
+ const value = { ...base, compensationHash: computeCompensationHash(base) };
1826
+ compensation.push(value);
1827
+ return value.id;
1828
+ };
1829
+ const addStep = (capability, dependsOn, inputRoles, outputRoles, purpose) => {
1830
+ const provisionalId = hashId('pipeline-step', { capability: capability.id, type: capability.type, adapterId: capability.adapterId, dependsOn, inputRoles, outputRoles });
1831
+ const compensationIds = capability.mayCreateChargedSubmission ? ['failure', 'cancel', 'submission_unknown', 'worker_restart'].map((trigger) => addCompensation(provisionalId, trigger)) : [];
1832
+ const step = makePipelineStep(capability, input.profile, input, dependsOn, [cleanup.id], compensationIds, inputRoles, outputRoles, purpose);
1833
+ if (step) {
1834
+ cleanup.appliesToStepIds.push(step.id);
1835
+ steps.push(step);
1836
+ }
1837
+ return step;
1838
+ };
1839
+ const resolveStep = resolver ? addStep(resolver, [], ['reference'], ['provider-readable-reference'], 'resolve provider-readable reference') : undefined;
1840
+ if (!resolveStep)
1841
+ reasons.push('ASSET_RESOLUTION_STEP_UNPLANNABLE');
1842
+ const previous = resolveStep ? [resolveStep.id] : [];
1843
+ let publishStep;
1844
+ if (temporaryPublication && publisher)
1845
+ publishStep = addStep(publisher, previous, ['provider-readable-reference'], ['published-reference'], 'publish provider-readable reference for the selected profile');
1846
+ if (temporaryPublication && !publishStep)
1847
+ reasons.push('ASSET_PUBLICATION_STEP_UNPLANNABLE');
1848
+ const generationDepends = publishStep ? [publishStep.id] : previous;
1849
+ const generation = generator ? addStep(generator, generationDepends, ['planned-reference', 'prompt'], ['generated-image'], 'generate source image') : undefined;
1850
+ if (!generation)
1851
+ reasons.push('GENERATION_STEP_UNPLANNABLE');
1852
+ let last = generation;
1853
+ const needsNormalize = Boolean(last && ((!targetTransparent && !directMedia) || dimensionsNeedNormalization));
1854
+ if (needsNormalize && normalizer && last) {
1855
+ const normalizeStep = addStep(normalizer, [last.id], ['generated-image'], ['normalized-image'], 'normalize format and dimensions');
1856
+ if (!normalizeStep)
1857
+ reasons.push('NORMALIZATION_STEP_UNPLANNABLE');
1858
+ last = normalizeStep;
1859
+ }
1860
+ const validateDepends = last ? [last.id] : [];
1861
+ const validationStep = validator ? addStep(validator, validateDepends, ['normalized-image', 'generated-image'], ['validated-output'], 'structural validate output contract') : undefined;
1862
+ if (!validationStep)
1863
+ reasons.push('STRUCTURAL_VALIDATION_STEP_UNPLANNABLE');
1864
+ if (reasons.length)
1865
+ return pipelineResult('blocked', blockedPipelinePlan(input, reasons), reasons, []);
1866
+ cleanup.appliesToStepIds = sortedStrings(cleanup.appliesToStepIds);
1867
+ cleanup.cleanupHash = computeCleanupHash(cleanup);
1868
+ const dependencies = pipelineDependencies(steps);
1869
+ if (pipelineHasCycle(steps, dependencies))
1870
+ return pipelineResult('blocked', blockedPipelinePlan(input, ['PIPELINE_DAG_CYCLE']), ['PIPELINE_DAG_CYCLE'], []);
1871
+ const stepBudgetReasons = steps.flatMap((step) => {
1872
+ const stepReasons = budgetValidationReasons(step.budget);
1873
+ if (step.budget.maximumCalls < 1)
1874
+ stepReasons.push('PIPELINE_BUDGET_CALLS_EXCEEDED');
1875
+ return stepReasons;
1876
+ });
1877
+ if (stepBudgetReasons.length)
1878
+ return pipelineResult('blocked', blockedPipelinePlan(input, stepBudgetReasons), stepBudgetReasons, []);
1879
+ const uniqueTransfers = uniqueSortedObjects(steps.map((step) => step.dataTransfer), (item) => item.id);
1880
+ const budgets = uniqueSortedObjects(steps.map((step) => step.budget), (item) => item.id);
1881
+ const planBase = {
1882
+ schemaVersion: PIPELINE_PLAN_SCHEMA_VERSION,
1883
+ id: hashId('pipeline-plan', { caseId: input.caseId, caseRevision: input.caseRevision, contextHash: input.contextHash, constraintSignature: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, profileDigest: profileDigest(input.profile) }),
1884
+ caseId: input.caseId,
1885
+ caseRevision: input.caseRevision,
1886
+ contextHash: input.contextHash,
1887
+ constraintSignature: input.constraintIR.deterministicSignature,
1888
+ referencePlanHash: input.referencePlan.planHash,
1889
+ outputContractHash: computeOutputContractHash(input.outputContract),
1890
+ profileDigest: profileDigest(input.profile),
1891
+ adapterDigests: sortedStrings(steps.map((step) => step.adapterVersion.digest)),
1892
+ steps: sortedBy(steps, (step) => step.id),
1893
+ dependencies,
1894
+ budgets,
1895
+ dataTransfers: uniqueTransfers,
1896
+ cleanup: [cleanup],
1897
+ compensation: sortedBy(compensation, (item) => item.id),
1898
+ warnings: sortedStrings(temporaryPublication ? ['TEMPORARY_PUBLICATION_CLEANUP_REQUIRED'] : []),
1899
+ blockedReasons: [],
1900
+ status: 'ok',
1901
+ planHash: '',
1902
+ };
1903
+ planBase.planHash = computePipelinePlanHash(planBase);
1904
+ return pipelineResult('ok', clone(planBase), [], planBase.warnings);
1905
+ }
1906
+ }
1907
+ export const DeterministicCapabilityAwarePipelinePlanner = CapabilityAwarePipelinePlanner;
1908
+ export function planPipeline(input) {
1909
+ return new CapabilityAwarePipelinePlanner().plan(input);
1910
+ }
1911
+ export function planCapabilityAwarePipeline(input) {
1912
+ return planPipeline(input);
1913
+ }
1914
+ function expired(expiresAt, now) {
1915
+ if (!expiresAt)
1916
+ return false;
1917
+ const expiry = Date.parse(expiresAt);
1918
+ const current = Date.parse(now);
1919
+ return !Number.isFinite(expiry) || !Number.isFinite(current) || current >= expiry;
1920
+ }
1921
+ function validString(value) {
1922
+ return typeof value === 'string' && value.length > 0;
1923
+ }
1924
+ function validDate(value) {
1925
+ return typeof value === 'string' && value.length > 0 && Number.isFinite(Date.parse(value));
1926
+ }
1927
+ function validNonNegativeInteger(value) {
1928
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0;
1929
+ }
1930
+ function validPositiveInteger(value) {
1931
+ return typeof value === 'number' && Number.isInteger(value) && value > 0;
1932
+ }
1933
+ function validNonNegativeNumber(value) {
1934
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
1935
+ }
1936
+ function validStringArray(value) {
1937
+ return Array.isArray(value) && value.every((item) => validString(item));
1938
+ }
1939
+ function validHashArray(value) {
1940
+ return Array.isArray(value) && value.every((item) => isHash(item));
1941
+ }
1942
+ function authorizationFieldReasons(field, value, validator, required = true) {
1943
+ if (value === undefined)
1944
+ return required ? [`AUTHORIZATION_FIELD_MISSING:${field}`] : [];
1945
+ return validator(value) ? [] : [`AUTHORIZATION_FIELD_INVALID:${field}`];
1946
+ }
1947
+ function remoteAuthorizationCompletenessReasons(authorization) {
1948
+ const reasons = [];
1949
+ const requireField = (field, value, validator) => { reasons.push(...authorizationFieldReasons(field, value, validator)); };
1950
+ const optionalField = (field, value, validator) => { reasons.push(...authorizationFieldReasons(field, value, validator, false)); };
1951
+ requireField('schemaVersion', authorization.schemaVersion, (value) => value === REMOTE_CALL_AUTHORIZATION_SCHEMA_VERSION);
1952
+ requireField('id', authorization.id, validString);
1953
+ requireField('caseId', authorization.caseId, validString);
1954
+ requireField('caseRevision', authorization.caseRevision, validNonNegativeInteger);
1955
+ requireField('contextHash', authorization.contextHash, isHash);
1956
+ requireField('stepId', authorization.stepId, validString);
1957
+ requireField('purpose', authorization.purpose, (value) => ['intent_interpretation', 'reference_interpretation', 'prompt_optimization', 'generation', 'postprocessing', 'semantic_review', 'asset_publication'].includes(value));
1958
+ requireField('inputHash', authorization.inputHash, isHash);
1959
+ requireField('permittedArtifactHashes', authorization.permittedArtifactHashes, validHashArray);
1960
+ requireField('permittedScopeIds', authorization.permittedScopeIds, validStringArray);
1961
+ requireField('constraintIds', authorization.constraintIds, validStringArray);
1962
+ requireField('adapterId', authorization.adapterId, validString);
1963
+ requireField('adapterDigest', authorization.adapterDigest, isHash);
1964
+ requireField('destination', authorization.destination, validString);
1965
+ requireField('dataCategories', authorization.dataCategories, validStringArray);
1966
+ requireField('maximumCalls', authorization.maximumCalls, validNonNegativeInteger);
1967
+ requireField('maximumRetries', authorization.maximumRetries, validNonNegativeInteger);
1968
+ requireField('timeoutMs', authorization.timeoutMs, validPositiveInteger);
1969
+ requireField('idempotencyKey', authorization.idempotencyKey, validString);
1970
+ requireField('authority', authorization.authority, validString);
1971
+ requireField('authorizedBy', authorization.authorizedBy, validString);
1972
+ requireField('authorizedAt', authorization.authorizedAt, validDate);
1973
+ requireField('authorizationHash', authorization.authorizationHash, isHash);
1974
+ optionalField('inputManifestHash', authorization.inputManifestHash, isHash);
1975
+ optionalField('modelId', authorization.modelId, validString);
1976
+ optionalField('modelVersion', authorization.modelVersion, validString);
1977
+ optionalField('profileDigest', authorization.profileDigest, isHash);
1978
+ optionalField('region', authorization.region, validString);
1979
+ optionalField('maximumBytes', authorization.maximumBytes, validNonNegativeInteger);
1980
+ optionalField('maximumCost', authorization.maximumCost, validNonNegativeNumber);
1981
+ optionalField('currency', authorization.currency, validString);
1982
+ optionalField('expiresAt', authorization.expiresAt, validDate);
1983
+ return sortedStrings(reasons);
1984
+ }
1985
+ function executionAuthorizationCompletenessReasons(authorization) {
1986
+ const reasons = [];
1987
+ const requireField = (field, value, validator) => { reasons.push(...authorizationFieldReasons(field, value, validator)); };
1988
+ const optionalField = (field, value, validator) => { reasons.push(...authorizationFieldReasons(field, value, validator, false)); };
1989
+ requireField('schemaVersion', authorization.schemaVersion, (value) => value === EXECUTION_AUTHORIZATION_SCHEMA_VERSION);
1990
+ requireField('id', authorization.id, validString);
1991
+ requireField('caseId', authorization.caseId, validString);
1992
+ requireField('caseRevision', authorization.caseRevision, validNonNegativeInteger);
1993
+ requireField('contextHash', authorization.contextHash, isHash);
1994
+ requireField('constraintIRHash', authorization.constraintIRHash, isHash);
1995
+ requireField('compilationSignature', authorization.compilationSignature, isHash);
1996
+ requireField('referencePlanHash', authorization.referencePlanHash, isHash);
1997
+ requireField('pipelinePlanHash', authorization.pipelinePlanHash, isHash);
1998
+ requireField('outputContractHash', authorization.outputContractHash, isHash);
1999
+ requireField('adapterProfileDigests', authorization.adapterProfileDigests, validHashArray);
2000
+ requireField('destinations', authorization.destinations, validStringArray);
2001
+ requireField('dataTransferDigest', authorization.dataTransferDigest, isHash);
2002
+ requireField('budgetDigest', authorization.budgetDigest, isHash);
2003
+ requireField('remoteCallAuthorizationIds', authorization.remoteCallAuthorizationIds, validStringArray);
2004
+ requireField('authority', authorization.authority, validString);
2005
+ requireField('authorizedBy', authorization.authorizedBy, validString);
2006
+ requireField('authorizedAt', authorization.authorizedAt, validDate);
2007
+ requireField('authorizationHash', authorization.authorizationHash, isHash);
2008
+ optionalField('promptArtifactHash', authorization.promptArtifactHash, isHash);
2009
+ optionalField('expiresAt', authorization.expiresAt, validDate);
2010
+ return sortedStrings(reasons);
2011
+ }
2012
+ function sameSnapshotValue(left, right) {
2013
+ return canonicalize(jsonReady(left)) === canonicalize(jsonReady(right));
2014
+ }
2015
+ function requiredSnapshotField(mismatches, snapshot, field, expected) {
2016
+ const name = String(field);
2017
+ if (expected === undefined) {
2018
+ mismatches.push(`AUTHORIZATION_FIELD_MISSING:${name}`);
2019
+ }
2020
+ else if (snapshot[field] === undefined) {
2021
+ mismatches.push(`SNAPSHOT_FIELD_MISSING:${name}`);
2022
+ }
2023
+ else if (!sameSnapshotValue(snapshot[field], expected)) {
2024
+ mismatches.push(`SNAPSHOT_FIELD_MISMATCH:${name}`);
2025
+ }
2026
+ }
2027
+ function optionalSnapshotField(mismatches, snapshot, field, expected) {
2028
+ const name = String(field);
2029
+ const actual = snapshot[field];
2030
+ if (expected === undefined && actual === undefined)
2031
+ return;
2032
+ if (expected === undefined) {
2033
+ mismatches.push(`SNAPSHOT_FIELD_UNEXPECTED:${name}`);
2034
+ }
2035
+ else if (actual === undefined) {
2036
+ mismatches.push(`SNAPSHOT_FIELD_MISSING:${name}`);
2037
+ }
2038
+ else if (!sameSnapshotValue(actual, expected)) {
2039
+ mismatches.push(`SNAPSHOT_FIELD_MISMATCH:${name}`);
2040
+ }
2041
+ }
2042
+ function remoteSnapshotMismatches(authorization, snapshot) {
2043
+ const mismatches = [];
2044
+ const required = [
2045
+ ['kind', 'remote_call'],
2046
+ ['caseId', authorization.caseId],
2047
+ ['caseRevision', authorization.caseRevision],
2048
+ ['contextHash', authorization.contextHash],
2049
+ ['stepId', authorization.stepId],
2050
+ ['purpose', authorization.purpose],
2051
+ ['inputHash', authorization.inputHash],
2052
+ ['permittedArtifactHashes', sortedStrings(authorization.permittedArtifactHashes)],
2053
+ ['permittedScopeIds', sortedStrings(authorization.permittedScopeIds)],
2054
+ ['constraintIds', sortedStrings(authorization.constraintIds)],
2055
+ ['adapterId', authorization.adapterId],
2056
+ ['adapterDigest', authorization.adapterDigest],
2057
+ ['destination', authorization.destination],
2058
+ ['dataCategories', sortedStrings(authorization.dataCategories)],
2059
+ ['maximumCalls', authorization.maximumCalls],
2060
+ ['maximumRetries', authorization.maximumRetries],
2061
+ ['timeoutMs', authorization.timeoutMs],
2062
+ ['idempotencyKey', authorization.idempotencyKey],
2063
+ ];
2064
+ for (const [field, expected] of required)
2065
+ requiredSnapshotField(mismatches, snapshot, field, expected);
2066
+ const optional = [
2067
+ ['inputManifestHash', authorization.inputManifestHash],
2068
+ ['modelId', authorization.modelId],
2069
+ ['modelVersion', authorization.modelVersion],
2070
+ ['profileDigest', authorization.profileDigest],
2071
+ ['region', authorization.region],
2072
+ ['maximumBytes', authorization.maximumBytes],
2073
+ ['maximumCost', authorization.maximumCost],
2074
+ ['currency', authorization.currency],
2075
+ ];
2076
+ for (const [field, expected] of optional)
2077
+ optionalSnapshotField(mismatches, snapshot, field, expected);
2078
+ return sortedStrings(mismatches);
2079
+ }
2080
+ function executionSnapshotMismatches(authorization, snapshot) {
2081
+ const mismatches = [];
2082
+ const required = [
2083
+ ['kind', 'execution'],
2084
+ ['caseId', authorization.caseId],
2085
+ ['caseRevision', authorization.caseRevision],
2086
+ ['contextHash', authorization.contextHash],
2087
+ ['constraintIRHash', authorization.constraintIRHash],
2088
+ ['compilationSignature', authorization.compilationSignature],
2089
+ ['referencePlanHash', authorization.referencePlanHash],
2090
+ ['pipelinePlanHash', authorization.pipelinePlanHash],
2091
+ ['outputContractHash', authorization.outputContractHash],
2092
+ ['adapterProfileDigests', sortedStrings(authorization.adapterProfileDigests)],
2093
+ ['destinations', sortedStrings(authorization.destinations)],
2094
+ ['dataTransferDigest', authorization.dataTransferDigest],
2095
+ ['budgetDigest', authorization.budgetDigest],
2096
+ ['remoteCallAuthorizationIds', sortedStrings(authorization.remoteCallAuthorizationIds)],
2097
+ ];
2098
+ for (const [field, expected] of required)
2099
+ requiredSnapshotField(mismatches, snapshot, field, expected);
2100
+ optionalSnapshotField(mismatches, snapshot, 'promptArtifactHash', authorization.promptArtifactHash);
2101
+ return sortedStrings(mismatches);
2102
+ }
2103
+ export function dispatchPreflight(authorization, snapshot, now = FIXED_M4_TIME) {
2104
+ try {
2105
+ const authorizationHash = typeof authorization?.authorizationHash === 'string' ? authorization.authorizationHash : '';
2106
+ const isRemote = authorization?.schemaVersion === REMOTE_CALL_AUTHORIZATION_SCHEMA_VERSION;
2107
+ const isExecution = authorization?.schemaVersion === EXECUTION_AUTHORIZATION_SCHEMA_VERSION;
2108
+ if (!isRemote && !isExecution)
2109
+ return { status: 'blocked', code: 'EXECUTION_NOT_AUTHORIZED', reasons: ['AUTHORIZATION_SCHEMA_INVALID'], authorizationHash };
2110
+ const completenessReasons = isRemote ? remoteAuthorizationCompletenessReasons(authorization) : executionAuthorizationCompletenessReasons(authorization);
2111
+ if (completenessReasons.length)
2112
+ return { status: 'blocked', code: 'EXECUTION_NOT_AUTHORIZED', reasons: completenessReasons, authorizationHash };
2113
+ const hashValid = isRemote ? computeRemoteCallAuthorizationHash(authorization) === authorization.authorizationHash : computeExecutionAuthorizationHash(authorization) === authorization.authorizationHash;
2114
+ if (!hashValid)
2115
+ return { status: 'blocked', code: 'EXECUTION_NOT_AUTHORIZED', reasons: ['AUTHORIZATION_HASH_MISMATCH'], authorizationHash };
2116
+ if (expired(authorization.expiresAt, now))
2117
+ return { status: 'blocked', code: 'AUTHORIZATION_STALE', reasons: ['AUTHORIZATION_EXPIRED'], authorizationHash };
2118
+ const reasons = isRemote ? remoteSnapshotMismatches(authorization, snapshot) : executionSnapshotMismatches(authorization, snapshot);
2119
+ if (reasons.length)
2120
+ return { status: 'blocked', code: 'AUTHORIZATION_STALE', reasons, authorizationHash };
2121
+ return { status: 'authorized', code: 'AUTHORIZED', reasons: [], authorizationHash };
2122
+ }
2123
+ catch {
2124
+ return { status: 'blocked', code: 'EXECUTION_NOT_AUTHORIZED', reasons: ['PREFLIGHT_INPUT_INVALID'], authorizationHash: typeof authorization?.authorizationHash === 'string' ? authorization.authorizationHash : '' };
2125
+ }
2126
+ }
2127
+ export const preflightDispatch = dispatchPreflight;
2128
+ export const dispatchPreflightPure = dispatchPreflight;
2129
+ function explainEntry(value) {
2130
+ const normalized = { ...value, sourceIds: sortedStrings(value.sourceIds), ruleIds: sortedStrings(value.ruleIds), constraintIds: sortedStrings(value.constraintIds), decisionIds: sortedStrings(value.decisionIds), assetIds: sortedStrings(value.assetIds) };
2131
+ return { ...clone(normalized), id: hashId('explain-entry', normalized) };
2132
+ }
2133
+ function finalExplain(kind, id, artifactHash, entries, status) {
2134
+ const base = { schemaVersion: EXPLAIN_RESULT_SCHEMA_VERSION, artifactKind: kind, artifactId: id, artifactHash, entries: sortedBy(entries, (entry) => entry.id), status };
2135
+ return clone({ ...base, explainHash: sha256(jsonReady(base)) });
2136
+ }
2137
+ export function explainConstraintIR(ir) {
2138
+ const entries = [];
2139
+ for (const goal of ir.goals)
2140
+ entries.push(explainEntry({ kind: 'constraint', sourceIds: goal.sourceIds, ruleIds: [], constraintIds: goal.constraintIds, decisionIds: [], assetIds: [], reasonCode: 'GOAL', message: goal.explanation }));
2141
+ for (const constraint of ir.constraints)
2142
+ entries.push(explainEntry({ kind: 'constraint', sourceIds: constraint.sourceIds, ruleIds: constraint.ruleId ? [constraint.ruleId] : [], constraintIds: [constraint.id], decisionIds: [], assetIds: [], reasonCode: constraint.reasonCode, message: constraint.explanation }));
2143
+ for (const conflict of ir.conflicts)
2144
+ entries.push(explainEntry({ kind: 'conflict', sourceIds: [], ruleIds: [], constraintIds: conflict.constraintIds, decisionIds: [], assetIds: [], reasonCode: conflict.code, message: conflict.message }));
2145
+ for (const degradation of ir.degradedPreferences)
2146
+ entries.push(explainEntry({ kind: 'degradation', sourceIds: [], ruleIds: [], constraintIds: degradation.constraintId ? [degradation.constraintId] : [], decisionIds: [], assetIds: [], reasonCode: degradation.reasonCode, message: degradation.explanation }));
2147
+ for (const trace of ir.ruleTraces)
2148
+ entries.push(explainEntry({ kind: 'rule', sourceIds: trace.inputIds, ruleIds: [trace.ruleId], constraintIds: trace.outputIds, decisionIds: [], assetIds: [], reasonCode: trace.reasonCode, message: trace.message }));
2149
+ return finalExplain('constraint-ir', ir.id, ir.deterministicSignature, entries, ir.status);
2150
+ }
2151
+ export function explainReferencePlan(plan) {
2152
+ const entries = [];
2153
+ for (const reference of plan.ordered)
2154
+ entries.push(explainEntry({ kind: 'asset', sourceIds: reference.sourceBindingIds, ruleIds: [], constraintIds: reference.constraintIds, decisionIds: [], assetIds: [reference.assetId], reasonCode: 'REFERENCE_SELECTED', message: `${reference.label} selected for role ${reference.role}.` }));
2155
+ for (const omission of [...plan.omitted, ...plan.blockedReferences])
2156
+ entries.push(explainEntry({ kind: 'asset', sourceIds: [], ruleIds: [], constraintIds: omission.constraintIds, decisionIds: [], assetIds: [omission.assetId], reasonCode: omission.reasonCode, message: omission.impact }));
2157
+ return finalExplain('reference-plan', plan.id, plan.planHash, entries, plan.status);
2158
+ }
2159
+ export function explainPipelinePlan(plan) {
2160
+ const entries = [];
2161
+ for (const step of plan.steps)
2162
+ entries.push(explainEntry({ kind: 'step', sourceIds: [], ruleIds: [], constraintIds: [], decisionIds: [], assetIds: [], reasonCode: step.capability, message: `${step.type} uses ${step.adapterId} at ${step.destination}.` }));
2163
+ for (const cleanup of plan.cleanup)
2164
+ entries.push(explainEntry({ kind: 'step', sourceIds: [], ruleIds: [], constraintIds: [], decisionIds: [], assetIds: [], reasonCode: 'CLEANUP_FINALLY', message: cleanup.explanation }));
2165
+ for (const reason of plan.blockedReasons)
2166
+ entries.push(explainEntry({ kind: 'conflict', sourceIds: [], ruleIds: [], constraintIds: [], decisionIds: [], assetIds: [], reasonCode: reason, message: reason }));
2167
+ return finalExplain('pipeline-plan', plan.id, plan.planHash, entries, plan.status);
2168
+ }
2169
+ function semanticRecord(value) {
2170
+ const object = objectOf(value);
2171
+ for (const field of ['constraintHash', 'goalHash', 'dependencyHash', 'resourceHash', 'conflictHash', 'degradationHash', 'traceHash', 'candidateHash', 'omissionHash', 'profileHash', 'budgetHash', 'transferHash', 'cleanupHash', 'compensationHash', 'stepHash', 'planHash', 'authorizationHash', 'explainHash', 'diffHash', 'resultHash'])
2172
+ delete object[field];
2173
+ delete object.authorizedAt;
2174
+ delete object.expiresAt;
2175
+ return object;
2176
+ }
2177
+ function recordsFor(kind, value) {
2178
+ const result = new Map();
2179
+ if (kind === 'constraint-ir') {
2180
+ const ir = value;
2181
+ for (const item of ir.goals)
2182
+ result.set(`goal:${item.id}`, semanticRecord(item));
2183
+ for (const item of ir.constraints)
2184
+ result.set(`constraint:${item.id}`, semanticRecord(item));
2185
+ for (const item of ir.dependencies)
2186
+ result.set(`dependency:${item.id}`, semanticRecord(item));
2187
+ for (const item of ir.resourceClaims)
2188
+ result.set(`resource:${item.id}`, semanticRecord(item));
2189
+ for (const item of ir.conflicts)
2190
+ result.set(`conflict:${item.id}`, semanticRecord(item));
2191
+ for (const item of ir.degradedPreferences)
2192
+ result.set(`degradation:${item.id}`, semanticRecord(item));
2193
+ for (const item of ir.ruleTraces)
2194
+ result.set(`trace:${item.id}`, semanticRecord(item));
2195
+ }
2196
+ else if (kind === 'reference-plan') {
2197
+ const plan = value;
2198
+ for (const item of plan.ordered)
2199
+ result.set(`reference:${item.candidateId}`, semanticRecord(item));
2200
+ for (const item of plan.omitted)
2201
+ result.set(`omitted:${item.candidateId}`, semanticRecord(item));
2202
+ for (const item of plan.blockedReferences)
2203
+ result.set(`blocked:${item.candidateId}`, semanticRecord(item));
2204
+ }
2205
+ else {
2206
+ const plan = value;
2207
+ for (const item of plan.steps)
2208
+ result.set(`step:${item.id}`, semanticRecord(item));
2209
+ for (const item of plan.dependencies)
2210
+ result.set(`dependency:${item.id}`, semanticRecord(item));
2211
+ for (const item of plan.cleanup)
2212
+ result.set(`cleanup:${item.id}`, semanticRecord(item));
2213
+ for (const item of plan.compensation)
2214
+ result.set(`compensation:${item.id}`, semanticRecord(item));
2215
+ }
2216
+ return result;
2217
+ }
2218
+ function artifactHash(kind, value) {
2219
+ if (kind === 'constraint-ir')
2220
+ return value.deterministicSignature;
2221
+ if (kind === 'reference-plan')
2222
+ return value.planHash;
2223
+ return value.planHash;
2224
+ }
2225
+ function statusOf(kind, value) {
2226
+ return value.status;
2227
+ }
2228
+ export function semanticDiff(kind, before, after) {
2229
+ const beforeRecords = recordsFor(kind, before);
2230
+ const afterRecords = recordsFor(kind, after);
2231
+ const added = [];
2232
+ const removed = [];
2233
+ const changed = [];
2234
+ for (const id of [...afterRecords.keys()].sort(compareCodeUnits)) {
2235
+ if (!beforeRecords.has(id))
2236
+ added.push(id);
2237
+ else if (canonicalize(beforeRecords.get(id)) !== canonicalize(afterRecords.get(id)))
2238
+ changed.push({ id, before: beforeRecords.get(id), after: afterRecords.get(id), reasonCode: 'SEMANTIC_FIELD_CHANGED' });
2239
+ }
2240
+ for (const id of [...beforeRecords.keys()].sort(compareCodeUnits))
2241
+ if (!afterRecords.has(id))
2242
+ removed.push(id);
2243
+ const degraded = kind === 'constraint-ir' ? after.degradedPreferences.map((item) => item.id).sort(compareCodeUnits) : kind === 'reference-plan' ? after.omitted.map((item) => item.id).sort(compareCodeUnits) : [];
2244
+ const blocked = [statusOf(kind, after) === 'blocked' ? 'artifact' : '', ...(kind === 'constraint-ir' ? after.conflicts.filter((item) => item.blocking).map((item) => item.id) : kind === 'reference-plan' ? after.blockedReferences.map((item) => item.id) : after.blockedReasons)].filter(Boolean).sort(compareCodeUnits);
2245
+ const base = { schemaVersion: SEMANTIC_DIFF_SCHEMA_VERSION, artifactKind: kind, beforeHash: artifactHash(kind, before), afterHash: artifactHash(kind, after), added: sortedStrings(added), removed: sortedStrings(removed), changed: sortedBy(changed, (item) => item.id), degraded, blocked };
2246
+ return clone({ ...base, diffHash: sha256(jsonReady(base)) });
2247
+ }
2248
+ export function diffConstraintIR(before, after) { return semanticDiff('constraint-ir', before, after); }
2249
+ export function diffReferencePlan(before, after) { return semanticDiff('reference-plan', before, after); }
2250
+ export function diffPipelinePlan(before, after) { return semanticDiff('pipeline-plan', before, after); }