arcane-os 0.3.3 → 0.3.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4
4
+
5
+ - Allowed external and integrated physical SDK workspaces to omit the optional
6
+ `security` runtime route while preserving the canonical functional route
7
+ order, `dependencies` and `sdk` projections, external license routing, and
8
+ compatibility with workspaces that still include `security`.
9
+ - Preserved a newer microphone retry's press, status, and operation identity
10
+ when an earlier pending capture request settles, while retaining the original
11
+ operation correlation for successful transcription.
12
+
3
13
  ## 0.3.3
4
14
 
5
15
  - Corrected installed-workspace import-map refresh to preserve rich browser
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
20
  event, cancellation, and browser run contracts.
21
21
 
22
- This checkout defines the `0.3.3` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.3.4` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -126,7 +126,7 @@ uses the same controller for automatic memory extraction.
126
126
  Create a new repository-shaped Arcane application with the exact stable SDK:
127
127
 
128
128
  ```bash
129
- npx arcane-os@0.3.3 new my-app --path ./my-app --target portable --git
129
+ npx arcane-os@0.3.4 new my-app --path ./my-app --target portable --git
130
130
  cd my-app
131
131
  npm install
132
132
  npm run check
@@ -137,7 +137,7 @@ To enroll an existing repository, install the exact SDK and initialize only
137
137
  missing Arcane files:
138
138
 
139
139
  ```bash
140
- npm install --save-dev --save-exact arcane-os@0.3.3
140
+ npm install --save-dev --save-exact arcane-os@0.3.4
141
141
  npm exec -- arcane init my-app --target portable
142
142
  ```
143
143
 
@@ -153,7 +153,7 @@ npm exec -- arcane-os targets
153
153
  No global SDK install or standalone Arcane CLI is required. The application
154
154
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
155
155
 
156
- Use `npx arcane-os@0.3.3` for the initial bootstrap because it names this npm
156
+ Use `npx arcane-os@0.3.4` for the initial bootstrap because it names this npm
157
157
  package explicitly; bare `npx arcane` outside an installed project could resolve
158
158
  a different package. Both installed commands invoke the same headless toolchain.
159
159
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -174,7 +174,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
174
174
 
175
175
  # From the generated app repository
176
176
  cd ../local-app
177
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.3.tgz
177
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.4.tgz
178
178
  npm run check
179
179
  npm ci
180
180
  ```
@@ -184,7 +184,7 @@ same location. The lockfile retains the selected package dependency while
184
184
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
185
185
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
186
186
  runner also needs that tarball at the locked path. After publication, replace
187
- the local declaration with the exact `arcane-os@0.3.3` registry package and
187
+ the local declaration with the exact `arcane-os@0.3.4` registry package and
188
188
  commit the regenerated lock.
189
189
 
190
190
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -322,7 +322,7 @@ package installation, or assertions.
322
322
 
323
323
  ## Current target support
324
324
 
325
- Version `0.3.3` exposes one browser target and five explicitly paired
325
+ Version `0.3.4` exposes one browser target and five explicitly paired
326
326
  native development targets: a non-runnable portable directory, a
327
327
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
328
328
  unsigned-local-test DEBs, and an Android development-signed APK. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1235,11 +1235,14 @@
1235
1235
  }
1236
1236
 
1237
1237
  const generation = ++recordingGeneration;
1238
- transcriptionOperationId = nextSpeechOperationId('transcription');
1238
+ const operationId = nextSpeechOperationId('transcription');
1239
+ transcriptionOperationId = operationId;
1239
1240
  recordingRequested = true;
1240
1241
  localStatus = {
1241
1242
  message: 'Recording. Release to transcribe.',
1242
- tone: ''
1243
+ tone: '',
1244
+ owner: 'recording',
1245
+ generation
1243
1246
  };
1244
1247
  renderControls();
1245
1248
  renderStatus();
@@ -1248,6 +1251,9 @@
1248
1251
  try {
1249
1252
  stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1250
1253
  } catch (error) {
1254
+ if (generation !== recordingGeneration || destroyed) {
1255
+ return false;
1256
+ }
1251
1257
  recordingRequested = false;
1252
1258
  resetRecordPress();
1253
1259
  if (
@@ -1268,7 +1274,7 @@
1268
1274
  {
1269
1275
  bubbles: true,
1270
1276
  composed: true,
1271
- operationId: transcriptionOperationId,
1277
+ operationId,
1272
1278
  publicDetail: {
1273
1279
  ...publicSpeechErrorFields(
1274
1280
  error,
@@ -1278,7 +1284,9 @@
1278
1284
  }
1279
1285
  }
1280
1286
  );
1281
- transcriptionOperationId = null;
1287
+ if (transcriptionOperationId === operationId) {
1288
+ transcriptionOperationId = null;
1289
+ }
1282
1290
  } else {
1283
1291
  reportTranscriptionError(error);
1284
1292
  }
@@ -1287,15 +1295,25 @@
1287
1295
  return false;
1288
1296
  }
1289
1297
 
1298
+ const currentGeneration = generation === recordingGeneration;
1290
1299
  if (
1291
1300
  !recordingRequested
1292
- || generation !== recordingGeneration
1301
+ || !currentGeneration
1293
1302
  || sttRole.state !== 'ready'
1294
1303
  || sttRole.busy
1295
1304
  || destroyed
1296
1305
  ) {
1297
1306
  stopMediaStream(stream);
1298
- resetRecordPress();
1307
+ if (currentGeneration) {
1308
+ recordingRequested = false;
1309
+ resetRecordPress();
1310
+ clearRecordingStatus(generation);
1311
+ if (transcriptionOperationId === operationId) {
1312
+ transcriptionOperationId = null;
1313
+ }
1314
+ renderControls();
1315
+ renderStatus();
1316
+ }
1299
1317
  return false;
1300
1318
  }
1301
1319
 
@@ -1311,6 +1329,7 @@
1311
1329
  const processor = audioContext.createScriptProcessor(4096, 1, 1);
1312
1330
  session = {
1313
1331
  generation,
1332
+ operationId,
1314
1333
  recorder,
1315
1334
  stream,
1316
1335
  audioContext,
@@ -1349,7 +1368,6 @@
1349
1368
  recorder.start();
1350
1369
  return true;
1351
1370
  } catch (error) {
1352
- recordingRequested = false;
1353
1371
  if (session) {
1354
1372
  session.cancelled = true;
1355
1373
  releaseCaptureSession(session);
@@ -1359,8 +1377,11 @@
1359
1377
  } else {
1360
1378
  stopMediaStream(stream);
1361
1379
  }
1362
- resetRecordPress();
1363
- reportTranscriptionError(error);
1380
+ if (generation === recordingGeneration) {
1381
+ recordingRequested = false;
1382
+ resetRecordPress();
1383
+ reportTranscriptionError(error);
1384
+ }
1364
1385
  return false;
1365
1386
  }
1366
1387
  }
@@ -1377,9 +1398,34 @@
1377
1398
  }
1378
1399
  }
1379
1400
 
1401
+ function clearRecordingStatus(generation) {
1402
+ if (
1403
+ localStatus?.owner === 'recording'
1404
+ && localStatus.generation === generation
1405
+ ) {
1406
+ localStatus = null;
1407
+ }
1408
+ }
1409
+
1380
1410
  function stop() {
1411
+ const pendingCaptureStart = recordingRequested && !captureSession;
1412
+ const generation = recordingGeneration;
1413
+ const operationId = transcriptionOperationId;
1381
1414
  recordingRequested = false;
1382
1415
  clearSilenceTimer();
1416
+ if (pendingCaptureStart) {
1417
+ // getUserMedia cannot be cancelled synchronously. Retire this
1418
+ // generation so its eventual settlement cannot touch a retry.
1419
+ recordingGeneration += 1;
1420
+ resetRecordPress();
1421
+ clearRecordingStatus(generation);
1422
+ if (transcriptionOperationId === operationId) {
1423
+ transcriptionOperationId = null;
1424
+ }
1425
+ renderControls();
1426
+ renderStatus();
1427
+ return true;
1428
+ }
1383
1429
  if (captureSession && captureSession.recorder.state !== 'inactive') {
1384
1430
  captureSession.recorder.stop();
1385
1431
  }
@@ -1387,22 +1433,32 @@
1387
1433
  }
1388
1434
 
1389
1435
  async function finalizeCapture(session) {
1436
+ const currentGeneration = session.generation === recordingGeneration;
1437
+ const currentOperation = transcriptionOperationId === session.operationId;
1390
1438
  releaseCaptureSession(session);
1391
1439
  if (captureSession === session) {
1392
1440
  captureSession = null;
1393
1441
  }
1394
- recordingRequested = false;
1395
- resetRecordPress();
1442
+ if (currentGeneration) {
1443
+ recordingRequested = false;
1444
+ resetRecordPress();
1445
+ }
1396
1446
 
1397
1447
  if (
1398
1448
  session.cancelled
1399
- || session.generation !== recordingGeneration
1449
+ || !currentGeneration
1400
1450
  || sttRole.state !== 'ready'
1401
1451
  || sttRole.busy
1402
1452
  || destroyed
1403
1453
  ) {
1404
- renderControls();
1405
- renderStatus();
1454
+ if (currentGeneration) {
1455
+ clearRecordingStatus(session.generation);
1456
+ if (currentOperation) {
1457
+ transcriptionOperationId = null;
1458
+ }
1459
+ renderControls();
1460
+ renderStatus();
1461
+ }
1406
1462
  return false;
1407
1463
  }
1408
1464
 
@@ -1415,12 +1471,13 @@
1415
1471
  'audio.webm',
1416
1472
  { type: 'audio/webm' }
1417
1473
  );
1418
- return transcribeAudio(audioFile);
1474
+ return transcribeAudio(audioFile, session.operationId);
1419
1475
  }
1420
1476
 
1421
- async function transcribeAudio(audioFile) {
1477
+ async function transcribeAudio(audioFile, operationId = null) {
1422
1478
  const generation = ++transcriptionGeneration;
1423
- transcriptionOperationId = transcriptionOperationId
1479
+ transcriptionOperationId = operationId
1480
+ || transcriptionOperationId
1424
1481
  || nextSpeechOperationId('transcription');
1425
1482
  const controller = new AbortController();
1426
1483
  transcriptionAbortController?.abort();
package/src/cli/main.mjs CHANGED
@@ -47,6 +47,7 @@ const REPORTABLE_COMMANDS=new Set([
47
47
  'native-doctor','native-prepare','new','package','repo','run','targets','test',
48
48
  'update-check','upgrade','verify','verify-bundle','version'
49
49
  ]);
50
+ const MAX_NODE_TIMER_DELAY_MS=2_147_483_647;
50
51
 
51
52
  export const HELP_TEXT=`Arcane OS application SDK ${SDK_VERSION}
52
53
 
@@ -76,7 +77,7 @@ Usage:
76
77
  ${CLI_NAME} mail key status <profile>
77
78
  ${CLI_NAME} mail key delete <profile>
78
79
  ${CLI_NAME} mail send --profile <profile> --from <address> --report-key <id> --report-stdin [--request-timeout <ms>]
79
- ${CLI_NAME} mail serve --profile <profile> --from <address> --app <id> --origin <origin> --allow-to <addresses> [--app-key-stdin] [--host 127.0.0.1] [--port 8025] [--request-timeout <ms>]
80
+ ${CLI_NAME} mail serve --profile <profile> --from <address> --app <id> --origin <origin> [--allow-to <addresses>] [--app-key-stdin] [--host 127.0.0.1] [--port 8025] [--request-timeout <ms>]
80
81
 
81
82
  Development:
82
83
  --sdk-runtime-source <sdk-root> Dev-only live SDK checkout; omitted preserves the workspace runtime mode.
@@ -174,16 +175,17 @@ function readPort(value,defaultValue){
174
175
  return port;
175
176
  }
176
177
 
177
- function readRequestTimeout(value,defaultValue){
178
- if(value===undefined){
179
- return defaultValue;
180
- }
178
+ function readRequestTimeout(value){
179
+ if(value===undefined)return undefined;
181
180
  if(!/^\d+$/u.test(value)){
182
181
  usage(`Invalid request timeout: ${value}.`);
183
182
  }
184
183
  const timeout=Number(value);
185
- if(!Number.isSafeInteger(timeout)||timeout<1_000||timeout>600_000){
186
- usage('Mail request timeout must be an integer between 1000 and 600000 milliseconds.');
184
+ if(!Number.isSafeInteger(timeout)||timeout<1||timeout>MAX_NODE_TIMER_DELAY_MS){
185
+ usage(
186
+ `Mail request timeout must be an integer from 1 through ${MAX_NODE_TIMER_DELAY_MS} `
187
+ +'milliseconds, the Node timer range.'
188
+ );
187
189
  }
188
190
  return timeout;
189
191
  }
@@ -649,8 +651,7 @@ function operationOptions(command,parsed,cwd){
649
651
  profile:values.profile,
650
652
  from:values.from,
651
653
  app:values.app,
652
- origin:values.origin,
653
- 'allow-to':values['allow-to'],
654
+ origin:values.origin
654
655
  })){
655
656
  if(!value)usage(`mail serve requires --${name} <value>.`);
656
657
  }
@@ -664,7 +665,7 @@ function operationOptions(command,parsed,cwd){
664
665
  appKeyStdin:flags.has('app-key-stdin'),
665
666
  host:values.host??'127.0.0.1',
666
667
  port:readPort(values.port,8025),
667
- requestTimeout:readRequestTimeout(values['request-timeout'],30_000),
668
+ requestTimeout:readRequestTimeout(values['request-timeout']),
668
669
  };
669
670
  }
670
671
  if(area==='send'){
@@ -693,7 +694,7 @@ function operationOptions(command,parsed,cwd){
693
694
  from:values.from,
694
695
  reportKey:values['report-key'],
695
696
  reportStdin:true,
696
- requestTimeout:readRequestTimeout(values['request-timeout'],30_000),
697
+ requestTimeout:readRequestTimeout(values['request-timeout']),
697
698
  };
698
699
  }
699
700
  usage('mail requires key set|status|delete <profile>, send, or serve.');
@@ -19,6 +19,7 @@ const PREFLIGHT_HEADERS=new Set([
19
19
  ]);
20
20
  const PERMANENT_RATE_CODES=new Set(['daily_quota_exceeded','monthly_quota_exceeded']);
21
21
  const RETRYABLE_PROVIDER_STATUSES=new Set([408,425,429,500,502,503,504]);
22
+ const MAX_NODE_TIMER_DELAY_MS=2_147_483_647;
22
23
 
23
24
  class MailGatewayFault extends Error {
24
25
  constructor(code,{details=null,retryable=false,retryAfterMs=0,statusCode=400,uncertain=false}={}){
@@ -61,9 +62,15 @@ function positiveInteger(value,fallback,{label,allowZero=false}={}){
61
62
  return resolved;
62
63
  }
63
64
 
64
- function optionalPositiveInteger(value,label){
65
+ function optionalTimerDelay(value,label){
65
66
  if(value===undefined||value===null) return null;
66
- return positiveInteger(value,null,{label});
67
+ if(!Number.isSafeInteger(value)||value<1||value>MAX_NODE_TIMER_DELAY_MS){
68
+ throw configurationError(
69
+ `${label} must be an integer from 1 through ${MAX_NODE_TIMER_DELAY_MS} `
70
+ +'milliseconds, the Node timer range.'
71
+ );
72
+ }
73
+ return value;
67
74
  }
68
75
 
69
76
  function normalizeRetryAfter(value){
@@ -248,7 +255,7 @@ function normalizeConfiguration(options={}){
248
255
  apiKey:validateApiKey(options.apiKey),
249
256
  appKeyDigest:callerAuthentication.appKeyDigest,
250
257
  appId:validateAppId(options.appId),
251
- bodyTimeoutMs:optionalPositiveInteger(options.bodyTimeoutMs,'bodyTimeoutMs'),
258
+ bodyTimeoutMs:optionalTimerDelay(options.bodyTimeoutMs,'bodyTimeoutMs'),
252
259
  errorRecipients,
253
260
  fetchImpl,
254
261
  from:validateFrom(options.from),
@@ -256,7 +263,7 @@ function normalizeConfiguration(options={}){
256
263
  callerAuthentication:callerAuthentication.callerAuthentication,
257
264
  onEvent:options.onEvent,
258
265
  port:portNumber(options.port,8025),
259
- providerTimeoutMs:optionalPositiveInteger(options.providerTimeoutMs,'providerTimeoutMs'),
266
+ providerTimeoutMs:optionalTimerDelay(options.providerTimeoutMs,'providerTimeoutMs'),
260
267
  requestIdFactory:options.requestIdFactory??randomUUID,
261
268
  retryableDelayMs:positiveInteger(
262
269
  options.retryableDelayMs,
@@ -968,7 +975,7 @@ function normalizeDirectSendOptions(options){
968
975
  errorRecipients:[],
969
976
  fetchImpl,
970
977
  from:validateFrom(options.from),
971
- providerTimeoutMs:optionalPositiveInteger(options.providerTimeoutMs,'providerTimeoutMs'),
978
+ providerTimeoutMs:optionalTimerDelay(options.providerTimeoutMs,'providerTimeoutMs'),
972
979
  requestIdFactory:options.requestIdFactory??randomUUID,
973
980
  retryableDelayMs:positiveInteger(
974
981
  options.retryableDelayMs,
package/src/mail.mjs CHANGED
@@ -40,13 +40,16 @@ function dependency(options,name,fallback){
40
40
  }
41
41
 
42
42
  function recipientList(value,label){
43
+ if(value===undefined||value===null||value===''||(Array.isArray(value)&&value.length===0)){
44
+ return [];
45
+ }
43
46
  const entries=Array.isArray(value)
44
47
  ? value
45
48
  : typeof value==='string'
46
49
  ? value.split(',')
47
50
  : null;
48
- if(!entries||entries.length===0||entries.length>50){
49
- usage(`${label} must contain one to 50 comma-separated email addresses.`);
51
+ if(!entries||entries.length===0){
52
+ usage(`${label} must contain at least one email address when supplied.`);
50
53
  }
51
54
  const result=entries.map(function normalizeMailRecipient(entry){
52
55
  if(typeof entry!=='string'||!entry.trim()){
@@ -111,21 +114,24 @@ async function deleteCredential(options){
111
114
  return remove(credentialOptions(options));
112
115
  }
113
116
 
114
- function safeSendFailure(result){
115
- return {
116
- provider:'resend',
117
- status:result.status,
118
- classification:result.classification,
119
- requestId:result.requestId,
120
- providerStatus:result.providerStatus,
121
- recipientCount:result.recipientCount,
122
- retryable:result.retryable===true,
123
- uncertain:result.uncertain===true,
124
- ...(typeof result.code==='string'?{code:result.code}:{}),
125
- ...(Number.isSafeInteger(result.retryAfterMs)&&result.retryAfterMs>0
126
- ?{retryAfterMs:result.retryAfterMs}
127
- :{})
128
- };
117
+ function withoutMailCredentials(value,seen=new WeakMap()){
118
+ if(!value||typeof value!=='object')return value;
119
+ if(seen.has(value))return seen.get(value);
120
+ const copy=Array.isArray(value)?[]:{};
121
+ seen.set(value,copy);
122
+ for(const [key,entry] of Object.entries(value)){
123
+ if(key==='apiKey'||key==='appKey')continue;
124
+ copy[key]=withoutMailCredentials(entry,seen);
125
+ }
126
+ return copy;
127
+ }
128
+
129
+ function completeSendFailure(result){
130
+ if(!result||typeof result!=='object'||Array.isArray(result)){
131
+ return {provider:'resend',result:withoutMailCredentials(result)};
132
+ }
133
+ // Complete provider detail remains visible; only credential fields are omitted.
134
+ return withoutMailCredentials(result);
129
135
  }
130
136
 
131
137
  async function sendMail(options){
@@ -164,9 +170,9 @@ async function sendMail(options){
164
170
  signal:options.signal
165
171
  });
166
172
  if(result?.classification==='accepted'&&result.status==='accepted'){
167
- return result;
173
+ return withoutMailCredentials(result);
168
174
  }
169
- const details=safeSendFailure(result||{});
175
+ const details=completeSendFailure(result);
170
176
  throw new ArcaneError(
171
177
  ERROR_CODES.operationFailed,
172
178
  `Resend did not authoritatively accept the mail request (${details.classification||'unknown'}).`,
package/src/workspace.mjs CHANGED
@@ -94,6 +94,18 @@ function dependencyNameForSdkPackageSource(source){
94
94
  }
95
95
  }
96
96
 
97
+ function routeIncludeMatches(actual,wanted,optionalSecurity){
98
+ if(!Array.isArray(actual)||!Array.isArray(wanted))return false;
99
+ if(actual.length===wanted.length
100
+ &&actual.every(function sameIncludedPath(value,index){return value===wanted[index];})){
101
+ return true;
102
+ }
103
+ return optionalSecurity
104
+ &&wanted.at(-1)==='security'
105
+ &&actual.length===wanted.length-1
106
+ &&actual.every(function sameFunctionalPath(value,index){return value===wanted[index];});
107
+ }
108
+
97
109
  export function resolveSdkPackageDeclaration(rootPackage,{
98
110
  allowMissing=false,
99
111
  packageSource
@@ -179,19 +191,30 @@ function classifyRootConfig(config){
179
191
  include:['index.js','licence','package.json']
180
192
  }
181
193
  ];
182
- const matches=expected=>routes.length===expected.length&&routes.every((route,index)=>{
183
- const wanted=expected[index];
184
- return isObject(route)&&Object.keys(route).every(key=>['source','destination','include','exclude'].includes(key))
185
- &&route.source===wanted.source&&route.destination===wanted.destination
186
- &&JSON.stringify(route.include)===JSON.stringify(wanted.include)
187
- &&Array.isArray(route.exclude)&&route.exclude.length===0;
188
- });
194
+ const matches=(expected,{optionalArcaneSecurity=false}={})=>{
195
+ if(routes.length!==expected.length)return false;
196
+ return routes.every(function routeMatches(route,index){
197
+ const wanted=expected[index];
198
+ return isObject(route)
199
+ &&Object.keys(route).every(function knownRouteKey(key){
200
+ return ['source','destination','include','exclude'].includes(key);
201
+ })
202
+ &&route.source===wanted.source&&route.destination===wanted.destination
203
+ &&routeIncludeMatches(
204
+ route.include,
205
+ wanted.include,
206
+ optionalArcaneSecurity&&index===0
207
+ &&wanted.source==='arcane'&&wanted.destination==='arcane'
208
+ )
209
+ &&Array.isArray(route.exclude)&&route.exclude.length===0;
210
+ });
211
+ };
189
212
  let workspaceMode;
190
213
  let browserRuntimeLayout;
191
- if(matches(external)){
214
+ if(matches(external,{optionalArcaneSecurity:true})){
192
215
  workspaceMode='external';
193
216
  browserRuntimeLayout='physical-v1';
194
- }else if(matches(integrated)){
217
+ }else if(matches(integrated,{optionalArcaneSecurity:true})){
195
218
  workspaceMode='integrated';
196
219
  browserRuntimeLayout='physical-v1';
197
220
  }else if(matches(integratedLegacy)){