@cohesivesystems/processes-presentation 0.1.0-alpha.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,847 @@
1
+ import { canonicalProcessAwaitClauseKinds, canonicalProcessNodeKinds, } from '@cohesivesystems/processes';
2
+ import { cloneWireValue, deepFreeze, isRecord, } from './process-presentation-values.js';
3
+ export const canonicalProcessPresentationCompatibility = Object.freeze({
4
+ definitionKind: 'process',
5
+ projectionVersion: 'cohesive-process-presentation/v1',
6
+ schemaVersions: Object.freeze(['cohesive-execution/v4']),
7
+ });
8
+ export function processPresentationElementId(category, canonicalId, ownerId) {
9
+ const owner = ownerId ? `${encodeURIComponent(ownerId)}/` : '';
10
+ return `${category}/${owner}${encodeURIComponent(canonicalId)}`;
11
+ }
12
+ export function processPresentationDefinitionReferenceId(reference) {
13
+ return processPresentationElementId('definition-reference', definitionReferenceKey(reference));
14
+ }
15
+ export function projectCanonicalProcessDocument(document) {
16
+ const diagnostics = [];
17
+ if (!isRecord(document)) {
18
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_INVALID', [], 'A canonical Process projection requires an execution-definition document object.');
19
+ return freezeProjectionResult(null, diagnostics);
20
+ }
21
+ const kind = readString(document.kind);
22
+ if (kind !== canonicalProcessPresentationCompatibility.definitionKind) {
23
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_KIND_UNSUPPORTED', ['kind'], `Process presentation supports definition kind '${canonicalProcessPresentationCompatibility.definitionKind}', ` +
24
+ `but observed '${kind ?? '<missing>'}'.`, kind);
25
+ return freezeProjectionResult(null, diagnostics);
26
+ }
27
+ const metadata = isRecord(document.metadata) ? document.metadata : null;
28
+ const schemaVersion = readString(metadata?.schemaVersion);
29
+ if (!schemaVersion || !canonicalProcessPresentationCompatibility.schemaVersions.includes(schemaVersion)) {
30
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_SCHEMA_UNSUPPORTED', ['metadata', 'schemaVersion'], `Process presentation does not support execution schema '${schemaVersion ?? '<missing>'}'.`, schemaVersion);
31
+ return freezeProjectionResult(null, diagnostics);
32
+ }
33
+ if (!metadata) {
34
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_INVALID', ['metadata'], 'A canonical Process document requires definition metadata.');
35
+ return freezeProjectionResult(null, diagnostics);
36
+ }
37
+ const definition = isRecord(document.definition) ? document.definition : null;
38
+ if (!definition) {
39
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_INVALID', ['definition'], 'A canonical Process document requires an object definition payload.');
40
+ return freezeProjectionResult(null, diagnostics);
41
+ }
42
+ const context = {
43
+ constructPaths: new Map(),
44
+ diagnostics,
45
+ document,
46
+ edgePaths: new Map(),
47
+ edges: [],
48
+ nodeIds: new Set(),
49
+ nodes: [],
50
+ pendingTargets: [],
51
+ };
52
+ verifyDispositionCompleteness(context);
53
+ projectProcessDefinition(definition, context);
54
+ resolvePendingTargets(context);
55
+ const definitionReference = readDefinitionReference(metadata);
56
+ if (!definitionReference) {
57
+ addDiagnostic(diagnostics, 'PROCESS_DEFINITION_REFERENCE_INVALID', ['metadata'], 'Process metadata must retain definition id, revision id, and a complete fingerprint.');
58
+ }
59
+ const entry = readString(definition.entry);
60
+ const entryNodeId = entry ? processPresentationElementId('process-node', entry) : null;
61
+ if (!entry || !context.nodeIds.has(entryNodeId)) {
62
+ addDiagnostic(diagnostics, 'PROCESS_ENTRY_UNRESOLVED', ['definition', 'entry'], 'The Process entry must identify a projected canonical Process node.', entry);
63
+ }
64
+ sortProjection(context);
65
+ const frozenDiagnostics = freezeDiagnostics(diagnostics);
66
+ const graph = deepFreeze({
67
+ diagnostics: frozenDiagnostics,
68
+ document: {
69
+ definition: definitionReference ?? missingDefinitionReference(metadata),
70
+ kind,
71
+ metadata: cloneWireValue(metadata),
72
+ schemaVersion,
73
+ typeDefinitions: Array.isArray(definition.$types) ? cloneWireValue(definition.$types) : [],
74
+ },
75
+ edges: context.edges.map(freezeEdge),
76
+ entryNodeId: entry && context.nodeIds.has(entryNodeId) ? entryNodeId : null,
77
+ nodes: context.nodes.map(freezeNode),
78
+ projectionVersion: canonicalProcessPresentationCompatibility.projectionVersion,
79
+ });
80
+ return deepFreeze({ graph, diagnostics: frozenDiagnostics });
81
+ }
82
+ function projectProcessDefinition(definition, context) {
83
+ const nodes = definition.nodes;
84
+ if (!Array.isArray(nodes)) {
85
+ addDiagnostic(context.diagnostics, 'PROCESS_NODE_TABLE_INVALID', ['definition', 'nodes'], 'A canonical Process definition requires a node array.');
86
+ return;
87
+ }
88
+ for (let index = 0; index < nodes.length; index += 1) {
89
+ const path = ['definition', 'nodes', String(index)];
90
+ const rawNode = nodes[index];
91
+ if (!isRecord(rawNode)) {
92
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', path, 'A Process node must be an object.');
93
+ continue;
94
+ }
95
+ const id = readString(rawNode.id);
96
+ if (!id) {
97
+ addDiagnostic(context.diagnostics, 'PROCESS_ELEMENT_ID_REQUIRED', [...path, 'id'], 'A Process node requires a stable canonical identity.');
98
+ continue;
99
+ }
100
+ const kind = readString(rawNode.$node);
101
+ if (!kind) {
102
+ const owner = addElementNode({
103
+ canonicalId: id,
104
+ category: 'process-node',
105
+ context,
106
+ label: 'Unknown Process node',
107
+ path,
108
+ processNodeKind: null,
109
+ source: rawNode,
110
+ });
111
+ addDiagnostic(context.diagnostics, 'PROCESS_NODE_KIND_REQUIRED', [...path, '$node'], 'A Process node requires its persisted wire discriminator.', owner?.canonicalId ?? id);
112
+ continue;
113
+ }
114
+ if (!isCanonicalProcessNodeKind(kind)) {
115
+ addElementNode({
116
+ canonicalId: id,
117
+ category: 'process-node',
118
+ context,
119
+ label: `Unsupported Process node (${kind})`,
120
+ path,
121
+ processNodeKind: kind,
122
+ source: rawNode,
123
+ });
124
+ addDiagnostic(context.diagnostics, 'PROCESS_NODE_KIND_UNSUPPORTED', [...path, '$node'], `Process node kind '${kind}' is outside the projector disposition inventory.`, id);
125
+ continue;
126
+ }
127
+ const disposition = processNodeDispositions[kind];
128
+ const owner = addElementNode({
129
+ canonicalId: id,
130
+ category: 'process-node',
131
+ context,
132
+ label: disposition.label,
133
+ path,
134
+ processNodeKind: kind,
135
+ source: rawNode,
136
+ terminal: disposition.terminal,
137
+ });
138
+ if (!owner) {
139
+ continue;
140
+ }
141
+ disposition.project(rawNode, owner, path, context);
142
+ }
143
+ }
144
+ const processNodeDispositions = {
145
+ invokeTransition: {
146
+ label: 'Invoke transition',
147
+ project(node, owner, path, context) {
148
+ addDefinitionReference(owner, 'transition', node.transition, [...path, 'transition'], context);
149
+ addContinuation(owner, node.continuation, 'continuation', [...path, 'continuation'], context);
150
+ },
151
+ },
152
+ evaluateRelation: {
153
+ label: 'Evaluate relation',
154
+ project(node, owner, path, context) {
155
+ addDefinitionReference(owner, 'relation', node.relation, [...path, 'relation'], context);
156
+ addContinuation(owner, node.continuation, 'continuation', [...path, 'continuation'], context);
157
+ },
158
+ },
159
+ request: {
160
+ label: 'Request',
161
+ project(node, owner, path, context) {
162
+ addContractReference(owner, 'request-contract', node.contract, [...path, 'contract'], context);
163
+ projectRequestOutcomes(owner, node.outcomes, [...path, 'outcomes'], context);
164
+ },
165
+ },
166
+ emitEvent: {
167
+ label: 'Emit event',
168
+ project(node, owner, path, context) {
169
+ addContractReference(owner, 'domain-event-contract', node.contract, [...path, 'contract'], context);
170
+ addControlEdge(owner, node.next, 'next', [...path, 'next'], context);
171
+ },
172
+ },
173
+ sendSignal: {
174
+ label: 'Send signal',
175
+ project(node, owner, path, context) {
176
+ addContractReference(owner, 'signal-contract', node.contract, [...path, 'contract'], context);
177
+ addControlEdge(owner, node.next, 'next', [...path, 'next'], context);
178
+ },
179
+ },
180
+ choice: {
181
+ label: 'Choice',
182
+ project(node, owner, path, context) {
183
+ projectChoiceCases(owner, node.cases, [...path, 'cases'], context);
184
+ projectFallback(owner, node.fallback, [...path, 'fallback'], context);
185
+ },
186
+ },
187
+ match: {
188
+ label: 'Match',
189
+ project(node, owner, path, context) {
190
+ addValueContract(owner, 'match-value', node.contract);
191
+ projectMatchCases(owner, node.cases, [...path, 'cases'], context);
192
+ projectFallback(owner, node.fallback, [...path, 'fallback'], context);
193
+ },
194
+ },
195
+ fork: {
196
+ label: 'Fork',
197
+ project(node, owner, path, context) {
198
+ projectForkBranches(owner, node.branches, [...path, 'branches'], context);
199
+ const join = readString(node.join);
200
+ if (join) {
201
+ addDerivedEdge(owner, processPresentationElementId('process-node', join), 'reciprocal', 'join', path, context);
202
+ }
203
+ else {
204
+ addDiagnostic(context.diagnostics, 'PROCESS_EDGE_TARGET_UNRESOLVED', [...path, 'join'], 'A Fork requires the stable identity of its reciprocal Join.', owner.canonicalId);
205
+ }
206
+ },
207
+ },
208
+ join: {
209
+ label: 'Join',
210
+ project(node, owner, path, context) {
211
+ addControlEdge(owner, node.next, 'next', [...path, 'next'], context);
212
+ projectJoinResults(owner, node.fork, node.result, [...path, 'result'], context);
213
+ },
214
+ },
215
+ awaitMatch: {
216
+ label: 'Await match',
217
+ project(node, owner, path, context) {
218
+ projectAwaitClauses(owner, node.clauses, [...path, 'clauses'], context);
219
+ },
220
+ },
221
+ timer: {
222
+ label: 'Timer',
223
+ project(node, owner, path, context) {
224
+ addControlEdge(owner, node.next, 'next', [...path, 'next'], context);
225
+ },
226
+ },
227
+ reply: {
228
+ label: 'Reply',
229
+ project(node, owner, path, context) {
230
+ addContractReference(owner, 'reply-contract', node.contract, [...path, 'contract'], context);
231
+ addControlEdge(owner, node.next, 'next', [...path, 'next'], context);
232
+ },
233
+ },
234
+ durableCut: {
235
+ label: 'Durable cut',
236
+ project(node, owner, path, context) {
237
+ addControlEdge(owner, node.resume, 'resume', [...path, 'resume'], context);
238
+ },
239
+ },
240
+ invokeProcess: {
241
+ label: 'Invoke process',
242
+ project(node, owner, path, context) {
243
+ addDefinitionReference(owner, 'child-process', node.process, [...path, 'process'], context);
244
+ addContractReference(owner, 'request-contract', node.contract, [...path, 'contract'], context);
245
+ projectRequestOutcomes(owner, node.outcomes, [...path, 'outcomes'], context);
246
+ },
247
+ },
248
+ forEachPartition: {
249
+ label: 'For each partition',
250
+ project(node, owner, path, context) {
251
+ addDefinitionReference(owner, 'child-process', node.process, [...path, 'process'], context);
252
+ addContractReference(owner, 'request-contract', node.contract, [...path, 'contract'], context);
253
+ addValueContract(owner, 'partition-output', node.partition?.contract);
254
+ addControlEdge(owner, node.completed, 'completed', [...path, 'completed'], context);
255
+ addControlEdge(owner, node.failed, 'failed', [...path, 'failed'], context);
256
+ },
257
+ },
258
+ repeatAcrossActivation: {
259
+ label: 'Repeat across activation',
260
+ project(node, owner, path, context) {
261
+ addValueContract(owner, 'progress', node.progressContract);
262
+ addValueContract(owner, 'state', node.stateContract);
263
+ addControlEdge(owner, node.repeat, 'repeat', [...path, 'repeat'], context);
264
+ addControlEdge(owner, node.completed, 'completed', [...path, 'completed'], context);
265
+ addControlEdge(owner, node.exhausted, 'exhausted', [...path, 'exhausted'], context);
266
+ addControlEdge(owner, node.stalled, 'stalled', [...path, 'stalled'], context);
267
+ },
268
+ },
269
+ cancellationFinalizer: {
270
+ label: 'Cancellation finalizer',
271
+ project(node, owner, path, context) {
272
+ addDefinitionReference(owner, 'cancellation-finalizer-process', node.process, [...path, 'process'], context);
273
+ addContractReference(owner, 'request-contract', node.contract, [...path, 'contract'], context);
274
+ },
275
+ },
276
+ return: {
277
+ label: 'Return',
278
+ terminal: true,
279
+ project() { },
280
+ },
281
+ fail: {
282
+ label: 'Fail',
283
+ terminal: true,
284
+ project() { },
285
+ },
286
+ };
287
+ const processAwaitClauseDispositions = {
288
+ interaction: {
289
+ label: 'Await interaction',
290
+ project(clause, owner, path, context) {
291
+ addContractReference(owner, 'interaction-contract', clause.contract, [...path, 'contract'], context);
292
+ addValueContract(owner, 'input', clause.input?.contract);
293
+ },
294
+ },
295
+ timer: {
296
+ label: 'Await timer',
297
+ project() { },
298
+ },
299
+ };
300
+ export const canonicalProcessPresentationDispositionKinds = Object.freeze(Object.keys(processNodeDispositions).sort());
301
+ export const canonicalProcessAwaitClausePresentationDispositionKinds = Object.freeze(Object.keys(processAwaitClauseDispositions).sort());
302
+ function projectRequestOutcomes(owner, rawOutcomes, path, context) {
303
+ forEachElement(rawOutcomes, path, context, (rawOutcome, outcomePath) => {
304
+ const id = readString(rawOutcome.id);
305
+ if (!id) {
306
+ missingElementId('Request outcome', outcomePath, owner, context);
307
+ return;
308
+ }
309
+ const role = readString(rawOutcome.outcome) ?? 'outcome';
310
+ const outcome = addElementNode({
311
+ canonicalId: id,
312
+ category: 'request-outcome',
313
+ context,
314
+ label: `Request outcome: ${role}`,
315
+ ownerId: owner.canonicalId,
316
+ path: outcomePath,
317
+ role,
318
+ source: rawOutcome,
319
+ });
320
+ if (!outcome) {
321
+ return;
322
+ }
323
+ addContainmentEdge(owner, outcome, 'outcome', outcomePath, context);
324
+ addContinuation(outcome, rawOutcome.continuation, role, [...outcomePath, 'continuation'], context);
325
+ });
326
+ }
327
+ function projectChoiceCases(owner, rawCases, path, context) {
328
+ forEachElement(rawCases, path, context, (rawCase, casePath) => {
329
+ const id = readString(rawCase.id);
330
+ if (!id) {
331
+ missingElementId('Choice case', casePath, owner, context);
332
+ return;
333
+ }
334
+ const choiceCase = addElementNode({
335
+ canonicalId: id,
336
+ category: 'choice-case',
337
+ context,
338
+ label: 'Choice case',
339
+ ownerId: owner.canonicalId,
340
+ path: casePath,
341
+ role: 'case',
342
+ source: rawCase,
343
+ });
344
+ if (!choiceCase) {
345
+ return;
346
+ }
347
+ addContainmentEdge(owner, choiceCase, 'case', casePath, context);
348
+ addControlEdge(choiceCase, rawCase.next, 'selected', [...casePath, 'next'], context);
349
+ });
350
+ }
351
+ function projectMatchCases(owner, rawCases, path, context) {
352
+ forEachElement(rawCases, path, context, (rawCase, casePath) => {
353
+ const id = readString(rawCase.id);
354
+ if (!id) {
355
+ missingElementId('Match case', casePath, owner, context);
356
+ return;
357
+ }
358
+ const matchCase = addElementNode({
359
+ canonicalId: id,
360
+ category: 'match-case',
361
+ context,
362
+ label: 'Match case',
363
+ ownerId: owner.canonicalId,
364
+ path: casePath,
365
+ role: 'case',
366
+ source: rawCase,
367
+ });
368
+ if (!matchCase) {
369
+ return;
370
+ }
371
+ const pattern = isRecord(rawCase.pattern) ? rawCase.pattern : null;
372
+ addValueContract(matchCase, 'pattern', pattern?.contract);
373
+ addContainmentEdge(owner, matchCase, 'case', casePath, context);
374
+ addControlEdge(matchCase, rawCase.next, 'selected', [...casePath, 'next'], context);
375
+ });
376
+ }
377
+ function projectFallback(owner, rawFallback, path, context) {
378
+ if (rawFallback === null || rawFallback === undefined) {
379
+ return;
380
+ }
381
+ if (!isRecord(rawFallback)) {
382
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', path, 'A Process fallback must be an object.', owner.canonicalId);
383
+ return;
384
+ }
385
+ const id = readString(rawFallback.id);
386
+ if (!id) {
387
+ missingElementId('Fallback', path, owner, context);
388
+ return;
389
+ }
390
+ const fallback = addElementNode({
391
+ canonicalId: id,
392
+ category: 'fallback',
393
+ context,
394
+ label: 'Fallback',
395
+ ownerId: owner.canonicalId,
396
+ path,
397
+ role: 'fallback',
398
+ source: rawFallback,
399
+ });
400
+ if (!fallback) {
401
+ return;
402
+ }
403
+ addContainmentEdge(owner, fallback, 'fallback', path, context);
404
+ addControlEdge(fallback, rawFallback.next, 'fallback', [...path, 'next'], context);
405
+ }
406
+ function projectForkBranches(owner, rawBranches, path, context) {
407
+ forEachElement(rawBranches, path, context, (rawBranch, branchPath) => {
408
+ const id = readString(rawBranch.id);
409
+ if (!id) {
410
+ missingElementId('Fork branch', branchPath, owner, context);
411
+ return;
412
+ }
413
+ const branch = addElementNode({
414
+ canonicalId: id,
415
+ category: 'fork-branch',
416
+ context,
417
+ label: 'Fork branch',
418
+ ownerId: owner.canonicalId,
419
+ path: branchPath,
420
+ role: readString(rawBranch.capacityDomain) ?? 'branch',
421
+ source: rawBranch,
422
+ });
423
+ if (!branch) {
424
+ return;
425
+ }
426
+ addContainmentEdge(owner, branch, 'branch', branchPath, context);
427
+ addControlEdge(branch, rawBranch.start, 'start', [...branchPath, 'start'], context);
428
+ });
429
+ }
430
+ function projectAwaitClauses(owner, rawClauses, path, context) {
431
+ forEachElement(rawClauses, path, context, (rawClause, clausePath) => {
432
+ const id = readString(rawClause.id);
433
+ if (!id) {
434
+ missingElementId('Await clause', clausePath, owner, context);
435
+ return;
436
+ }
437
+ const clauseKind = readString(rawClause.$clause);
438
+ const disposition = clauseKind && isCanonicalProcessAwaitClauseKind(clauseKind)
439
+ ? processAwaitClauseDispositions[clauseKind]
440
+ : null;
441
+ const clause = addElementNode({
442
+ canonicalId: id,
443
+ category: 'await-clause',
444
+ context,
445
+ label: disposition?.label ?? `Unsupported Await clause (${clauseKind ?? 'missing'})`,
446
+ ownerId: owner.canonicalId,
447
+ path: clausePath,
448
+ role: clauseKind ?? 'unknown',
449
+ source: rawClause,
450
+ });
451
+ if (!clause) {
452
+ return;
453
+ }
454
+ addContainmentEdge(owner, clause, 'clause', clausePath, context);
455
+ if (!disposition || !clauseKind) {
456
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', [...clausePath, '$clause'], `Await clause kind '${clauseKind ?? '<missing>'}' is unsupported.`, id);
457
+ }
458
+ else {
459
+ disposition.project(rawClause, clause, clausePath, context);
460
+ }
461
+ addContinuation(clause, rawClause.continuation, 'selected', [...clausePath, 'continuation'], context);
462
+ });
463
+ }
464
+ function projectJoinResults(owner, rawForkId, rawResult, path, context) {
465
+ if (rawResult === null || rawResult === undefined) {
466
+ return;
467
+ }
468
+ if (!isRecord(rawResult)) {
469
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', path, 'A Join result projection must be an object.', owner.canonicalId);
470
+ return;
471
+ }
472
+ const forkId = readString(rawForkId);
473
+ addValueContract(owner, 'join-result', rawResult.resultContract);
474
+ forEachElement(rawResult.branches, [...path, 'branches'], context, (rawBranch, branchPath) => {
475
+ const branchId = readString(rawBranch.branch);
476
+ if (!branchId) {
477
+ missingElementId('Join result branch', branchPath, owner, context);
478
+ return;
479
+ }
480
+ const result = addElementNode({
481
+ canonicalId: branchId,
482
+ category: 'join-result',
483
+ context,
484
+ label: 'Join branch result',
485
+ ownerId: owner.canonicalId,
486
+ path: branchPath,
487
+ registerConstruct: false,
488
+ role: 'result',
489
+ source: rawBranch,
490
+ });
491
+ if (!result) {
492
+ return;
493
+ }
494
+ addValueContract(result, 'join-result', rawResult.resultContract);
495
+ addContainmentEdge(owner, result, 'result', branchPath, context);
496
+ addDerivedEdge(result, processPresentationElementId('fork-branch', branchId, forkId), 'join-result', 'branch', branchPath, context);
497
+ });
498
+ }
499
+ function addElementNode({ canonicalId, category, context, label, ownerId = null, path, processNodeKind = null, registerConstruct = true, role = null, source, terminal = false, }) {
500
+ if (registerConstruct) {
501
+ const priorPath = context.constructPaths.get(canonicalId);
502
+ if (priorPath) {
503
+ addDiagnostic(context.diagnostics, 'PROCESS_ELEMENT_ID_DUPLICATED', path, `Canonical Process element identity '${canonicalId}' is duplicated; first observed at '${formatPath(priorPath)}'.`, canonicalId);
504
+ return null;
505
+ }
506
+ context.constructPaths.set(canonicalId, [...path]);
507
+ }
508
+ const id = processPresentationElementId(category, canonicalId, ownerId);
509
+ if (context.nodeIds.has(id)) {
510
+ return context.nodes.find((node) => node.id === id) ?? null;
511
+ }
512
+ const node = {
513
+ canonicalId,
514
+ category,
515
+ details: createDetails(source, path, context.document),
516
+ id,
517
+ label,
518
+ ownerId,
519
+ processNodeKind,
520
+ role,
521
+ terminal,
522
+ };
523
+ context.nodeIds.add(id);
524
+ context.nodes.push(node);
525
+ return node;
526
+ }
527
+ function addContainmentEdge(owner, child, role, path, context) {
528
+ addDerivedEdge(owner, child.id, 'contains', role, path, context);
529
+ }
530
+ function addDerivedEdge(owner, target, kind, role, path, context) {
531
+ const id = derivedEdgeId(kind, owner.id, target, role);
532
+ if (context.edges.some((edge) => edge.id === id)) {
533
+ return;
534
+ }
535
+ context.edges.push({
536
+ canonicalEdgeId: null,
537
+ details: createDetails(null, path, context.document),
538
+ id,
539
+ kind,
540
+ role,
541
+ source: owner.id,
542
+ target,
543
+ });
544
+ context.pendingTargets.push({ edge: context.edges.at(-1), path, targetCanonicalId: target });
545
+ }
546
+ function addControlEdge(owner, rawEdge, role, path, context) {
547
+ if (!isRecord(rawEdge)) {
548
+ addDiagnostic(context.diagnostics, 'PROCESS_EDGE_INVALID', path, `Process control edge '${role}' must be an object.`, owner.canonicalId);
549
+ return;
550
+ }
551
+ const edgeId = readString(rawEdge.id);
552
+ const targetCanonicalId = readString(rawEdge.target);
553
+ if (!edgeId || !targetCanonicalId) {
554
+ addDiagnostic(context.diagnostics, 'PROCESS_EDGE_INVALID', path, `Process control edge '${role}' requires stable edge and target identities.`, edgeId ?? owner.canonicalId);
555
+ return;
556
+ }
557
+ const priorPath = context.edgePaths.get(edgeId);
558
+ if (priorPath) {
559
+ addDiagnostic(context.diagnostics, 'PROCESS_ELEMENT_ID_DUPLICATED', path, `Canonical Process edge identity '${edgeId}' is duplicated; first observed at '${formatPath(priorPath)}'.`, edgeId);
560
+ return;
561
+ }
562
+ context.edgePaths.set(edgeId, [...path]);
563
+ const edge = {
564
+ canonicalEdgeId: edgeId,
565
+ details: createDetails(rawEdge, path, context.document),
566
+ id: `control/${encodeURIComponent(edgeId)}`,
567
+ kind: 'control',
568
+ role,
569
+ source: owner.id,
570
+ target: processPresentationElementId('process-node', targetCanonicalId),
571
+ };
572
+ context.edges.push(edge);
573
+ context.pendingTargets.push({ edge, path, targetCanonicalId });
574
+ }
575
+ function addContinuation(owner, rawContinuation, role, path, context) {
576
+ if (!isRecord(rawContinuation)) {
577
+ addDiagnostic(context.diagnostics, 'PROCESS_EDGE_INVALID', path, `Process continuation '${role}' must be an object.`, owner.canonicalId);
578
+ return;
579
+ }
580
+ const output = isRecord(rawContinuation.output) ? rawContinuation.output : null;
581
+ addValueContract(owner, `${role}-output`, output?.contract);
582
+ addControlEdge(owner, rawContinuation.edge, role, [...path, 'edge'], context);
583
+ }
584
+ function addContractReference(owner, role, rawContract, path, context) {
585
+ if (!isRecord(rawContract)) {
586
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_REFERENCE_INVALID', path, `Interaction contract '${role}' must retain an exact definition reference.`, owner.canonicalId);
587
+ return;
588
+ }
589
+ addDefinitionReference(owner, role, rawContract.definition, [...path, 'definition'], context);
590
+ }
591
+ function addDefinitionReference(owner, role, rawReference, path, context) {
592
+ const reference = readDefinitionReference(rawReference);
593
+ if (!reference) {
594
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_REFERENCE_INVALID', path, `Definition link '${role}' must retain definition id, revision id, and a complete fingerprint.`, owner.canonicalId);
595
+ return;
596
+ }
597
+ const frozenReference = cloneWireValue(reference);
598
+ owner.details.definitionReferences.push(deepFreeze({ reference: frozenReference, role }));
599
+ const key = definitionReferenceKey(reference);
600
+ const referenceNodeId = processPresentationDefinitionReferenceId(reference);
601
+ let referenceNode = context.nodes.find((node) => node.id === referenceNodeId);
602
+ if (!referenceNode) {
603
+ referenceNode = addElementNode({
604
+ canonicalId: key,
605
+ category: 'definition-reference',
606
+ context,
607
+ label: `Definition: ${reference.definitionId}`,
608
+ path,
609
+ registerConstruct: false,
610
+ role: 'definition',
611
+ source: reference,
612
+ }) ?? undefined;
613
+ }
614
+ if (!referenceNode) {
615
+ return;
616
+ }
617
+ const edgeId = derivedEdgeId('definition-reference', owner.id, referenceNode.id, role);
618
+ if (context.edges.some((edge) => edge.id === edgeId)) {
619
+ return;
620
+ }
621
+ context.edges.push({
622
+ canonicalEdgeId: null,
623
+ details: createDetails(reference, path, context.document),
624
+ id: edgeId,
625
+ kind: 'definition-reference',
626
+ role,
627
+ source: owner.id,
628
+ target: referenceNode.id,
629
+ });
630
+ }
631
+ function addValueContract(owner, role, rawContract) {
632
+ if (!isRecord(rawContract)) {
633
+ return;
634
+ }
635
+ owner.details.valueContracts.push(deepFreeze({
636
+ contract: cloneWireValue(rawContract),
637
+ role,
638
+ }));
639
+ }
640
+ function resolvePendingTargets(context) {
641
+ for (const pending of context.pendingTargets) {
642
+ if (context.nodeIds.has(pending.edge.target)) {
643
+ continue;
644
+ }
645
+ const targetCanonicalId = pending.edge.kind === 'control'
646
+ ? pending.targetCanonicalId
647
+ : pending.edge.target;
648
+ const unresolvedId = processPresentationElementId('unresolved-node', targetCanonicalId);
649
+ addElementNode({
650
+ canonicalId: targetCanonicalId,
651
+ category: 'unresolved-node',
652
+ context,
653
+ label: `Unresolved target: ${targetCanonicalId}`,
654
+ path: pending.path,
655
+ registerConstruct: false,
656
+ role: 'unresolved-target',
657
+ source: null,
658
+ });
659
+ pending.edge.target = unresolvedId;
660
+ addDiagnostic(context.diagnostics, 'PROCESS_EDGE_TARGET_UNRESOLVED', pending.path, `Projected edge '${pending.edge.id}' targets an unresolved semantic element.`, targetCanonicalId);
661
+ }
662
+ }
663
+ function verifyDispositionCompleteness(context) {
664
+ verifyInventoryDispositionCompleteness(canonicalProcessNodeKinds, Object.keys(processNodeDispositions), 'Process construct', context);
665
+ verifyInventoryDispositionCompleteness(canonicalProcessAwaitClauseKinds, Object.keys(processAwaitClauseDispositions), 'Process Await clause', context);
666
+ }
667
+ function verifyInventoryDispositionCompleteness(authoritativeValues, dispositionValues, label, context) {
668
+ const authoritative = new Set(authoritativeValues);
669
+ const dispositions = new Set(dispositionValues);
670
+ for (const kind of [...authoritative].sort()) {
671
+ if (!dispositions.has(kind)) {
672
+ addDiagnostic(context.diagnostics, 'PROCESS_CONSTRUCT_DISPOSITION_MISSING', [], `Canonical ${label} '${kind}' has no presentation disposition.`, kind);
673
+ }
674
+ }
675
+ for (const kind of [...dispositions].sort()) {
676
+ if (!authoritative.has(kind)) {
677
+ addDiagnostic(context.diagnostics, 'PROCESS_CONSTRUCT_DISPOSITION_STALE', [], `${label} presentation disposition '${kind}' is absent from the canonical inventory.`, kind);
678
+ }
679
+ }
680
+ }
681
+ function forEachElement(rawElements, path, context, project) {
682
+ if (!Array.isArray(rawElements)) {
683
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', path, 'A canonical Process element collection must be an array.');
684
+ return;
685
+ }
686
+ for (let index = 0; index < rawElements.length; index += 1) {
687
+ const elementPath = [...path, String(index)];
688
+ const element = rawElements[index];
689
+ if (!isRecord(element)) {
690
+ addDiagnostic(context.diagnostics, 'PROCESS_DEFINITION_INVALID', elementPath, 'A canonical Process element must be an object.');
691
+ continue;
692
+ }
693
+ project(element, elementPath);
694
+ }
695
+ }
696
+ function missingElementId(label, path, owner, context) {
697
+ addDiagnostic(context.diagnostics, 'PROCESS_ELEMENT_ID_REQUIRED', [...path, 'id'], `${label} requires a stable canonical identity.`, owner.canonicalId);
698
+ }
699
+ function createDetails(source, path, document) {
700
+ return {
701
+ definitionReferences: [],
702
+ source: cloneWireValue(source),
703
+ sourceMap: sourceMapForPath(document, path),
704
+ sourcePath: [...path],
705
+ valueContracts: [],
706
+ };
707
+ }
708
+ function sourceMapForPath(document, path) {
709
+ const entries = document.metadata?.sourceMap?.entries;
710
+ if (!Array.isArray(entries)) {
711
+ return [];
712
+ }
713
+ const definitionPath = path[0] === 'definition' ? path.slice(1) : path;
714
+ let deepest = -1;
715
+ const matches = [];
716
+ for (const entry of entries) {
717
+ const mapped = entry?.semanticPath?.segments;
718
+ if (!Array.isArray(mapped) ||
719
+ !isStringArray(mapped) ||
720
+ mapped.length < deepest ||
721
+ !isPrefix(mapped, definitionPath)) {
722
+ continue;
723
+ }
724
+ if (mapped.length > deepest) {
725
+ deepest = mapped.length;
726
+ matches.length = 0;
727
+ }
728
+ matches.push(entry);
729
+ }
730
+ return matches
731
+ .sort((left, right) => sourceMapKey(left).localeCompare(sourceMapKey(right)))
732
+ .map((entry) => cloneWireValue(entry));
733
+ }
734
+ function sortProjection(context) {
735
+ context.nodes.sort((left, right) => left.id.localeCompare(right.id));
736
+ context.edges.sort((left, right) => left.id.localeCompare(right.id));
737
+ context.diagnostics.sort((left, right) => left.id.localeCompare(right.id));
738
+ }
739
+ function freezeNode(node) {
740
+ return deepFreeze({
741
+ ...node,
742
+ details: freezeDetails(node.details),
743
+ });
744
+ }
745
+ function freezeEdge(edge) {
746
+ return deepFreeze({
747
+ ...edge,
748
+ details: freezeDetails(edge.details),
749
+ });
750
+ }
751
+ function freezeDetails(details) {
752
+ details.definitionReferences.sort((left, right) => `${left.role}:${definitionReferenceKey(left.reference)}`.localeCompare(`${right.role}:${definitionReferenceKey(right.reference)}`));
753
+ details.valueContracts.sort((left, right) => left.role.localeCompare(right.role));
754
+ return deepFreeze({
755
+ definitionReferences: details.definitionReferences,
756
+ source: details.source,
757
+ sourceMap: details.sourceMap,
758
+ sourcePath: details.sourcePath,
759
+ valueContracts: details.valueContracts,
760
+ });
761
+ }
762
+ function readDefinitionReference(raw) {
763
+ if (!isRecord(raw)) {
764
+ return null;
765
+ }
766
+ const definitionId = readString(raw.definitionId);
767
+ const revisionId = readString(raw.revisionId);
768
+ const fingerprint = readFingerprint(raw.fingerprint);
769
+ return definitionId && revisionId && fingerprint
770
+ ? { definitionId, fingerprint, revisionId }
771
+ : null;
772
+ }
773
+ function readFingerprint(raw) {
774
+ if (!isRecord(raw)) {
775
+ return null;
776
+ }
777
+ const algorithm = readString(raw.algorithm);
778
+ const canonicalization = readString(raw.canonicalization);
779
+ const value = readString(raw.value);
780
+ return algorithm && canonicalization && value
781
+ ? { algorithm, canonicalization, value }
782
+ : null;
783
+ }
784
+ function missingDefinitionReference(metadata) {
785
+ return deepFreeze({
786
+ definitionId: readString(metadata.definitionId) ?? '<missing>',
787
+ fingerprint: {
788
+ algorithm: '<missing>',
789
+ canonicalization: '<missing>',
790
+ value: '<missing>',
791
+ },
792
+ revisionId: readString(metadata.revisionId) ?? '<missing>',
793
+ });
794
+ }
795
+ function definitionReferenceKey(reference) {
796
+ return [
797
+ reference.definitionId,
798
+ reference.revisionId,
799
+ reference.fingerprint.algorithm,
800
+ reference.fingerprint.canonicalization,
801
+ reference.fingerprint.value,
802
+ ].join('|');
803
+ }
804
+ function addDiagnostic(diagnostics, code, path, message, subject = null, severity = 'error') {
805
+ diagnostics.push(deepFreeze({
806
+ code,
807
+ id: `diagnostic/${encodeURIComponent(code)}/${encodeURIComponent(formatPath(path))}/${encodeURIComponent(subject ?? '')}`,
808
+ message,
809
+ path: [...path],
810
+ severity,
811
+ subject,
812
+ }));
813
+ }
814
+ function freezeProjectionResult(graph, diagnostics) {
815
+ diagnostics.sort((left, right) => left.id.localeCompare(right.id));
816
+ return deepFreeze({ diagnostics: freezeDiagnostics(diagnostics), graph });
817
+ }
818
+ function freezeDiagnostics(diagnostics) {
819
+ return deepFreeze([...diagnostics]);
820
+ }
821
+ function derivedEdgeId(kind, source, target, role) {
822
+ return `derived/${encodeURIComponent(kind)}/${encodeURIComponent(source)}/${encodeURIComponent(role)}/${encodeURIComponent(target)}`;
823
+ }
824
+ function isCanonicalProcessNodeKind(value) {
825
+ return canonicalProcessNodeKinds.includes(value);
826
+ }
827
+ function isCanonicalProcessAwaitClauseKind(value) {
828
+ return canonicalProcessAwaitClauseKinds.includes(value);
829
+ }
830
+ function readString(value) {
831
+ return typeof value === 'string' && value.trim().length > 0 ? value : null;
832
+ }
833
+ function isStringArray(value) {
834
+ return value.every((item) => typeof item === 'string');
835
+ }
836
+ function isPrefix(prefix, value) {
837
+ return prefix.length <= value.length && prefix.every((segment, index) => segment === value[index]);
838
+ }
839
+ function sourceMapKey(source) {
840
+ return `${source.semanticPath?.segments.join('/') ?? ''}|${source.reference}|${source.description ?? ''}`;
841
+ }
842
+ function formatPath(path) {
843
+ return path.length === 0
844
+ ? '/'
845
+ : `/${path.map((segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1')).join('/')}`;
846
+ }
847
+ //# sourceMappingURL=process-presentation-graph.js.map