@cohesivesystems/processes-presentation 0.1.0-alpha.63 → 0.1.0-alpha.65
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/README.md +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/process-presentation-graph.d.ts +2 -3
- package/dist/process-presentation-graph.d.ts.map +1 -1
- package/dist/process-presentation-graph.js +1 -38
- package/dist/process-presentation-graph.js.map +1 -1
- package/dist/process-presentation-values.d.ts +8 -0
- package/dist/process-presentation-values.d.ts.map +1 -0
- package/dist/process-presentation-values.js +42 -0
- package/dist/process-presentation-values.js.map +1 -0
- package/dist/process-runtime-overlay.d.ts +78 -0
- package/dist/process-runtime-overlay.d.ts.map +1 -0
- package/dist/process-runtime-overlay.js +381 -0
- package/dist/process-runtime-overlay.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/process-presentation-graph.ts +8 -52
- package/src/process-presentation-values.ts +55 -0
- package/src/process-runtime-overlay.ts +896 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExecutionDefinitionReference,
|
|
3
|
+
type ExecutionStatus,
|
|
4
|
+
type ExecutionStatusDisclosure,
|
|
5
|
+
type ExecutionTokenStatus,
|
|
6
|
+
type ExecutionWaitStatus,
|
|
7
|
+
type NormalizedExecutionTrace,
|
|
8
|
+
type NormalizedExecutionTraceEvent,
|
|
9
|
+
type ProcessExecutionTraceArtifact,
|
|
10
|
+
} from '@cohesivesystems/processes'
|
|
11
|
+
import {
|
|
12
|
+
type ProcessPresentationGraph,
|
|
13
|
+
type ProcessPresentationNode,
|
|
14
|
+
} from './process-presentation-graph'
|
|
15
|
+
import {
|
|
16
|
+
cloneWireValue,
|
|
17
|
+
compareOrdinal,
|
|
18
|
+
deepFreeze,
|
|
19
|
+
isRecord,
|
|
20
|
+
type DeepReadonly,
|
|
21
|
+
} from './process-presentation-values'
|
|
22
|
+
|
|
23
|
+
export const canonicalProcessRuntimePresentationCompatibility = Object.freeze({
|
|
24
|
+
processPresentationProjectionVersions: Object.freeze([
|
|
25
|
+
'cohesive-process-presentation/v1',
|
|
26
|
+
] as const),
|
|
27
|
+
statusSchemaVersions: Object.freeze([
|
|
28
|
+
'cohesive-execution-status/v1',
|
|
29
|
+
] as const),
|
|
30
|
+
traceArtifactSchemaVersions: Object.freeze([
|
|
31
|
+
'cohesive-process-execution-traces/v2',
|
|
32
|
+
] as const),
|
|
33
|
+
traceSchemaVersions: Object.freeze([
|
|
34
|
+
'cohesive-execution-trace/v2',
|
|
35
|
+
] as const),
|
|
36
|
+
projectionVersion: 'cohesive-process-runtime-presentation/v1',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export type ProcessRuntimePresentationDiagnosticSeverity = 'error' | 'warning' | 'info'
|
|
40
|
+
|
|
41
|
+
export type ProcessRuntimePresentationDiagnosticCode =
|
|
42
|
+
| 'PROCESS_RUNTIME_DEFINITION_MISMATCH'
|
|
43
|
+
| 'PROCESS_RUNTIME_GRAPH_PROJECTION_UNSUPPORTED'
|
|
44
|
+
| 'PROCESS_RUNTIME_HEALTH_UNKNOWN'
|
|
45
|
+
| 'PROCESS_RUNTIME_INSTANCE_MISMATCH'
|
|
46
|
+
| 'PROCESS_RUNTIME_OCCURRENCE_DISCLOSURE_GAP'
|
|
47
|
+
| 'PROCESS_RUNTIME_STATUS_FACET_REDACTED'
|
|
48
|
+
| 'PROCESS_RUNTIME_STATUS_FACET_UNKNOWN'
|
|
49
|
+
| 'PROCESS_RUNTIME_STATUS_SCHEMA_UNSUPPORTED'
|
|
50
|
+
| 'PROCESS_RUNTIME_TRACE_ARTIFACT_SCHEMA_UNSUPPORTED'
|
|
51
|
+
| 'PROCESS_RUNTIME_TRACE_ATTEMPT_UNMATCHED'
|
|
52
|
+
| 'PROCESS_RUNTIME_TRACE_BRANCH_UNMATCHED'
|
|
53
|
+
| 'PROCESS_RUNTIME_TRACE_DEFINITION_REFERENCE_UNMATCHED'
|
|
54
|
+
| 'PROCESS_RUNTIME_TRACE_KIND_UNSUPPORTED'
|
|
55
|
+
| 'PROCESS_RUNTIME_TRACE_NODE_UNMATCHED'
|
|
56
|
+
| 'PROCESS_RUNTIME_TRACE_OUTCOME_UNMATCHED'
|
|
57
|
+
| 'PROCESS_RUNTIME_TRACE_PREFIX_MISSING'
|
|
58
|
+
| 'PROCESS_RUNTIME_TRACE_SCHEMA_UNSUPPORTED'
|
|
59
|
+
| 'PROCESS_RUNTIME_TRACE_UNAVAILABLE'
|
|
60
|
+
| 'PROCESS_RUNTIME_WAIT_NODE_UNMATCHED'
|
|
61
|
+
| 'PROCESS_RUNTIME_TOKEN_NODE_UNMATCHED'
|
|
62
|
+
|
|
63
|
+
export interface ProcessRuntimePresentationDiagnostic {
|
|
64
|
+
readonly code: ProcessRuntimePresentationDiagnosticCode
|
|
65
|
+
readonly evidenceId: string | null
|
|
66
|
+
readonly id: string
|
|
67
|
+
readonly message: string
|
|
68
|
+
readonly path: readonly string[]
|
|
69
|
+
readonly severity: ProcessRuntimePresentationDiagnosticSeverity
|
|
70
|
+
readonly subject: string | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ProcessRuntimeTokenOverlay {
|
|
74
|
+
readonly id: string
|
|
75
|
+
readonly token: DeepReadonly<ExecutionTokenStatus>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ProcessRuntimeWaitOverlay {
|
|
79
|
+
readonly id: string
|
|
80
|
+
readonly wait: DeepReadonly<ExecutionWaitStatus>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ProcessRuntimeTraceEventOverlay {
|
|
84
|
+
readonly activationId: string
|
|
85
|
+
readonly attemptId: string
|
|
86
|
+
readonly branchOrClauseElementId: string | null
|
|
87
|
+
readonly elementIds: readonly string[]
|
|
88
|
+
readonly event: DeepReadonly<NormalizedExecutionTraceEvent>
|
|
89
|
+
readonly id: string
|
|
90
|
+
readonly occurrenceDefinitionElementId: string | null
|
|
91
|
+
readonly primaryElementId: string | null
|
|
92
|
+
readonly relatedDefinitionElementId: string | null
|
|
93
|
+
readonly relatedNodeElementId: string | null
|
|
94
|
+
readonly requestOutcomeElementId: string | null
|
|
95
|
+
readonly traceDisposition: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ProcessRuntimeElementOverlay {
|
|
99
|
+
readonly elementId: string
|
|
100
|
+
readonly id: string
|
|
101
|
+
readonly traceEvents: readonly ProcessRuntimeTraceEventOverlay[]
|
|
102
|
+
readonly tokens: readonly ProcessRuntimeTokenOverlay[]
|
|
103
|
+
readonly waits: readonly ProcessRuntimeWaitOverlay[]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ProcessRuntimeUnmatchedEvidenceKind =
|
|
107
|
+
| 'definition-reference'
|
|
108
|
+
| 'status-token-node'
|
|
109
|
+
| 'status-wait-node'
|
|
110
|
+
| 'trace-branch-or-clause'
|
|
111
|
+
| 'trace-node'
|
|
112
|
+
| 'trace-related-node'
|
|
113
|
+
| 'trace-request-outcome'
|
|
114
|
+
|
|
115
|
+
export type ProcessRuntimeUnmatchedEvidenceSource =
|
|
116
|
+
| ExecutionTokenStatus
|
|
117
|
+
| ExecutionWaitStatus
|
|
118
|
+
| NormalizedExecutionTraceEvent
|
|
119
|
+
|
|
120
|
+
export interface ProcessRuntimeUnmatchedEvidence {
|
|
121
|
+
readonly evidenceId: string
|
|
122
|
+
readonly id: string
|
|
123
|
+
readonly kind: ProcessRuntimeUnmatchedEvidenceKind
|
|
124
|
+
readonly reference: string
|
|
125
|
+
readonly source: DeepReadonly<ProcessRuntimeUnmatchedEvidenceSource>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ProcessRuntimePresentationOverlay {
|
|
129
|
+
readonly definition: DeepReadonly<ExecutionDefinitionReference>
|
|
130
|
+
readonly diagnostics: readonly ProcessRuntimePresentationDiagnostic[]
|
|
131
|
+
readonly elementOverlays: readonly ProcessRuntimeElementOverlay[]
|
|
132
|
+
readonly id: string
|
|
133
|
+
readonly missingTracePrefixCount: number
|
|
134
|
+
readonly processInstanceId: string
|
|
135
|
+
readonly projectionVersion: string
|
|
136
|
+
readonly status: DeepReadonly<ExecutionStatus>
|
|
137
|
+
readonly traceArtifact: DeepReadonly<ProcessExecutionTraceArtifact> | null
|
|
138
|
+
readonly unmatchedEvidence: readonly ProcessRuntimeUnmatchedEvidence[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface ProcessRuntimePresentationProjectionResult {
|
|
142
|
+
readonly diagnostics: readonly ProcessRuntimePresentationDiagnostic[]
|
|
143
|
+
readonly overlay: ProcessRuntimePresentationOverlay | null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface MutableProcessRuntimeElementOverlay {
|
|
147
|
+
readonly elementId: string
|
|
148
|
+
readonly traceEvents: ProcessRuntimeTraceEventOverlay[]
|
|
149
|
+
readonly tokens: ProcessRuntimeTokenOverlay[]
|
|
150
|
+
readonly waits: ProcessRuntimeWaitOverlay[]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface GraphIndex {
|
|
154
|
+
readonly definitionReferences: readonly ProcessPresentationNode[]
|
|
155
|
+
readonly elements: Map<string, MutableProcessRuntimeElementOverlay>
|
|
156
|
+
readonly nodes: readonly ProcessPresentationNode[]
|
|
157
|
+
readonly processNodesByCanonicalId: Map<string, readonly ProcessPresentationNode[]>
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface RuntimeProjectionContext {
|
|
161
|
+
readonly diagnostics: Map<string, ProcessRuntimePresentationDiagnostic>
|
|
162
|
+
readonly graph: ProcessPresentationGraph
|
|
163
|
+
readonly index: GraphIndex
|
|
164
|
+
readonly processInstanceId: string
|
|
165
|
+
readonly unmatchedEvidence: Map<string, ProcessRuntimeUnmatchedEvidence>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function processRuntimePresentationOverlayId(
|
|
169
|
+
processInstanceId: string,
|
|
170
|
+
definition: ExecutionDefinitionReference,
|
|
171
|
+
): string {
|
|
172
|
+
return runtimeIdentity(
|
|
173
|
+
'overlay',
|
|
174
|
+
processInstanceId,
|
|
175
|
+
definition.definitionId,
|
|
176
|
+
definition.revisionId,
|
|
177
|
+
definition.fingerprint.algorithm,
|
|
178
|
+
definition.fingerprint.canonicalization,
|
|
179
|
+
definition.fingerprint.value,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function projectCanonicalProcessRuntime(
|
|
184
|
+
graph: ProcessPresentationGraph,
|
|
185
|
+
status: ExecutionStatus,
|
|
186
|
+
traceArtifact: ProcessExecutionTraceArtifact | null = null,
|
|
187
|
+
): ProcessRuntimePresentationProjectionResult {
|
|
188
|
+
const diagnostics = new Map<string, ProcessRuntimePresentationDiagnostic>()
|
|
189
|
+
validateCompatibility(graph, status, traceArtifact, diagnostics)
|
|
190
|
+
if ([...diagnostics.values()].some((diagnostic) => diagnostic.severity === 'error')) {
|
|
191
|
+
return freezeProjectionResult(null, diagnostics)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const index = createGraphIndex(graph)
|
|
195
|
+
const context: RuntimeProjectionContext = {
|
|
196
|
+
diagnostics,
|
|
197
|
+
graph,
|
|
198
|
+
index,
|
|
199
|
+
processInstanceId: status.processInstanceId,
|
|
200
|
+
unmatchedEvidence: new Map(),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
projectStatus(status, context)
|
|
204
|
+
if (traceArtifact) {
|
|
205
|
+
projectTraceArtifact(traceArtifact, context)
|
|
206
|
+
} else {
|
|
207
|
+
addDiagnostic(
|
|
208
|
+
context.diagnostics,
|
|
209
|
+
'PROCESS_RUNTIME_TRACE_UNAVAILABLE',
|
|
210
|
+
'warning',
|
|
211
|
+
['traceArtifact'],
|
|
212
|
+
'No retained trace artifact was supplied; event absence cannot imply execution or element completion.',
|
|
213
|
+
null,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const frozenDiagnostics = freezeDiagnostics(diagnostics)
|
|
218
|
+
const overlay: ProcessRuntimePresentationOverlay = deepFreeze({
|
|
219
|
+
definition: cloneWireValue(status.definition),
|
|
220
|
+
diagnostics: frozenDiagnostics,
|
|
221
|
+
elementOverlays: [...index.elements.values()]
|
|
222
|
+
.sort((left, right) => compareOrdinal(left.elementId, right.elementId))
|
|
223
|
+
.map((element) => ({
|
|
224
|
+
elementId: element.elementId,
|
|
225
|
+
id: runtimeIdentity('element', status.processInstanceId, element.elementId),
|
|
226
|
+
traceEvents: element.traceEvents.sort(compareEvidenceById),
|
|
227
|
+
tokens: element.tokens.sort(compareEvidenceById),
|
|
228
|
+
waits: element.waits.sort(compareEvidenceById),
|
|
229
|
+
})),
|
|
230
|
+
id: processRuntimePresentationOverlayId(status.processInstanceId, status.definition),
|
|
231
|
+
missingTracePrefixCount: traceArtifact?.missingTracePrefixCount ?? 0,
|
|
232
|
+
processInstanceId: status.processInstanceId,
|
|
233
|
+
projectionVersion: canonicalProcessRuntimePresentationCompatibility.projectionVersion,
|
|
234
|
+
status: cloneWireValue(status),
|
|
235
|
+
traceArtifact: traceArtifact ? cloneWireValue(traceArtifact) : null,
|
|
236
|
+
unmatchedEvidence: [...context.unmatchedEvidence.values()]
|
|
237
|
+
.sort(compareEvidenceById),
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return deepFreeze({ diagnostics: frozenDiagnostics, overlay })
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function validateCompatibility(
|
|
244
|
+
graph: ProcessPresentationGraph,
|
|
245
|
+
status: ExecutionStatus,
|
|
246
|
+
traceArtifact: ProcessExecutionTraceArtifact | null,
|
|
247
|
+
diagnostics: Map<string, ProcessRuntimePresentationDiagnostic>,
|
|
248
|
+
) {
|
|
249
|
+
if (!canonicalProcessRuntimePresentationCompatibility.processPresentationProjectionVersions.includes(
|
|
250
|
+
graph.projectionVersion as (typeof canonicalProcessRuntimePresentationCompatibility.processPresentationProjectionVersions)[number],
|
|
251
|
+
)) {
|
|
252
|
+
addDiagnostic(
|
|
253
|
+
diagnostics,
|
|
254
|
+
'PROCESS_RUNTIME_GRAPH_PROJECTION_UNSUPPORTED',
|
|
255
|
+
'error',
|
|
256
|
+
['graph', 'projectionVersion'],
|
|
257
|
+
`Runtime presentation does not support Process graph projection '${graph.projectionVersion}'.`,
|
|
258
|
+
graph.projectionVersion,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!canonicalProcessRuntimePresentationCompatibility.statusSchemaVersions.includes(
|
|
263
|
+
status.schemaVersion as (typeof canonicalProcessRuntimePresentationCompatibility.statusSchemaVersions)[number],
|
|
264
|
+
)) {
|
|
265
|
+
addDiagnostic(
|
|
266
|
+
diagnostics,
|
|
267
|
+
'PROCESS_RUNTIME_STATUS_SCHEMA_UNSUPPORTED',
|
|
268
|
+
'error',
|
|
269
|
+
['status', 'schemaVersion'],
|
|
270
|
+
`Runtime presentation does not support execution status schema '${status.schemaVersion}'.`,
|
|
271
|
+
status.schemaVersion,
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!sameDefinitionReference(graph.document.definition, status.definition)) {
|
|
276
|
+
addDiagnostic(
|
|
277
|
+
diagnostics,
|
|
278
|
+
'PROCESS_RUNTIME_DEFINITION_MISMATCH',
|
|
279
|
+
'error',
|
|
280
|
+
['status', 'definition'],
|
|
281
|
+
'Execution status does not identify the exact Process definition projected by the graph.',
|
|
282
|
+
definitionReferenceSubject(status.definition),
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!traceArtifact) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!canonicalProcessRuntimePresentationCompatibility.traceArtifactSchemaVersions.includes(
|
|
291
|
+
traceArtifact.schemaVersion as (typeof canonicalProcessRuntimePresentationCompatibility.traceArtifactSchemaVersions)[number],
|
|
292
|
+
)) {
|
|
293
|
+
addDiagnostic(
|
|
294
|
+
diagnostics,
|
|
295
|
+
'PROCESS_RUNTIME_TRACE_ARTIFACT_SCHEMA_UNSUPPORTED',
|
|
296
|
+
'error',
|
|
297
|
+
['traceArtifact', 'schemaVersion'],
|
|
298
|
+
`Runtime presentation does not support trace artifact schema '${traceArtifact.schemaVersion}'.`,
|
|
299
|
+
traceArtifact.schemaVersion,
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!sameDefinitionReference(status.definition, traceArtifact.definition)) {
|
|
304
|
+
addDiagnostic(
|
|
305
|
+
diagnostics,
|
|
306
|
+
'PROCESS_RUNTIME_DEFINITION_MISMATCH',
|
|
307
|
+
'error',
|
|
308
|
+
['traceArtifact', 'definition'],
|
|
309
|
+
'Trace artifact and execution status do not identify the same exact Process definition.',
|
|
310
|
+
definitionReferenceSubject(traceArtifact.definition),
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (status.processInstanceId !== traceArtifact.processInstanceId) {
|
|
315
|
+
addDiagnostic(
|
|
316
|
+
diagnostics,
|
|
317
|
+
'PROCESS_RUNTIME_INSTANCE_MISMATCH',
|
|
318
|
+
'error',
|
|
319
|
+
['traceArtifact', 'processInstanceId'],
|
|
320
|
+
'Trace artifact and execution status do not identify the same Process instance.',
|
|
321
|
+
traceArtifact.processInstanceId,
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const attempts = new Set(status.attempts.map((attempt) => attempt.attemptId))
|
|
326
|
+
traceArtifact.traces.forEach((trace, traceIndex) => {
|
|
327
|
+
if (!canonicalProcessRuntimePresentationCompatibility.traceSchemaVersions.includes(
|
|
328
|
+
trace.schemaVersion as (typeof canonicalProcessRuntimePresentationCompatibility.traceSchemaVersions)[number],
|
|
329
|
+
)) {
|
|
330
|
+
addDiagnostic(
|
|
331
|
+
diagnostics,
|
|
332
|
+
'PROCESS_RUNTIME_TRACE_SCHEMA_UNSUPPORTED',
|
|
333
|
+
'error',
|
|
334
|
+
['traceArtifact', 'traces', String(traceIndex), 'schemaVersion'],
|
|
335
|
+
`Runtime presentation does not support normalized trace schema '${trace.schemaVersion}'.`,
|
|
336
|
+
trace.schemaVersion,
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
if (trace.kind !== graph.document.kind) {
|
|
340
|
+
addDiagnostic(
|
|
341
|
+
diagnostics,
|
|
342
|
+
'PROCESS_RUNTIME_TRACE_KIND_UNSUPPORTED',
|
|
343
|
+
'error',
|
|
344
|
+
['traceArtifact', 'traces', String(traceIndex), 'kind'],
|
|
345
|
+
`Normalized trace kind '${trace.kind}' does not match graph kind '${graph.document.kind}'.`,
|
|
346
|
+
trace.kind,
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
if (!sameDefinitionReference(status.definition, trace.definition)) {
|
|
350
|
+
addDiagnostic(
|
|
351
|
+
diagnostics,
|
|
352
|
+
'PROCESS_RUNTIME_DEFINITION_MISMATCH',
|
|
353
|
+
'error',
|
|
354
|
+
['traceArtifact', 'traces', String(traceIndex), 'definition'],
|
|
355
|
+
'Normalized trace does not identify the same exact Process definition as execution status.',
|
|
356
|
+
definitionReferenceSubject(trace.definition),
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
if (!trace.continuation
|
|
360
|
+
|| trace.continuation.processInstanceId !== status.processInstanceId) {
|
|
361
|
+
addDiagnostic(
|
|
362
|
+
diagnostics,
|
|
363
|
+
'PROCESS_RUNTIME_INSTANCE_MISMATCH',
|
|
364
|
+
'error',
|
|
365
|
+
['traceArtifact', 'traces', String(traceIndex), 'continuation'],
|
|
366
|
+
'Normalized Process trace does not identify the status Process instance.',
|
|
367
|
+
trace.continuation?.processInstanceId ?? null,
|
|
368
|
+
)
|
|
369
|
+
} else if (!attempts.has(trace.continuation.processAttemptId)) {
|
|
370
|
+
addDiagnostic(
|
|
371
|
+
diagnostics,
|
|
372
|
+
'PROCESS_RUNTIME_TRACE_ATTEMPT_UNMATCHED',
|
|
373
|
+
'error',
|
|
374
|
+
['traceArtifact', 'traces', String(traceIndex), 'continuation', 'processAttemptId'],
|
|
375
|
+
'Normalized trace attempt is absent from the authoritative execution status lineage.',
|
|
376
|
+
trace.continuation.processAttemptId,
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function projectStatus(status: ExecutionStatus, context: RuntimeProjectionContext) {
|
|
383
|
+
discloseStatusFacet('tokens', status.runtime.tokensDisclosure, context)
|
|
384
|
+
discloseStatusFacet('waits', status.runtime.waitsDisclosure, context)
|
|
385
|
+
discloseStatusFacet('progress', status.runtime.progressDisclosure, context)
|
|
386
|
+
discloseStatusFacet('demand', status.runtime.demandDisclosure, context)
|
|
387
|
+
discloseStatusFacet('capacity', status.runtime.capacityDisclosure, context)
|
|
388
|
+
if (status.terminalOutcome.detail) {
|
|
389
|
+
discloseStatusValue(
|
|
390
|
+
'terminalOutcome.detail',
|
|
391
|
+
status.terminalOutcome.detail.disclosure,
|
|
392
|
+
['status', 'terminalOutcome', 'detail', 'disclosure'],
|
|
393
|
+
context,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
status.runtime.extensions.forEach((extension, extensionIndex) => {
|
|
397
|
+
discloseStatusValue(
|
|
398
|
+
`extension:${extension.id}`,
|
|
399
|
+
extension.value.disclosure,
|
|
400
|
+
['status', 'runtime', 'extensions', String(extensionIndex), 'value', 'disclosure'],
|
|
401
|
+
context,
|
|
402
|
+
)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
if (status.runtime.health === 'Unknown') {
|
|
406
|
+
addDiagnostic(
|
|
407
|
+
context.diagnostics,
|
|
408
|
+
'PROCESS_RUNTIME_HEALTH_UNKNOWN',
|
|
409
|
+
'warning',
|
|
410
|
+
['status', 'runtime', 'health'],
|
|
411
|
+
'The runtime did not disclose an authoritative health observation.',
|
|
412
|
+
'health',
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
status.runtime.tokens.forEach((token, tokenIndex) => {
|
|
417
|
+
const id = runtimeIdentity('token', status.processInstanceId, token.tokenId)
|
|
418
|
+
const node = findSingleProcessNode(token.node, context.index)
|
|
419
|
+
if (!node) {
|
|
420
|
+
addUnmatchedEvidence(
|
|
421
|
+
context,
|
|
422
|
+
'status-token-node',
|
|
423
|
+
id,
|
|
424
|
+
token.node,
|
|
425
|
+
token,
|
|
426
|
+
'PROCESS_RUNTIME_TOKEN_NODE_UNMATCHED',
|
|
427
|
+
['status', 'runtime', 'tokens', String(tokenIndex), 'node'],
|
|
428
|
+
`Token '${token.tokenId}' references Process node '${token.node}', which is absent or ambiguous in the graph.`,
|
|
429
|
+
)
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
context.index.elements.get(node.id)!.tokens.push(deepFreeze({
|
|
434
|
+
id,
|
|
435
|
+
token: cloneWireValue(token),
|
|
436
|
+
}))
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
status.runtime.waits.forEach((wait, waitIndex) => {
|
|
440
|
+
const id = runtimeIdentity('wait', status.processInstanceId, wait.tokenId, wait.node)
|
|
441
|
+
const node = findSingleProcessNode(wait.node, context.index)
|
|
442
|
+
if (!node) {
|
|
443
|
+
addUnmatchedEvidence(
|
|
444
|
+
context,
|
|
445
|
+
'status-wait-node',
|
|
446
|
+
id,
|
|
447
|
+
wait.node,
|
|
448
|
+
wait,
|
|
449
|
+
'PROCESS_RUNTIME_WAIT_NODE_UNMATCHED',
|
|
450
|
+
['status', 'runtime', 'waits', String(waitIndex), 'node'],
|
|
451
|
+
`Wait for token '${wait.tokenId}' references Process node '${wait.node}', which is absent or ambiguous in the graph.`,
|
|
452
|
+
)
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
context.index.elements.get(node.id)!.waits.push(deepFreeze({
|
|
457
|
+
id,
|
|
458
|
+
wait: cloneWireValue(wait),
|
|
459
|
+
}))
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function discloseStatusFacet(
|
|
464
|
+
facet: string,
|
|
465
|
+
disclosure: ExecutionStatusDisclosure,
|
|
466
|
+
context: RuntimeProjectionContext,
|
|
467
|
+
) {
|
|
468
|
+
if (disclosure === 'Disclosed') {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const redacted = disclosure === 'Redacted'
|
|
473
|
+
addDiagnostic(
|
|
474
|
+
context.diagnostics,
|
|
475
|
+
redacted
|
|
476
|
+
? 'PROCESS_RUNTIME_STATUS_FACET_REDACTED'
|
|
477
|
+
: 'PROCESS_RUNTIME_STATUS_FACET_UNKNOWN',
|
|
478
|
+
redacted ? 'info' : 'warning',
|
|
479
|
+
['status', 'runtime', `${facet}Disclosure`],
|
|
480
|
+
redacted
|
|
481
|
+
? `Runtime ${facet} evidence exists but was redacted.`
|
|
482
|
+
: `The runtime has no authoritative ${facet} observation.`,
|
|
483
|
+
facet,
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function discloseStatusValue(
|
|
488
|
+
subject: string,
|
|
489
|
+
disclosure: ExecutionStatusDisclosure,
|
|
490
|
+
path: readonly string[],
|
|
491
|
+
context: RuntimeProjectionContext,
|
|
492
|
+
) {
|
|
493
|
+
if (disclosure === 'Disclosed') {
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const redacted = disclosure === 'Redacted'
|
|
498
|
+
addDiagnostic(
|
|
499
|
+
context.diagnostics,
|
|
500
|
+
redacted
|
|
501
|
+
? 'PROCESS_RUNTIME_STATUS_FACET_REDACTED'
|
|
502
|
+
: 'PROCESS_RUNTIME_STATUS_FACET_UNKNOWN',
|
|
503
|
+
redacted ? 'info' : 'warning',
|
|
504
|
+
path,
|
|
505
|
+
redacted
|
|
506
|
+
? `Runtime status value '${subject}' exists but was redacted.`
|
|
507
|
+
: `The runtime status value '${subject}' is explicitly unknown.`,
|
|
508
|
+
subject,
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function projectTraceArtifact(
|
|
513
|
+
artifact: ProcessExecutionTraceArtifact,
|
|
514
|
+
context: RuntimeProjectionContext,
|
|
515
|
+
) {
|
|
516
|
+
if (artifact.missingTracePrefixCount > 0) {
|
|
517
|
+
addDiagnostic(
|
|
518
|
+
context.diagnostics,
|
|
519
|
+
'PROCESS_RUNTIME_TRACE_PREFIX_MISSING',
|
|
520
|
+
'warning',
|
|
521
|
+
['traceArtifact', 'missingTracePrefixCount'],
|
|
522
|
+
`The retained trace omits ${artifact.missingTracePrefixCount} earlier activation(s); absence cannot imply completion.`,
|
|
523
|
+
String(artifact.missingTracePrefixCount),
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
artifact.traces.forEach((trace, traceIndex) => projectTrace(trace, traceIndex, context))
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function projectTrace(
|
|
531
|
+
trace: NormalizedExecutionTrace,
|
|
532
|
+
traceIndex: number,
|
|
533
|
+
context: RuntimeProjectionContext,
|
|
534
|
+
) {
|
|
535
|
+
const attemptId = trace.continuation!.processAttemptId
|
|
536
|
+
trace.events.forEach((event, eventIndex) => {
|
|
537
|
+
const id = runtimeIdentity(
|
|
538
|
+
'trace-event',
|
|
539
|
+
context.processInstanceId,
|
|
540
|
+
attemptId,
|
|
541
|
+
trace.activation,
|
|
542
|
+
String(event.sequence),
|
|
543
|
+
)
|
|
544
|
+
const elementIds = new Set<string>()
|
|
545
|
+
const primary = findSingleProcessNode(event.node, context.index)
|
|
546
|
+
if (primary) {
|
|
547
|
+
elementIds.add(primary.id)
|
|
548
|
+
} else {
|
|
549
|
+
addUnmatchedEvidence(
|
|
550
|
+
context,
|
|
551
|
+
'trace-node',
|
|
552
|
+
id,
|
|
553
|
+
event.node,
|
|
554
|
+
event,
|
|
555
|
+
'PROCESS_RUNTIME_TRACE_NODE_UNMATCHED',
|
|
556
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'node'],
|
|
557
|
+
`Trace event references Process node '${event.node}', which is absent or ambiguous in the graph.`,
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const branchOrClause = event.branchOrClause
|
|
562
|
+
? findOwnedElement(event.node, event.branchOrClause, context.index)
|
|
563
|
+
: null
|
|
564
|
+
if (event.branchOrClause) {
|
|
565
|
+
if (branchOrClause) {
|
|
566
|
+
elementIds.add(branchOrClause.id)
|
|
567
|
+
} else {
|
|
568
|
+
addUnmatchedEvidence(
|
|
569
|
+
context,
|
|
570
|
+
'trace-branch-or-clause',
|
|
571
|
+
id,
|
|
572
|
+
event.branchOrClause,
|
|
573
|
+
event,
|
|
574
|
+
'PROCESS_RUNTIME_TRACE_BRANCH_UNMATCHED',
|
|
575
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'branchOrClause'],
|
|
576
|
+
`Trace event branch or clause '${event.branchOrClause}' is absent or ambiguous under Process node '${event.node}'.`,
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const requestOutcome = event.requestOutcome
|
|
582
|
+
? findRequestOutcome(event.node, event.requestOutcome, context.index)
|
|
583
|
+
: null
|
|
584
|
+
if (event.requestOutcome) {
|
|
585
|
+
if (requestOutcome) {
|
|
586
|
+
elementIds.add(requestOutcome.id)
|
|
587
|
+
} else if (!primary || hasOwnedRequestOutcomes(event.node, context.index)) {
|
|
588
|
+
addUnmatchedEvidence(
|
|
589
|
+
context,
|
|
590
|
+
'trace-request-outcome',
|
|
591
|
+
id,
|
|
592
|
+
event.requestOutcome,
|
|
593
|
+
event,
|
|
594
|
+
'PROCESS_RUNTIME_TRACE_OUTCOME_UNMATCHED',
|
|
595
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'requestOutcome'],
|
|
596
|
+
`Trace Request outcome '${event.requestOutcome}' is absent or ambiguous under Process node '${event.node}'.`,
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const relatedDefinition = matchDefinitionReference(
|
|
602
|
+
event.relatedDefinition ?? null,
|
|
603
|
+
id,
|
|
604
|
+
event,
|
|
605
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'relatedDefinition'],
|
|
606
|
+
context,
|
|
607
|
+
)
|
|
608
|
+
if (relatedDefinition) {
|
|
609
|
+
elementIds.add(relatedDefinition.id)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
let relatedNode: ProcessPresentationNode | null = null
|
|
613
|
+
if (event.relatedNode) {
|
|
614
|
+
if (event.relatedDefinition
|
|
615
|
+
&& sameDefinitionReference(event.relatedDefinition, context.graph.document.definition)) {
|
|
616
|
+
relatedNode = findSingleProcessNode(event.relatedNode, context.index)
|
|
617
|
+
if (relatedNode) {
|
|
618
|
+
elementIds.add(relatedNode.id)
|
|
619
|
+
} else {
|
|
620
|
+
addUnmatchedEvidence(
|
|
621
|
+
context,
|
|
622
|
+
'trace-related-node',
|
|
623
|
+
id,
|
|
624
|
+
event.relatedNode,
|
|
625
|
+
event,
|
|
626
|
+
'PROCESS_RUNTIME_TRACE_NODE_UNMATCHED',
|
|
627
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'relatedNode'],
|
|
628
|
+
`Trace event related node '${event.relatedNode}' claims the current Process definition but is absent or ambiguous.`,
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
} else if (!event.relatedDefinition) {
|
|
632
|
+
addUnmatchedEvidence(
|
|
633
|
+
context,
|
|
634
|
+
'trace-related-node',
|
|
635
|
+
id,
|
|
636
|
+
event.relatedNode,
|
|
637
|
+
event,
|
|
638
|
+
'PROCESS_RUNTIME_TRACE_NODE_UNMATCHED',
|
|
639
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'relatedNode'],
|
|
640
|
+
`Trace event related node '${event.relatedNode}' has no exact related definition for a safe graph join.`,
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const occurrenceDefinition = matchDefinitionReference(
|
|
646
|
+
event.processOccurrence?.definition ?? null,
|
|
647
|
+
id,
|
|
648
|
+
event,
|
|
649
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'processOccurrence', 'definition'],
|
|
650
|
+
context,
|
|
651
|
+
)
|
|
652
|
+
if (occurrenceDefinition) {
|
|
653
|
+
elementIds.add(occurrenceDefinition.id)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (event.processOccurrence && event.processOccurrence.disclosure !== 'Disclosed') {
|
|
657
|
+
const disclosure = event.processOccurrence.disclosure
|
|
658
|
+
addDiagnostic(
|
|
659
|
+
context.diagnostics,
|
|
660
|
+
'PROCESS_RUNTIME_OCCURRENCE_DISCLOSURE_GAP',
|
|
661
|
+
disclosure === 'Redacted' ? 'info' : 'warning',
|
|
662
|
+
['traceArtifact', 'traces', String(traceIndex), 'events', String(eventIndex), 'processOccurrence', 'disclosure'],
|
|
663
|
+
`Process occurrence evidence is explicitly ${disclosure.toLowerCase()}; no occurrence completion or lineage may be inferred.`,
|
|
664
|
+
event.processOccurrence.kind,
|
|
665
|
+
id,
|
|
666
|
+
)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const traceEvent: ProcessRuntimeTraceEventOverlay = deepFreeze({
|
|
670
|
+
activationId: trace.activation,
|
|
671
|
+
attemptId,
|
|
672
|
+
branchOrClauseElementId: branchOrClause?.id ?? null,
|
|
673
|
+
elementIds: [...elementIds].sort(),
|
|
674
|
+
event: cloneWireValue(event),
|
|
675
|
+
id,
|
|
676
|
+
occurrenceDefinitionElementId: occurrenceDefinition?.id ?? null,
|
|
677
|
+
primaryElementId: primary?.id ?? null,
|
|
678
|
+
relatedDefinitionElementId: relatedDefinition?.id ?? null,
|
|
679
|
+
relatedNodeElementId: relatedNode?.id ?? null,
|
|
680
|
+
requestOutcomeElementId: requestOutcome?.id ?? null,
|
|
681
|
+
traceDisposition: trace.disposition,
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
for (const elementId of traceEvent.elementIds) {
|
|
685
|
+
context.index.elements.get(elementId)!.traceEvents.push(traceEvent)
|
|
686
|
+
}
|
|
687
|
+
})
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function matchDefinitionReference(
|
|
691
|
+
reference: ExecutionDefinitionReference | null,
|
|
692
|
+
evidenceId: string,
|
|
693
|
+
source: NormalizedExecutionTraceEvent,
|
|
694
|
+
path: readonly string[],
|
|
695
|
+
context: RuntimeProjectionContext,
|
|
696
|
+
): ProcessPresentationNode | null {
|
|
697
|
+
if (!reference || sameDefinitionReference(reference, context.graph.document.definition)) {
|
|
698
|
+
return null
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const matches = context.index.definitionReferences.filter((node) =>
|
|
702
|
+
isDefinitionReference(node.details.source)
|
|
703
|
+
&& sameDefinitionReference(node.details.source, reference))
|
|
704
|
+
if (matches.length === 1) {
|
|
705
|
+
return matches[0]!
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const subject = definitionReferenceSubject(reference)
|
|
709
|
+
addUnmatchedEvidence(
|
|
710
|
+
context,
|
|
711
|
+
'definition-reference',
|
|
712
|
+
evidenceId,
|
|
713
|
+
subject,
|
|
714
|
+
source,
|
|
715
|
+
'PROCESS_RUNTIME_TRACE_DEFINITION_REFERENCE_UNMATCHED',
|
|
716
|
+
path,
|
|
717
|
+
`Trace lineage definition '${subject}' is absent or ambiguous in the Process presentation graph.`,
|
|
718
|
+
)
|
|
719
|
+
return null
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function createGraphIndex(graph: ProcessPresentationGraph): GraphIndex {
|
|
723
|
+
const elements = new Map<string, MutableProcessRuntimeElementOverlay>()
|
|
724
|
+
const processNodesByCanonicalId = new Map<string, ProcessPresentationNode[]>()
|
|
725
|
+
const definitionReferences: ProcessPresentationNode[] = []
|
|
726
|
+
|
|
727
|
+
for (const node of graph.nodes) {
|
|
728
|
+
elements.set(node.id, {
|
|
729
|
+
elementId: node.id,
|
|
730
|
+
traceEvents: [],
|
|
731
|
+
tokens: [],
|
|
732
|
+
waits: [],
|
|
733
|
+
})
|
|
734
|
+
if (node.category === 'process-node') {
|
|
735
|
+
const matches = processNodesByCanonicalId.get(node.canonicalId) ?? []
|
|
736
|
+
matches.push(node)
|
|
737
|
+
processNodesByCanonicalId.set(node.canonicalId, matches)
|
|
738
|
+
}
|
|
739
|
+
if (node.category === 'definition-reference') {
|
|
740
|
+
definitionReferences.push(node)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
definitionReferences,
|
|
746
|
+
elements,
|
|
747
|
+
nodes: graph.nodes,
|
|
748
|
+
processNodesByCanonicalId,
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function findSingleProcessNode(
|
|
753
|
+
canonicalId: string,
|
|
754
|
+
index: GraphIndex,
|
|
755
|
+
): ProcessPresentationNode | null {
|
|
756
|
+
const matches = index.processNodesByCanonicalId.get(canonicalId) ?? []
|
|
757
|
+
return matches.length === 1 ? matches[0]! : null
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function findOwnedElement(
|
|
761
|
+
ownerCanonicalId: string,
|
|
762
|
+
canonicalId: string,
|
|
763
|
+
index: GraphIndex,
|
|
764
|
+
): ProcessPresentationNode | null {
|
|
765
|
+
const matches = index.nodes.filter((node) =>
|
|
766
|
+
node.ownerId === ownerCanonicalId
|
|
767
|
+
&& node.canonicalId === canonicalId)
|
|
768
|
+
return matches.length === 1 ? matches[0]! : null
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function findRequestOutcome(
|
|
772
|
+
ownerCanonicalId: string,
|
|
773
|
+
outcome: string,
|
|
774
|
+
index: GraphIndex,
|
|
775
|
+
): ProcessPresentationNode | null {
|
|
776
|
+
const matches = index.nodes.filter((node) =>
|
|
777
|
+
node.category === 'request-outcome'
|
|
778
|
+
&& node.ownerId === ownerCanonicalId
|
|
779
|
+
&& node.role === outcome)
|
|
780
|
+
return matches.length === 1 ? matches[0]! : null
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function hasOwnedRequestOutcomes(
|
|
784
|
+
ownerCanonicalId: string,
|
|
785
|
+
index: GraphIndex,
|
|
786
|
+
): boolean {
|
|
787
|
+
return index.nodes.some((node) =>
|
|
788
|
+
node.category === 'request-outcome'
|
|
789
|
+
&& node.ownerId === ownerCanonicalId)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function addUnmatchedEvidence(
|
|
793
|
+
context: RuntimeProjectionContext,
|
|
794
|
+
kind: ProcessRuntimeUnmatchedEvidenceKind,
|
|
795
|
+
evidenceId: string,
|
|
796
|
+
reference: string,
|
|
797
|
+
source: ProcessRuntimeUnmatchedEvidenceSource,
|
|
798
|
+
diagnosticCode: ProcessRuntimePresentationDiagnosticCode,
|
|
799
|
+
path: readonly string[],
|
|
800
|
+
message: string,
|
|
801
|
+
) {
|
|
802
|
+
const id = runtimeIdentity('unmatched', evidenceId, kind, reference)
|
|
803
|
+
if (!context.unmatchedEvidence.has(id)) {
|
|
804
|
+
context.unmatchedEvidence.set(id, deepFreeze({
|
|
805
|
+
evidenceId,
|
|
806
|
+
id,
|
|
807
|
+
kind,
|
|
808
|
+
reference,
|
|
809
|
+
source: cloneWireValue(source),
|
|
810
|
+
}))
|
|
811
|
+
}
|
|
812
|
+
addDiagnostic(
|
|
813
|
+
context.diagnostics,
|
|
814
|
+
diagnosticCode,
|
|
815
|
+
'warning',
|
|
816
|
+
path,
|
|
817
|
+
message,
|
|
818
|
+
reference,
|
|
819
|
+
evidenceId,
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function addDiagnostic(
|
|
824
|
+
diagnostics: Map<string, ProcessRuntimePresentationDiagnostic>,
|
|
825
|
+
code: ProcessRuntimePresentationDiagnosticCode,
|
|
826
|
+
severity: ProcessRuntimePresentationDiagnosticSeverity,
|
|
827
|
+
path: readonly string[],
|
|
828
|
+
message: string,
|
|
829
|
+
subject: string | null,
|
|
830
|
+
evidenceId: string | null = null,
|
|
831
|
+
) {
|
|
832
|
+
const id = runtimeIdentity('diagnostic', code, evidenceId ?? '', subject ?? '', ...path)
|
|
833
|
+
if (diagnostics.has(id)) {
|
|
834
|
+
return
|
|
835
|
+
}
|
|
836
|
+
diagnostics.set(id, deepFreeze({
|
|
837
|
+
code,
|
|
838
|
+
evidenceId,
|
|
839
|
+
id,
|
|
840
|
+
message,
|
|
841
|
+
path: [...path],
|
|
842
|
+
severity,
|
|
843
|
+
subject,
|
|
844
|
+
}))
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function freezeProjectionResult(
|
|
848
|
+
overlay: ProcessRuntimePresentationOverlay | null,
|
|
849
|
+
diagnostics: Map<string, ProcessRuntimePresentationDiagnostic>,
|
|
850
|
+
): ProcessRuntimePresentationProjectionResult {
|
|
851
|
+
return deepFreeze({
|
|
852
|
+
diagnostics: freezeDiagnostics(diagnostics),
|
|
853
|
+
overlay,
|
|
854
|
+
})
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function freezeDiagnostics(
|
|
858
|
+
diagnostics: Map<string, ProcessRuntimePresentationDiagnostic>,
|
|
859
|
+
): readonly ProcessRuntimePresentationDiagnostic[] {
|
|
860
|
+
return deepFreeze([...diagnostics.values()].sort(compareEvidenceById))
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function sameDefinitionReference(
|
|
864
|
+
left: DeepReadonly<ExecutionDefinitionReference>,
|
|
865
|
+
right: DeepReadonly<ExecutionDefinitionReference>,
|
|
866
|
+
): boolean {
|
|
867
|
+
return left.definitionId === right.definitionId
|
|
868
|
+
&& left.revisionId === right.revisionId
|
|
869
|
+
&& left.fingerprint.algorithm === right.fingerprint.algorithm
|
|
870
|
+
&& left.fingerprint.canonicalization === right.fingerprint.canonicalization
|
|
871
|
+
&& left.fingerprint.value === right.fingerprint.value
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function definitionReferenceSubject(reference: DeepReadonly<ExecutionDefinitionReference>): string {
|
|
875
|
+
return `${reference.definitionId}@${reference.revisionId}`
|
|
876
|
+
+ `#${reference.fingerprint.algorithm}:${reference.fingerprint.canonicalization}:${reference.fingerprint.value}`
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function isDefinitionReference(value: unknown): value is ExecutionDefinitionReference {
|
|
880
|
+
if (!isRecord(value) || !isRecord(value.fingerprint)) {
|
|
881
|
+
return false
|
|
882
|
+
}
|
|
883
|
+
return typeof value.definitionId === 'string'
|
|
884
|
+
&& typeof value.revisionId === 'string'
|
|
885
|
+
&& typeof value.fingerprint.algorithm === 'string'
|
|
886
|
+
&& typeof value.fingerprint.canonicalization === 'string'
|
|
887
|
+
&& typeof value.fingerprint.value === 'string'
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function runtimeIdentity(kind: string, ...parts: readonly string[]): string {
|
|
891
|
+
return `process-runtime/${kind}/${parts.map((part) => encodeURIComponent(part)).join('/')}`
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function compareEvidenceById<T extends { readonly id: string }>(left: T, right: T): number {
|
|
895
|
+
return compareOrdinal(left.id, right.id)
|
|
896
|
+
}
|