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,171 @@
|
|
|
1
|
+
const {
|
|
2
|
+
evaluateEmbeddingQualification,
|
|
3
|
+
} = require('../embeddingQualificationGate.js');
|
|
4
|
+
|
|
5
|
+
const CHANNELS = new Set(['Element', 'ArchitectureRelationship', 'View']);
|
|
6
|
+
const STRING_METADATA_FIELDS = Object.freeze([
|
|
7
|
+
'canonicalIdentity',
|
|
8
|
+
'canonicalVersion',
|
|
9
|
+
'contentVersion',
|
|
10
|
+
'indexVersion',
|
|
11
|
+
'provider',
|
|
12
|
+
'model',
|
|
13
|
+
'modelVersion',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function createProductionSemanticProjectionStore(dependencies = {}) {
|
|
17
|
+
const {
|
|
18
|
+
persistenceAdapter,
|
|
19
|
+
canonicalAuthority,
|
|
20
|
+
configuration,
|
|
21
|
+
qualification,
|
|
22
|
+
} = dependencies;
|
|
23
|
+
requireAdapter(persistenceAdapter);
|
|
24
|
+
if (!canonicalAuthority || typeof canonicalAuthority.assertProjectionOnly !== 'function') {
|
|
25
|
+
throw new TypeError('canonicalAuthority.assertProjectionOnly is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function requireReleaseGates() {
|
|
29
|
+
requireExternalConfiguration(configuration);
|
|
30
|
+
const approved = evaluateEmbeddingQualification(qualification);
|
|
31
|
+
canonicalAuthority.assertProjectionOnly();
|
|
32
|
+
return approved;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
async upsertRecords(records) {
|
|
37
|
+
const supplied = requireArray(records, 'records');
|
|
38
|
+
rejectRunIds(supplied);
|
|
39
|
+
const approved = requireReleaseGates();
|
|
40
|
+
const validated = supplied.map(record => validateRecord(record, approved));
|
|
41
|
+
await persistenceAdapter.upsertRecords(validated);
|
|
42
|
+
return Object.freeze({ count: validated.length });
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async deleteTombstones(tombstones) {
|
|
46
|
+
const supplied = requireArray(tombstones, 'tombstones');
|
|
47
|
+
rejectRunIds(supplied);
|
|
48
|
+
requireReleaseGates();
|
|
49
|
+
const validated = supplied.map(validateTombstone);
|
|
50
|
+
await persistenceAdapter.deleteTombstones(validated);
|
|
51
|
+
return Object.freeze({ count: validated.length });
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async readRecords() {
|
|
55
|
+
requireReleaseGates();
|
|
56
|
+
const records = await persistenceAdapter.readRecords();
|
|
57
|
+
return Object.freeze((records || []).map(record => Object.freeze({
|
|
58
|
+
...record,
|
|
59
|
+
...(Array.isArray(record.vector) ? { vector: Object.freeze([...record.vector]) } : {}),
|
|
60
|
+
})));
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async close() {
|
|
64
|
+
await persistenceAdapter.close();
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireAdapter(adapter) {
|
|
70
|
+
for (const method of ['upsertRecords', 'deleteTombstones', 'readRecords', 'close']) {
|
|
71
|
+
if (!adapter || typeof adapter[method] !== 'function') {
|
|
72
|
+
throw new TypeError(`persistenceAdapter.${method} is required`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function requireExternalConfiguration(configuration) {
|
|
78
|
+
const supplied = configuration && typeof configuration === 'object' ? configuration : {};
|
|
79
|
+
const required = [
|
|
80
|
+
['neo4jDatabaseUrl', 'neo4jUri'],
|
|
81
|
+
['neo4jDatabaseUsername', 'neo4jUsername'],
|
|
82
|
+
['neo4jDatabasePassword', 'neo4jPassword'],
|
|
83
|
+
['embeddingCredential'],
|
|
84
|
+
];
|
|
85
|
+
for (const alternatives of required) {
|
|
86
|
+
const value = alternatives.map(field => supplied[field]).find(isNonBlankString);
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
throw categoryError('EXTERNAL_CREDENTIALS_REQUIRED', alternatives[0]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateRecord(record, qualification) {
|
|
94
|
+
if (!record || typeof record !== 'object') {
|
|
95
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', 'record');
|
|
96
|
+
}
|
|
97
|
+
for (const field of STRING_METADATA_FIELDS) {
|
|
98
|
+
if (!isNonBlankString(record[field])) {
|
|
99
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', field);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!CHANNELS.has(record.channel)) {
|
|
103
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', 'channel');
|
|
104
|
+
}
|
|
105
|
+
if (!Number.isInteger(record.dimensions) || record.dimensions <= 0) {
|
|
106
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', 'dimensions');
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
!Array.isArray(record.vector)
|
|
110
|
+
|| record.vector.length !== record.dimensions
|
|
111
|
+
|| record.vector.some(value => typeof value !== 'number' || !Number.isFinite(value))
|
|
112
|
+
) {
|
|
113
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', 'vector');
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
record.provider !== qualification.provider
|
|
117
|
+
|| record.model !== qualification.model
|
|
118
|
+
|| record.modelVersion !== qualification.version
|
|
119
|
+
|| record.dimensions !== qualification.dimensions
|
|
120
|
+
) {
|
|
121
|
+
throw categoryError('SP02_SEMANTIC_RECORD_QUALIFICATION_MISMATCH', 'qualification');
|
|
122
|
+
}
|
|
123
|
+
return Object.freeze({
|
|
124
|
+
...record,
|
|
125
|
+
vector: Object.freeze([...record.vector]),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function validateTombstone(tombstone) {
|
|
130
|
+
if (
|
|
131
|
+
!tombstone
|
|
132
|
+
|| !isNonBlankString(tombstone.canonicalIdentity)
|
|
133
|
+
|| !CHANNELS.has(tombstone.channel)
|
|
134
|
+
|| !isNonBlankString(tombstone.canonicalVersion)
|
|
135
|
+
) {
|
|
136
|
+
throw categoryError('SP02_TOMBSTONE_METADATA_INVALID', 'tombstone');
|
|
137
|
+
}
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
canonicalIdentity: tombstone.canonicalIdentity.trim(),
|
|
140
|
+
channel: tombstone.channel,
|
|
141
|
+
canonicalVersion: tombstone.canonicalVersion.trim(),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rejectRunIds(records) {
|
|
146
|
+
if (records.some(record => record && Object.prototype.hasOwnProperty.call(record, 'runId'))) {
|
|
147
|
+
throw categoryError('SP02_PRODUCTION_RUNID_PROHIBITED', 'runId');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function requireArray(value, field) {
|
|
152
|
+
if (!Array.isArray(value)) {
|
|
153
|
+
throw categoryError('SP02_SEMANTIC_RECORD_METADATA_INVALID', field);
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isNonBlankString(value) {
|
|
159
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function categoryError(category, field) {
|
|
163
|
+
const error = new Error(category);
|
|
164
|
+
error.category = category;
|
|
165
|
+
error.field = field;
|
|
166
|
+
return error;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
createProductionSemanticProjectionStore,
|
|
171
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function semanticOperatorErrorPayload(error) {
|
|
2
|
+
return Object.freeze({
|
|
3
|
+
category: typeof error.category === 'string' ? error.category : 'SEMANTIC_OPERATOR_ERROR',
|
|
4
|
+
state: typeof error.state === 'string' ? error.state : null,
|
|
5
|
+
verified: error.verified === true,
|
|
6
|
+
canonicalVersion: typeof error.canonicalVersion === 'string' ? error.canonicalVersion : null,
|
|
7
|
+
contentVersion: typeof error.contentVersion === 'string' ? error.contentVersion : null,
|
|
8
|
+
indexVersion: typeof error.indexVersion === 'string' ? error.indexVersion : null,
|
|
9
|
+
completedChannels: Array.isArray(error.completedChannels) ? [...error.completedChannels] : [],
|
|
10
|
+
missingChannels: Array.isArray(error.missingChannels) ? [...error.missingChannels] : [],
|
|
11
|
+
mismatchedChannels: Array.isArray(error.mismatchedChannels) ? [...error.mismatchedChannels] : [],
|
|
12
|
+
fullSnapshotFallback: false,
|
|
13
|
+
action: typeof error.action === 'string' ? error.action : 'Correct readiness and retry',
|
|
14
|
+
...(typeof error.field === 'string' ? { field: error.field } : {}),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function semanticOperatorErrorResult(error) {
|
|
19
|
+
const payload = Object.freeze({
|
|
20
|
+
status: 'failed',
|
|
21
|
+
error: semanticOperatorErrorPayload(error),
|
|
22
|
+
});
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
...payload,
|
|
25
|
+
content: Object.freeze([
|
|
26
|
+
Object.freeze({
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify(payload),
|
|
29
|
+
}),
|
|
30
|
+
]),
|
|
31
|
+
isError: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
semanticOperatorErrorPayload,
|
|
37
|
+
semanticOperatorErrorResult,
|
|
38
|
+
};
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
const crypto = require('node:crypto');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
getWorkspaceRoot,
|
|
7
|
+
} = require('../argo-paths.js');
|
|
8
|
+
|
|
9
|
+
const LIVE_PROVIDER_GATE = 'ARGO_LIVE_PROVIDER_E2E';
|
|
10
|
+
const MUTATION_VECTOR_GATE = 'ARGO_W31_LIVE_MUTATION_VECTOR_E2E';
|
|
11
|
+
const SEMANTIC_CHANNELS = Object.freeze(['Element', 'ArchitectureRelationship', 'View']);
|
|
12
|
+
|
|
13
|
+
function createProductionSemanticOperatorJourney(dependencies) {
|
|
14
|
+
assertDependencies(dependencies);
|
|
15
|
+
|
|
16
|
+
async function runBackfill(request, automatic) {
|
|
17
|
+
const configurationRequest = request.approvedConfigurationRequest || request;
|
|
18
|
+
await resolveConfigurationSafely(
|
|
19
|
+
dependencies.resolveApprovedConfiguration,
|
|
20
|
+
configurationRequest,
|
|
21
|
+
);
|
|
22
|
+
const explicitOptIn = automatic
|
|
23
|
+
? request.automaticBackfillOptIn === true
|
|
24
|
+
: request.explicitOptIn;
|
|
25
|
+
return dependencies.runSemanticBackfill({
|
|
26
|
+
...request,
|
|
27
|
+
explicitOptIn,
|
|
28
|
+
automatic,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
async startNewProject(request = {}) {
|
|
34
|
+
const workspace = await dependencies.initializeWorkspace(request);
|
|
35
|
+
const structuralProjection = await dependencies.syncCanonicalStructuralProjection(request);
|
|
36
|
+
const pending = {
|
|
37
|
+
...structuralProjection,
|
|
38
|
+
workspace,
|
|
39
|
+
semanticState: 'SemanticIndexPending',
|
|
40
|
+
guidance: 'Enable both canonical semantic gates with approved external configuration, then run argo init again.',
|
|
41
|
+
};
|
|
42
|
+
if (request.automaticBackfillOptIn !== true) {
|
|
43
|
+
return Object.freeze(pending);
|
|
44
|
+
}
|
|
45
|
+
const backfill = await runBackfill(request, true);
|
|
46
|
+
return Object.freeze({ ...pending, backfill });
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
runExplicitBackfill(request = {}) {
|
|
50
|
+
return runBackfill(request, false);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async verifyReadiness(request = {}) {
|
|
54
|
+
const readiness = await dependencies.readSemanticReadiness(request);
|
|
55
|
+
if (readiness.verified !== true) {
|
|
56
|
+
throw readinessError(readiness);
|
|
57
|
+
}
|
|
58
|
+
return readiness;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async query(request = {}) {
|
|
62
|
+
return dependencies.querySystemArchitecture({ query: request });
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
readFullSnapshot() {
|
|
66
|
+
return dependencies.querySystemArchitecture({});
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function assertDependencies(dependencies) {
|
|
72
|
+
for (const name of [
|
|
73
|
+
'initializeWorkspace',
|
|
74
|
+
'syncCanonicalStructuralProjection',
|
|
75
|
+
'resolveApprovedConfiguration',
|
|
76
|
+
'runSemanticBackfill',
|
|
77
|
+
'readSemanticReadiness',
|
|
78
|
+
'querySystemArchitecture',
|
|
79
|
+
]) {
|
|
80
|
+
if (!dependencies || typeof dependencies[name] !== 'function') {
|
|
81
|
+
throw new TypeError(`${name} is required`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runCanonicalSemanticInit(dependencies, request = {}) {
|
|
87
|
+
requireCanonicalInitDependencies(dependencies);
|
|
88
|
+
const providerGate = readGate(dependencies.configurationBehavior, LIVE_PROVIDER_GATE);
|
|
89
|
+
const mutationGate = readGate(dependencies.configurationBehavior, MUTATION_VECTOR_GATE);
|
|
90
|
+
const gateDecision = evaluateDualGate(providerGate, mutationGate);
|
|
91
|
+
const versions = canonicalInitVersions(request);
|
|
92
|
+
await dependencies.finalReadiness.invalidate(canonicalInitReadiness(
|
|
93
|
+
gateDecision === 'disabled' ? 'SemanticIndexPending' : 'Stale',
|
|
94
|
+
versions,
|
|
95
|
+
));
|
|
96
|
+
if (gateDecision === 'disabled') {
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
state: 'SemanticDisabled',
|
|
99
|
+
alignment: 'SemanticIndexPending',
|
|
100
|
+
readiness: canonicalInitReadiness('SemanticIndexPending', versions),
|
|
101
|
+
fullSnapshotFallback: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (gateDecision !== 'enabled') {
|
|
105
|
+
const error = safeLifecycleError(
|
|
106
|
+
'SEMANTIC_LIFECYCLE_GATE_INVALID',
|
|
107
|
+
'Set both semantic lifecycle gates to exactly 1, or disable both.',
|
|
108
|
+
'Semantic lifecycle gates must both be exactly 1 or both disabled.',
|
|
109
|
+
);
|
|
110
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Failed');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await resolveCanonicalConfiguration(dependencies.configurationBehavior, request);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Failed');
|
|
117
|
+
}
|
|
118
|
+
let backfill;
|
|
119
|
+
try {
|
|
120
|
+
backfill = await dependencies.productionGraphRagRuntime.runSemanticBackfill({
|
|
121
|
+
...request,
|
|
122
|
+
explicitOptIn: true,
|
|
123
|
+
automatic: true,
|
|
124
|
+
});
|
|
125
|
+
} catch {
|
|
126
|
+
const error = reconciliationFailure();
|
|
127
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Stale');
|
|
128
|
+
}
|
|
129
|
+
if (!backfill || backfill.alignmentState !== 'Aligned') {
|
|
130
|
+
const error = reconciliationFailure();
|
|
131
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Stale');
|
|
132
|
+
}
|
|
133
|
+
let queryable;
|
|
134
|
+
try {
|
|
135
|
+
queryable = await dependencies.finalReadiness.verifyQueryability(backfill);
|
|
136
|
+
} catch {
|
|
137
|
+
queryable = false;
|
|
138
|
+
}
|
|
139
|
+
if (queryable !== true) {
|
|
140
|
+
const error = safeLifecycleError(
|
|
141
|
+
'SEMANTIC_QUERYABILITY_NOT_VERIFIED',
|
|
142
|
+
'Repair semantic vector queryability, then run argo init again.',
|
|
143
|
+
);
|
|
144
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Stale');
|
|
145
|
+
}
|
|
146
|
+
let coherent;
|
|
147
|
+
try {
|
|
148
|
+
coherent = await dependencies.finalReadiness.verifyGlobalCoherence(backfill);
|
|
149
|
+
} catch {
|
|
150
|
+
coherent = false;
|
|
151
|
+
}
|
|
152
|
+
if (coherent !== true) {
|
|
153
|
+
const error = safeLifecycleError(
|
|
154
|
+
'SEMANTIC_GLOBAL_COHERENCE_NOT_VERIFIED',
|
|
155
|
+
'Repair semantic global coherence, then run argo init again.',
|
|
156
|
+
);
|
|
157
|
+
throw await recordCanonicalInitFailure(dependencies.finalReadiness, versions, error, 'Stale');
|
|
158
|
+
}
|
|
159
|
+
const alignedEvidence = Object.freeze({
|
|
160
|
+
state: 'Aligned',
|
|
161
|
+
verified: true,
|
|
162
|
+
...versions,
|
|
163
|
+
completedChannels: ['Element', 'ArchitectureRelationship', 'View'],
|
|
164
|
+
missingChannels: [],
|
|
165
|
+
mismatchedChannels: [],
|
|
166
|
+
fullSnapshotFallback: false,
|
|
167
|
+
channels: Object.freeze(Object.entries(backfill.channels || {}).map(([channel]) => (
|
|
168
|
+
Object.freeze({
|
|
169
|
+
channel,
|
|
170
|
+
state: 'Aligned',
|
|
171
|
+
...versions,
|
|
172
|
+
})
|
|
173
|
+
))),
|
|
174
|
+
});
|
|
175
|
+
await dependencies.finalReadiness.recordAligned(alignedEvidence);
|
|
176
|
+
return Object.freeze({
|
|
177
|
+
state: 'Aligned',
|
|
178
|
+
alignment: 'Aligned',
|
|
179
|
+
backfill,
|
|
180
|
+
readiness: alignedEvidence,
|
|
181
|
+
fullSnapshotFallback: false,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function requireCanonicalInitDependencies(dependencies) {
|
|
186
|
+
if (!dependencies || !dependencies.configurationBehavior) {
|
|
187
|
+
throw new TypeError('configurationBehavior is required');
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
!dependencies.productionGraphRagRuntime
|
|
191
|
+
|| typeof dependencies.productionGraphRagRuntime.runSemanticBackfill !== 'function'
|
|
192
|
+
) {
|
|
193
|
+
throw new TypeError('productionGraphRagRuntime.runSemanticBackfill is required');
|
|
194
|
+
}
|
|
195
|
+
for (const name of [
|
|
196
|
+
'invalidate',
|
|
197
|
+
'recordFailure',
|
|
198
|
+
'verifyQueryability',
|
|
199
|
+
'verifyGlobalCoherence',
|
|
200
|
+
'recordAligned',
|
|
201
|
+
]) {
|
|
202
|
+
if (!dependencies.finalReadiness || typeof dependencies.finalReadiness[name] !== 'function') {
|
|
203
|
+
throw new TypeError(`finalReadiness.${name} is required`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readGate(configurationBehavior, name) {
|
|
209
|
+
if (typeof configurationBehavior.readGate === 'function') {
|
|
210
|
+
return configurationBehavior.readGate(name);
|
|
211
|
+
}
|
|
212
|
+
if (configurationBehavior.gates && typeof configurationBehavior.gates === 'object') {
|
|
213
|
+
return configurationBehavior.gates[name];
|
|
214
|
+
}
|
|
215
|
+
return process.env[name];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function evaluateDualGate(providerGate, mutationGate) {
|
|
219
|
+
const providerDisabled = providerGate === undefined || providerGate === '';
|
|
220
|
+
const mutationDisabled = mutationGate === undefined || mutationGate === '';
|
|
221
|
+
if (providerDisabled && mutationDisabled) return 'disabled';
|
|
222
|
+
if (providerGate === '1' && mutationGate === '1') return 'enabled';
|
|
223
|
+
return 'invalid';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function resolveCanonicalConfiguration(configurationBehavior, request) {
|
|
227
|
+
try {
|
|
228
|
+
if (typeof configurationBehavior.readExternalConfiguration === 'function') {
|
|
229
|
+
return await configurationBehavior.readExternalConfiguration(request);
|
|
230
|
+
}
|
|
231
|
+
if (configurationBehavior.state === 'valid-external-only') {
|
|
232
|
+
return configurationBehavior;
|
|
233
|
+
}
|
|
234
|
+
if (typeof configurationBehavior.resolve === 'function') {
|
|
235
|
+
return await configurationBehavior.resolve(request);
|
|
236
|
+
}
|
|
237
|
+
throw safeLifecycleError(
|
|
238
|
+
'EXTERNAL_CREDENTIALS_REQUIRED',
|
|
239
|
+
'Provide approved external semantic configuration, then run argo init again.',
|
|
240
|
+
);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
throw sanitizeLifecycleError(error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sanitizeLifecycleError(sourceError) {
|
|
247
|
+
const approvedCategories = new Set([
|
|
248
|
+
'APPROVED_SECRET_REQUIRED',
|
|
249
|
+
'SECRET_FILE_ACL_UNSAFE',
|
|
250
|
+
'SECRET_FILE_DUPLICATE_KEY',
|
|
251
|
+
'SECRET_FILE_UNKNOWN_KEY',
|
|
252
|
+
'SECRET_FILE_TRACKED',
|
|
253
|
+
'SECRET_FILE_NOT_IGNORED',
|
|
254
|
+
'SECRET_FILE_PATH_PROHIBITED',
|
|
255
|
+
'SECRET_FILE_REPARSE_PROHIBITED',
|
|
256
|
+
'SECRET_SOURCE_CONFLICT',
|
|
257
|
+
'SECRET_SOURCE_PROVENANCE_PROHIBITED',
|
|
258
|
+
'LIVE_PROVIDER_CONFIGURATION_CONFLICT',
|
|
259
|
+
'EXTERNAL_CREDENTIALS_REQUIRED',
|
|
260
|
+
'EMBEDDING_QUALIFICATION_REQUIRED',
|
|
261
|
+
'EMBEDDING_CONFIGURATION_REQUIRED',
|
|
262
|
+
]);
|
|
263
|
+
const sourceCategory = sourceError && sourceError.category;
|
|
264
|
+
const category = sourceCategory === 'LIVE_PROVIDER_CONFIGURATION_REQUIRED'
|
|
265
|
+
? 'EXTERNAL_CREDENTIALS_REQUIRED'
|
|
266
|
+
: (approvedCategories.has(sourceCategory)
|
|
267
|
+
? sourceCategory
|
|
268
|
+
: 'EMBEDDING_CONFIGURATION_REQUIRED');
|
|
269
|
+
const field = safeConfigurationField(sourceError && sourceError.field);
|
|
270
|
+
const error = safeLifecycleError(
|
|
271
|
+
category,
|
|
272
|
+
configurationAction(category, field),
|
|
273
|
+
configurationMessage(category, field),
|
|
274
|
+
);
|
|
275
|
+
if (field) error.field = field;
|
|
276
|
+
return error;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function safeConfigurationField(field) {
|
|
280
|
+
if (typeof field !== 'string') return null;
|
|
281
|
+
return /^(ARGO|QWEN)_[A-Z0-9_]+$/.test(field) ? field : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function configurationMessage(category, field) {
|
|
285
|
+
if (category === 'SECRET_FILE_UNKNOWN_KEY') {
|
|
286
|
+
return field
|
|
287
|
+
? `Approved semantic configuration contains unsupported key ${field}.`
|
|
288
|
+
: 'Approved semantic configuration contains an unsupported key.';
|
|
289
|
+
}
|
|
290
|
+
if (category === 'SECRET_FILE_DUPLICATE_KEY') {
|
|
291
|
+
return field
|
|
292
|
+
? `Approved semantic configuration contains duplicate key ${field}.`
|
|
293
|
+
: 'Approved semantic configuration contains a duplicate key.';
|
|
294
|
+
}
|
|
295
|
+
if (category === 'SECRET_SOURCE_CONFLICT' || category === 'LIVE_PROVIDER_CONFIGURATION_CONFLICT') {
|
|
296
|
+
return field
|
|
297
|
+
? `Approved semantic configuration has conflicting sources for ${field}.`
|
|
298
|
+
: 'Approved semantic configuration has conflicting sources.';
|
|
299
|
+
}
|
|
300
|
+
if (category === 'SECRET_FILE_TRACKED') {
|
|
301
|
+
return 'Approved semantic configuration file is tracked by Git.';
|
|
302
|
+
}
|
|
303
|
+
if (category === 'SECRET_FILE_NOT_IGNORED') {
|
|
304
|
+
return 'Approved semantic configuration file is not ignored by Git.';
|
|
305
|
+
}
|
|
306
|
+
if (category === 'SECRET_FILE_PATH_PROHIBITED') {
|
|
307
|
+
return 'Semantic configuration must come from repository-relative .argo/.env.';
|
|
308
|
+
}
|
|
309
|
+
if (category === 'SECRET_FILE_REPARSE_PROHIBITED') {
|
|
310
|
+
return 'Approved semantic configuration file must not be a reparse point or symlink.';
|
|
311
|
+
}
|
|
312
|
+
return 'Approved external semantic configuration was rejected.';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function configurationAction(category, field) {
|
|
316
|
+
if (category === 'SECRET_FILE_UNKNOWN_KEY') {
|
|
317
|
+
return field
|
|
318
|
+
? `Remove or rename unsupported key ${field} in .argo/.env, then run argo init again.`
|
|
319
|
+
: 'Remove unsupported keys from .argo/.env, then run argo init again.';
|
|
320
|
+
}
|
|
321
|
+
if (category === 'SECRET_FILE_DUPLICATE_KEY') {
|
|
322
|
+
return field
|
|
323
|
+
? `Keep exactly one ${field} entry in .argo/.env, then run argo init again.`
|
|
324
|
+
: 'Remove duplicate keys from .argo/.env, then run argo init again.';
|
|
325
|
+
}
|
|
326
|
+
if (category === 'SECRET_SOURCE_CONFLICT' || category === 'LIVE_PROVIDER_CONFIGURATION_CONFLICT') {
|
|
327
|
+
return field
|
|
328
|
+
? `Make process environment and .argo/.env agree for ${field}, then run argo init again.`
|
|
329
|
+
: 'Resolve conflicting approved configuration sources, then run argo init again.';
|
|
330
|
+
}
|
|
331
|
+
if (category === 'SECRET_FILE_TRACKED') {
|
|
332
|
+
return 'Remove .argo/.env from Git tracking, add it to .gitignore, then run argo init again.';
|
|
333
|
+
}
|
|
334
|
+
if (category === 'SECRET_FILE_NOT_IGNORED') {
|
|
335
|
+
return 'Add .argo/.env to .gitignore, then run argo init again.';
|
|
336
|
+
}
|
|
337
|
+
if (category === 'SECRET_FILE_PATH_PROHIBITED') {
|
|
338
|
+
return 'Move semantic secrets to .argo/.env and remove alternate env file usage, then run argo init again.';
|
|
339
|
+
}
|
|
340
|
+
if (category === 'SECRET_FILE_REPARSE_PROHIBITED') {
|
|
341
|
+
return 'Replace .argo/.env with a regular file, then run argo init again.';
|
|
342
|
+
}
|
|
343
|
+
return 'Correct approved external configuration and retry argo init.';
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function safeLifecycleError(category, action, message = category) {
|
|
347
|
+
const error = new Error(message);
|
|
348
|
+
error.category = category;
|
|
349
|
+
error.action = action;
|
|
350
|
+
error.fullSnapshotFallback = false;
|
|
351
|
+
error.safeSemanticLifecycleMessage = true;
|
|
352
|
+
return error;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function reconciliationFailure() {
|
|
356
|
+
return safeLifecycleError(
|
|
357
|
+
'SEMANTIC_RECONCILIATION_FAILED',
|
|
358
|
+
'Repair the durable semantic reconciliation failure, then run argo init again.',
|
|
359
|
+
'Semantic reconciliation failed before readiness could be verified.',
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function canonicalInitVersions(request) {
|
|
364
|
+
const repositoryRoot = path.resolve(
|
|
365
|
+
request.repositoryRoot
|
|
366
|
+
|| (request.workspace && request.workspace.workspaceRoot)
|
|
367
|
+
|| getWorkspaceRoot(),
|
|
368
|
+
);
|
|
369
|
+
const graph = JSON.parse(fs.readFileSync(
|
|
370
|
+
path.join(repositoryRoot, 'design', 'KG', 'SystemArchitecture.json'),
|
|
371
|
+
'utf8',
|
|
372
|
+
));
|
|
373
|
+
const identity = {
|
|
374
|
+
name: graph.name || 'System',
|
|
375
|
+
elements: (graph.elements || []).map(element => element.id).sort(),
|
|
376
|
+
relationships: (graph.relationships || []).map(relationship => relationship.id).sort(),
|
|
377
|
+
views: (graph.views || []).map(view => view.view_id).sort(),
|
|
378
|
+
};
|
|
379
|
+
const canonicalVersion = `canonical:${crypto.createHash('sha256')
|
|
380
|
+
.update(JSON.stringify(identity))
|
|
381
|
+
.digest('hex')}`;
|
|
382
|
+
return Object.freeze({
|
|
383
|
+
canonicalVersion,
|
|
384
|
+
contentVersion: `content:${canonicalVersion}`,
|
|
385
|
+
indexVersion: `index:${canonicalVersion}`,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function canonicalInitReadiness(state, versions, failure = {}) {
|
|
390
|
+
return Object.freeze({
|
|
391
|
+
state,
|
|
392
|
+
verified: false,
|
|
393
|
+
...versions,
|
|
394
|
+
completedChannels: [],
|
|
395
|
+
missingChannels: [...SEMANTIC_CHANNELS],
|
|
396
|
+
mismatchedChannels: [],
|
|
397
|
+
channels: Object.freeze([]),
|
|
398
|
+
fullSnapshotFallback: false,
|
|
399
|
+
...failure,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function recordCanonicalInitFailure(finalReadiness, versions, error, state) {
|
|
404
|
+
const evidence = canonicalInitReadiness(state, versions, {
|
|
405
|
+
category: error.category || 'SEMANTIC_LIFECYCLE_FAILED',
|
|
406
|
+
message: error.message || 'Semantic lifecycle failed.',
|
|
407
|
+
action: error.action || 'Repair semantic readiness, then run argo init again.',
|
|
408
|
+
...(typeof error.field === 'string' ? { field: error.field } : {}),
|
|
409
|
+
});
|
|
410
|
+
await finalReadiness.recordFailure(evidence);
|
|
411
|
+
for (const [field, value] of Object.entries(evidence)) {
|
|
412
|
+
error[field] = value;
|
|
413
|
+
}
|
|
414
|
+
return error;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function resolveConfigurationSafely(resolveApprovedConfiguration, request) {
|
|
418
|
+
try {
|
|
419
|
+
return await resolveApprovedConfiguration(request);
|
|
420
|
+
} catch (sourceError) {
|
|
421
|
+
const category = safeConfigurationCategory(sourceError && sourceError.category);
|
|
422
|
+
const error = new Error(`${category}: approved external configuration was rejected`);
|
|
423
|
+
error.category = category;
|
|
424
|
+
error.action = 'Correct approved external configuration and retry argo semantic init';
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function safeConfigurationCategory(category) {
|
|
430
|
+
const approvedCategories = new Set([
|
|
431
|
+
'APPROVED_SECRET_REQUIRED',
|
|
432
|
+
'SECRET_FILE_ACL_UNSAFE',
|
|
433
|
+
'SECRET_SOURCE_PROVENANCE_PROHIBITED',
|
|
434
|
+
'EXTERNAL_CREDENTIALS_REQUIRED',
|
|
435
|
+
'EMBEDDING_QUALIFICATION_REQUIRED',
|
|
436
|
+
'EMBEDDING_CONFIGURATION_REQUIRED',
|
|
437
|
+
]);
|
|
438
|
+
return approvedCategories.has(category) ? category : 'APPROVED_CONFIGURATION_REJECTED';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function readinessError(readiness = {}) {
|
|
442
|
+
const error = new Error('SemanticIndexPending');
|
|
443
|
+
error.category = readiness.state || 'SemanticIndexPending';
|
|
444
|
+
error.state = readiness.state || 'SemanticIndexPending';
|
|
445
|
+
error.verified = readiness.verified;
|
|
446
|
+
error.canonicalVersion = readiness.canonicalVersion;
|
|
447
|
+
error.contentVersion = readiness.contentVersion;
|
|
448
|
+
error.indexVersion = readiness.indexVersion;
|
|
449
|
+
error.completedChannels = readiness.completedChannels;
|
|
450
|
+
error.missingChannels = readiness.missingChannels;
|
|
451
|
+
error.mismatchedChannels = readiness.mismatchedChannels;
|
|
452
|
+
error.fullSnapshotFallback = readiness.fullSnapshotFallback;
|
|
453
|
+
return error;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
module.exports = {
|
|
457
|
+
createProductionSemanticOperatorJourney,
|
|
458
|
+
runCanonicalSemanticInit,
|
|
459
|
+
};
|