arcane-os 0.2.0 → 0.2.2

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +5 -5
  4. package/browser-runtime/ai/browser-speech-providers.mjs +331 -25
  5. package/docs/architecture.md +2 -2
  6. package/docs/reference/README.md +79 -13
  7. package/docs/reference/ai/browser-speech.md +336 -0
  8. package/docs/reference/ai/browser-wasm.md +207 -82
  9. package/docs/reference/availability-and-normalization.md +30 -4
  10. package/docs/reference/behavioral-testing.md +4 -1
  11. package/docs/reference/cli.md +29 -10
  12. package/docs/reference/core/arcane-ai-contracts.md +43 -9
  13. package/docs/reference/inventory/package-api.json +110 -14
  14. package/docs/reference/inventory/runtime-components.json +19 -6
  15. package/docs/reference/inventory/runtime-modules.json +113 -9
  16. package/docs/reference/protocols.md +260 -38
  17. package/docs/reference/runtime-components.md +108 -15
  18. package/docs/reference/runtime-modules.md +422 -8
  19. package/docs/reference/sdk-api.md +626 -85
  20. package/package.json +1 -1
  21. package/runtime/ARCANE_RUNTIME_RELEASE.json +19 -19
  22. package/runtime/arcane/components/chat.html +288 -52
  23. package/runtime/arcane/components/speech.html +339 -15
  24. package/runtime/arcane/modules/AI.js +1245 -136
  25. package/runtime/arcane/modules/AIProviderRuntime.js +299 -30
  26. package/runtime/arcane/modules/AIRuntimeState.js +23 -4
  27. package/runtime/arcane/modules/ConfiguredAIChatSession.js +93 -8
  28. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +448 -24
  29. package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
  30. package/schemas/arcane-lock.schema.json +6 -4
  31. package/src/dev-server.mjs +29 -13
  32. package/src/doctor.mjs +1 -3
  33. package/src/import-map.mjs +134 -83
  34. package/src/packager/core.mjs +311 -39
  35. package/src/scaffold.mjs +45 -17
  36. package/src/templates/workspace-template.mjs +23 -4
  37. package/src/toolchain.mjs +10 -2
  38. package/src/workspace.mjs +177 -24
@@ -4,6 +4,9 @@ import {
4
4
  SDK_BROWSER_RUNTIME_MANIFEST_SHA256
5
5
  } from '../sdk-browser-runtime.mjs';
6
6
 
7
+ const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
8
+ const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
9
+
7
10
  function json(value){
8
11
  return `${JSON.stringify(value,null,2)}\n`;
9
12
  }
@@ -28,12 +31,28 @@ export function workspaceTemplate({
28
31
  displayName,
29
32
  runtimeRelease,
30
33
  sdkBrowserRuntimeRelease,
34
+ sdkDependencyName=SDK_NAME,
35
+ sdkDependencySpecifier=SDK_VERSION,
36
+ sdkPackageSource=`node_modules/${sdkDependencyName}`,
31
37
  appOnly=false,
32
38
  namedImports=true,
33
39
  minimumCoreVersion='0.8.12',
34
40
  target='browser',
35
41
  appIcon
36
42
  }){
43
+ const canonicalSpecifier=sdkDependencyName===SDK_NAME
44
+ &&(sdkDependencySpecifier===SDK_VERSION
45
+ ||(typeof sdkDependencySpecifier==='string'
46
+ &&LOCAL_TARBALL_PATTERN.test(sdkDependencySpecifier)
47
+ &&!/[\x00-\x1f\x7f]/.test(sdkDependencySpecifier)));
48
+ const aliasSpecifier=sdkDependencyName!==SDK_NAME
49
+ &&sdkDependencySpecifier===`npm:${SDK_NAME}@${SDK_VERSION}`;
50
+ if(typeof sdkDependencyName!=='string'||sdkDependencyName.length>214
51
+ ||!NPM_PACKAGE_NAME_PATTERN.test(sdkDependencyName)
52
+ ||sdkPackageSource!==`node_modules/${sdkDependencyName}`
53
+ ||(!canonicalSpecifier&&!aliasSpecifier)){
54
+ throw new Error('Invalid scaffold SDK installation authority.');
55
+ }
37
56
  const supportedTargets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
38
57
  if(!supportedTargets.includes(target)){
39
58
  throw new Error(`Unsupported scaffold target: ${String(target)}.`);
@@ -222,7 +241,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
222
241
  }:{})
223
242
  },
224
243
  devDependencies:{
225
- [SDK_NAME]:SDK_VERSION
244
+ [sdkDependencyName]:sdkDependencySpecifier
226
245
  },
227
246
  engines:{node:'>=22.23.2'}
228
247
  }));
@@ -239,7 +258,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
239
258
  exclude:[]
240
259
  },
241
260
  {
242
- source:'node_modules/arcane-os',
261
+ source:sdkPackageSource,
243
262
  destination:'licenses/arcane-os',
244
263
  include:['LICENSE','COMMERCIAL-LICENSE.md','NOTICE'],
245
264
  exclude:[]
@@ -251,12 +270,12 @@ Every browser release also carries Arcane OS licensing material under \`licenses
251
270
  schemaVersion:1,
252
271
  sdk:{name:SDK_NAME,version:SDK_VERSION},
253
272
  runtime:{
254
- manifest:'node_modules/arcane-os/runtime/ARCANE_RUNTIME_RELEASE.json',
273
+ manifest:`${sdkPackageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
255
274
  contentSha256:runtimeContentSha256,
256
275
  upstreamCommit
257
276
  },
258
277
  sdkBrowserRuntime:{
259
- manifest:'node_modules/arcane-os/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json',
278
+ manifest:`${sdkPackageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`,
260
279
  manifestSha256:browserManifestSha256,
261
280
  contentSha256:browserContentSha256,
262
281
  builder:sdkBrowserRuntimeRelease?.builder,
package/src/toolchain.mjs CHANGED
@@ -380,11 +380,19 @@ async function preparedWorkspace(options){
380
380
  onEvent:options.onEvent
381
381
  });
382
382
  const external=workspace.workspaceMode==='external';
383
+ if(external&&(!workspace.sdkInstallation
384
+ ||typeof workspace.sdkInstallation.runtimeRoot!=='string'
385
+ ||typeof workspace.sdkInstallation.browserRuntimeRoot!=='string')){
386
+ throw new ArcaneError(
387
+ ERROR_CODES.workspaceInvalid,
388
+ 'Validated external workspace is missing its bound SDK installation authority.'
389
+ );
390
+ }
383
391
  const runtimeRoot=external
384
- ?path.join(workspace.workspaceRoot,'node_modules','arcane-os','runtime')
392
+ ?workspace.sdkInstallation.runtimeRoot
385
393
  :workspace.workspaceRoot;
386
394
  const browserRuntimeRoot=external
387
- ?path.join(workspace.workspaceRoot,'node_modules','arcane-os','browser-runtime')
395
+ ?workspace.sdkInstallation.browserRuntimeRoot
388
396
  :null;
389
397
  const runtimeReceipt=external&&!options.deferRuntimeVerification
390
398
  ?await verifyRuntime({
package/src/workspace.mjs CHANGED
@@ -2,6 +2,7 @@ import {createHash} from 'node:crypto';
2
2
  import {lstat,readFile,readdir,realpath} from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import {
5
+ normalizeRelativePath,
5
6
  validateAppConfig as validatePackagerAppConfig,
6
7
  validateRootConfig as validatePackagerRootConfig
7
8
  } from './packager/core.mjs';
@@ -19,6 +20,8 @@ import {
19
20
  const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
20
21
  const SHA256_PATTERN=/^[a-f0-9]{64}$/;
21
22
  const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
23
+ const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
24
+ const EXACT_SDK_ALIAS=`npm:${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}`;
22
25
  const ROOT_CONFIG_NAME='arcane-packager.json';
23
26
  const APP_CONFIG_NAME='arcane-package.json';
24
27
 
@@ -145,10 +148,105 @@ async function assertRealDirectoryIdentity(captured,label){
145
148
  }
146
149
  }
147
150
 
151
+ function sdkPackageSourceForDependency(dependencyName){
152
+ if(typeof dependencyName!=='string'||dependencyName.length>214
153
+ ||!NPM_PACKAGE_NAME_PATTERN.test(dependencyName)){
154
+ fail(`Invalid installed SDK dependency name: ${String(dependencyName)}.`);
155
+ }
156
+ const source=`node_modules/${dependencyName}`;
157
+ try{
158
+ if(normalizeRelativePath(source,'installed SDK package source')!==source)throw new Error();
159
+ }catch{
160
+ fail(`Invalid installed SDK package source for dependency ${dependencyName}.`);
161
+ }
162
+ return source;
163
+ }
164
+
165
+ function dependencyNameForSdkPackageSource(source){
166
+ if(typeof source!=='string'||!source.startsWith('node_modules/'))return null;
167
+ const dependencyName=source.slice('node_modules/'.length);
168
+ try{
169
+ return sdkPackageSourceForDependency(dependencyName)===source?dependencyName:null;
170
+ }catch{
171
+ return null;
172
+ }
173
+ }
174
+
175
+ function supportedCanonicalSdkDeclaration(value){
176
+ return value===EXPECTED_SDK_VERSION
177
+ ||(typeof value==='string'&&LOCAL_TARBALL_PATTERN.test(value)
178
+ &&!/[\x00-\x1f\x7f]/.test(value));
179
+ }
180
+
181
+ export function resolveSdkPackageDeclaration(rootPackage,{
182
+ allowMissing=false,
183
+ packageSource
184
+ }={}){
185
+ if(!isObject(rootPackage))fail('package.json must contain a JSON object.');
186
+ const candidates=[];
187
+ for(const groupName of ['devDependencies','dependencies']){
188
+ const group=rootPackage[groupName];
189
+ if(group===undefined)continue;
190
+ if(!isObject(group))fail(`package.json ${groupName} must be a JSON object.`);
191
+ for(const [dependencyName,specifier] of Object.entries(group)){
192
+ const canonicalName=dependencyName===EXPECTED_SDK_NAME;
193
+ const aliasTarget=typeof specifier==='string'
194
+ &&(specifier===`npm:${EXPECTED_SDK_NAME}`
195
+ ||specifier.startsWith(`npm:${EXPECTED_SDK_NAME}@`));
196
+ if(!canonicalName&&!aliasTarget)continue;
197
+ const supported=canonicalName
198
+ ?supportedCanonicalSdkDeclaration(specifier)
199
+ :specifier===EXACT_SDK_ALIAS;
200
+ if(!supported){
201
+ fail(
202
+ `package.json ${groupName}.${dependencyName} must be ${EXPECTED_SDK_VERSION}, `
203
+ +`a local file: tarball under ${EXPECTED_SDK_NAME}, or the exact npm alias `
204
+ +`${EXACT_SDK_ALIAS} under a distinct dependency key.`
205
+ );
206
+ }
207
+ candidates.push(Object.freeze({
208
+ dependencyName,
209
+ dependencyGroup:groupName,
210
+ packageSource:sdkPackageSourceForDependency(dependencyName),
211
+ specifier
212
+ }));
213
+ }
214
+ }
215
+ if(candidates.length>1){
216
+ fail('package.json must declare exactly one Arcane SDK installation; remove duplicate canonical or alias declarations.');
217
+ }
218
+ if(candidates.length===0){
219
+ if(allowMissing&&packageSource===undefined)return null;
220
+ fail(
221
+ `package.json must declare exactly one ${EXPECTED_SDK_NAME} installation as `
222
+ +`${EXPECTED_SDK_VERSION}, a local file: tarball, or the exact npm alias ${EXACT_SDK_ALIAS}.`
223
+ );
224
+ }
225
+ const [declaration]=candidates;
226
+ if(packageSource!==undefined&&declaration.packageSource!==packageSource){
227
+ fail(
228
+ `${ROOT_CONFIG_NAME} SDK license source ${String(packageSource)} does not match `
229
+ +`the declared SDK installation ${declaration.packageSource}.`
230
+ );
231
+ }
232
+ return declaration;
233
+ }
234
+
235
+ function sdkManifestPaths(packageSource){
236
+ return Object.freeze({
237
+ runtimeManifest:`${packageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
238
+ browserRuntimeManifest:`${packageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`
239
+ });
240
+ }
241
+
148
242
  function classifyRootConfig(config){
149
243
  const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
150
244
  const routes=validated.sharedPayloads['browser-runtime'];
151
245
  if(!Array.isArray(routes))fail(`${ROOT_CONFIG_NAME} must define browser-runtime routes.`);
246
+ const externalPackageSource=routes.length===2
247
+ &&dependencyNameForSdkPackageSource(routes[1]?.source)!==null
248
+ ?routes[1].source
249
+ :null;
152
250
  const external=[
153
251
  {
154
252
  source:'arcane',
@@ -156,7 +254,7 @@ function classifyRootConfig(config){
156
254
  include:['components','css','dependencies','entities','img','modules','sdk','security']
157
255
  },
158
256
  {
159
- source:'node_modules/arcane-os',
257
+ source:externalPackageSource,
160
258
  destination:'licenses/arcane-os',
161
259
  include:['LICENSE','COMMERCIAL-LICENSE.md','NOTICE']
162
260
  }
@@ -201,7 +299,12 @@ function classifyRootConfig(config){
201
299
  }else{
202
300
  fail(`${ROOT_CONFIG_NAME} browser-runtime routes must match the external SDK or integrated Arcane workspace contract.`);
203
301
  }
204
- return Object.freeze({...validated,workspaceMode,browserRuntimeLayout});
302
+ return Object.freeze({
303
+ ...validated,
304
+ workspaceMode,
305
+ browserRuntimeLayout,
306
+ ...(workspaceMode==='external'?{sdkPackageSource:externalPackageSource}:{})
307
+ });
205
308
  }
206
309
 
207
310
  async function discoverAppsInRoot(root,config){
@@ -307,7 +410,7 @@ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
307
410
  });
308
411
  }
309
412
 
310
- function validateLock(lock){
413
+ function validateLock(lock,sdkDeclaration){
311
414
  const browser=lock?.sdkBrowserRuntime;
312
415
  const browserSource=browser?.source;
313
416
  const dependencies=browserSource?.dependencies;
@@ -340,6 +443,7 @@ function validateLock(lock){
340
443
  &&actual.name===expected.name&&actual.version===expected.version
341
444
  &&actual.resolved===expected.resolved&&actual.integrity===expected.integrity;
342
445
  });
446
+ const manifests=sdkManifestPaths(sdkDeclaration.packageSource);
343
447
  if(!exactKeys(lock,['schemaVersion','sdk','runtime','sdkBrowserRuntime','protocols'])
344
448
  ||lock.schemaVersion!==1
345
449
  ||!exactKeys(lock.sdk,['name','version'])
@@ -347,11 +451,11 @@ function validateLock(lock){
347
451
  ||!exactKeys(lock.runtime,['manifest','contentSha256','upstreamCommit'])
348
452
  ||!SHA256_PATTERN.test(lock.runtime.contentSha256)
349
453
  ||!/^([a-f0-9]{40})$/.test(lock.runtime.upstreamCommit)
350
- ||lock.runtime.manifest!=='node_modules/arcane-os/runtime/ARCANE_RUNTIME_RELEASE.json'
454
+ ||lock.runtime.manifest!==manifests.runtimeManifest
351
455
  ||!exactKeys(browser,[
352
456
  'manifest','manifestSha256','contentSha256','builder','sdkVersion','source'
353
457
  ])
354
- ||browser.manifest!=='node_modules/arcane-os/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json'
458
+ ||browser.manifest!==manifests.browserRuntimeManifest
355
459
  ||browser.manifestSha256!==SDK_BROWSER_RUNTIME_MANIFEST_SHA256
356
460
  ||browser.contentSha256!==SDK_BROWSER_RUNTIME_CONTENT_SHA256
357
461
  ||browser.builder!=='arcane-sdk-browser-runtime-v1'
@@ -372,6 +476,43 @@ function validateLock(lock){
372
476
  return lock;
373
477
  }
374
478
 
479
+ async function resolveInstalledSdkInstallation(workspaceRoot,declaration){
480
+ let current=workspaceRoot;
481
+ for(const segment of declaration.packageSource.split('/')){
482
+ current=path.join(current,segment);
483
+ await assertRealDirectory(current,`Installed SDK package path ${declaration.packageSource}`);
484
+ }
485
+ const canonicalPackageRoot=await realpath(current);
486
+ if(!sameDirectoryPath(current,canonicalPackageRoot)){
487
+ fail(`Installed SDK package root must be one direct physical directory: ${current}.`);
488
+ }
489
+ const installedPackage=await readJson(
490
+ path.join(canonicalPackageRoot,'package.json'),
491
+ 'installed SDK package manifest'
492
+ );
493
+ if(installedPackage.name!==EXPECTED_SDK_NAME||installedPackage.version!==EXPECTED_SDK_VERSION){
494
+ fail(`Installed SDK package must identify exactly as ${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}.`);
495
+ }
496
+ const runtimeRoot=path.join(canonicalPackageRoot,'runtime');
497
+ const browserRuntimeRoot=path.join(canonicalPackageRoot,'browser-runtime');
498
+ await Promise.all([
499
+ assertRealDirectory(runtimeRoot,'Installed SDK runtime root'),
500
+ assertRealDirectory(browserRuntimeRoot,'Installed SDK browser runtime root')
501
+ ]);
502
+ const manifests=sdkManifestPaths(declaration.packageSource);
503
+ return Object.freeze({
504
+ dependencyName:declaration.dependencyName,
505
+ packageSource:declaration.packageSource,
506
+ canonicalPackageRoot,
507
+ packageName:installedPackage.name,
508
+ packageVersion:installedPackage.version,
509
+ runtimeRoot,
510
+ browserRuntimeRoot,
511
+ runtimeManifest:manifests.runtimeManifest,
512
+ browserRuntimeManifest:manifests.browserRuntimeManifest
513
+ });
514
+ }
515
+
375
516
  function sameBrowserRuntimeSource(actual,pinned){
376
517
  const exactKeys=(value,keys)=>isObject(value)
377
518
  &&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
@@ -585,6 +726,8 @@ export async function validateWorkspace({
585
726
  await emit(onEvent,{type:'workspace.validate.check',name,ok:true});
586
727
  };
587
728
  let lock;
729
+ let sdkDeclaration;
730
+ let sdkInstallation;
588
731
  let validatedApplication;
589
732
  const workspaceMode=resolved.config.workspaceMode;
590
733
  await add('workspace-profile',async()=>{
@@ -599,7 +742,14 @@ export async function validateWorkspace({
599
742
  });
600
743
  if(workspaceMode==='external'){
601
744
  await add('lock',async()=>{
602
- lock=validateLock(await readJson(path.join(resolved.workspaceRoot,'arcane.lock.json'),'arcane.lock.json'));
745
+ sdkDeclaration=resolveSdkPackageDeclaration(
746
+ await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json'),
747
+ {packageSource:resolved.config.sdkPackageSource}
748
+ );
749
+ lock=validateLock(
750
+ await readJson(path.join(resolved.workspaceRoot,'arcane.lock.json'),'arcane.lock.json'),
751
+ sdkDeclaration
752
+ );
603
753
  });
604
754
  }
605
755
  await add('package',async()=>{
@@ -610,26 +760,31 @@ export async function validateWorkspace({
610
760
  }
611
761
  return;
612
762
  }
613
- const configured=rootPackage?.devDependencies?.[EXPECTED_SDK_NAME]
614
- ??rootPackage?.dependencies?.[EXPECTED_SDK_NAME];
615
- const supported=configured===EXPECTED_SDK_VERSION
616
- ||(typeof configured==='string'&&LOCAL_TARBALL_PATTERN.test(configured)
617
- &&!/[\x00-\x1f\x7f]/.test(configured));
618
- if(rootPackage?.private!==true||rootPackage?.type!=='module'||!supported){
619
- fail(`package.json must be private, use modules, and declare ${EXPECTED_SDK_NAME} as ${EXPECTED_SDK_VERSION} or a local file: tarball.`);
763
+ const declaration=resolveSdkPackageDeclaration(rootPackage,{
764
+ packageSource:resolved.config.sdkPackageSource
765
+ });
766
+ if(rootPackage?.private!==true||rootPackage?.type!=='module'){
767
+ fail(
768
+ `package.json must be private, use modules, and declare one exact `
769
+ +`${EXPECTED_SDK_NAME} installation.`
770
+ );
771
+ }
772
+ if(declaration.dependencyName!==sdkDeclaration.dependencyName
773
+ ||declaration.dependencyGroup!==sdkDeclaration.dependencyGroup
774
+ ||declaration.packageSource!==sdkDeclaration.packageSource
775
+ ||declaration.specifier!==sdkDeclaration.specifier){
776
+ fail('The declared SDK installation changed after workspace profile inspection.',
777
+ 'ARCANE_INTEGRITY_FAILED');
620
778
  }
621
779
  });
622
780
  if(workspaceMode==='external'){
623
781
  await add('installed-runtime',async()=>{
624
- const installedPackage=await readJson(
625
- path.join(resolved.workspaceRoot,'node_modules','arcane-os','package.json'),
626
- 'installed SDK package manifest'
782
+ sdkInstallation=await resolveInstalledSdkInstallation(
783
+ resolved.workspaceRoot,
784
+ sdkDeclaration
627
785
  );
628
- if(installedPackage.name!==EXPECTED_SDK_NAME||installedPackage.version!==EXPECTED_SDK_VERSION){
629
- fail(`Installed SDK package must identify exactly as ${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}.`);
630
- }
631
786
  const installed=await readJson(
632
- path.join(resolved.workspaceRoot,'node_modules','arcane-os','runtime','ARCANE_RUNTIME_RELEASE.json'),
787
+ path.join(sdkInstallation.runtimeRoot,'ARCANE_RUNTIME_RELEASE.json'),
633
788
  'installed SDK runtime manifest'
634
789
  );
635
790
  if(installed.contentSha256!==lock.runtime.contentSha256
@@ -637,10 +792,7 @@ export async function validateWorkspace({
637
792
  fail('Installed SDK runtime does not match arcane.lock.json.');
638
793
  }
639
794
  const browserManifestPath=path.join(
640
- resolved.workspaceRoot,
641
- 'node_modules',
642
- 'arcane-os',
643
- 'browser-runtime',
795
+ sdkInstallation.browserRuntimeRoot,
644
796
  'ARCANE_SDK_BROWSER_RELEASE.json'
645
797
  );
646
798
  const browserBytes=await readFile(browserManifestPath);
@@ -697,6 +849,7 @@ export async function validateWorkspace({
697
849
  config:resolved.config,
698
850
  app:validatedApplication.app,
699
851
  lock,
852
+ ...(sdkInstallation?{sdkInstallation}:{}),
700
853
  checks:Object.freeze(checks)
701
854
  });
702
855
  await emit(onEvent,{type:'workspace.validate.completed',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId,checks});