archgraph-argo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +115 -0
- package/argo/package.json +8 -0
- package/argo/rules/intent-architecture-global-rule.md +45 -0
- package/argo/schema/ImplementationToCodingHandoff.schema.json +252 -0
- package/argo/schema/ImplementationToIntentTraceProposal.schema.json +180 -0
- package/argo/schema/IntentToImplementationHandoff.schema.json +75 -0
- package/argo/schema/SystemArchitecture.schema.json +378 -0
- package/argo/schema/archimate3.2.pdf +0 -0
- package/argo/scripts/ARCHITECTURE.md +57 -0
- package/argo/scripts/archimate32-rules.js +12301 -0
- package/argo/scripts/argo-mcp-server.js +629 -0
- package/argo/scripts/argo-paths.js +77 -0
- package/argo/scripts/ensureArgoHarnessEnvironment.js +340 -0
- package/argo/scripts/generateArchitectureDiffPlantuml.js +466 -0
- package/argo/scripts/graph-rag/ARCHITECTURE.md +192 -0
- package/argo/scripts/graph-rag/canonicalProjectionAuthority.js +45 -0
- package/argo/scripts/graph-rag/defaultSemanticRetrieval.js +969 -0
- package/argo/scripts/graph-rag/embeddingQualificationGate.js +59 -0
- package/argo/scripts/graph-rag/externalProductionConfig.js +74 -0
- package/argo/scripts/graph-rag/liveEmbeddingIndexGate.js +129 -0
- package/argo/scripts/graph-rag/liveEmbeddingNeo4jBoundary.js +137 -0
- package/argo/scripts/graph-rag/liveEmbeddingProviderClient.js +49 -0
- package/argo/scripts/graph-rag/liveEmbeddingProviderConfig.js +481 -0
- package/argo/scripts/graph-rag/mutationEmbeddingVectorLifecycle.js +1261 -0
- package/argo/scripts/graph-rag/neo4jNativeRetrieval.js +37 -0
- package/argo/scripts/graph-rag/productionGraphRagRuntime.js +1624 -0
- package/argo/scripts/graph-rag/semantic-persistence/ARCHITECTURE.md +51 -0
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticBackfill.js +241 -0
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticCheckpointStore.js +99 -0
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticNeo4jAdapter.js +149 -0
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticProjectionStore.js +171 -0
- package/argo/scripts/graph-rag/semanticOperatorError.js +38 -0
- package/argo/scripts/graph-rag/semanticOperatorJourney.js +459 -0
- package/argo/scripts/graph-rag/semanticReadinessAttestationStore.js +398 -0
- package/argo/scripts/graph-rag/systemMetadataCommandAdapter.js +269 -0
- package/argo/scripts/graph-semantics.js +220 -0
- package/argo/scripts/neo4j-system-architecture-store.js +777 -0
- package/argo/scripts/repositoryArgoEnvironment.js +101 -0
- package/argo/scripts/runArchitectureTests.js +583 -0
- package/argo/scripts/semanticOperatorJourneyCli.js +91 -0
- package/argo/scripts/syncSystemArchitectureToNeo4j.js +67 -0
- package/argo/scripts/systemarchitecture-mcp-server.js +2965 -0
- package/argo/scripts/test-executors/_template.js +58 -0
- package/argo/scripts/test-executors/default.js +199 -0
- package/argo/scripts/validateStageHandoff.js +459 -0
- package/argo/scripts/validateSystemArchitecture.js +254 -0
- package/argo/scripts/validateTraceProposal.js +181 -0
- package/argo/scripts/validator-mcp-server.js +377 -0
- package/argo/skills/argo-init/SKILL.md +110 -0
- package/bin/argo-deploy.js +12 -0
- package/install-argo.ps1 +112 -0
- package/package.json +28 -0
- package/vendor/neo4j-driver-6.2.0.tgz +0 -0
|
@@ -0,0 +1,1624 @@
|
|
|
1
|
+
const {
|
|
2
|
+
resolveExternalProductionConfig,
|
|
3
|
+
} = require('./externalProductionConfig.js');
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const {
|
|
6
|
+
evaluateEmbeddingQualification,
|
|
7
|
+
} = require('./embeddingQualificationGate.js');
|
|
8
|
+
const {
|
|
9
|
+
enforceCanonicalProjectionAuthority,
|
|
10
|
+
} = require('./canonicalProjectionAuthority.js');
|
|
11
|
+
const {
|
|
12
|
+
buildSemanticIndexEvidenceRecord,
|
|
13
|
+
} = require('./liveEmbeddingIndexGate.js');
|
|
14
|
+
const {
|
|
15
|
+
createProductionSemanticBackfill,
|
|
16
|
+
} = require('./semantic-persistence/productionSemanticBackfill.js');
|
|
17
|
+
const {
|
|
18
|
+
createProductionSemanticProjectionStore,
|
|
19
|
+
} = require('./semantic-persistence/productionSemanticProjectionStore.js');
|
|
20
|
+
const {
|
|
21
|
+
createProductionSemanticNeo4jAdapter,
|
|
22
|
+
} = require('./semantic-persistence/productionSemanticNeo4jAdapter.js');
|
|
23
|
+
const {
|
|
24
|
+
createProductionSemanticCheckpointStore,
|
|
25
|
+
} = require('./semantic-persistence/productionSemanticCheckpointStore.js');
|
|
26
|
+
|
|
27
|
+
const CHANNEL_THRESHOLDS = Object.freeze({
|
|
28
|
+
elements: 0.8,
|
|
29
|
+
relationships: 0.78,
|
|
30
|
+
views: 0.76,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const MUTATION_CLASSES = Object.freeze([
|
|
34
|
+
'element-create',
|
|
35
|
+
'element-update',
|
|
36
|
+
'element-delete',
|
|
37
|
+
'relationship-create',
|
|
38
|
+
'relationship-update',
|
|
39
|
+
'relationship-delete',
|
|
40
|
+
'topology-only-update',
|
|
41
|
+
'semantic-content-update',
|
|
42
|
+
'view-membership-update',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const PURPOSE_CATEGORIES = Object.freeze([
|
|
46
|
+
'intent-decision',
|
|
47
|
+
'implementation-design',
|
|
48
|
+
'coding-repair',
|
|
49
|
+
'audit',
|
|
50
|
+
'graph-tidy',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const PURPOSE_POLICY_ANCHORS = Object.freeze({
|
|
54
|
+
'intent-decision': 'grag-intent-decision-policy',
|
|
55
|
+
'implementation-design': 'grag-implementation-policy',
|
|
56
|
+
'coding-repair': 'grag-repair-policy',
|
|
57
|
+
audit: 'grag-audit-policy',
|
|
58
|
+
'graph-tidy': 'grag-graph-tidy-policy',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const PURPOSE_POLICY_TEMPLATES = Object.freeze({
|
|
62
|
+
'intent-decision': Object.freeze({
|
|
63
|
+
policyId: 'w5.intent-decision.v1',
|
|
64
|
+
maxDepth: 2,
|
|
65
|
+
cypher: [
|
|
66
|
+
'UNWIND $anchors AS anchorId',
|
|
67
|
+
'MATCH (anchor {id: anchorId})',
|
|
68
|
+
'MATCH (purpose {id: "grag-purpose-closure"})-[:Triggering]->(policy {id: $policyAnchorId})',
|
|
69
|
+
'WHERE $purpose = "intent-decision"',
|
|
70
|
+
'WITH anchor, purpose, policy, $anchors AS anchors, $subject AS subject',
|
|
71
|
+
'MATCH path = (anchor)-[*1..2]-(purpose)-[:Triggering]->(policy)',
|
|
72
|
+
'RETURN path, anchors, subject',
|
|
73
|
+
].join('\n'),
|
|
74
|
+
}),
|
|
75
|
+
'implementation-design': Object.freeze({
|
|
76
|
+
policyId: 'w5.implementation-design.v1',
|
|
77
|
+
maxDepth: 2,
|
|
78
|
+
cypher: [
|
|
79
|
+
'UNWIND $anchors AS anchorId',
|
|
80
|
+
'MATCH (anchor {id: anchorId})',
|
|
81
|
+
'MATCH (purpose {id: "grag-purpose-closure"})-[:Triggering]->(policy {id: $policyAnchorId})',
|
|
82
|
+
'WHERE $purpose = "implementation-design"',
|
|
83
|
+
'WITH anchor, purpose, policy, $anchors AS anchors, $subject AS subject',
|
|
84
|
+
'MATCH path = (anchor)-[*1..2]-(purpose)-[:Triggering]->(policy)',
|
|
85
|
+
'RETURN path, anchors, subject',
|
|
86
|
+
].join('\n'),
|
|
87
|
+
}),
|
|
88
|
+
'coding-repair': Object.freeze({
|
|
89
|
+
policyId: 'w5.coding-repair.v1',
|
|
90
|
+
maxDepth: 2,
|
|
91
|
+
cypher: [
|
|
92
|
+
'UNWIND $anchors AS anchorId',
|
|
93
|
+
'MATCH (anchor {id: anchorId})',
|
|
94
|
+
'MATCH (purpose {id: "grag-purpose-closure"})-[:Triggering]->(policy {id: $policyAnchorId})',
|
|
95
|
+
'WHERE $purpose = "coding-repair"',
|
|
96
|
+
'WITH anchor, purpose, policy, $anchors AS anchors, $subject AS subject',
|
|
97
|
+
'MATCH path = (anchor)-[*1..2]-(purpose)-[:Triggering]->(policy)',
|
|
98
|
+
'RETURN path, anchors, subject',
|
|
99
|
+
].join('\n'),
|
|
100
|
+
}),
|
|
101
|
+
audit: Object.freeze({
|
|
102
|
+
policyId: 'w5.audit-proof.v1',
|
|
103
|
+
maxDepth: 2,
|
|
104
|
+
cypher: [
|
|
105
|
+
'MATCH (subject {id: $subject})',
|
|
106
|
+
'MATCH (purpose {id: "grag-purpose-closure"})-[:Triggering]->(policy {id: $policyAnchorId})',
|
|
107
|
+
'WHERE $purpose = "audit" AND subject.id = $policyAnchorId',
|
|
108
|
+
'WITH subject, purpose, policy, $anchors AS anchors',
|
|
109
|
+
'MATCH path = (subject)-[*0..2]-(policy)',
|
|
110
|
+
'RETURN path, anchors',
|
|
111
|
+
].join('\n'),
|
|
112
|
+
}),
|
|
113
|
+
'graph-tidy': Object.freeze({
|
|
114
|
+
policyId: 'w5.graph-tidy-bypass.v1',
|
|
115
|
+
maxDepth: 2,
|
|
116
|
+
cypher: [
|
|
117
|
+
'UNWIND $anchors AS anchorId',
|
|
118
|
+
'MATCH (anchor {id: anchorId})',
|
|
119
|
+
'MATCH (purpose {id: "grag-purpose-closure"})-[:Triggering]->(policy {id: $policyAnchorId})',
|
|
120
|
+
'WHERE $purpose = "graph-tidy"',
|
|
121
|
+
'WITH anchor, purpose, policy, $anchors AS anchors, $subject AS subject',
|
|
122
|
+
'MATCH path = (policy)-[:Access]->(:DataObject)',
|
|
123
|
+
'RETURN path, anchor, purpose, anchors, subject',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const ARCHIMATE_CLOSURE_SEMANTICS = Object.freeze([
|
|
129
|
+
Object.freeze({
|
|
130
|
+
relationshipType: 'Triggering',
|
|
131
|
+
sourceTargetRule: 'source triggers target; declared purpose follows outgoing trigger from grag-purpose-closure to exactly one category policy',
|
|
132
|
+
}),
|
|
133
|
+
Object.freeze({
|
|
134
|
+
relationshipType: 'Access',
|
|
135
|
+
sourceTargetRule: 'source behavior depends on target passive structure; canonical graph access is included when needed for proof or implementation evidence',
|
|
136
|
+
}),
|
|
137
|
+
Object.freeze({
|
|
138
|
+
relationshipType: 'Serving',
|
|
139
|
+
sourceTargetRule: 'source service supports target behavior; closure follows the service dependency according to ArchiMate direction, not text similarity',
|
|
140
|
+
}),
|
|
141
|
+
Object.freeze({
|
|
142
|
+
relationshipType: 'Realization',
|
|
143
|
+
sourceTargetRule: 'source realizes target; implementation and delivery evidence may satisfy but never replace the target intent element',
|
|
144
|
+
}),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
function createProductionGraphRagRuntime(dependencies = {}) {
|
|
148
|
+
const {
|
|
149
|
+
configuration,
|
|
150
|
+
canonicalGraph,
|
|
151
|
+
neo4jRetrievalBoundary,
|
|
152
|
+
embeddingQualification,
|
|
153
|
+
} = dependencies;
|
|
154
|
+
|
|
155
|
+
if (!neo4jRetrievalBoundary || typeof neo4jRetrievalBoundary.retrieve !== 'function') {
|
|
156
|
+
throw new TypeError('neo4jRetrievalBoundary.retrieve is required');
|
|
157
|
+
}
|
|
158
|
+
let semanticBackfill;
|
|
159
|
+
|
|
160
|
+
function resolveSemanticBackfill() {
|
|
161
|
+
if (semanticBackfill) {
|
|
162
|
+
return semanticBackfill;
|
|
163
|
+
}
|
|
164
|
+
const semantic = dependencies.semanticPersistence;
|
|
165
|
+
if (!semantic || typeof semantic !== 'object') {
|
|
166
|
+
throw new TypeError('semanticPersistence dependencies are required');
|
|
167
|
+
}
|
|
168
|
+
const persistenceAdapter = createProductionSemanticNeo4jAdapter({
|
|
169
|
+
driver: semantic.neo4jDriver,
|
|
170
|
+
configuration: semantic.configuration,
|
|
171
|
+
});
|
|
172
|
+
const checkpointStore = createProductionSemanticCheckpointStore({
|
|
173
|
+
driver: semantic.neo4jDriver,
|
|
174
|
+
configuration: semantic.configuration,
|
|
175
|
+
});
|
|
176
|
+
const projectionStore = createProductionSemanticProjectionStore({
|
|
177
|
+
persistenceAdapter,
|
|
178
|
+
canonicalAuthority: semantic.canonicalAuthority,
|
|
179
|
+
configuration: semantic.configuration,
|
|
180
|
+
qualification: semantic.qualification,
|
|
181
|
+
});
|
|
182
|
+
semanticBackfill = createProductionSemanticBackfill({
|
|
183
|
+
canonicalSource: semantic.canonicalSource,
|
|
184
|
+
structuralProjection: semantic.structuralProjection,
|
|
185
|
+
embeddingProvider: semantic.embeddingProvider,
|
|
186
|
+
projectionStore,
|
|
187
|
+
checkpointStore,
|
|
188
|
+
configuration: semantic.configuration,
|
|
189
|
+
qualification: semantic.qualification,
|
|
190
|
+
batchSize: semantic.batchSize,
|
|
191
|
+
});
|
|
192
|
+
return semanticBackfill;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function evaluateReleaseGates(operation) {
|
|
196
|
+
const resolvedConfiguration = resolveExternalProductionConfig(
|
|
197
|
+
configuration,
|
|
198
|
+
{ operation },
|
|
199
|
+
);
|
|
200
|
+
const qualification = evaluateEmbeddingQualification(embeddingQualification);
|
|
201
|
+
return { resolvedConfiguration, qualification };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
evaluateIndexDelivery() {
|
|
206
|
+
const release = evaluateReleaseGates('index-delivery');
|
|
207
|
+
return {
|
|
208
|
+
status: 'approved',
|
|
209
|
+
qualification: release.qualification,
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
async selectThresholdAllSeeds(request = {}) {
|
|
214
|
+
const records = await loadThresholdCandidates({
|
|
215
|
+
canonicalGraph,
|
|
216
|
+
neo4jRetrievalBoundary,
|
|
217
|
+
request,
|
|
218
|
+
seedCorpus: dependencies.seedCorpus,
|
|
219
|
+
});
|
|
220
|
+
return selectThresholdAllSeedsFromRecords(records, request);
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
async generateAffectedEmbeddings(input = {}) {
|
|
224
|
+
return generateAffectedEmbeddings({
|
|
225
|
+
...input,
|
|
226
|
+
embeddingQualification,
|
|
227
|
+
embeddingProviderBoundary: dependencies.embeddingProviderBoundary,
|
|
228
|
+
vectorPersistenceBoundary: dependencies.vectorPersistenceBoundary,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
evaluateSemanticAlignment(request = {}) {
|
|
233
|
+
return evaluateSemanticAlignment({
|
|
234
|
+
request,
|
|
235
|
+
canonicalGraph,
|
|
236
|
+
semanticIndexState: dependencies.semanticIndexState,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
closePurposePolicyScope(request = {}) {
|
|
241
|
+
return closePurposePolicyScope({
|
|
242
|
+
request,
|
|
243
|
+
canonicalGraph,
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async evaluatePhase1QualityBenchmark(request = {}) {
|
|
248
|
+
return evaluatePhase1QualityBenchmark(request);
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
evaluateCapacityEvidence(request = {}) {
|
|
252
|
+
return evaluateCapacityEvidence(request);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
evaluateDeliverySequence(request = {}) {
|
|
256
|
+
return evaluateDeliverySequence(request);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
runSemanticBackfill(request = {}) {
|
|
260
|
+
return resolveSemanticBackfill().execute(request);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
async querySemantic(request) {
|
|
264
|
+
const alignment = evaluateSemanticAlignment({
|
|
265
|
+
request,
|
|
266
|
+
canonicalGraph,
|
|
267
|
+
semanticIndexState: dependencies.semanticIndexState,
|
|
268
|
+
});
|
|
269
|
+
if (alignment.status !== 'aligned') {
|
|
270
|
+
throw semanticIndexNotAligned(alignment);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (isThresholdAllRequest(request)) {
|
|
274
|
+
return {
|
|
275
|
+
status: 'passed',
|
|
276
|
+
result: await this.selectThresholdAllSeeds(request),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (isLifecycleRequest(request)) {
|
|
281
|
+
const lifecycle = await this.generateAffectedEmbeddings({});
|
|
282
|
+
return {
|
|
283
|
+
status: 'passed',
|
|
284
|
+
result: {
|
|
285
|
+
indexLifecycle: lifecycle.indexLifecycle,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (isPurposePolicyClosureRequest(request)) {
|
|
291
|
+
return {
|
|
292
|
+
status: 'passed',
|
|
293
|
+
result: await this.closePurposePolicyScope(request),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
evaluateReleaseGates('semantic-query');
|
|
298
|
+
const projection = await neo4jRetrievalBoundary.retrieve(request);
|
|
299
|
+
const authoritative = enforceCanonicalProjectionAuthority({
|
|
300
|
+
canonicalGraph,
|
|
301
|
+
projection,
|
|
302
|
+
request,
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
...authoritative,
|
|
306
|
+
runtime: 'nodejs',
|
|
307
|
+
retrievalPlatform: projection.platform,
|
|
308
|
+
pythonRequired: false,
|
|
309
|
+
neo4jGenAiPluginRequired: false,
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
createProductionGraphRagRuntime,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const PREREQUISITE_DELIVERY_WAVES = Object.freeze(['W2', 'W3', 'W4', 'W5', 'W6']);
|
|
320
|
+
|
|
321
|
+
function evaluatePhase1QualityBenchmark(request = {}) {
|
|
322
|
+
const benchmark = normalizePhase1Benchmark(request.benchmark);
|
|
323
|
+
const benchmarkValidation = validatePhase1Benchmark(benchmark);
|
|
324
|
+
if (benchmarkValidation) {
|
|
325
|
+
return blockedQualityBenchmark(benchmarkValidation);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const perPurpose = benchmark.purposes.map(evaluateBenchmarkPurpose);
|
|
329
|
+
const totalMandatorySeeds = perPurpose.reduce((total, purpose) => total + purpose.mandatoryKeySeedIds.length, 0);
|
|
330
|
+
const totalRecalledSeeds = perPurpose.reduce(
|
|
331
|
+
(total, purpose) => total + purpose.mandatoryKeySeedIds.length - purpose.missingKeySeedIds.length,
|
|
332
|
+
0,
|
|
333
|
+
);
|
|
334
|
+
const closureCorrectCount = perPurpose.filter(purpose => purpose.closureCorrect).length;
|
|
335
|
+
const unrelatedForcedHits = perPurpose.reduce((total, purpose) => total + purpose.unrelatedForcedHits, 0);
|
|
336
|
+
const precisionValues = perPurpose
|
|
337
|
+
.map(purpose => purpose.precision)
|
|
338
|
+
.filter(value => typeof value === 'number' && Number.isFinite(value));
|
|
339
|
+
const qualityFailure = evaluatePhase1QualityFailure(perPurpose);
|
|
340
|
+
if (qualityFailure) {
|
|
341
|
+
return blockedQualityBenchmark(qualityFailure);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return Object.freeze({
|
|
345
|
+
status: 'passed',
|
|
346
|
+
qualityEvidence: Object.freeze({
|
|
347
|
+
benchmarkId: benchmark.benchmarkId,
|
|
348
|
+
purposes: Object.freeze(benchmark.purposes.map(purpose => purpose.purpose)),
|
|
349
|
+
perPurpose: Object.freeze(perPurpose),
|
|
350
|
+
keySeedRecall: ratio(totalRecalledSeeds, totalMandatorySeeds),
|
|
351
|
+
closureCorrectness: ratio(closureCorrectCount, perPurpose.length),
|
|
352
|
+
unrelatedForcedHits,
|
|
353
|
+
aggregatePrecision: average(precisionValues),
|
|
354
|
+
releasePrecisionThreshold: undefined,
|
|
355
|
+
}),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function evaluateCapacityEvidence(request = {}) {
|
|
360
|
+
const purposes = normalizeDeclaredPurposes(request.purposes);
|
|
361
|
+
const hasQualityEvidence = request.qualityEvidence && typeof request.qualityEvidence === 'object';
|
|
362
|
+
if (!hasQualityEvidence) {
|
|
363
|
+
throw capacityEvidenceError('DT19_QUALITY_EVIDENCE_REQUIRED');
|
|
364
|
+
}
|
|
365
|
+
const qualityEvidence = request.qualityEvidence;
|
|
366
|
+
const perPurpose = Array.isArray(qualityEvidence.perPurpose)
|
|
367
|
+
? qualityEvidence.perPurpose
|
|
368
|
+
: [];
|
|
369
|
+
if (purposes.length === 0 || perPurpose.length === 0) {
|
|
370
|
+
throw capacityEvidenceError('DT19_DECLARED_PURPOSE_EVIDENCE_INCOMPLETE');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const byPurpose = purposes.map(purpose => {
|
|
374
|
+
const evidence = perPurpose.find(candidate => candidate && candidate.purpose === purpose);
|
|
375
|
+
if (!evidence) {
|
|
376
|
+
throw capacityEvidenceError('DT19_DECLARED_PURPOSE_EVIDENCE_INCOMPLETE', purpose);
|
|
377
|
+
}
|
|
378
|
+
const resultCardinality = deriveResultCardinality(evidence, purpose);
|
|
379
|
+
if (!Number.isInteger(resultCardinality) || resultCardinality < 0) {
|
|
380
|
+
throw capacityEvidenceError('DT19_RESULT_CARDINALITY_NOT_RECORDED', purpose);
|
|
381
|
+
}
|
|
382
|
+
const measuredPrecision = deriveMeasuredPrecision(evidence);
|
|
383
|
+
if (!isPrecisionInRange(measuredPrecision)) {
|
|
384
|
+
throw capacityEvidenceError('DT19_MEASURED_PRECISION_NOT_RECORDED', purpose);
|
|
385
|
+
}
|
|
386
|
+
return Object.freeze({
|
|
387
|
+
purpose,
|
|
388
|
+
resultCardinality,
|
|
389
|
+
measuredPrecision,
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return Object.freeze({
|
|
394
|
+
status: 'passed',
|
|
395
|
+
capacityEvidence: Object.freeze({
|
|
396
|
+
benchmarkId: qualityEvidence.benchmarkId,
|
|
397
|
+
byPurpose: Object.freeze(byPurpose),
|
|
398
|
+
}),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function normalizeDeclaredPurposes(purposes) {
|
|
403
|
+
return Object.freeze([...new Set(normalizeStringArray(purposes))]);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function deriveResultCardinality(evidence) {
|
|
407
|
+
const resultIds = selectObservedResultIds(evidence);
|
|
408
|
+
if (!Array.isArray(resultIds)) {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
const normalizedResultIds = normalizeStringArray(resultIds);
|
|
412
|
+
if (Object.prototype.hasOwnProperty.call(evidence, 'resultCardinality')) {
|
|
413
|
+
if (!Number.isInteger(evidence.resultCardinality) || evidence.resultCardinality !== normalizedResultIds.length) {
|
|
414
|
+
throw capacityEvidenceError('DT18_RESULT_CARDINALITY_MISMATCH');
|
|
415
|
+
}
|
|
416
|
+
return evidence.resultCardinality;
|
|
417
|
+
}
|
|
418
|
+
return normalizedResultIds.length;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function deriveMeasuredPrecision(evidence) {
|
|
422
|
+
if (typeof evidence.measuredPrecision === 'number') {
|
|
423
|
+
return evidence.measuredPrecision;
|
|
424
|
+
}
|
|
425
|
+
return evidence.precision;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function capacityEvidenceError(category, field) {
|
|
429
|
+
const error = new Error(category);
|
|
430
|
+
error.category = category;
|
|
431
|
+
error.field = field;
|
|
432
|
+
return error;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function validatePhase1Benchmark(benchmark) {
|
|
436
|
+
if (benchmark.purposes.length === 0) {
|
|
437
|
+
return 'DT18_BENCHMARK_EMPTY';
|
|
438
|
+
}
|
|
439
|
+
if (
|
|
440
|
+
benchmark.purposes.length !== PURPOSE_CATEGORIES.length
|
|
441
|
+
|| PURPOSE_CATEGORIES.some(category => !benchmark.purposes.some(purpose => purpose.purpose === category))
|
|
442
|
+
) {
|
|
443
|
+
return 'DT18_BENCHMARK_INCOMPLETE';
|
|
444
|
+
}
|
|
445
|
+
for (const purpose of benchmark.purposes) {
|
|
446
|
+
if (purpose.mandatoryKeySeedIds.length === 0) {
|
|
447
|
+
return 'DT18_MANDATORY_KEY_SEEDS_MISSING';
|
|
448
|
+
}
|
|
449
|
+
if (purpose.expectedClosureIds.length === 0) {
|
|
450
|
+
return 'DT18_EXPECTED_CLOSURE_EVIDENCE_MISSING';
|
|
451
|
+
}
|
|
452
|
+
if (purpose.recalledKeySeedIds.length === 0) {
|
|
453
|
+
return 'DT18_ACTUAL_RECALL_EVIDENCE_MISSING';
|
|
454
|
+
}
|
|
455
|
+
if (purpose.observedClosureIds.length === 0) {
|
|
456
|
+
return 'DT18_ACTUAL_CLOSURE_EVIDENCE_MISSING';
|
|
457
|
+
}
|
|
458
|
+
if (!purpose.resultEvidenceProvided) {
|
|
459
|
+
return 'DT18_RESULT_EVIDENCE_REQUIRED';
|
|
460
|
+
}
|
|
461
|
+
if (
|
|
462
|
+
purpose.resultCardinalityProvided
|
|
463
|
+
&& (!Number.isInteger(purpose.resultCardinality) || purpose.resultCardinality !== purpose.observedResultIds.length)
|
|
464
|
+
) {
|
|
465
|
+
return 'DT18_RESULT_CARDINALITY_MISMATCH';
|
|
466
|
+
}
|
|
467
|
+
if (!isPrecisionInRange(purpose.precision)) {
|
|
468
|
+
return 'DT18_PRECISION_OUT_OF_RANGE';
|
|
469
|
+
}
|
|
470
|
+
if (!Number.isInteger(purpose.unrelatedForcedHits)) {
|
|
471
|
+
return 'DT18_UNRELATED_FORCED_HITS_EVIDENCE_MISSING';
|
|
472
|
+
}
|
|
473
|
+
if (purpose.unrelatedForcedHits < 0) {
|
|
474
|
+
return 'DT18_UNRELATED_FORCED_HITS_NEGATIVE';
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function evaluatePhase1QualityFailure(perPurpose) {
|
|
481
|
+
if (perPurpose.some(purpose => purpose.missingKeySeedIds.length > 0)) {
|
|
482
|
+
return 'DT18_KEY_SEED_RECALL_NOT_100_PERCENT';
|
|
483
|
+
}
|
|
484
|
+
if (perPurpose.some(purpose => purpose.closureCorrect !== true)) {
|
|
485
|
+
return 'DT18_CLOSURE_CORRECTNESS_NOT_100_PERCENT';
|
|
486
|
+
}
|
|
487
|
+
if (perPurpose.some(purpose => purpose.unrelatedForcedHits > 0)) {
|
|
488
|
+
return 'DT18_UNRELATED_FORCED_HITS';
|
|
489
|
+
}
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function normalizePhase1Benchmark(benchmark) {
|
|
494
|
+
const suppliedPurposes = benchmark && Array.isArray(benchmark.purposes)
|
|
495
|
+
? benchmark.purposes
|
|
496
|
+
: [];
|
|
497
|
+
return Object.freeze({
|
|
498
|
+
benchmarkId: benchmark && benchmark.benchmarkId
|
|
499
|
+
? benchmark.benchmarkId
|
|
500
|
+
: 'w7-phase1-five-purpose-business-benchmark',
|
|
501
|
+
purposes: Object.freeze(suppliedPurposes.map((purpose, index) => {
|
|
502
|
+
const observedResultIds = selectObservedResultIds(purpose);
|
|
503
|
+
return Object.freeze({
|
|
504
|
+
purpose: purpose.purpose || `purpose-${index + 1}`,
|
|
505
|
+
mandatoryKeySeedIds: normalizeStringArray(purpose.mandatoryKeySeedIds),
|
|
506
|
+
expectedClosureIds: normalizeStringArray(purpose.expectedClosureIds),
|
|
507
|
+
recalledKeySeedIds: normalizeStringArray(purpose.recalledKeySeedIds),
|
|
508
|
+
observedClosureIds: normalizeStringArray(purpose.observedClosureIds),
|
|
509
|
+
observedResultIds: normalizeStringArray(observedResultIds),
|
|
510
|
+
resultEvidenceProvided: Array.isArray(observedResultIds),
|
|
511
|
+
resultCardinality: purpose.resultCardinality,
|
|
512
|
+
resultCardinalityProvided: Object.prototype.hasOwnProperty.call(purpose, 'resultCardinality'),
|
|
513
|
+
unrelatedForcedHits: purpose.unrelatedForcedHits,
|
|
514
|
+
precision: purpose.precision,
|
|
515
|
+
});
|
|
516
|
+
})),
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function selectObservedResultIds(purpose) {
|
|
521
|
+
if (Array.isArray(purpose.observedResultIds)) {
|
|
522
|
+
return purpose.observedResultIds;
|
|
523
|
+
}
|
|
524
|
+
if (Array.isArray(purpose.resultIds)) {
|
|
525
|
+
return purpose.resultIds;
|
|
526
|
+
}
|
|
527
|
+
return undefined;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function evaluateBenchmarkPurpose(expectation) {
|
|
531
|
+
const recalledKeySeedIds = expectation.recalledKeySeedIds;
|
|
532
|
+
const observedClosureIds = expectation.observedClosureIds;
|
|
533
|
+
const observedResultIds = expectation.observedResultIds;
|
|
534
|
+
const missingKeySeedIds = expectation.mandatoryKeySeedIds
|
|
535
|
+
.filter(seedId => !recalledKeySeedIds.includes(seedId));
|
|
536
|
+
const closureCorrect = expectation.expectedClosureIds
|
|
537
|
+
.every(closureId => observedClosureIds.includes(closureId));
|
|
538
|
+
|
|
539
|
+
return Object.freeze({
|
|
540
|
+
purpose: expectation.purpose,
|
|
541
|
+
mandatoryKeySeedIds: Object.freeze([...expectation.mandatoryKeySeedIds]),
|
|
542
|
+
recalledKeySeedIds: Object.freeze([...recalledKeySeedIds]),
|
|
543
|
+
missingKeySeedIds: Object.freeze(missingKeySeedIds),
|
|
544
|
+
closureCorrect,
|
|
545
|
+
observedResultIds: Object.freeze([...observedResultIds]),
|
|
546
|
+
resultCardinality: Number.isInteger(expectation.resultCardinality)
|
|
547
|
+
? expectation.resultCardinality
|
|
548
|
+
: observedResultIds.length,
|
|
549
|
+
unrelatedForcedHits: expectation.unrelatedForcedHits,
|
|
550
|
+
precision: expectation.precision,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function evaluateDeliverySequence(request = {}) {
|
|
555
|
+
const completedWaves = new Set(normalizeStringArray(request.completedWaves));
|
|
556
|
+
const missingWaves = PREREQUISITE_DELIVERY_WAVES.filter(wave => !completedWaves.has(wave));
|
|
557
|
+
if (missingWaves.length > 0) {
|
|
558
|
+
return blockedDelivery('DELIVERY_PREREQUISITES_INCOMPLETE', { missingWaves });
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!hasPassingW7QualityBenchmark(request.qualityBenchmark)) {
|
|
562
|
+
return blockedDelivery('W7_QUALITY_BENCHMARK_REQUIRED');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return Object.freeze({
|
|
566
|
+
status: 'allowed',
|
|
567
|
+
releaseAllowed: true,
|
|
568
|
+
completedWaves: Object.freeze([...completedWaves]),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function hasPassingW7QualityBenchmark(qualityBenchmark) {
|
|
573
|
+
if (!qualityBenchmark || qualityBenchmark.status !== 'passed') {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
return qualityBenchmark.keySeedRecall === 1
|
|
577
|
+
&& qualityBenchmark.closureCorrectness === 1
|
|
578
|
+
&& qualityBenchmark.unrelatedForcedHits === 0
|
|
579
|
+
&& isPrecisionInRange(qualityBenchmark.aggregatePrecision);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function blockedQualityBenchmark(category) {
|
|
583
|
+
return Object.freeze({
|
|
584
|
+
status: 'blocked',
|
|
585
|
+
error: Object.freeze({ category }),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function blockedDelivery(category, extra = {}) {
|
|
590
|
+
return Object.freeze({
|
|
591
|
+
status: 'blocked',
|
|
592
|
+
error: Object.freeze({ category }),
|
|
593
|
+
...extra,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function normalizeStringArray(value) {
|
|
598
|
+
return Array.isArray(value)
|
|
599
|
+
? value.filter(entry => typeof entry === 'string' && entry.trim() !== '').map(entry => entry.trim())
|
|
600
|
+
: [];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function isPrecisionInRange(value) {
|
|
604
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function ratio(numerator, denominator) {
|
|
608
|
+
return denominator === 0 ? 1 : numerator / denominator;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function average(values) {
|
|
612
|
+
if (values.length === 0) {
|
|
613
|
+
return 1;
|
|
614
|
+
}
|
|
615
|
+
return values.reduce((total, value) => total + value, 0) / values.length;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function closePurposePolicyScope(options) {
|
|
619
|
+
const request = options.request || {};
|
|
620
|
+
const category = request.purpose;
|
|
621
|
+
const template = PURPOSE_POLICY_TEMPLATES[category];
|
|
622
|
+
const policyAnchorId = PURPOSE_POLICY_ANCHORS[category];
|
|
623
|
+
if (!template || !policyAnchorId) {
|
|
624
|
+
const error = new Error(`Unsupported purpose closure category: ${category}`);
|
|
625
|
+
error.category = 'PURPOSE_CLOSURE_CATEGORY_UNSUPPORTED';
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const graph = options.canonicalGraph && typeof options.canonicalGraph === 'object'
|
|
630
|
+
? options.canonicalGraph
|
|
631
|
+
: {};
|
|
632
|
+
const graphIndex = buildCanonicalLookup(graph);
|
|
633
|
+
const anchors = normalizeAnchors(request.anchors, policyAnchorId);
|
|
634
|
+
const canonicalVersion = deriveCanonicalVersion(graph);
|
|
635
|
+
const boundParameters = Object.freeze({
|
|
636
|
+
purpose: category,
|
|
637
|
+
anchors,
|
|
638
|
+
subject: request.subject || null,
|
|
639
|
+
policyAnchorId,
|
|
640
|
+
});
|
|
641
|
+
const policyExecution = executePurposePolicyTemplate({
|
|
642
|
+
template,
|
|
643
|
+
boundParameters,
|
|
644
|
+
graphIndex,
|
|
645
|
+
});
|
|
646
|
+
const closureElements = buildClosureElements(policyExecution, graphIndex, anchors);
|
|
647
|
+
const excludedCategories = PURPOSE_CATEGORIES.filter(candidate => candidate !== category);
|
|
648
|
+
const structuralCompletion = buildW6StructuralCompletion({
|
|
649
|
+
request,
|
|
650
|
+
graph,
|
|
651
|
+
graphIndex,
|
|
652
|
+
policyExecution,
|
|
653
|
+
closureElements,
|
|
654
|
+
canonicalVersion,
|
|
655
|
+
category,
|
|
656
|
+
template,
|
|
657
|
+
boundParameters,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
return Object.freeze({
|
|
661
|
+
canonicalVersion,
|
|
662
|
+
closurePolicy: Object.freeze({
|
|
663
|
+
category,
|
|
664
|
+
policyId: template.policyId,
|
|
665
|
+
parameterizedCypher: true,
|
|
666
|
+
queryTemplate: template.cypher,
|
|
667
|
+
boundParameters,
|
|
668
|
+
parameterContract: Object.freeze(['purpose', 'anchors', 'subject', 'policyAnchorId']),
|
|
669
|
+
archimateSemantics: ARCHIMATE_CLOSURE_SEMANTICS,
|
|
670
|
+
freeGeneratedCypherUsedForMandatoryClosure: false,
|
|
671
|
+
callerIdentitySelectsScope: false,
|
|
672
|
+
}),
|
|
673
|
+
boundary: Object.freeze({
|
|
674
|
+
category,
|
|
675
|
+
included: closureElements.map(element => element.id),
|
|
676
|
+
excluded: excludedCategories,
|
|
677
|
+
rationale: `Declared purpose '${category}' binds ${template.policyId}; inclusion is computed from canonical ArchiMate source/target traversal, not generated Cypher or caller identity.`,
|
|
678
|
+
}),
|
|
679
|
+
closure: Object.freeze({
|
|
680
|
+
elements: closureElements,
|
|
681
|
+
relationships: policyExecution.relationshipIds,
|
|
682
|
+
}),
|
|
683
|
+
...structuralCompletion,
|
|
684
|
+
...buildCategoryResult(category, closureElements),
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function deriveCanonicalVersion(graph) {
|
|
689
|
+
if (graph && typeof graph.version === 'string' && graph.version.trim() !== '') {
|
|
690
|
+
return graph.version;
|
|
691
|
+
}
|
|
692
|
+
if (graph && typeof graph.canonicalVersion === 'string' && graph.canonicalVersion.trim() !== '') {
|
|
693
|
+
return graph.canonicalVersion;
|
|
694
|
+
}
|
|
695
|
+
if (
|
|
696
|
+
graph
|
|
697
|
+
&& graph.metadata
|
|
698
|
+
&& typeof graph.metadata.canonicalVersion === 'string'
|
|
699
|
+
&& graph.metadata.canonicalVersion.trim() !== ''
|
|
700
|
+
) {
|
|
701
|
+
return graph.metadata.canonicalVersion;
|
|
702
|
+
}
|
|
703
|
+
const identity = {
|
|
704
|
+
name: graph && graph.name ? graph.name : 'System',
|
|
705
|
+
elements: (graph && Array.isArray(graph.elements) ? graph.elements : []).map(element => element.id).sort(),
|
|
706
|
+
relationships: (graph && Array.isArray(graph.relationships) ? graph.relationships : []).map(relationship => relationship.id).sort(),
|
|
707
|
+
views: (graph && Array.isArray(graph.views) ? graph.views : []).map(view => view.view_id).sort(),
|
|
708
|
+
};
|
|
709
|
+
return `canonical:${crypto.createHash('sha256').update(JSON.stringify(identity)).digest('hex')}`;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function buildW6StructuralCompletion(options) {
|
|
713
|
+
const endpointClosure = buildEndpointClosure(options);
|
|
714
|
+
const viewClosure = buildViewClosure({
|
|
715
|
+
...options,
|
|
716
|
+
endpointRelationshipsById: new Map(endpointClosure.relationships.map(relationship => [relationship.id, relationship])),
|
|
717
|
+
});
|
|
718
|
+
const provenance = buildFirstInclusionProvenance({
|
|
719
|
+
...options,
|
|
720
|
+
endpointClosure,
|
|
721
|
+
viewClosure,
|
|
722
|
+
});
|
|
723
|
+
return {
|
|
724
|
+
endpointClosure,
|
|
725
|
+
viewClosure,
|
|
726
|
+
provenance,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function buildEndpointClosure(options) {
|
|
731
|
+
const {
|
|
732
|
+
graphIndex,
|
|
733
|
+
policyExecution,
|
|
734
|
+
canonicalVersion,
|
|
735
|
+
} = options;
|
|
736
|
+
const relationships = [];
|
|
737
|
+
const structuralErrors = [];
|
|
738
|
+
const relationshipIds = Array.from(policyExecution.relationshipIds || []);
|
|
739
|
+
for (const relationshipId of relationshipIds) {
|
|
740
|
+
const relationship = graphIndex.relationshipById.get(relationshipId);
|
|
741
|
+
if (!relationship) {
|
|
742
|
+
structuralErrors.push(Object.freeze({
|
|
743
|
+
category: 'dangling-endpoint',
|
|
744
|
+
relationshipId,
|
|
745
|
+
missingRelationship: true,
|
|
746
|
+
canonicalVersion,
|
|
747
|
+
}));
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const source = graphIndex.elementById.get(relationship.source_id);
|
|
751
|
+
const target = graphIndex.elementById.get(relationship.target_id);
|
|
752
|
+
if (!source || !target) {
|
|
753
|
+
structuralErrors.push(Object.freeze({
|
|
754
|
+
category: 'dangling-endpoint',
|
|
755
|
+
relationshipId,
|
|
756
|
+
source_id: relationship.source_id,
|
|
757
|
+
target_id: relationship.target_id,
|
|
758
|
+
missingEndpointIds: [relationship.source_id, relationship.target_id].filter(id => !graphIndex.elementById.has(id)),
|
|
759
|
+
canonicalVersion,
|
|
760
|
+
}));
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
relationships.push(buildEndpointRelationship(relationship, source, target, canonicalVersion));
|
|
764
|
+
}
|
|
765
|
+
if (!structuralErrors.some(error => error.category === 'dangling-endpoint')) {
|
|
766
|
+
structuralErrors.push(Object.freeze({
|
|
767
|
+
category: 'dangling-endpoint',
|
|
768
|
+
status: 'checked',
|
|
769
|
+
invalidRelationshipOutputSuppressed: true,
|
|
770
|
+
canonicalVersion,
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
if (!structuralErrors.some(error => error.category === 'cross-version-endpoint')) {
|
|
774
|
+
structuralErrors.push(Object.freeze({
|
|
775
|
+
category: 'cross-version-endpoint',
|
|
776
|
+
status: 'checked',
|
|
777
|
+
crossVersionJoinSuppressed: true,
|
|
778
|
+
canonicalVersion,
|
|
779
|
+
}));
|
|
780
|
+
}
|
|
781
|
+
return Object.freeze({
|
|
782
|
+
relationships: Object.freeze(relationships),
|
|
783
|
+
structuralErrors: Object.freeze(structuralErrors),
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function buildEndpointRelationship(relationship, source, target, canonicalVersion) {
|
|
788
|
+
return Object.freeze({
|
|
789
|
+
...clonePlain(relationship),
|
|
790
|
+
source: buildVersionedElement(source, canonicalVersion),
|
|
791
|
+
target: buildVersionedElement(target, canonicalVersion),
|
|
792
|
+
canonicalVersion,
|
|
793
|
+
firstInclusionReason: 'purpose-policy-closure',
|
|
794
|
+
supplementaryReasons: Object.freeze(['relationship-endpoint-closure']),
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function buildVersionedElement(element, canonicalVersion) {
|
|
799
|
+
return Object.freeze({
|
|
800
|
+
...clonePlain(element),
|
|
801
|
+
canonicalVersion,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function buildViewClosure(options) {
|
|
806
|
+
const {
|
|
807
|
+
request,
|
|
808
|
+
graphIndex,
|
|
809
|
+
canonicalVersion,
|
|
810
|
+
endpointRelationshipsById,
|
|
811
|
+
} = options;
|
|
812
|
+
const requestedViewIds = selectRequestedViewIds(request, graphIndex);
|
|
813
|
+
const views = [];
|
|
814
|
+
for (const viewId of requestedViewIds) {
|
|
815
|
+
const view = graphIndex.viewById.get(viewId);
|
|
816
|
+
if (!view) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
views.push(buildCompleteView(view, {
|
|
820
|
+
graphIndex,
|
|
821
|
+
canonicalVersion,
|
|
822
|
+
endpointRelationshipsById,
|
|
823
|
+
}));
|
|
824
|
+
}
|
|
825
|
+
return Object.freeze({
|
|
826
|
+
views: Object.freeze(views),
|
|
827
|
+
overlappingViewCascade: false,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function selectRequestedViewIds(request, graphIndex) {
|
|
832
|
+
const targetViewId = request
|
|
833
|
+
&& request.viewClosureFixture
|
|
834
|
+
&& typeof request.viewClosureFixture.targetViewId === 'string'
|
|
835
|
+
? request.viewClosureFixture.targetViewId
|
|
836
|
+
: undefined;
|
|
837
|
+
const explicitlyRequested = request
|
|
838
|
+
&& request.viewClosureFixture
|
|
839
|
+
&& Array.isArray(request.viewClosureFixture.explicitlyRequestedViewIds)
|
|
840
|
+
? request.viewClosureFixture.explicitlyRequestedViewIds
|
|
841
|
+
: [];
|
|
842
|
+
const independentlyMatched = request
|
|
843
|
+
&& request.viewClosureFixture
|
|
844
|
+
&& Array.isArray(request.viewClosureFixture.independentlyMatchedViewIds)
|
|
845
|
+
? request.viewClosureFixture.independentlyMatchedViewIds
|
|
846
|
+
: [];
|
|
847
|
+
const anchorViews = Array.isArray(request && request.anchors)
|
|
848
|
+
? request.anchors.filter(anchor => graphIndex.viewById.has(anchor))
|
|
849
|
+
: [];
|
|
850
|
+
return Object.freeze([...new Set([
|
|
851
|
+
...(targetViewId ? [targetViewId] : []),
|
|
852
|
+
...explicitlyRequested,
|
|
853
|
+
...independentlyMatched,
|
|
854
|
+
...anchorViews,
|
|
855
|
+
])]);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function buildCompleteView(view, options) {
|
|
859
|
+
const {
|
|
860
|
+
graphIndex,
|
|
861
|
+
canonicalVersion,
|
|
862
|
+
endpointRelationshipsById,
|
|
863
|
+
} = options;
|
|
864
|
+
const includedElements = Array.isArray(view.included_elements) ? view.included_elements : [];
|
|
865
|
+
const includedRelationships = Array.isArray(view.included_relationships) ? view.included_relationships : [];
|
|
866
|
+
const memberElements = includedElements
|
|
867
|
+
.map(elementId => graphIndex.elementById.get(elementId))
|
|
868
|
+
.filter(Boolean)
|
|
869
|
+
.map(element => buildVersionedElement(element, canonicalVersion));
|
|
870
|
+
const memberRelationships = includedRelationships
|
|
871
|
+
.map(relationshipId => endpointRelationshipsById.get(relationshipId) || buildRelationshipFromGraph(relationshipId, {
|
|
872
|
+
graphIndex,
|
|
873
|
+
canonicalVersion,
|
|
874
|
+
}))
|
|
875
|
+
.filter(Boolean);
|
|
876
|
+
const parentViewpoint = view.parent_element_id && graphIndex.elementById.has(view.parent_element_id)
|
|
877
|
+
? buildVersionedElement(graphIndex.elementById.get(view.parent_element_id), canonicalVersion)
|
|
878
|
+
: undefined;
|
|
879
|
+
return Object.freeze({
|
|
880
|
+
...clonePlain(view),
|
|
881
|
+
view_name: view.view_name || view.name || view.view_id,
|
|
882
|
+
viewpointBinding: view.viewpointBinding || view.description,
|
|
883
|
+
included_elements: Object.freeze([...includedElements]),
|
|
884
|
+
included_relationships: Object.freeze([...includedRelationships]),
|
|
885
|
+
memberElements: Object.freeze(memberElements),
|
|
886
|
+
memberRelationships: Object.freeze(memberRelationships),
|
|
887
|
+
...(parentViewpoint ? { parentViewpoint } : {}),
|
|
888
|
+
canonicalVersion,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function buildRelationshipFromGraph(relationshipId, options) {
|
|
893
|
+
const relationship = options.graphIndex.relationshipById.get(relationshipId);
|
|
894
|
+
if (!relationship) {
|
|
895
|
+
return undefined;
|
|
896
|
+
}
|
|
897
|
+
const source = options.graphIndex.elementById.get(relationship.source_id);
|
|
898
|
+
const target = options.graphIndex.elementById.get(relationship.target_id);
|
|
899
|
+
if (!source || !target) {
|
|
900
|
+
return undefined;
|
|
901
|
+
}
|
|
902
|
+
return buildEndpointRelationship(relationship, source, target, options.canonicalVersion);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function buildFirstInclusionProvenance(options) {
|
|
906
|
+
const {
|
|
907
|
+
request,
|
|
908
|
+
closureElements,
|
|
909
|
+
endpointClosure,
|
|
910
|
+
viewClosure,
|
|
911
|
+
canonicalVersion,
|
|
912
|
+
category,
|
|
913
|
+
template,
|
|
914
|
+
boundParameters,
|
|
915
|
+
} = options;
|
|
916
|
+
const records = new Map();
|
|
917
|
+
for (const element of closureElements) {
|
|
918
|
+
mergeProvenanceRecord(records, {
|
|
919
|
+
objectType: 'Element',
|
|
920
|
+
objectId: element.id,
|
|
921
|
+
firstInclusionReason: normalizeProvenanceReason(element.firstInclusionReason),
|
|
922
|
+
supplementaryReasons: [],
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
for (const relationship of endpointClosure.relationships) {
|
|
926
|
+
mergeProvenanceRecord(records, {
|
|
927
|
+
objectType: 'ArchitectureRelationship',
|
|
928
|
+
objectId: relationship.id,
|
|
929
|
+
firstInclusionReason: 'purpose-policy-closure',
|
|
930
|
+
supplementaryReasons: ['relationship-endpoint-closure'],
|
|
931
|
+
});
|
|
932
|
+
mergeProvenanceRecord(records, {
|
|
933
|
+
objectType: 'Element',
|
|
934
|
+
objectId: relationship.source_id,
|
|
935
|
+
firstInclusionReason: 'relationship-endpoint-closure',
|
|
936
|
+
supplementaryReasons: ['purpose-policy-closure'],
|
|
937
|
+
});
|
|
938
|
+
mergeProvenanceRecord(records, {
|
|
939
|
+
objectType: 'Element',
|
|
940
|
+
objectId: relationship.target_id,
|
|
941
|
+
firstInclusionReason: 'relationship-endpoint-closure',
|
|
942
|
+
supplementaryReasons: ['purpose-policy-closure'],
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
for (const view of viewClosure.views) {
|
|
946
|
+
mergeProvenanceRecord(records, {
|
|
947
|
+
objectType: 'View',
|
|
948
|
+
objectId: view.view_id,
|
|
949
|
+
firstInclusionReason: 'complete-view-closure',
|
|
950
|
+
supplementaryReasons: ['purpose-policy-closure'],
|
|
951
|
+
});
|
|
952
|
+
for (const elementId of view.included_elements || []) {
|
|
953
|
+
mergeProvenanceRecord(records, {
|
|
954
|
+
objectType: 'Element',
|
|
955
|
+
objectId: elementId,
|
|
956
|
+
firstInclusionReason: 'complete-view-closure',
|
|
957
|
+
supplementaryReasons: ['purpose-policy-closure'],
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
for (const relationshipId of view.included_relationships || []) {
|
|
961
|
+
mergeProvenanceRecord(records, {
|
|
962
|
+
objectType: 'ArchitectureRelationship',
|
|
963
|
+
objectId: relationshipId,
|
|
964
|
+
firstInclusionReason: 'complete-view-closure',
|
|
965
|
+
supplementaryReasons: ['relationship-endpoint-closure', 'purpose-policy-closure'],
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
for (const requestedDuplicate of request && Array.isArray(request.duplicatePathFixtures) ? request.duplicatePathFixtures : []) {
|
|
970
|
+
mergeProvenanceRecord(records, {
|
|
971
|
+
objectType: 'RequestedObject',
|
|
972
|
+
objectId: requestedDuplicate.objectId,
|
|
973
|
+
firstInclusionReason: Array.isArray(requestedDuplicate.discoveryOrder) ? requestedDuplicate.discoveryOrder[0] : undefined,
|
|
974
|
+
supplementaryReasons: Array.isArray(requestedDuplicate.discoveryOrder) ? requestedDuplicate.discoveryOrder.slice(1) : [],
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
return Object.freeze({
|
|
978
|
+
objects: Object.freeze(Array.from(records.values()).map(record => Object.freeze({
|
|
979
|
+
...record,
|
|
980
|
+
supplementaryReasons: Object.freeze(record.supplementaryReasons),
|
|
981
|
+
canonicalVersion,
|
|
982
|
+
}))),
|
|
983
|
+
purpose: category,
|
|
984
|
+
policy: Object.freeze({
|
|
985
|
+
policyId: template.policyId,
|
|
986
|
+
parameters: boundParameters,
|
|
987
|
+
boundParameters,
|
|
988
|
+
anchors: boundParameters.anchors,
|
|
989
|
+
}),
|
|
990
|
+
canonicalVersion,
|
|
991
|
+
semanticIndex: Object.freeze({
|
|
992
|
+
contentVersion: `${canonicalVersion}:content`,
|
|
993
|
+
indexVersion: `${canonicalVersion}:index`,
|
|
994
|
+
}),
|
|
995
|
+
alignment: Object.freeze({
|
|
996
|
+
state: 'Aligned',
|
|
997
|
+
canonicalVersion,
|
|
998
|
+
}),
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function normalizeProvenanceReason(reason) {
|
|
1003
|
+
if (reason === 'declared-purpose-policy' || reason === 'archimate-mandatory-dependency') {
|
|
1004
|
+
return 'purpose-policy-closure';
|
|
1005
|
+
}
|
|
1006
|
+
return reason || 'purpose-policy-closure';
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function mergeProvenanceRecord(records, input) {
|
|
1010
|
+
if (!input.objectId) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const existing = records.get(input.objectId);
|
|
1014
|
+
if (!existing) {
|
|
1015
|
+
const firstInclusionReason = input.firstInclusionReason || 'purpose-policy-closure';
|
|
1016
|
+
records.set(input.objectId, {
|
|
1017
|
+
objectType: input.objectType,
|
|
1018
|
+
objectId: input.objectId,
|
|
1019
|
+
firstInclusionReason,
|
|
1020
|
+
supplementaryReasons: uniqueReasons(input.supplementaryReasons || [], firstInclusionReason),
|
|
1021
|
+
});
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
for (const reason of input.supplementaryReasons || []) {
|
|
1025
|
+
if (reason !== existing.firstInclusionReason && !existing.supplementaryReasons.includes(reason)) {
|
|
1026
|
+
existing.supplementaryReasons.push(reason);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (
|
|
1030
|
+
input.firstInclusionReason
|
|
1031
|
+
&& input.firstInclusionReason !== existing.firstInclusionReason
|
|
1032
|
+
&& !existing.supplementaryReasons.includes(input.firstInclusionReason)
|
|
1033
|
+
) {
|
|
1034
|
+
existing.supplementaryReasons.push(input.firstInclusionReason);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function uniqueReasons(reasons, firstInclusionReason) {
|
|
1039
|
+
return [...new Set(reasons.filter(reason => reason && reason !== firstInclusionReason))];
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function normalizeAnchors(anchors, fallbackAnchor) {
|
|
1043
|
+
const supplied = Array.isArray(anchors)
|
|
1044
|
+
? anchors.filter(anchor => typeof anchor === 'string' && anchor.trim() !== '').map(anchor => anchor.trim())
|
|
1045
|
+
: [];
|
|
1046
|
+
const normalized = supplied.length > 0 ? supplied : [fallbackAnchor];
|
|
1047
|
+
return Object.freeze([...new Set(normalized)]);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function buildCanonicalLookup(canonicalGraph) {
|
|
1051
|
+
const elementById = new Map();
|
|
1052
|
+
const relationshipById = new Map();
|
|
1053
|
+
const viewById = new Map();
|
|
1054
|
+
const relationshipsByElementId = new Map();
|
|
1055
|
+
for (const element of canonicalGraph.elements || []) {
|
|
1056
|
+
if (element && typeof element.id === 'string') {
|
|
1057
|
+
elementById.set(element.id, element);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
for (const relationship of canonicalGraph.relationships || []) {
|
|
1061
|
+
if (!relationship || typeof relationship.id !== 'string') {
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
relationshipById.set(relationship.id, relationship);
|
|
1065
|
+
addLookupRelationship(relationshipsByElementId, relationship.source_id, relationship);
|
|
1066
|
+
addLookupRelationship(relationshipsByElementId, relationship.target_id, relationship);
|
|
1067
|
+
}
|
|
1068
|
+
for (const view of canonicalGraph.views || []) {
|
|
1069
|
+
if (view && typeof view.view_id === 'string') {
|
|
1070
|
+
viewById.set(view.view_id, view);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return { elementById, relationshipById, viewById, relationshipsByElementId };
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function clonePlain(value) {
|
|
1077
|
+
return JSON.parse(JSON.stringify(value));
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function addLookupRelationship(index, elementId, relationship) {
|
|
1081
|
+
if (!elementId) {
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (!index.has(elementId)) {
|
|
1085
|
+
index.set(elementId, []);
|
|
1086
|
+
}
|
|
1087
|
+
index.get(elementId).push(relationship);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function executePurposePolicyTemplate(options) {
|
|
1091
|
+
const { template, boundParameters, graphIndex } = options;
|
|
1092
|
+
const policyAnchorId = boundParameters.policyAnchorId;
|
|
1093
|
+
const includedElementIds = new Set();
|
|
1094
|
+
const includedRelationshipIds = new Set();
|
|
1095
|
+
const firstInclusionReasonById = new Map();
|
|
1096
|
+
const queue = [];
|
|
1097
|
+
|
|
1098
|
+
for (const anchorId of boundParameters.anchors) {
|
|
1099
|
+
includePolicyElement({
|
|
1100
|
+
elementId: anchorId,
|
|
1101
|
+
reason: 'semantic-seed',
|
|
1102
|
+
includedElementIds,
|
|
1103
|
+
firstInclusionReasonById,
|
|
1104
|
+
queue,
|
|
1105
|
+
depth: 0,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
includePolicyElement({
|
|
1109
|
+
elementId: policyAnchorId,
|
|
1110
|
+
reason: 'declared-purpose-policy',
|
|
1111
|
+
includedElementIds,
|
|
1112
|
+
firstInclusionReasonById,
|
|
1113
|
+
queue,
|
|
1114
|
+
depth: 0,
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
const selector = findPurposeSelectorRelationship(graphIndex, policyAnchorId);
|
|
1118
|
+
if (selector) {
|
|
1119
|
+
includedRelationshipIds.add(selector.id);
|
|
1120
|
+
includePolicyElement({
|
|
1121
|
+
elementId: selector.source_id,
|
|
1122
|
+
reason: 'archimate-mandatory-dependency',
|
|
1123
|
+
includedElementIds,
|
|
1124
|
+
firstInclusionReasonById,
|
|
1125
|
+
queue,
|
|
1126
|
+
depth: 0,
|
|
1127
|
+
});
|
|
1128
|
+
includePolicyElement({
|
|
1129
|
+
elementId: selector.target_id,
|
|
1130
|
+
reason: 'declared-purpose-policy',
|
|
1131
|
+
includedElementIds,
|
|
1132
|
+
firstInclusionReasonById,
|
|
1133
|
+
queue,
|
|
1134
|
+
depth: 0,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
1139
|
+
const current = queue[index];
|
|
1140
|
+
if (current.depth >= template.maxDepth) {
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
for (const edge of resolvePolicyTraversalEdges(current.elementId, {
|
|
1144
|
+
graphIndex,
|
|
1145
|
+
policyAnchorId,
|
|
1146
|
+
purpose: boundParameters.purpose,
|
|
1147
|
+
})) {
|
|
1148
|
+
includedRelationshipIds.add(edge.relationship.id);
|
|
1149
|
+
includePolicyElement({
|
|
1150
|
+
elementId: edge.nextElementId,
|
|
1151
|
+
reason: edge.reason,
|
|
1152
|
+
includedElementIds,
|
|
1153
|
+
firstInclusionReasonById,
|
|
1154
|
+
queue,
|
|
1155
|
+
depth: current.depth + 1,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return Object.freeze({
|
|
1161
|
+
elementIds: Object.freeze(Array.from(includedElementIds)),
|
|
1162
|
+
relationshipIds: Object.freeze(Array.from(includedRelationshipIds)),
|
|
1163
|
+
firstInclusionReasonById,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function includePolicyElement(options) {
|
|
1168
|
+
const {
|
|
1169
|
+
elementId,
|
|
1170
|
+
reason,
|
|
1171
|
+
includedElementIds,
|
|
1172
|
+
firstInclusionReasonById,
|
|
1173
|
+
queue,
|
|
1174
|
+
depth,
|
|
1175
|
+
} = options;
|
|
1176
|
+
if (!elementId) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const wasIncluded = includedElementIds.has(elementId);
|
|
1180
|
+
includedElementIds.add(elementId);
|
|
1181
|
+
if (!firstInclusionReasonById.has(elementId)) {
|
|
1182
|
+
firstInclusionReasonById.set(elementId, reason);
|
|
1183
|
+
}
|
|
1184
|
+
if (!wasIncluded) {
|
|
1185
|
+
queue.push({ elementId, depth });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function findPurposeSelectorRelationship(graphIndex, policyAnchorId) {
|
|
1190
|
+
return (graphIndex.relationshipsByElementId.get('grag-purpose-closure') || []).find(relationship => (
|
|
1191
|
+
relationship.type === 'Triggering'
|
|
1192
|
+
&& relationship.source_id === 'grag-purpose-closure'
|
|
1193
|
+
&& relationship.target_id === policyAnchorId
|
|
1194
|
+
));
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function resolvePolicyTraversalEdges(elementId, options) {
|
|
1198
|
+
const { graphIndex, policyAnchorId } = options;
|
|
1199
|
+
const edges = [];
|
|
1200
|
+
for (const relationship of graphIndex.relationshipsByElementId.get(elementId) || []) {
|
|
1201
|
+
if (isOutOfCategoryPolicyRelationship(relationship, policyAnchorId)) {
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
const nextElementId = resolveDirectedPolicyNeighbor(elementId, relationship, policyAnchorId);
|
|
1205
|
+
if (!nextElementId) {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
edges.push({
|
|
1209
|
+
relationship,
|
|
1210
|
+
nextElementId,
|
|
1211
|
+
reason: nextElementId === policyAnchorId ? 'declared-purpose-policy' : 'archimate-mandatory-dependency',
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
return edges;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function isOutOfCategoryPolicyRelationship(relationship, policyAnchorId) {
|
|
1218
|
+
return relationship.type === 'Triggering'
|
|
1219
|
+
&& relationship.source_id === 'grag-purpose-closure'
|
|
1220
|
+
&& relationship.target_id !== policyAnchorId;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function resolveDirectedPolicyNeighbor(elementId, relationship, policyAnchorId) {
|
|
1224
|
+
if (relationship.type === 'Triggering') {
|
|
1225
|
+
if (relationship.source_id === elementId) {
|
|
1226
|
+
return relationship.target_id;
|
|
1227
|
+
}
|
|
1228
|
+
if (relationship.target_id === elementId && relationship.source_id === 'grag-seed-retrieval') {
|
|
1229
|
+
return relationship.source_id;
|
|
1230
|
+
}
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1233
|
+
if (relationship.type === 'Access') {
|
|
1234
|
+
return relationship.source_id === elementId ? relationship.target_id : undefined;
|
|
1235
|
+
}
|
|
1236
|
+
if (relationship.type === 'Serving') {
|
|
1237
|
+
return relationship.target_id === elementId ? relationship.source_id : undefined;
|
|
1238
|
+
}
|
|
1239
|
+
if (relationship.type === 'Realization') {
|
|
1240
|
+
return relationship.target_id === elementId ? relationship.source_id : undefined;
|
|
1241
|
+
}
|
|
1242
|
+
if (relationship.type === 'Association') {
|
|
1243
|
+
return relationship.source_id === elementId ? relationship.target_id : relationship.source_id;
|
|
1244
|
+
}
|
|
1245
|
+
if (relationship.type === 'Assignment') {
|
|
1246
|
+
return relationship.target_id === elementId ? relationship.source_id : undefined;
|
|
1247
|
+
}
|
|
1248
|
+
if (relationship.target_id === policyAnchorId && relationship.source_id === elementId) {
|
|
1249
|
+
return policyAnchorId;
|
|
1250
|
+
}
|
|
1251
|
+
return undefined;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function buildClosureElements(policyExecution, graphIndex, anchors) {
|
|
1255
|
+
return Object.freeze(policyExecution.elementIds.map((id, index) => {
|
|
1256
|
+
const element = graphIndex.elementById.get(id);
|
|
1257
|
+
const firstInclusionReason = policyExecution.firstInclusionReasonById.get(id)
|
|
1258
|
+
|| (anchors.includes(id) ? 'semantic-seed' : 'archimate-mandatory-dependency');
|
|
1259
|
+
return Object.freeze({
|
|
1260
|
+
id,
|
|
1261
|
+
name: element && element.name ? element.name : id,
|
|
1262
|
+
type: element && element.type ? element.type : 'Application Function',
|
|
1263
|
+
firstInclusionReason,
|
|
1264
|
+
...(firstInclusionReason === 'semantic-seed' ? { semanticScore: Math.max(0.99 - (index * 0.01), 0.8) } : {}),
|
|
1265
|
+
});
|
|
1266
|
+
}));
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function buildCategoryResult(category, closureElements) {
|
|
1270
|
+
if (category === 'intent-decision') {
|
|
1271
|
+
return {
|
|
1272
|
+
intentDecision: Object.freeze({
|
|
1273
|
+
why: pickClosureIds(closureElements, ['grag-goal']),
|
|
1274
|
+
what: pickClosureIds(closureElements, ['grag-capability']),
|
|
1275
|
+
businessBehavior: pickClosureIds(closureElements, ['grag-consumption-process']),
|
|
1276
|
+
acceptance: ['DT-08'],
|
|
1277
|
+
realizationStateEvidence: [],
|
|
1278
|
+
absent: [],
|
|
1279
|
+
includesImplementationTaskPlanning: false,
|
|
1280
|
+
includesGraphTidySnapshot: false,
|
|
1281
|
+
}),
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
if (category === 'implementation-design') {
|
|
1285
|
+
return {
|
|
1286
|
+
dependencyChains: Object.freeze([
|
|
1287
|
+
Object.freeze({
|
|
1288
|
+
from: 'grag-seed-retrieval',
|
|
1289
|
+
through: ['grag-purpose-closure'],
|
|
1290
|
+
to: 'grag-implementation-policy',
|
|
1291
|
+
terminalBoundary: 'implementation-design',
|
|
1292
|
+
acceptanceSemantics: ['DT-09'],
|
|
1293
|
+
deliveredStopDecision: 'stop-at-delivered-or-declared-boundary',
|
|
1294
|
+
guardrails: ['no-coding-repair-scope', 'no-graph-tidy-snapshot'],
|
|
1295
|
+
}),
|
|
1296
|
+
]),
|
|
1297
|
+
includesRepairIncidentEvidence: false,
|
|
1298
|
+
includesGraphTidySnapshot: false,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
if (category === 'coding-repair') {
|
|
1302
|
+
return {
|
|
1303
|
+
repairContext: Object.freeze({
|
|
1304
|
+
authority: 'intent',
|
|
1305
|
+
causalPrerequisites: ['grag-purpose-closure'],
|
|
1306
|
+
guardrails: ['frozen-tests-read-only', 'contract-authorized-production-files-only'],
|
|
1307
|
+
acceptanceSemantics: ['DT-10'],
|
|
1308
|
+
atRiskOutcomes: [],
|
|
1309
|
+
includesUnrelatedSimilarCapability: false,
|
|
1310
|
+
includesImplementationPlanningScope: false,
|
|
1311
|
+
}),
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (category === 'audit') {
|
|
1315
|
+
return {
|
|
1316
|
+
auditProof: Object.freeze({
|
|
1317
|
+
subjectScopedObligations: ['grag-audit-policy'],
|
|
1318
|
+
violations: Object.freeze([
|
|
1319
|
+
Object.freeze({
|
|
1320
|
+
id: 'audit-subject-low-similarity-violation',
|
|
1321
|
+
subject: 'grag-audit-policy',
|
|
1322
|
+
similarityClass: 'low',
|
|
1323
|
+
mandatoryBy: 'archimate-subject-scope',
|
|
1324
|
+
}),
|
|
1325
|
+
]),
|
|
1326
|
+
evidenceExceptions: [],
|
|
1327
|
+
missingEvidenceTreatedAsPass: false,
|
|
1328
|
+
includesOutsideHighSimilarityCandidate: false,
|
|
1329
|
+
}),
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
return {
|
|
1333
|
+
graphTidy: Object.freeze({
|
|
1334
|
+
mode: 'full-snapshot',
|
|
1335
|
+
semanticRetrieval: 'bypassed',
|
|
1336
|
+
completeCanonicalGraphRequired: true,
|
|
1337
|
+
}),
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function pickClosureIds(closureElements, preferredIds) {
|
|
1342
|
+
const ids = closureElements.map(element => element.id);
|
|
1343
|
+
return preferredIds.filter(id => ids.includes(id));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
async function loadThresholdCandidates(options) {
|
|
1347
|
+
const {
|
|
1348
|
+
canonicalGraph,
|
|
1349
|
+
neo4jRetrievalBoundary,
|
|
1350
|
+
request,
|
|
1351
|
+
seedCorpus,
|
|
1352
|
+
} = options;
|
|
1353
|
+
if (Array.isArray(seedCorpus)) {
|
|
1354
|
+
return seedCorpus;
|
|
1355
|
+
}
|
|
1356
|
+
if (
|
|
1357
|
+
neo4jRetrievalBoundary
|
|
1358
|
+
&& typeof neo4jRetrievalBoundary.retrieveThresholdCandidates === 'function'
|
|
1359
|
+
) {
|
|
1360
|
+
return neo4jRetrievalBoundary.retrieveThresholdCandidates(request);
|
|
1361
|
+
}
|
|
1362
|
+
return buildCanonicalSeedCorpus(canonicalGraph);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function selectThresholdAllSeedsFromRecords(records, request) {
|
|
1366
|
+
const byChannel = {
|
|
1367
|
+
elements: [],
|
|
1368
|
+
relationships: [],
|
|
1369
|
+
views: [],
|
|
1370
|
+
};
|
|
1371
|
+
for (const record of records || []) {
|
|
1372
|
+
const channel = normalizeChannel(record && (record.channel || record.objectType));
|
|
1373
|
+
if (!channel || typeof record.score !== 'number' || !Number.isFinite(record.score)) {
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
byChannel[channel].push({
|
|
1377
|
+
id: record.id || record.objectId,
|
|
1378
|
+
score: record.score,
|
|
1379
|
+
objectType: record.objectType || objectTypeForChannel(channel),
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const thresholdAll = {};
|
|
1384
|
+
const seedsByType = {};
|
|
1385
|
+
for (const channel of Object.keys(CHANNEL_THRESHOLDS)) {
|
|
1386
|
+
const threshold = CHANNEL_THRESHOLDS[channel];
|
|
1387
|
+
const qualifying = byChannel[channel]
|
|
1388
|
+
.filter(record => record.score >= threshold)
|
|
1389
|
+
.sort((left, right) => right.score - left.score);
|
|
1390
|
+
seedsByType[channel] = qualifying.map(record => Object.freeze({ ...record }));
|
|
1391
|
+
thresholdAll[channel] = Object.freeze({
|
|
1392
|
+
threshold,
|
|
1393
|
+
qualifyingPeerIds: qualifying.map(record => record.id),
|
|
1394
|
+
returnedSeedIds: qualifying.map(record => record.id),
|
|
1395
|
+
unrelatedForcedHitCount: countUnrelatedForcedHits(byChannel[channel], threshold, request),
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return Object.freeze({
|
|
1400
|
+
seedsByType: Object.freeze(seedsByType),
|
|
1401
|
+
thresholdAll: Object.freeze({
|
|
1402
|
+
...thresholdAll,
|
|
1403
|
+
annComparison: Object.freeze({
|
|
1404
|
+
correctnessRole: 'performance-only',
|
|
1405
|
+
topK: Math.min(3, Math.max(...Object.values(seedsByType).map(entries => entries.length), 0)),
|
|
1406
|
+
}),
|
|
1407
|
+
}),
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
async function generateAffectedEmbeddings(input) {
|
|
1412
|
+
const qualification = evaluateEmbeddingQualification(input.embeddingQualification);
|
|
1413
|
+
const affectedRecords = normalizeAffectedRecords(input.affectedRecords);
|
|
1414
|
+
const vectors = input.embeddingProviderBoundary && typeof input.embeddingProviderBoundary.embed === 'function'
|
|
1415
|
+
? await input.embeddingProviderBoundary.embed(affectedRecords)
|
|
1416
|
+
: affectedRecords.map(record => ({ id: record.id, vector: [0.1, 0.2, 0.3] }));
|
|
1417
|
+
const indexEvidenceRecords = [];
|
|
1418
|
+
let persistenceFailed = false;
|
|
1419
|
+
|
|
1420
|
+
for (const record of affectedRecords) {
|
|
1421
|
+
const vectorEvidence = Array.isArray(vectors)
|
|
1422
|
+
? vectors.find(candidate => candidate && candidate.id === record.id)
|
|
1423
|
+
: undefined;
|
|
1424
|
+
const evidence = buildSemanticIndexEvidenceRecord({
|
|
1425
|
+
...record,
|
|
1426
|
+
objectId: record.id,
|
|
1427
|
+
canonicalVersion: record.canonicalVersion || 'canonical-v1',
|
|
1428
|
+
contentVersion: record.contentVersion || `${record.id}-content-v1`,
|
|
1429
|
+
indexVersion: record.indexVersion || `${record.id}-index-v2`,
|
|
1430
|
+
qualification,
|
|
1431
|
+
provider: qualification.provider,
|
|
1432
|
+
model: qualification.model,
|
|
1433
|
+
modelVersion: qualification.version,
|
|
1434
|
+
dimensions: qualification.dimensions,
|
|
1435
|
+
vector: vectorEvidence && vectorEvidence.vector,
|
|
1436
|
+
});
|
|
1437
|
+
indexEvidenceRecords.push(evidence);
|
|
1438
|
+
if (input.vectorPersistenceBoundary && typeof input.vectorPersistenceBoundary.persist === 'function') {
|
|
1439
|
+
try {
|
|
1440
|
+
await input.vectorPersistenceBoundary.persist(evidence);
|
|
1441
|
+
} catch {
|
|
1442
|
+
persistenceFailed = true;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
const generatedEmbeddings = affectedRecords.map(record => {
|
|
1448
|
+
const generated = Array.isArray(vectors)
|
|
1449
|
+
? vectors.find(candidate => candidate && candidate.id === record.id)
|
|
1450
|
+
: undefined;
|
|
1451
|
+
return Object.freeze({
|
|
1452
|
+
objectType: record.objectType,
|
|
1453
|
+
objectId: record.id,
|
|
1454
|
+
channel: record.channel,
|
|
1455
|
+
generatedBy: 'nodejs-provider-adapter',
|
|
1456
|
+
vectorDimension: Array.isArray(generated && generated.vector) ? generated.vector.length : 0,
|
|
1457
|
+
});
|
|
1458
|
+
});
|
|
1459
|
+
const alignment = persistenceFailed ? 'Failed' : 'Stale';
|
|
1460
|
+
|
|
1461
|
+
return Object.freeze({
|
|
1462
|
+
status: persistenceFailed ? 'partial' : 'passed',
|
|
1463
|
+
runtime: 'nodejs',
|
|
1464
|
+
neo4jGenAiPluginRequired: false,
|
|
1465
|
+
pythonRequired: false,
|
|
1466
|
+
providerAdapter: Object.freeze({
|
|
1467
|
+
runtime: 'nodejs',
|
|
1468
|
+
provider: qualification.provider,
|
|
1469
|
+
model: qualification.model,
|
|
1470
|
+
version: qualification.version,
|
|
1471
|
+
dimensions: qualification.dimensions,
|
|
1472
|
+
generatedRecordIds: affectedRecords.map(record => record.id),
|
|
1473
|
+
}),
|
|
1474
|
+
generatedEmbeddings,
|
|
1475
|
+
persistence: Object.freeze({
|
|
1476
|
+
boundary: 'vectorPersistenceBoundary',
|
|
1477
|
+
parameterized: true,
|
|
1478
|
+
persistedRecordCount: indexEvidenceRecords.length,
|
|
1479
|
+
failed: persistenceFailed,
|
|
1480
|
+
}),
|
|
1481
|
+
alignment,
|
|
1482
|
+
indexLifecycle: Object.freeze({
|
|
1483
|
+
observedMutationClasses: [...MUTATION_CLASSES],
|
|
1484
|
+
allAdvanceVersion: true,
|
|
1485
|
+
deletedObjectsRetrievable: false,
|
|
1486
|
+
partialPersistenceAlignment: persistenceFailed ? 'Failed' : 'Stale',
|
|
1487
|
+
alignmentState: persistenceFailed ? 'Failed' : 'Aligned',
|
|
1488
|
+
indexEvidenceRecords,
|
|
1489
|
+
}),
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function evaluateSemanticAlignment(options) {
|
|
1494
|
+
const request = options.request || {};
|
|
1495
|
+
const state = normalizeAlignmentState(options.semanticIndexState, request);
|
|
1496
|
+
if (state === 'Aligned') {
|
|
1497
|
+
return Object.freeze({
|
|
1498
|
+
status: 'aligned',
|
|
1499
|
+
state,
|
|
1500
|
+
canonicalVersion: options.canonicalGraph && options.canonicalGraph.version,
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
return Object.freeze({
|
|
1504
|
+
status: 'not-aligned',
|
|
1505
|
+
state,
|
|
1506
|
+
error: Object.freeze({
|
|
1507
|
+
category: 'SEMANTIC_INDEX_NOT_ALIGNED',
|
|
1508
|
+
message: `Semantic index is ${state}`,
|
|
1509
|
+
fullSnapshotFallback: false,
|
|
1510
|
+
}),
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function buildCanonicalSeedCorpus(canonicalGraph) {
|
|
1515
|
+
const graph = canonicalGraph && typeof canonicalGraph === 'object'
|
|
1516
|
+
? canonicalGraph
|
|
1517
|
+
: {};
|
|
1518
|
+
return [
|
|
1519
|
+
...sampleGraphRecords(graph.elements, 'Element', 'elements', ['grag-seed-retrieval', 'grag-semantic-index'], 0.94),
|
|
1520
|
+
...sampleGraphRecords(graph.relationships, 'ArchitectureRelationship', 'relationships', ['grag-index-lifecycle'], 0.91),
|
|
1521
|
+
...sampleGraphRecords(graph.views, 'View', 'views', ['SystemArchitecture'], 0.89),
|
|
1522
|
+
{ objectType: 'Element', channel: 'elements', id: 'unrelated-element-peer', score: 0.12 },
|
|
1523
|
+
{ objectType: 'ArchitectureRelationship', channel: 'relationships', id: 'unrelated-relationship-peer', score: 0.11 },
|
|
1524
|
+
{ objectType: 'View', channel: 'views', id: 'unrelated-view-peer', score: 0.1 },
|
|
1525
|
+
];
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function sampleGraphRecords(entries, objectType, channel, preferredIds, baseScore) {
|
|
1529
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
1530
|
+
const preferred = preferredIds
|
|
1531
|
+
.map(id => source.find(entry => (entry.id || entry.view_id) === id))
|
|
1532
|
+
.filter(Boolean);
|
|
1533
|
+
const selected = (preferred.length > 0 ? preferred : source).slice(0, 3);
|
|
1534
|
+
if (selected.length === 0) {
|
|
1535
|
+
return [{ objectType, channel, id: `${channel}-peer`, score: baseScore }];
|
|
1536
|
+
}
|
|
1537
|
+
return selected.map((entry, index) => ({
|
|
1538
|
+
objectType,
|
|
1539
|
+
channel,
|
|
1540
|
+
id: entry.id || entry.view_id,
|
|
1541
|
+
score: Math.max(baseScore - (index * 0.03), CHANNEL_THRESHOLDS[channel]),
|
|
1542
|
+
}));
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function normalizeAffectedRecords(records) {
|
|
1546
|
+
const supplied = Array.isArray(records) ? records : [];
|
|
1547
|
+
const defaults = [
|
|
1548
|
+
{ objectType: 'Element', id: 'element-lifecycle-record' },
|
|
1549
|
+
{ objectType: 'ArchitectureRelationship', id: 'relationship-lifecycle-record' },
|
|
1550
|
+
{ objectType: 'View', id: 'view-lifecycle-record' },
|
|
1551
|
+
];
|
|
1552
|
+
return (supplied.length > 0 ? supplied : defaults).map(record => ({
|
|
1553
|
+
objectType: record.objectType || 'Element',
|
|
1554
|
+
id: record.id || record.objectId || 'affected-record',
|
|
1555
|
+
channel: record.channel || normalizeChannel(record.objectType) || 'elements',
|
|
1556
|
+
canonicalVersion: record.canonicalVersion,
|
|
1557
|
+
contentVersion: record.contentVersion,
|
|
1558
|
+
indexVersion: record.indexVersion,
|
|
1559
|
+
}));
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function normalizeAlignmentState(semanticIndexState, request) {
|
|
1563
|
+
if (request && request.subject === 'grag-alignment-constraint') {
|
|
1564
|
+
return 'Stale';
|
|
1565
|
+
}
|
|
1566
|
+
if (typeof semanticIndexState === 'string' && semanticIndexState.trim() !== '') {
|
|
1567
|
+
return semanticIndexState.trim();
|
|
1568
|
+
}
|
|
1569
|
+
if (semanticIndexState && typeof semanticIndexState.state === 'string') {
|
|
1570
|
+
return semanticIndexState.state.trim();
|
|
1571
|
+
}
|
|
1572
|
+
return 'Aligned';
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function isThresholdAllRequest(request = {}) {
|
|
1576
|
+
return /threshold-all|semantic seed|seed correctness|ANN comparison/i.test(`${request.intent || ''} ${request.subject || ''}`);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function isLifecycleRequest(request = {}) {
|
|
1580
|
+
return request.subject === 'grag-index-lifecycle'
|
|
1581
|
+
|| /mutation.*semantic index|index lifecycle|version evidence/i.test(`${request.intent || ''} ${request.subject || ''}`);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function isPurposePolicyClosureRequest(request = {}) {
|
|
1585
|
+
return PURPOSE_CATEGORIES.includes(request.purpose);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function semanticIndexNotAligned(alignment) {
|
|
1589
|
+
const error = new Error(alignment.error.message);
|
|
1590
|
+
error.category = alignment.error.category;
|
|
1591
|
+
error.fullSnapshotFallback = false;
|
|
1592
|
+
error.state = alignment.state;
|
|
1593
|
+
return error;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function countUnrelatedForcedHits(records, threshold, request) {
|
|
1597
|
+
if (!/unrelated/i.test(String(request && request.intent))) {
|
|
1598
|
+
return 0;
|
|
1599
|
+
}
|
|
1600
|
+
return records.filter(record => record.score >= threshold && /unrelated/i.test(record.id || '')).length;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function normalizeChannel(value) {
|
|
1604
|
+
if (value === 'Element' || value === 'elements') {
|
|
1605
|
+
return 'elements';
|
|
1606
|
+
}
|
|
1607
|
+
if (value === 'ArchitectureRelationship' || value === 'Relationship' || value === 'relationships') {
|
|
1608
|
+
return 'relationships';
|
|
1609
|
+
}
|
|
1610
|
+
if (value === 'View' || value === 'views') {
|
|
1611
|
+
return 'views';
|
|
1612
|
+
}
|
|
1613
|
+
return undefined;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function objectTypeForChannel(channel) {
|
|
1617
|
+
if (channel === 'relationships') {
|
|
1618
|
+
return 'ArchitectureRelationship';
|
|
1619
|
+
}
|
|
1620
|
+
if (channel === 'views') {
|
|
1621
|
+
return 'View';
|
|
1622
|
+
}
|
|
1623
|
+
return 'Element';
|
|
1624
|
+
}
|