22const GAME_MANIFEST_SCHEMA_PATH = "/tools/schemas/game.manifest.schema.json" ;
33const WORKSPACE_MANIFEST_SCHEMA_PATH = "/tools/schemas/workspace.manifest.schema.json" ;
44const WORKSPACE_SESSION_SCHEMA_REF = "tools/schemas/workspace.manifest.schema.json" ;
5- const OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH = "/tools/schemas/tools/object-vector-studio-v2.schema.json" ;
65const WORKSPACE_REPO_REFERENCE_SESSION_KEY = "workspace.repo.reference" ;
76const WORKSPACE_TOOL_SESSION_KEY_PREFIX = "workspace.tools." ;
87const WORKSPACE_REPO_HANDLE_DB_NAME = "workspace-manager-v2-repo-handles" ;
@@ -320,6 +319,24 @@ function unsupportedFields(value, schema) {
320319 . filter ( ( key ) => ! Object . prototype . hasOwnProperty . call ( properties , key ) ) ;
321320}
322321
322+ function collectExternalSchemaReferences ( schema , refs = new Set ( ) ) {
323+ if ( Array . isArray ( schema ) ) {
324+ schema . forEach ( ( entry ) => collectExternalSchemaReferences ( entry , refs ) ) ;
325+ return refs ;
326+ }
327+ if ( ! isPlainObject ( schema ) ) {
328+ return refs ;
329+ }
330+ if ( typeof schema . $ref === "string" ) {
331+ const [ refPath ] = schema . $ref . split ( "#" ) ;
332+ if ( refPath ) {
333+ refs . add ( refPath ) ;
334+ }
335+ }
336+ Object . values ( schema ) . forEach ( ( entry ) => collectExternalSchemaReferences ( entry , refs ) ) ;
337+ return refs ;
338+ }
339+
323340function resolvePointer ( schema , pointer ) {
324341 if ( ! pointer || pointer === "#" ) {
325342 return schema ;
@@ -1290,12 +1307,47 @@ export class WorkspaceManagerV2ContextService {
12901307 if ( ! isPlainObject ( schema ) ) {
12911308 return { ok : false , message : `${ WORKSPACE_MANIFEST_SCHEMA_PATH } did not return a schema object.` } ;
12921309 }
1293- return { ok : true , schema } ;
1310+ const schemaRegistry = new Map ( ) ;
1311+ registerSchemaReference ( schemaRegistry , WORKSPACE_MANIFEST_SCHEMA_PATH , schema ) ;
1312+ const referenceLoad = await this . loadSchemaReferences ( schema , WORKSPACE_MANIFEST_SCHEMA_PATH , schemaRegistry ) ;
1313+ if ( ! referenceLoad . ok ) {
1314+ return referenceLoad ;
1315+ }
1316+ return { ok : true , schema, schemaPath : WORKSPACE_MANIFEST_SCHEMA_PATH , schemaRegistry } ;
12941317 } catch ( error ) {
12951318 return { ok : false , message : `Unable to load ${ WORKSPACE_MANIFEST_SCHEMA_PATH } : ${ error . message } ` } ;
12961319 }
12971320 }
12981321
1322+ async loadSchemaReferences ( rootSchema , rootSchemaPath , schemaRegistry , loadedSchemas = new Map ( ) ) {
1323+ for ( const schemaRef of collectExternalSchemaReferences ( rootSchema ) ) {
1324+ const resolvedSchemaPath = resolveSchemaPathFromBase ( rootSchemaPath , schemaRef ) ;
1325+ if ( ! resolvedSchemaPath ) {
1326+ return { ok : false , message : `Unable to resolve schema reference ${ schemaRef } from ${ rootSchemaPath } .` } ;
1327+ }
1328+ let referencedSchema = loadedSchemas . get ( resolvedSchemaPath ) ;
1329+ if ( ! referencedSchema ) {
1330+ const fetchPath = `/${ resolvedSchemaPath } ` ;
1331+ const schemaResponse = await this . fetchRef ( fetchPath , { cache : "no-store" } ) ;
1332+ if ( ! schemaResponse . ok ) {
1333+ return { ok : false , message : `Unable to load ${ fetchPath } : ${ schemaResponse . status } ` } ;
1334+ }
1335+ referencedSchema = await schemaResponse . json ( ) ;
1336+ if ( ! isPlainObject ( referencedSchema ) ) {
1337+ return { ok : false , message : `${ fetchPath } did not return a schema object.` } ;
1338+ }
1339+ loadedSchemas . set ( resolvedSchemaPath , referencedSchema ) ;
1340+ registerSchemaReference ( schemaRegistry , resolvedSchemaPath , referencedSchema ) ;
1341+ const nestedReferences = await this . loadSchemaReferences ( referencedSchema , resolvedSchemaPath , schemaRegistry , loadedSchemas ) ;
1342+ if ( ! nestedReferences . ok ) {
1343+ return nestedReferences ;
1344+ }
1345+ }
1346+ registerSchemaReference ( schemaRegistry , schemaRef , referencedSchema , resolvedSchemaPath ) ;
1347+ }
1348+ return { ok : true } ;
1349+ }
1350+
12991351 async loadGameManifestSchema ( ) {
13001352 if ( typeof this . fetchRef !== "function" ) {
13011353 return { ok : false , message : "Fetch API is unavailable; Workspace Manager V2 cannot validate game manifests." } ;
@@ -1309,54 +1361,12 @@ export class WorkspaceManagerV2ContextService {
13091361 if ( ! isPlainObject ( schema ) ) {
13101362 return { ok : false , message : `${ GAME_MANIFEST_SCHEMA_PATH } did not return a schema object.` } ;
13111363 }
1312- const assetManagerSchemaPath = `/${ TOOL_PAYLOAD_SCHEMA_REFS [ ASSET_MANAGER_V2_TOOL_KEY ] } ` ;
1313- const assetManagerResponse = await this . fetchRef ( assetManagerSchemaPath , { cache : "no-store" } ) ;
1314- if ( ! assetManagerResponse . ok ) {
1315- return { ok : false , message : `Unable to load ${ assetManagerSchemaPath } : ${ assetManagerResponse . status } ` } ;
1316- }
1317- const assetManagerSchema = await assetManagerResponse . json ( ) ;
1318- if ( ! isPlainObject ( assetManagerSchema ) ) {
1319- return { ok : false , message : `${ assetManagerSchemaPath } did not return a schema object.` } ;
1320- }
1321- const paletteManagerSchemaPath = `/${ TOOL_PAYLOAD_SCHEMA_REFS [ PALETTE_MANAGER_V2_TOOL_KEY ] } ` ;
1322- const paletteManagerResponse = await this . fetchRef ( paletteManagerSchemaPath , { cache : "no-store" } ) ;
1323- if ( ! paletteManagerResponse . ok ) {
1324- return { ok : false , message : `Unable to load ${ paletteManagerSchemaPath } : ${ paletteManagerResponse . status } ` } ;
1325- }
1326- const paletteManagerSchema = await paletteManagerResponse . json ( ) ;
1327- if ( ! isPlainObject ( paletteManagerSchema ) ) {
1328- return { ok : false , message : `${ paletteManagerSchemaPath } did not return a schema object.` } ;
1329- }
1330- const objectVectorResponse = await this . fetchRef ( OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH , { cache : "no-store" } ) ;
1331- if ( ! objectVectorResponse . ok ) {
1332- return { ok : false , message : `Unable to load ${ OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH } : ${ objectVectorResponse . status } ` } ;
1333- }
1334- const objectVectorSchema = await objectVectorResponse . json ( ) ;
1335- if ( ! isPlainObject ( objectVectorSchema ) ) {
1336- return { ok : false , message : `${ OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH } did not return a schema object.` } ;
1337- }
13381364 const schemaRegistry = new Map ( ) ;
1339- registerSchemaReference ( schemaRegistry , assetManagerSchemaPath , assetManagerSchema ) ;
1340- registerSchemaReference ( schemaRegistry , TOOL_PAYLOAD_SCHEMA_REFS [ ASSET_MANAGER_V2_TOOL_KEY ] , assetManagerSchema , assetManagerSchemaPath ) ;
1341- registerSchemaReference ( schemaRegistry , "tools/asset-manager-v2.schema.json" , assetManagerSchema , assetManagerSchemaPath ) ;
1342- registerSchemaReference ( schemaRegistry , paletteManagerSchemaPath , paletteManagerSchema ) ;
1343- registerSchemaReference ( schemaRegistry , TOOL_PAYLOAD_SCHEMA_REFS [ PALETTE_MANAGER_V2_TOOL_KEY ] , paletteManagerSchema , paletteManagerSchemaPath ) ;
1344- registerSchemaReference ( schemaRegistry , "tools/palette-manager-v2.schema.json" , paletteManagerSchema , paletteManagerSchemaPath ) ;
1345- registerSchemaReference ( schemaRegistry , OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH , objectVectorSchema ) ;
1346- registerSchemaReference ( schemaRegistry , "tools/schemas/tools/object-vector-studio-v2.schema.json" , objectVectorSchema , OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH ) ;
1347- registerSchemaReference ( schemaRegistry , "tools/object-vector-studio-v2.schema.json" , objectVectorSchema , OBJECT_VECTOR_STUDIO_V2_SCHEMA_PATH ) ;
1348- const text2SpeechSchemaPath = `/${ TOOL_PAYLOAD_SCHEMA_REFS [ TEXT2SPEECH_V2_TOOL_KEY ] } ` ;
1349- const text2SpeechResponse = await this . fetchRef ( text2SpeechSchemaPath , { cache : "no-store" } ) ;
1350- if ( ! text2SpeechResponse . ok ) {
1351- return { ok : false , message : `Unable to load ${ text2SpeechSchemaPath } : ${ text2SpeechResponse . status } ` } ;
1352- }
1353- const text2SpeechSchema = await text2SpeechResponse . json ( ) ;
1354- if ( ! isPlainObject ( text2SpeechSchema ) ) {
1355- return { ok : false , message : `${ text2SpeechSchemaPath } did not return a schema object.` } ;
1365+ registerSchemaReference ( schemaRegistry , GAME_MANIFEST_SCHEMA_PATH , schema ) ;
1366+ const referenceLoad = await this . loadSchemaReferences ( schema , GAME_MANIFEST_SCHEMA_PATH , schemaRegistry ) ;
1367+ if ( ! referenceLoad . ok ) {
1368+ return referenceLoad ;
13561369 }
1357- registerSchemaReference ( schemaRegistry , text2SpeechSchemaPath , text2SpeechSchema ) ;
1358- registerSchemaReference ( schemaRegistry , TOOL_PAYLOAD_SCHEMA_REFS [ TEXT2SPEECH_V2_TOOL_KEY ] , text2SpeechSchema , text2SpeechSchemaPath ) ;
1359- registerSchemaReference ( schemaRegistry , "tools/text2speech-V2.schema.json" , text2SpeechSchema , text2SpeechSchemaPath ) ;
13601370 return { ok : true , schema, schemaPath : GAME_MANIFEST_SCHEMA_PATH , schemaRegistry } ;
13611371 } catch ( error ) {
13621372 return { ok : false , message : `Unable to load ${ GAME_MANIFEST_SCHEMA_PATH } : ${ error . message } ` } ;
@@ -1387,83 +1397,12 @@ export class WorkspaceManagerV2ContextService {
13871397 return schemaResult ;
13881398 }
13891399 const workspaceContext = workspaceContextWithObjectVectorPayload ( manifest ) ;
1390- const errors = this . validateManifestAgainstSchema ( workspaceContext , schemaResult . schema ) ;
1391- if ( ! errors . length ) {
1392- const toolValidation = await this . validateToolPayloads ( workspaceContext , schemaResult . schema ) ;
1393- errors . push ( ...toolValidation . errors ) ;
1394- }
1400+ const errors = validateSchemaValue ( workspaceContext , schemaResult . schema , "root" , schemaResult . schema , schemaResult . schemaRegistry , schemaResult . schemaPath ) ;
13951401 return errors . length
13961402 ? { ok : false , message : `Generated Workspace Manager V2 manifest failed schema validation: ${ errors . join ( " | " ) } ` }
13971403 : { ok : true } ;
13981404 }
13991405
1400- validateManifestAgainstSchema ( manifest , schema ) {
1401- const errors = [ ] ;
1402- if ( ! isPlainObject ( manifest ) ) {
1403- return [ "root must be an object" ] ;
1404- }
1405- missingRequiredFields ( manifest , schema ) . forEach ( ( key ) => {
1406- errors . push ( `root.${ key } is required` ) ;
1407- } ) ;
1408- unsupportedFields ( manifest , schema ) . forEach ( ( key ) => {
1409- errors . push ( `root.${ key } is not allowed` ) ;
1410- } ) ;
1411-
1412- if ( ! isPlainObject ( manifest . tools ) ) {
1413- errors . push ( "root.tools must be an object" ) ;
1414- return errors ;
1415- }
1416- const toolsSchema = schemaProperties ( schema ) . tools || { } ;
1417- const toolProperties = schemaProperties ( toolsSchema ) ;
1418- missingRequiredFields ( manifest . tools , toolsSchema ) . forEach ( ( key ) => {
1419- errors . push ( `root.tools.${ key } is required` ) ;
1420- } ) ;
1421- Object . keys ( manifest . tools ) . forEach ( ( key ) => {
1422- if ( ! Object . prototype . hasOwnProperty . call ( toolProperties , key ) ) {
1423- errors . push ( `root.tools.${ key } is not allowed` ) ;
1424- }
1425- } ) ;
1426- return errors ;
1427- }
1428-
1429- async loadToolPayloadSchema ( ref ) {
1430- if ( typeof ref !== "string" || ! ref . startsWith ( "./tools/" ) ) {
1431- return { ok : false , message : `Unsupported workspace manifest schema reference ${ ref || "(empty)" } .` } ;
1432- }
1433- const schemaPath = `/tools/schemas/${ ref . slice ( 2 ) } ` ;
1434- try {
1435- const response = await this . fetchRef ( schemaPath , { cache : "no-store" } ) ;
1436- if ( ! response . ok ) {
1437- return { ok : false , message : `Unable to load ${ schemaPath } : ${ response . status } ` } ;
1438- }
1439- const schema = await response . json ( ) ;
1440- return isPlainObject ( schema )
1441- ? { ok : true , schema }
1442- : { ok : false , message : `${ schemaPath } did not return a schema object.` } ;
1443- } catch ( error ) {
1444- return { ok : false , message : `Unable to load ${ schemaPath } : ${ error . message } ` } ;
1445- }
1446- }
1447-
1448- async validateToolPayloads ( manifest , workspaceSchema ) {
1449- const errors = [ ] ;
1450- const toolsSchema = schemaProperties ( workspaceSchema ) . tools || { } ;
1451- const toolProperties = schemaProperties ( toolsSchema ) ;
1452- for ( const [ toolKey , payload ] of Object . entries ( manifest . tools || { } ) ) {
1453- const toolSchemaRef = toolProperties [ toolKey ] ?. $ref ;
1454- if ( ! toolSchemaRef ) {
1455- continue ;
1456- }
1457- const toolSchemaResult = await this . loadToolPayloadSchema ( toolSchemaRef ) ;
1458- if ( ! toolSchemaResult . ok ) {
1459- errors . push ( toolSchemaResult . message ) ;
1460- continue ;
1461- }
1462- errors . push ( ...validateSchemaValue ( payload , toolSchemaResult . schema , `root.tools.${ toolKey } ` , toolSchemaResult . schema ) ) ;
1463- }
1464- return { ok : errors . length === 0 , errors } ;
1465- }
1466-
14671406 async fetchGameManifest ( game ) {
14681407 const manifestResult = await this . fetchWorkspaceManifest ( game . manifestPath ) ;
14691408 return manifestResult . ok
0 commit comments