arcane-os 0.1.0-dev.5 → 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/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +86 -0
- package/browser-runtime/dependencies/event-pubsub/index.js +141 -0
- package/browser-runtime/dependencies/event-pubsub/licence +21 -0
- package/browser-runtime/dependencies/event-pubsub/package.json +59 -0
- package/browser-runtime/dependencies/strong-type/index.js +1151 -0
- package/browser-runtime/dependencies/strong-type/licence +21 -0
- package/browser-runtime/dependencies/strong-type/package.json +61 -0
- package/browser-runtime/dom-event-instrumentation.mjs +594 -0
- package/browser-runtime/event-manager.mjs +1342 -0
- package/docs/publishing.md +52 -59
- package/docs/reference/event-manager.md +5 -5
- package/docs/work-amplification.md +4 -3
- package/package.json +11 -7
- package/runtime/ARCANE_RUNTIME_RELEASE.json +1 -1
- package/schemas/arcane-lock.schema.json +86 -1
- package/src/cli/main.mjs +5 -0
- package/src/dev-server.mjs +78 -34
- package/src/doctor.mjs +77 -3
- package/src/import-map.mjs +2328 -0
- package/src/packager/core.mjs +607 -29
- package/src/scaffold.mjs +122 -5
- package/src/sdk-browser-runtime.mjs +585 -0
- package/src/targets/index.mjs +31 -4
- package/src/templates/workspace-template.mjs +135 -23
- package/src/toolchain.mjs +288 -55
- package/src/workspace-operation-lock.mjs +716 -0
- package/src/workspace-runtime.mjs +841 -0
- package/src/workspace.mjs +286 -37
package/src/packager/core.mjs
CHANGED
|
@@ -16,12 +16,25 @@ import path from 'node:path';
|
|
|
16
16
|
import {pathToFileURL} from 'node:url';
|
|
17
17
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
18
18
|
import {isDeepStrictEqual} from 'node:util';
|
|
19
|
+
import {withWorkspaceOperationLock} from '../workspace-operation-lock.mjs';
|
|
20
|
+
import {generateImportMap} from '../import-map.mjs';
|
|
21
|
+
import {authenticateRuntimeReceipt,verifyRuntime} from '../runtime.mjs';
|
|
22
|
+
import {
|
|
23
|
+
authenticateSdkBrowserRuntimeReceipt,
|
|
24
|
+
verifySdkBrowserRuntime
|
|
25
|
+
} from '../sdk-browser-runtime.mjs';
|
|
26
|
+
import {
|
|
27
|
+
authenticateWorkspaceRuntimeReceipt,
|
|
28
|
+
verifyWorkspaceRuntime
|
|
29
|
+
} from '../workspace-runtime.mjs';
|
|
19
30
|
|
|
20
31
|
export const ROOT_CONFIG_NAME='arcane-packager.json';
|
|
21
32
|
export const APP_CONFIG_NAME='arcane-package.json';
|
|
22
33
|
export const RELEASE_MANIFEST_NAME='ARCANE_APP_RELEASE.json';
|
|
23
34
|
export const PACKAGER_VERSION='arcane-app-packager-v1';
|
|
24
35
|
|
|
36
|
+
const RUNTIME_AUTHORITIES_NAME='ARCANE_RUNTIME_AUTHORITIES.json';
|
|
37
|
+
|
|
25
38
|
const RENAME_RETRY_CODES=new Set(['EACCES','EBUSY','EPERM']);
|
|
26
39
|
const RENAME_RETRY_LIMIT=20;
|
|
27
40
|
const RENAME_RETRY_DELAY_MS=250;
|
|
@@ -135,6 +148,7 @@ const OLLAMA_MODEL_IDENTIFIER=
|
|
|
135
148
|
const MODEL_DEFINITION_PATTERN=/^(?:Modelfile|[A-Za-z0-9][A-Za-z0-9._-]{0,118}\.Modelfile)$/;
|
|
136
149
|
const MAX_LOCAL_AI_MODELS=64;
|
|
137
150
|
const MAX_MODEL_DEFINITION_BYTES=512*1024;
|
|
151
|
+
const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
|
|
138
152
|
|
|
139
153
|
function fail(message,code){
|
|
140
154
|
const error=new Error(message);
|
|
@@ -1015,6 +1029,413 @@ async function assertValidatedDescriptorAuthority(validation,descriptorAuthority
|
|
|
1015
1029
|
}
|
|
1016
1030
|
}
|
|
1017
1031
|
|
|
1032
|
+
function packagedRuntimeAuthorities(receipt){
|
|
1033
|
+
const arcane=receipt?.sources?.arcane;
|
|
1034
|
+
const sdkBrowser=receipt?.sources?.sdkBrowser;
|
|
1035
|
+
if(receipt?.kind!=='arcane-workspace-runtime-verification'
|
|
1036
|
+
||!Number.isSafeInteger(receipt.fileCount)||receipt.fileCount<1
|
|
1037
|
+
||!Number.isSafeInteger(receipt.totalBytes)||receipt.totalBytes<1
|
|
1038
|
+
||!SHA256_PATTERN.test(receipt.contentSha256??'')
|
|
1039
|
+
||!isPlainObject(arcane)||arcane.authority!=='arcane-os-upstream'
|
|
1040
|
+
||!SHA256_PATTERN.test(arcane.manifestSha256??'')
|
|
1041
|
+
||!SHA256_PATTERN.test(arcane.contentSha256??'')
|
|
1042
|
+
||!isPlainObject(arcane.source)
|
|
1043
|
+
||!isPlainObject(sdkBrowser)||sdkBrowser.authority!=='arcane-os-sdk'
|
|
1044
|
+
||!SHA256_PATTERN.test(sdkBrowser.manifestSha256??'')
|
|
1045
|
+
||!SHA256_PATTERN.test(sdkBrowser.contentSha256??'')
|
|
1046
|
+
||!isPlainObject(sdkBrowser.source)
|
|
1047
|
+
||!Array.isArray(sdkBrowser.source.dependencies)){
|
|
1048
|
+
fail('The composed workspace runtime receipt is missing its source authorities.');
|
|
1049
|
+
}
|
|
1050
|
+
return immutableJsonCopy({
|
|
1051
|
+
schemaVersion:1,
|
|
1052
|
+
kind:'arcane-app-runtime-authorities',
|
|
1053
|
+
sdkVersion:receipt.sdkVersion,
|
|
1054
|
+
projection:{
|
|
1055
|
+
fileCount:receipt.fileCount,
|
|
1056
|
+
totalBytes:receipt.totalBytes,
|
|
1057
|
+
contentSha256:receipt.contentSha256
|
|
1058
|
+
},
|
|
1059
|
+
sources:{
|
|
1060
|
+
arcane:{
|
|
1061
|
+
authority:arcane.authority,
|
|
1062
|
+
manifestSha256:arcane.manifestSha256,
|
|
1063
|
+
contentSha256:arcane.contentSha256,
|
|
1064
|
+
source:arcane.source
|
|
1065
|
+
},
|
|
1066
|
+
sdkBrowser:{
|
|
1067
|
+
authority:sdkBrowser.authority,
|
|
1068
|
+
manifestSha256:sdkBrowser.manifestSha256,
|
|
1069
|
+
contentSha256:sdkBrowser.contentSha256,
|
|
1070
|
+
source:sdkBrowser.source
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
async function hasExternalRuntimeAdmission(context){
|
|
1077
|
+
const lockPath=path.join(context.workspaceRoot,'arcane.lock.json');
|
|
1078
|
+
try{
|
|
1079
|
+
const info=await lstat(lockPath);
|
|
1080
|
+
if(info.isSymbolicLink()||!info.isFile()){
|
|
1081
|
+
fail('arcane.lock.json must be a real file before runtime provenance can be packaged.');
|
|
1082
|
+
}
|
|
1083
|
+
return true;
|
|
1084
|
+
}catch(error){
|
|
1085
|
+
if(error?.code==='ENOENT')return false;
|
|
1086
|
+
throw error;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
async function validateExternalRuntimeAdmission(context,{signal,onEvent}={}){
|
|
1091
|
+
const {validateWorkspace}=await import('../workspace.mjs');
|
|
1092
|
+
const validation=await validateWorkspace({
|
|
1093
|
+
workspaceRoot:context.workspaceRoot,
|
|
1094
|
+
appId:context.config.id,
|
|
1095
|
+
signal,
|
|
1096
|
+
onEvent
|
|
1097
|
+
});
|
|
1098
|
+
if(validation.workspaceMode!=='external'){
|
|
1099
|
+
fail('A workspace with arcane.lock.json must use the external SDK runtime contract.');
|
|
1100
|
+
}
|
|
1101
|
+
return validation;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
async function integratedWorkspaceCandidate(context){
|
|
1105
|
+
const packagePath=path.join(context.workspaceRoot,'package.json');
|
|
1106
|
+
let info;
|
|
1107
|
+
try{
|
|
1108
|
+
info=await lstat(packagePath);
|
|
1109
|
+
}catch(error){
|
|
1110
|
+
if(error?.code==='ENOENT')return false;
|
|
1111
|
+
throw error;
|
|
1112
|
+
}
|
|
1113
|
+
if(info.isSymbolicLink()||!info.isFile()){
|
|
1114
|
+
fail('workspace package.json must be a real file.');
|
|
1115
|
+
}
|
|
1116
|
+
const document=await readJsonDocument(packagePath,'workspace package.json');
|
|
1117
|
+
return document.value?.name==='arcane-os'&&document.value?.type==='module';
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function packageRuntimeLocations(context){
|
|
1121
|
+
const installedRoot=path.join(context.workspaceRoot,'node_modules','arcane-os');
|
|
1122
|
+
return Object.freeze({
|
|
1123
|
+
runtimeRoot:path.join(installedRoot,'runtime'),
|
|
1124
|
+
browserRuntimeRoot:path.join(installedRoot,'browser-runtime')
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async function authenticatePackageRuntimeVerificationState(context,state,{signal}={}){
|
|
1129
|
+
assertOnlyKeys(
|
|
1130
|
+
state,
|
|
1131
|
+
new Set(['runtimeReceipt','sdkBrowserRuntimeReceipt','workspaceRuntimeReceipt']),
|
|
1132
|
+
'runtime verification state'
|
|
1133
|
+
);
|
|
1134
|
+
const descriptors=Object.getOwnPropertyDescriptors(state);
|
|
1135
|
+
const required=['runtimeReceipt','sdkBrowserRuntimeReceipt','workspaceRuntimeReceipt'];
|
|
1136
|
+
if(required.some(key=>!Object.hasOwn(descriptors,key)||!Object.hasOwn(descriptors[key],'value'))){
|
|
1137
|
+
fail('The runtime verification state must use fixed receipt references, not accessors.');
|
|
1138
|
+
}
|
|
1139
|
+
const snapshot=Object.freeze({
|
|
1140
|
+
runtimeReceipt:descriptors.runtimeReceipt.value,
|
|
1141
|
+
sdkBrowserRuntimeReceipt:descriptors.sdkBrowserRuntimeReceipt.value,
|
|
1142
|
+
workspaceRuntimeReceipt:descriptors.workspaceRuntimeReceipt.value
|
|
1143
|
+
});
|
|
1144
|
+
if(!snapshot.runtimeReceipt
|
|
1145
|
+
||!snapshot.sdkBrowserRuntimeReceipt
|
|
1146
|
+
||!snapshot.workspaceRuntimeReceipt){
|
|
1147
|
+
fail('The runtime verification state must contain all three authenticated receipts.');
|
|
1148
|
+
}
|
|
1149
|
+
const {runtimeRoot,browserRuntimeRoot}=packageRuntimeLocations(context);
|
|
1150
|
+
await authenticateRuntimeReceipt(snapshot.runtimeReceipt,{runtimeRoot,signal});
|
|
1151
|
+
await authenticateSdkBrowserRuntimeReceipt(snapshot.sdkBrowserRuntimeReceipt,{
|
|
1152
|
+
browserRuntimeRoot,
|
|
1153
|
+
signal
|
|
1154
|
+
});
|
|
1155
|
+
await authenticateWorkspaceRuntimeReceipt(snapshot.workspaceRuntimeReceipt,{
|
|
1156
|
+
workspaceRoot:context.workspaceRoot,
|
|
1157
|
+
signal
|
|
1158
|
+
});
|
|
1159
|
+
const workspaceReceipt=snapshot.workspaceRuntimeReceipt;
|
|
1160
|
+
const expectedArcaneSource={
|
|
1161
|
+
authority:'arcane-os-upstream',
|
|
1162
|
+
location:snapshot.runtimeReceipt.canonicalLocation,
|
|
1163
|
+
manifestSha256:snapshot.runtimeReceipt.manifestSha256,
|
|
1164
|
+
contentSha256:snapshot.runtimeReceipt.contentSha256,
|
|
1165
|
+
source:snapshot.runtimeReceipt.source
|
|
1166
|
+
};
|
|
1167
|
+
const expectedBrowserSource={
|
|
1168
|
+
authority:'arcane-os-sdk',
|
|
1169
|
+
location:snapshot.sdkBrowserRuntimeReceipt.canonicalLocation,
|
|
1170
|
+
manifestSha256:snapshot.sdkBrowserRuntimeReceipt.manifestSha256,
|
|
1171
|
+
contentSha256:snapshot.sdkBrowserRuntimeReceipt.contentSha256,
|
|
1172
|
+
source:snapshot.sdkBrowserRuntimeReceipt.source
|
|
1173
|
+
};
|
|
1174
|
+
if(workspaceReceipt.sourceRuntimeLocation!==snapshot.runtimeReceipt.canonicalLocation
|
|
1175
|
+
||workspaceReceipt.sourceManifestSha256!==snapshot.runtimeReceipt.manifestSha256
|
|
1176
|
+
||workspaceReceipt.sourceContentSha256!==snapshot.runtimeReceipt.contentSha256
|
|
1177
|
+
||workspaceReceipt.sourceBrowserRuntimeLocation
|
|
1178
|
+
!==snapshot.sdkBrowserRuntimeReceipt.canonicalLocation
|
|
1179
|
+
||workspaceReceipt.sourceBrowserManifestSha256
|
|
1180
|
+
!==snapshot.sdkBrowserRuntimeReceipt.manifestSha256
|
|
1181
|
+
||workspaceReceipt.sourceBrowserContentSha256
|
|
1182
|
+
!==snapshot.sdkBrowserRuntimeReceipt.contentSha256
|
|
1183
|
+
||workspaceReceipt.sdkVersion!==snapshot.runtimeReceipt.sdkVersion
|
|
1184
|
+
||workspaceReceipt.sdkVersion!==snapshot.sdkBrowserRuntimeReceipt.sdkVersion
|
|
1185
|
+
||!isDeepStrictEqual(workspaceReceipt.sources?.arcane,expectedArcaneSource)
|
|
1186
|
+
||!isDeepStrictEqual(workspaceReceipt.sources?.sdkBrowser,expectedBrowserSource)){
|
|
1187
|
+
fail('The workspace runtime receipt is not bound to the supplied source runtime receipts.');
|
|
1188
|
+
}
|
|
1189
|
+
return snapshot;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
async function issuePackageRuntimeVerificationState(context,{signal,onEvent}={}){
|
|
1193
|
+
const {runtimeRoot,browserRuntimeRoot}=packageRuntimeLocations(context);
|
|
1194
|
+
const [runtimeReceipt,sdkBrowserRuntimeReceipt]=await Promise.all([
|
|
1195
|
+
verifyRuntime({runtimeRoot,signal,onEvent}),
|
|
1196
|
+
verifySdkBrowserRuntime({browserRuntimeRoot,signal,onEvent})
|
|
1197
|
+
]);
|
|
1198
|
+
const workspaceRuntimeReceipt=await verifyWorkspaceRuntime({
|
|
1199
|
+
workspaceRoot:context.workspaceRoot,
|
|
1200
|
+
runtimeRoot,
|
|
1201
|
+
runtimeReceipt,
|
|
1202
|
+
browserRuntimeRoot,
|
|
1203
|
+
sdkBrowserRuntimeReceipt,
|
|
1204
|
+
signal,
|
|
1205
|
+
onEvent
|
|
1206
|
+
});
|
|
1207
|
+
return Object.freeze({runtimeReceipt,sdkBrowserRuntimeReceipt,workspaceRuntimeReceipt});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function refreshPackageImportMap(context,{
|
|
1211
|
+
runtimeVerificationState,
|
|
1212
|
+
workspaceOperationLease,
|
|
1213
|
+
signal,
|
|
1214
|
+
onEvent
|
|
1215
|
+
}={}){
|
|
1216
|
+
const external=await hasExternalRuntimeAdmission(context);
|
|
1217
|
+
if(!external&&!await integratedWorkspaceCandidate(context)){
|
|
1218
|
+
if(runtimeVerificationState!==undefined){
|
|
1219
|
+
fail('A runtime verification state cannot be supplied without an external runtime admission.');
|
|
1220
|
+
}
|
|
1221
|
+
return Object.freeze({importMapReceipt:null,runtimeVerificationState:null});
|
|
1222
|
+
}
|
|
1223
|
+
const {validateWorkspace}=await import('../workspace.mjs');
|
|
1224
|
+
const validation=await validateWorkspace({
|
|
1225
|
+
workspaceRoot:context.workspaceRoot,
|
|
1226
|
+
appId:context.config.id,
|
|
1227
|
+
allowMissingManagedImportMap:true,
|
|
1228
|
+
signal,
|
|
1229
|
+
onEvent
|
|
1230
|
+
});
|
|
1231
|
+
if(validation.workspaceMode!=='external'&&runtimeVerificationState!==undefined){
|
|
1232
|
+
fail('A runtime verification state cannot be supplied to an integrated workspace.');
|
|
1233
|
+
}
|
|
1234
|
+
if(validation.workspaceMode==='integrated'
|
|
1235
|
+
&&validation.config.browserRuntimeLayout==='integrated-legacy'){
|
|
1236
|
+
return Object.freeze({
|
|
1237
|
+
importMapReceipt:Object.freeze({skipped:true,workspaceMode:'integrated'}),
|
|
1238
|
+
runtimeVerificationState:null
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
let workspaceRuntimeReceipt;
|
|
1243
|
+
let authenticatedRuntimeState=null;
|
|
1244
|
+
if(validation.workspaceMode==='external'){
|
|
1245
|
+
authenticatedRuntimeState=runtimeVerificationState===undefined
|
|
1246
|
+
?await issuePackageRuntimeVerificationState(context,{signal,onEvent})
|
|
1247
|
+
:await authenticatePackageRuntimeVerificationState(
|
|
1248
|
+
context,
|
|
1249
|
+
runtimeVerificationState,
|
|
1250
|
+
{signal}
|
|
1251
|
+
);
|
|
1252
|
+
workspaceRuntimeReceipt=authenticatedRuntimeState.workspaceRuntimeReceipt;
|
|
1253
|
+
}
|
|
1254
|
+
const importMapReceipt=await generateImportMap({
|
|
1255
|
+
workspaceRoot:context.workspaceRoot,
|
|
1256
|
+
appId:context.config.id,
|
|
1257
|
+
appRoot:context.appRoot,
|
|
1258
|
+
entry:context.config.entry,
|
|
1259
|
+
workspaceRuntimeReceipt,
|
|
1260
|
+
workspaceOperationLease,
|
|
1261
|
+
signal,
|
|
1262
|
+
onEvent
|
|
1263
|
+
});
|
|
1264
|
+
return Object.freeze({importMapReceipt,runtimeVerificationState:authenticatedRuntimeState});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function authenticatedImportMapReceipt(receipt){
|
|
1268
|
+
if(receipt==null||receipt.skipped===true)return receipt;
|
|
1269
|
+
if(receipt.committed!==true||!Array.isArray(receipt.cleanupWarnings)){
|
|
1270
|
+
fail(
|
|
1271
|
+
'The generated import-map receipt is incomplete; the package release was not assembled.'
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
if(receipt.cleanupWarnings.length!==0){
|
|
1275
|
+
fail(
|
|
1276
|
+
'The generated import map committed with cleanup warnings; the package release '
|
|
1277
|
+
+`was not assembled: ${receipt.cleanupWarnings.join('; ')}`,
|
|
1278
|
+
'ARCANE_IMPORT_MAP_CLEANUP_FAILED'
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
return receipt;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function importMapReceiptFiles(context,receipt){
|
|
1285
|
+
if(receipt==null||receipt.skipped===true)return Object.freeze([]);
|
|
1286
|
+
if(!Array.isArray(receipt.files)||receipt.files.length!==2){
|
|
1287
|
+
fail('The generated import-map receipt does not bind its committed artifact and entry files.');
|
|
1288
|
+
}
|
|
1289
|
+
const expected=Object.freeze([
|
|
1290
|
+
Object.freeze({
|
|
1291
|
+
role:'artifact',
|
|
1292
|
+
path:`apps/${context.config.id}/modules/arcane.importmap.json`
|
|
1293
|
+
}),
|
|
1294
|
+
Object.freeze({
|
|
1295
|
+
role:'entry',
|
|
1296
|
+
path:`apps/${context.config.id}/${context.config.entry}`
|
|
1297
|
+
})
|
|
1298
|
+
]);
|
|
1299
|
+
const records=[];
|
|
1300
|
+
for(const [index,record] of receipt.files.entries()){
|
|
1301
|
+
assertOnlyKeys(
|
|
1302
|
+
record,
|
|
1303
|
+
new Set(['role','path','bytes','sha256']),
|
|
1304
|
+
`import-map receipt files[${index}]`
|
|
1305
|
+
);
|
|
1306
|
+
const wanted=expected[index];
|
|
1307
|
+
if(record.role!==wanted.role
|
|
1308
|
+
||normalizeRelativePath(record.path,`import-map receipt files[${index}].path`)
|
|
1309
|
+
!==wanted.path
|
|
1310
|
+
||!Number.isSafeInteger(record.bytes)||record.bytes<1
|
|
1311
|
+
||!SHA256_PATTERN.test(record.sha256??'')){
|
|
1312
|
+
fail(`The generated import-map receipt ${wanted.role} record is invalid.`);
|
|
1313
|
+
}
|
|
1314
|
+
records.push(Object.freeze({...record}));
|
|
1315
|
+
}
|
|
1316
|
+
return Object.freeze(records);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
async function authenticateImportMapPair(context,receipt,{signal}={}){
|
|
1320
|
+
const records=importMapReceiptFiles(context,receipt);
|
|
1321
|
+
for(const record of records){
|
|
1322
|
+
const filePath=resolveInside(
|
|
1323
|
+
context.workspaceRoot,
|
|
1324
|
+
record.path,
|
|
1325
|
+
`import-map receipt ${record.role} path`
|
|
1326
|
+
);
|
|
1327
|
+
let verified;
|
|
1328
|
+
try{
|
|
1329
|
+
verified=await sha256WithIdentity(filePath,{
|
|
1330
|
+
signal,
|
|
1331
|
+
label:`import-map receipt ${record.role}`
|
|
1332
|
+
});
|
|
1333
|
+
}catch(error){
|
|
1334
|
+
fail(
|
|
1335
|
+
`The generated import-map ${record.role} is unavailable after commit: ${error.message}`
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
if(verified.identity.bytes!==record.bytes||verified.sha256!==record.sha256){
|
|
1339
|
+
fail(`The generated import-map ${record.role} changed after it was committed.`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return records;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async function authenticateCollectedImportMapPair(files,records,{signal}={}){
|
|
1346
|
+
for(const record of records){
|
|
1347
|
+
const collected=files.find(file=>file.destination===record.path);
|
|
1348
|
+
if(!collected||collected.bytes!==record.bytes){
|
|
1349
|
+
fail(`The package payload does not contain the committed import-map ${record.role}.`);
|
|
1350
|
+
}
|
|
1351
|
+
const verified=await sha256WithIdentity(collected.source,{
|
|
1352
|
+
signal,
|
|
1353
|
+
expectedIdentity:collected.identity,
|
|
1354
|
+
label:`collected import-map ${record.role}`
|
|
1355
|
+
});
|
|
1356
|
+
if(verified.identity.bytes!==record.bytes||verified.sha256!==record.sha256){
|
|
1357
|
+
fail(`The collected import-map ${record.role} does not match its committed receipt.`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function authenticatePackagedImportMapPair(release,records){
|
|
1363
|
+
for(const record of records){
|
|
1364
|
+
const packaged=release.files.find(file=>file.path===record.path);
|
|
1365
|
+
if(!packaged||packaged.bytes!==record.bytes||packaged.sha256!==record.sha256){
|
|
1366
|
+
fail(`The packaged import-map ${record.role} does not match its committed receipt.`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
async function prepareRuntimeAuthorityState(context,{
|
|
1372
|
+
validation,
|
|
1373
|
+
runtimeVerificationState,
|
|
1374
|
+
signal,
|
|
1375
|
+
onEvent
|
|
1376
|
+
}={}){
|
|
1377
|
+
if(!await hasExternalRuntimeAdmission(context)){
|
|
1378
|
+
if(runtimeVerificationState!==undefined){
|
|
1379
|
+
fail('A runtime verification state cannot be supplied to an integrated workspace.');
|
|
1380
|
+
}
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
await validateExternalRuntimeAdmission(context,{signal,onEvent});
|
|
1384
|
+
|
|
1385
|
+
let verificationState=null;
|
|
1386
|
+
let receipt=null;
|
|
1387
|
+
if(runtimeVerificationState!==undefined){
|
|
1388
|
+
verificationState=await authenticatePackageRuntimeVerificationState(
|
|
1389
|
+
context,
|
|
1390
|
+
runtimeVerificationState,
|
|
1391
|
+
{signal}
|
|
1392
|
+
);
|
|
1393
|
+
receipt=verificationState.workspaceRuntimeReceipt;
|
|
1394
|
+
}else if(validation?.kind==='arcane-workspace-runtime-verification'){
|
|
1395
|
+
receipt=validation;
|
|
1396
|
+
await authenticateWorkspaceRuntimeReceipt(receipt,{
|
|
1397
|
+
workspaceRoot:context.workspaceRoot,
|
|
1398
|
+
signal
|
|
1399
|
+
});
|
|
1400
|
+
}else{
|
|
1401
|
+
verificationState=await issuePackageRuntimeVerificationState(context,{signal,onEvent});
|
|
1402
|
+
receipt=verificationState.workspaceRuntimeReceipt;
|
|
1403
|
+
}
|
|
1404
|
+
return Object.freeze({
|
|
1405
|
+
receipt,
|
|
1406
|
+
document:packagedRuntimeAuthorities(receipt),
|
|
1407
|
+
verificationState
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
async function authenticateRuntimeAuthorityState(context,state,{signal,onEvent}={}){
|
|
1412
|
+
if(state===null){
|
|
1413
|
+
if(await hasExternalRuntimeAdmission(context)){
|
|
1414
|
+
fail('External runtime authority admission appeared during package verification.');
|
|
1415
|
+
}
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if(!await hasExternalRuntimeAdmission(context)){
|
|
1419
|
+
fail('External runtime authority admission disappeared during package verification.');
|
|
1420
|
+
}
|
|
1421
|
+
await validateExternalRuntimeAdmission(context,{signal,onEvent});
|
|
1422
|
+
if(state.verificationState){
|
|
1423
|
+
await authenticatePackageRuntimeVerificationState(
|
|
1424
|
+
context,
|
|
1425
|
+
state.verificationState,
|
|
1426
|
+
{signal}
|
|
1427
|
+
);
|
|
1428
|
+
}else{
|
|
1429
|
+
await authenticateWorkspaceRuntimeReceipt(state.receipt,{
|
|
1430
|
+
workspaceRoot:context.workspaceRoot,
|
|
1431
|
+
signal
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
if(!isDeepStrictEqual(packagedRuntimeAuthorities(state.receipt),state.document)){
|
|
1435
|
+
fail('External runtime source authorities changed during package verification.');
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1018
1439
|
async function getAppContext({
|
|
1019
1440
|
workspaceRoot:requestedWorkspaceRoot,
|
|
1020
1441
|
appId,
|
|
@@ -1991,6 +2412,56 @@ async function writeReleaseManifest(root,context,version,{signal,onEvent}={}){
|
|
|
1991
2412
|
};
|
|
1992
2413
|
}
|
|
1993
2414
|
|
|
2415
|
+
async function writeRuntimeAuthorities(root,state){
|
|
2416
|
+
if(state==null)return;
|
|
2417
|
+
const authorityPath=path.join(root,RUNTIME_AUTHORITIES_NAME);
|
|
2418
|
+
await writeFile(
|
|
2419
|
+
authorityPath,
|
|
2420
|
+
`${JSON.stringify(state.document,null,2)}\n`,
|
|
2421
|
+
{encoding:'utf8',flag:'wx'}
|
|
2422
|
+
);
|
|
2423
|
+
const authority=await openStableRegularFile(authorityPath,RUNTIME_AUTHORITIES_NAME);
|
|
2424
|
+
await authority.handle.close();
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
async function verifyRuntimeAuthorities(root,state){
|
|
2428
|
+
const authorityPath=path.join(root,RUNTIME_AUTHORITIES_NAME);
|
|
2429
|
+
if(state==null){
|
|
2430
|
+
try{
|
|
2431
|
+
await lstat(authorityPath);
|
|
2432
|
+
fail(`${RUNTIME_AUTHORITIES_NAME} is not allowed without an external runtime authority.`);
|
|
2433
|
+
}catch(error){
|
|
2434
|
+
if(error?.code!=='ENOENT')throw error;
|
|
2435
|
+
}
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
const document=await readJsonDocument(authorityPath,RUNTIME_AUTHORITIES_NAME);
|
|
2439
|
+
if(!isDeepStrictEqual(document.value,state.document)){
|
|
2440
|
+
fail(`${RUNTIME_AUTHORITIES_NAME} does not match the admitted workspace runtime authorities.`);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
function verifyRuntimeProjectionAuthority(release,state){
|
|
2445
|
+
if(state==null)return;
|
|
2446
|
+
const projection=release.files
|
|
2447
|
+
.filter(file=>file.path.startsWith('arcane/'))
|
|
2448
|
+
.map(file=>({
|
|
2449
|
+
path:file.path.slice('arcane/'.length),
|
|
2450
|
+
bytes:file.bytes,
|
|
2451
|
+
sha256:file.sha256
|
|
2452
|
+
}));
|
|
2453
|
+
const totalBytes=projection.reduce((total,file)=>total+file.bytes,0);
|
|
2454
|
+
const contentSha256=createHash('sha256')
|
|
2455
|
+
.update(JSON.stringify(projection))
|
|
2456
|
+
.digest('hex');
|
|
2457
|
+
const expected=state.document.projection;
|
|
2458
|
+
if(projection.length!==expected.fileCount
|
|
2459
|
+
||totalBytes!==expected.totalBytes
|
|
2460
|
+
||contentSha256!==expected.contentSha256){
|
|
2461
|
+
fail('The packaged arcane runtime does not match its admitted runtime authorities.');
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
|
|
1994
2465
|
function expectedReleaseApp(context,version){
|
|
1995
2466
|
const {config}=context;
|
|
1996
2467
|
return {
|
|
@@ -2004,7 +2475,10 @@ function expectedReleaseApp(context,version){
|
|
|
2004
2475
|
};
|
|
2005
2476
|
}
|
|
2006
2477
|
|
|
2007
|
-
async function verifyFreshStaticRelease(root,context,version,releaseState,{
|
|
2478
|
+
async function verifyFreshStaticRelease(root,context,version,releaseState,{
|
|
2479
|
+
runtimeAuthorityState,
|
|
2480
|
+
signal
|
|
2481
|
+
}={}){
|
|
2008
2482
|
throwIfAborted(signal);
|
|
2009
2483
|
const {config}=context;
|
|
2010
2484
|
const expectedApp=expectedReleaseApp(context,version);
|
|
@@ -2034,10 +2508,17 @@ async function verifyFreshStaticRelease(root,context,version,releaseState,{signa
|
|
|
2034
2508
|
fail(`Package entry files for ${config.id} are invalid.`);
|
|
2035
2509
|
}
|
|
2036
2510
|
|
|
2511
|
+
await verifyRuntimeAuthorities(root,runtimeAuthorityState);
|
|
2512
|
+
verifyRuntimeProjectionAuthority(release,runtimeAuthorityState);
|
|
2513
|
+
|
|
2037
2514
|
return releaseState;
|
|
2038
2515
|
}
|
|
2039
2516
|
|
|
2040
|
-
async function verifyGenericRelease(root,context,version,{
|
|
2517
|
+
async function verifyGenericRelease(root,context,version,{
|
|
2518
|
+
runtimeAuthorityState,
|
|
2519
|
+
signal,
|
|
2520
|
+
onEvent
|
|
2521
|
+
}={}){
|
|
2041
2522
|
const {config}=context;
|
|
2042
2523
|
const manifestDocument=await readJsonDocument(
|
|
2043
2524
|
path.join(root,RELEASE_MANIFEST_NAME),
|
|
@@ -2076,6 +2557,9 @@ async function verifyGenericRelease(root,context,version,{signal,onEvent}={}){
|
|
|
2076
2557
|
fail(`Package entry files for ${config.id} are invalid.`);
|
|
2077
2558
|
}
|
|
2078
2559
|
|
|
2560
|
+
await verifyRuntimeAuthorities(root,runtimeAuthorityState);
|
|
2561
|
+
verifyRuntimeProjectionAuthority(release,runtimeAuthorityState);
|
|
2562
|
+
|
|
2079
2563
|
return {
|
|
2080
2564
|
release,
|
|
2081
2565
|
identities:Object.freeze([
|
|
@@ -2118,7 +2602,11 @@ async function loadAdapter(context){
|
|
|
2118
2602
|
return module;
|
|
2119
2603
|
}
|
|
2120
2604
|
|
|
2121
|
-
async function verifyBuiltPackage(context,outputRoot,version,adapter,{
|
|
2605
|
+
async function verifyBuiltPackage(context,outputRoot,version,adapter,{
|
|
2606
|
+
runtimeAuthorityState,
|
|
2607
|
+
signal,
|
|
2608
|
+
onEvent
|
|
2609
|
+
}={}){
|
|
2122
2610
|
throwIfAborted(signal);
|
|
2123
2611
|
if(adapter){
|
|
2124
2612
|
await adapter.verifyArcanePackage({
|
|
@@ -2132,7 +2620,11 @@ async function verifyBuiltPackage(context,outputRoot,version,adapter,{signal,onE
|
|
|
2132
2620
|
});
|
|
2133
2621
|
}
|
|
2134
2622
|
|
|
2135
|
-
return verifyGenericRelease(outputRoot,context,version,{
|
|
2623
|
+
return verifyGenericRelease(outputRoot,context,version,{
|
|
2624
|
+
runtimeAuthorityState,
|
|
2625
|
+
signal,
|
|
2626
|
+
onEvent
|
|
2627
|
+
});
|
|
2136
2628
|
}
|
|
2137
2629
|
|
|
2138
2630
|
async function writeAppVersion(context,version){
|
|
@@ -2376,6 +2868,8 @@ async function packageAppUnlocked({
|
|
|
2376
2868
|
context:preparedContext,
|
|
2377
2869
|
sharedPayloadSnapshot,
|
|
2378
2870
|
authenticatedSharedPayloadState,
|
|
2871
|
+
importMapReceipt,
|
|
2872
|
+
runtimeVerificationState,
|
|
2379
2873
|
signal,
|
|
2380
2874
|
onEvent,
|
|
2381
2875
|
validateSourceState
|
|
@@ -2392,10 +2886,16 @@ async function packageAppUnlocked({
|
|
|
2392
2886
|
});
|
|
2393
2887
|
const currentVersion=context.config.version;
|
|
2394
2888
|
const version=resolveTargetVersion(currentVersion,{bump,exactVersion,preid});
|
|
2889
|
+
const importMapFiles=dryRun
|
|
2890
|
+
?Object.freeze([])
|
|
2891
|
+
:await authenticateImportMapPair(context,importMapReceipt,{signal});
|
|
2395
2892
|
const files=await collectPackageFiles(context,{
|
|
2396
2893
|
signal,
|
|
2397
2894
|
sharedPayloadState:authenticatedSharedPayloadState
|
|
2398
2895
|
});
|
|
2896
|
+
if(!dryRun){
|
|
2897
|
+
await authenticateCollectedImportMapPair(files,importMapFiles,{signal});
|
|
2898
|
+
}
|
|
2399
2899
|
const preview={
|
|
2400
2900
|
app:appId,
|
|
2401
2901
|
currentVersion,
|
|
@@ -2423,6 +2923,7 @@ async function packageAppUnlocked({
|
|
|
2423
2923
|
let promoted=false;
|
|
2424
2924
|
let operationSucceeded=false;
|
|
2425
2925
|
let rollbackRestored=false;
|
|
2926
|
+
let runtimeAuthorityState=null;
|
|
2426
2927
|
|
|
2427
2928
|
try{
|
|
2428
2929
|
await rm(staging,{recursive:true,force:true});
|
|
@@ -2468,16 +2969,42 @@ async function packageAppUnlocked({
|
|
|
2468
2969
|
}
|
|
2469
2970
|
|
|
2470
2971
|
throwIfAborted(signal);
|
|
2972
|
+
let sourceValidation;
|
|
2973
|
+
if(validateSourceState){
|
|
2974
|
+
sourceValidation=await validateSourceState({signal});
|
|
2975
|
+
await assertValidatedDescriptorAuthority(
|
|
2976
|
+
sourceValidation,
|
|
2977
|
+
context.descriptorAuthority
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
runtimeAuthorityState=await prepareRuntimeAuthorityState(context,{
|
|
2981
|
+
validation:sourceValidation,
|
|
2982
|
+
runtimeVerificationState,
|
|
2983
|
+
signal,
|
|
2984
|
+
onEvent
|
|
2985
|
+
});
|
|
2986
|
+
await writeRuntimeAuthorities(staging,runtimeAuthorityState);
|
|
2471
2987
|
const releaseState=await writeReleaseManifest(staging,context,version,{signal,onEvent});
|
|
2472
2988
|
const verifiedRelease=adapter
|
|
2473
|
-
?await verifyBuiltPackage(context,staging,version,adapter,{
|
|
2474
|
-
|
|
2989
|
+
?await verifyBuiltPackage(context,staging,version,adapter,{
|
|
2990
|
+
runtimeAuthorityState,
|
|
2991
|
+
signal,
|
|
2992
|
+
onEvent
|
|
2993
|
+
})
|
|
2994
|
+
:await verifyFreshStaticRelease(staging,context,version,releaseState,{
|
|
2995
|
+
runtimeAuthorityState,
|
|
2996
|
+
signal
|
|
2997
|
+
});
|
|
2475
2998
|
|
|
2476
2999
|
throwIfAborted(signal);
|
|
2477
3000
|
if(validateSourceState){
|
|
2478
3001
|
const validation=await validateSourceState({signal});
|
|
2479
|
-
await assertValidatedDescriptorAuthority(
|
|
3002
|
+
await assertValidatedDescriptorAuthority(
|
|
3003
|
+
validation,
|
|
3004
|
+
context.descriptorAuthority
|
|
3005
|
+
);
|
|
2480
3006
|
}
|
|
3007
|
+
await authenticateRuntimeAuthorityState(context,runtimeAuthorityState,{signal,onEvent});
|
|
2481
3008
|
await assertAppDescriptorAuthorityCurrent(context,{signal});
|
|
2482
3009
|
if(sharedPayloadSnapshot!==undefined){
|
|
2483
3010
|
await authenticateSharedPayloadSnapshotState(sharedPayloadSnapshot,{
|
|
@@ -2486,6 +3013,8 @@ async function packageAppUnlocked({
|
|
|
2486
3013
|
signal
|
|
2487
3014
|
});
|
|
2488
3015
|
}
|
|
3016
|
+
await authenticateImportMapPair(context,importMapReceipt,{signal});
|
|
3017
|
+
authenticatePackagedImportMapPair(verifiedRelease.release,importMapFiles);
|
|
2489
3018
|
await assertArtifactState(staging,verifiedRelease.identities,{signal});
|
|
2490
3019
|
throwIfAborted(signal);
|
|
2491
3020
|
|
|
@@ -2584,20 +3113,56 @@ export async function packageApp(options){
|
|
|
2584
3113
|
signal:options?.signal
|
|
2585
3114
|
});
|
|
2586
3115
|
if(options?.dryRun){
|
|
3116
|
+
if(options?.runtimeVerificationState!==undefined){
|
|
3117
|
+
if(!await hasExternalRuntimeAdmission(context)){
|
|
3118
|
+
fail('A runtime verification state cannot be supplied to an integrated workspace.');
|
|
3119
|
+
}
|
|
3120
|
+
await authenticatePackageRuntimeVerificationState(
|
|
3121
|
+
context,
|
|
3122
|
+
options.runtimeVerificationState,
|
|
3123
|
+
{signal:options?.signal}
|
|
3124
|
+
);
|
|
3125
|
+
}
|
|
2587
3126
|
return packageAppUnlocked({...options,context,authenticatedSharedPayloadState});
|
|
2588
3127
|
}
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
}
|
|
2596
|
-
await
|
|
2597
|
-
|
|
3128
|
+
return withWorkspaceOperationLock({
|
|
3129
|
+
workspaceRoot:context.workspaceRoot,
|
|
3130
|
+
operation:'package',
|
|
3131
|
+
workspaceOperationLease:options?.workspaceOperationLease,
|
|
3132
|
+
signal:options?.signal,
|
|
3133
|
+
onEvent:options?.onEvent
|
|
3134
|
+
},async workspaceOperationLease=>{
|
|
3135
|
+
await assertSafeDistBoundary(context.workspaceRoot,context.distRoot,{create:true});
|
|
3136
|
+
const releaseLock=await acquirePackageLock(context.distRoot,options?.appId);
|
|
3137
|
+
try{
|
|
3138
|
+
const refreshed=await refreshPackageImportMap(context,{
|
|
3139
|
+
runtimeVerificationState:options?.runtimeVerificationState,
|
|
3140
|
+
workspaceOperationLease,
|
|
3141
|
+
signal:options?.signal,
|
|
3142
|
+
onEvent:options?.onEvent
|
|
3143
|
+
});
|
|
3144
|
+
const importMapReceipt=authenticatedImportMapReceipt(refreshed.importMapReceipt);
|
|
3145
|
+
const packaged=await packageAppUnlocked({
|
|
3146
|
+
...options,
|
|
3147
|
+
context,
|
|
3148
|
+
authenticatedSharedPayloadState,
|
|
3149
|
+
importMapReceipt,
|
|
3150
|
+
runtimeVerificationState:refreshed.runtimeVerificationState??undefined
|
|
3151
|
+
});
|
|
3152
|
+
return {...packaged,importMapReceipt};
|
|
3153
|
+
}finally{
|
|
3154
|
+
await releaseLock();
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
2598
3157
|
}
|
|
2599
3158
|
|
|
2600
|
-
export async function verifyApp({
|
|
3159
|
+
export async function verifyApp({
|
|
3160
|
+
workspaceRoot,
|
|
3161
|
+
appId,
|
|
3162
|
+
runtimeVerificationState,
|
|
3163
|
+
signal,
|
|
3164
|
+
onEvent
|
|
3165
|
+
}){
|
|
2601
3166
|
throwIfAborted(signal);
|
|
2602
3167
|
const context=await getAppContext({
|
|
2603
3168
|
workspaceRoot,
|
|
@@ -2605,14 +3170,20 @@ export async function verifyApp({workspaceRoot,appId,signal,onEvent}){
|
|
|
2605
3170
|
bindDescriptorAuthority:true,
|
|
2606
3171
|
signal
|
|
2607
3172
|
});
|
|
3173
|
+
const runtimeAuthorityState=await prepareRuntimeAuthorityState(context,{
|
|
3174
|
+
runtimeVerificationState,
|
|
3175
|
+
signal,
|
|
3176
|
+
onEvent
|
|
3177
|
+
});
|
|
2608
3178
|
const adapter=await loadAdapter(context);
|
|
2609
3179
|
const releaseState=await verifyBuiltPackage(
|
|
2610
3180
|
context,
|
|
2611
3181
|
context.outputRoot,
|
|
2612
3182
|
context.config.version,
|
|
2613
3183
|
adapter,
|
|
2614
|
-
{signal,onEvent}
|
|
3184
|
+
{runtimeAuthorityState,signal,onEvent}
|
|
2615
3185
|
);
|
|
3186
|
+
await authenticateRuntimeAuthorityState(context,runtimeAuthorityState,{signal,onEvent});
|
|
2616
3187
|
const release=releaseState.release;
|
|
2617
3188
|
const receipt=await issueAppReleaseReceipt(
|
|
2618
3189
|
context.outputRoot,
|
|
@@ -2674,18 +3245,25 @@ export async function bumpVersion(options){
|
|
|
2674
3245
|
return bumpVersionUnlocked(options);
|
|
2675
3246
|
}
|
|
2676
3247
|
|
|
2677
|
-
await getAppContext({
|
|
3248
|
+
const context=await getAppContext({
|
|
2678
3249
|
workspaceRoot:options?.workspaceRoot,
|
|
2679
3250
|
appId:options?.appId
|
|
2680
3251
|
});
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
3252
|
+
return withWorkspaceOperationLock({
|
|
3253
|
+
workspaceRoot:context.workspaceRoot,
|
|
3254
|
+
operation:'version-bump',
|
|
3255
|
+
workspaceOperationLease:options?.workspaceOperationLease,
|
|
3256
|
+
signal:options?.signal,
|
|
3257
|
+
onEvent:options?.onEvent
|
|
3258
|
+
},async()=>{
|
|
3259
|
+
const releaseLock=await acquireOperationLock(
|
|
3260
|
+
context.workspaceRoot,
|
|
3261
|
+
options?.appId
|
|
3262
|
+
);
|
|
3263
|
+
try{
|
|
3264
|
+
return await bumpVersionUnlocked({...options,workspaceRoot:context.workspaceRoot});
|
|
3265
|
+
}finally{
|
|
3266
|
+
await releaseLock();
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
2691
3269
|
}
|