arcane-os 0.1.0-dev.5
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 +154 -0
- package/COMMERCIAL-LICENSE.md +13 -0
- package/LICENSE +661 -0
- package/NOTICE +70 -0
- package/README.md +448 -0
- package/bin/arcane-test.mjs +741 -0
- package/bin/arcane.mjs +5 -0
- package/docs/architecture.md +234 -0
- package/docs/compatibility.md +36 -0
- package/docs/event-manager.md +166 -0
- package/docs/platform-targets.md +108 -0
- package/docs/publishing.md +203 -0
- package/docs/reference/README.md +117 -0
- package/docs/reference/arcane-ollama.md +288 -0
- package/docs/reference/availability-and-normalization.md +123 -0
- package/docs/reference/behavioral-testing.md +86 -0
- package/docs/reference/cli.md +569 -0
- package/docs/reference/core/README.md +62 -0
- package/docs/reference/core/arcane-ai-contracts.md +873 -0
- package/docs/reference/core/arcane-api.md +601 -0
- package/docs/reference/core/arcane-entities.md +65 -0
- package/docs/reference/core/arcane-events.md +134 -0
- package/docs/reference/core/ollama-module.md +181 -0
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
- package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
- package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
- package/docs/reference/event-manager.md +957 -0
- package/docs/reference/inventory/package-api.json +2632 -0
- package/docs/reference/inventory/runtime-components.json +934 -0
- package/docs/reference/inventory/runtime-entities.json +26 -0
- package/docs/reference/inventory/runtime-modules.json +1249 -0
- package/docs/reference/protocols.md +242 -0
- package/docs/reference/runtime-components.md +1098 -0
- package/docs/reference/runtime-entities.md +303 -0
- package/docs/reference/runtime-modules.md +2010 -0
- package/docs/reference/sdk-api.md +4901 -0
- package/docs/roadmap.md +79 -0
- package/docs/work-amplification.md +124 -0
- package/node_modules/event-pubsub/CHANGELOG.md +55 -0
- package/node_modules/event-pubsub/MIGRATION.md +70 -0
- package/node_modules/event-pubsub/README.md +363 -0
- package/node_modules/event-pubsub/SECURITY.md +37 -0
- package/node_modules/event-pubsub/index.js +141 -0
- package/node_modules/event-pubsub/licence +21 -0
- package/node_modules/event-pubsub/package.json +59 -0
- package/node_modules/strong-type/README.md +408 -0
- package/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/strong-type/index.js +1151 -0
- package/node_modules/strong-type/licence +21 -0
- package/node_modules/strong-type/node.js +125 -0
- package/node_modules/strong-type/package.json +61 -0
- package/package.json +95 -0
- package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
- package/runtime/arcane/components/app-bar.html +468 -0
- package/runtime/arcane/components/assistant-panel.html +715 -0
- package/runtime/arcane/components/calculator.html +8 -0
- package/runtime/arcane/components/chart.html +655 -0
- package/runtime/arcane/components/chat.html +1225 -0
- package/runtime/arcane/components/conversation-view.html +13 -0
- package/runtime/arcane/components/dashboard-config.html +341 -0
- package/runtime/arcane/components/data-maintenance.html +112 -0
- package/runtime/arcane/components/data-view.html +92 -0
- package/runtime/arcane/components/directory-picker.html +197 -0
- package/runtime/arcane/components/document-inspector.html +252 -0
- package/runtime/arcane/components/file-drop.html +264 -0
- package/runtime/arcane/components/file-inspector.html +293 -0
- package/runtime/arcane/components/file-manager.html +1715 -0
- package/runtime/arcane/components/header.html +142 -0
- package/runtime/arcane/components/integration-settings.html +14 -0
- package/runtime/arcane/components/local-ai-status.html +360 -0
- package/runtime/arcane/components/markdown-document.html +1048 -0
- package/runtime/arcane/components/markdown-editor.html +360 -0
- package/runtime/arcane/components/media-embed.html +8 -0
- package/runtime/arcane/components/modal.html +402 -0
- package/runtime/arcane/components/output-panel.html +259 -0
- package/runtime/arcane/components/preferences-form.html +135 -0
- package/runtime/arcane/components/record-timeline.html +105 -0
- package/runtime/arcane/components/relationship-board.html +116 -0
- package/runtime/arcane/components/screen-capture.html +8 -0
- package/runtime/arcane/components/source-code-viewer.html +441 -0
- package/runtime/arcane/components/source-explanation.html +124 -0
- package/runtime/arcane/components/speech.html +365 -0
- package/runtime/arcane/components/summary-strip.html +177 -0
- package/runtime/arcane/components/table.html +77 -0
- package/runtime/arcane/components/task-progress.html +282 -0
- package/runtime/arcane/components/terminal-workspace.html +65 -0
- package/runtime/arcane/components/theme-editor.html +41 -0
- package/runtime/arcane/components/theme-switcher.html +46 -0
- package/runtime/arcane/components/unified-inbox.html +20 -0
- package/runtime/arcane/components/voice-transcription.html +476 -0
- package/runtime/arcane/components/weather-widget.html +8 -0
- package/runtime/arcane/components/web-navigator.html +239 -0
- package/runtime/arcane/css/communications.css +1 -0
- package/runtime/arcane/css/dashboard-config.css +45 -0
- package/runtime/arcane/css/document-site.css +981 -0
- package/runtime/arcane/css/layout.css +438 -0
- package/runtime/arcane/css/primitives.css +321 -0
- package/runtime/arcane/css/theme.css +112 -0
- package/runtime/arcane/css/utility-workspace.css +1 -0
- package/runtime/arcane/entities/ApiModelRecord.js +20 -0
- package/runtime/arcane/entities/Calculation.js +13 -0
- package/runtime/arcane/entities/Chat.js +581 -0
- package/runtime/arcane/entities/CommunicationMessage.js +29 -0
- package/runtime/arcane/entities/CommunicationThread.js +21 -0
- package/runtime/arcane/entities/Document.js +10 -0
- package/runtime/arcane/entities/File.js +143 -0
- package/runtime/arcane/entities/Image.js +135 -0
- package/runtime/arcane/entities/IntentEnvelope.js +834 -0
- package/runtime/arcane/entities/Preference.js +83 -0
- package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
- package/runtime/arcane/entities/TerminalSession.js +46 -0
- package/runtime/arcane/entities/Theme.js +107 -0
- package/runtime/arcane/entities/User.js +1046 -0
- package/runtime/arcane/entities/Weather.js +23 -0
- package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
- package/runtime/arcane/img/arrow-left.png +0 -0
- package/runtime/arcane/img/arrow-right.png +0 -0
- package/runtime/arcane/img/doc.svg +5 -0
- package/runtime/arcane/img/folder.svg +4 -0
- package/runtime/arcane/img/image.svg +5 -0
- package/runtime/arcane/img/refresh.png +0 -0
- package/runtime/arcane/img/send.svg +9 -0
- package/runtime/arcane/img/trash.svg +5 -0
- package/runtime/arcane/img/upload.svg +5 -0
- package/runtime/arcane/modules/AI.js +2048 -0
- package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
- package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
- package/runtime/arcane/modules/AIResponseLength.js +42 -0
- package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
- package/runtime/arcane/modules/AnsiText.js +53 -0
- package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
- package/runtime/arcane/modules/AppDataScope.js +245 -0
- package/runtime/arcane/modules/AppearancePreferences.js +28 -0
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
- package/runtime/arcane/modules/AsyncBoundary.js +161 -0
- package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
- package/runtime/arcane/modules/CalculatorEngine.js +20 -0
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
- package/runtime/arcane/modules/ChartLibrary.js +35 -0
- package/runtime/arcane/modules/ChatRecords.js +13 -0
- package/runtime/arcane/modules/CommunicationAppController.js +43 -0
- package/runtime/arcane/modules/CommunicationHub.js +16 -0
- package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
- package/runtime/arcane/modules/ComponentContracts.js +586 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
- package/runtime/arcane/modules/ConversationActionItems.js +488 -0
- package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
- package/runtime/arcane/modules/ConversationTimebox.js +527 -0
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
- package/runtime/arcane/modules/DBLS.js +171 -0
- package/runtime/arcane/modules/DBOPFS.js +1154 -0
- package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
- package/runtime/arcane/modules/DataMaintenance.js +91 -0
- package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
- package/runtime/arcane/modules/DirectoryPicker.js +109 -0
- package/runtime/arcane/modules/DocumentNavigation.js +223 -0
- package/runtime/arcane/modules/Errors.js +1025 -0
- package/runtime/arcane/modules/GifEncoder.js +29 -0
- package/runtime/arcane/modules/HTMLImport.js +114 -0
- package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
- package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
- package/runtime/arcane/modules/MD.js +111 -0
- package/runtime/arcane/modules/Mail.js +352 -0
- package/runtime/arcane/modules/MailTransport.mjs +180 -0
- package/runtime/arcane/modules/Marked.min.js +71 -0
- package/runtime/arcane/modules/MemoryRecords.js +44 -0
- package/runtime/arcane/modules/MessageAdvisory.js +38 -0
- package/runtime/arcane/modules/ModelDefinition.js +189 -0
- package/runtime/arcane/modules/Ollama.js +74 -0
- package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
- package/runtime/arcane/modules/OllamaSettings.js +24 -0
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
- package/runtime/arcane/modules/PreferenceStore.js +109 -0
- package/runtime/arcane/modules/QRCode.min.js +1 -0
- package/runtime/arcane/modules/Questionnaire.js +61 -0
- package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
- package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
- package/runtime/arcane/modules/RecordReviewStore.js +94 -0
- package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
- package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
- package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
- package/runtime/arcane/modules/ScreenCapture.js +20 -0
- package/runtime/arcane/modules/SpeechPlayback.js +581 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
- package/runtime/arcane/modules/SystemAppearance.js +20 -0
- package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
- package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
- package/runtime/arcane/modules/TerminalClient.js +52 -0
- package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
- package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
- package/runtime/arcane/modules/ThemeManager.js +131 -0
- package/runtime/arcane/modules/TimeGuard.js +149 -0
- package/runtime/arcane/modules/ToolCallRouter.js +83 -0
- package/runtime/arcane/modules/WaitForComponent.js +102 -0
- package/runtime/arcane/modules/YouTubeMedia.js +16 -0
- package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
- package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
- package/runtime/arcane/modules/uPlot.min.css +1 -0
- package/runtime/arcane/security/arcane-network-policy.json +6 -0
- package/runtime/strong-type/index.js +352 -0
- package/runtime/strong-type/licence +21 -0
- package/runtime/strong-type/package.json +45 -0
- package/schemas/arcane-app-bundle.schema.json +125 -0
- package/schemas/arcane-app.schema.json +329 -0
- package/schemas/arcane-lock.schema.json +86 -0
- package/schemas/arcane-package.schema.json +224 -0
- package/schemas/cli-event.schema.json +122 -0
- package/schemas/event-stack.schema.json +152 -0
- package/schemas/native-build-plan.schema.json +176 -0
- package/schemas/target-adapter.schema.json +117 -0
- package/src/app-descriptor.mjs +500 -0
- package/src/cli/main.mjs +561 -0
- package/src/constants.mjs +31 -0
- package/src/dev-server.mjs +718 -0
- package/src/doctor.mjs +315 -0
- package/src/dom-event-instrumentation.mjs +594 -0
- package/src/errors.mjs +75 -0
- package/src/event-manager.mjs +1342 -0
- package/src/event-queue.mjs +138 -0
- package/src/events.mjs +219 -0
- package/src/index.mjs +177 -0
- package/src/integrated-provider-loader.mjs +432 -0
- package/src/native-plan.mjs +698 -0
- package/src/native-provider-loader.mjs +1126 -0
- package/src/packager/core.mjs +2691 -0
- package/src/process.mjs +353 -0
- package/src/release-bundle.mjs +2523 -0
- package/src/repository.mjs +90 -0
- package/src/runtime.mjs +452 -0
- package/src/scaffold.mjs +380 -0
- package/src/targets/index.mjs +436 -0
- package/src/templates/assets/app-icon.png +0 -0
- package/src/templates/workspace-template.mjs +388 -0
- package/src/testing-loader.mjs +9 -0
- package/src/testing.mjs +427 -0
- package/src/toolchain.mjs +1335 -0
- package/src/update-check.mjs +307 -0
- package/src/workspace.mjs +449 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import {randomBytes} from 'node:crypto';
|
|
2
|
+
import {lstat,realpath} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
appDescriptorSha256,
|
|
6
|
+
projectNativeDescriptor,
|
|
7
|
+
projectPackageManifest,
|
|
8
|
+
validateAppDescriptor
|
|
9
|
+
} from './app-descriptor.mjs';
|
|
10
|
+
import {
|
|
11
|
+
authenticateAppReleaseAuthority,
|
|
12
|
+
parseSemver,
|
|
13
|
+
readVerifiedAppReleaseFile
|
|
14
|
+
} from './packager/core.mjs';
|
|
15
|
+
import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
|
|
16
|
+
|
|
17
|
+
export const NATIVE_BUILD_PLAN_PROTOCOL='arcane-native-build-plan/1';
|
|
18
|
+
export const NATIVE_BUILDER_PROTOCOL='arcane-native-builder/1';
|
|
19
|
+
|
|
20
|
+
const NATIVE_TARGETS=Object.freeze({
|
|
21
|
+
portable:Object.freeze({
|
|
22
|
+
platforms:new Set(['windows','linux']),
|
|
23
|
+
architectures:new Set(['x64','arm64']),
|
|
24
|
+
format:'portable',
|
|
25
|
+
signingMode:'unsigned-local-test',
|
|
26
|
+
signingProfileId:null
|
|
27
|
+
}),
|
|
28
|
+
'windows-x64':Object.freeze({
|
|
29
|
+
platforms:new Set(['windows']),architectures:new Set(['x64']),format:'exe',
|
|
30
|
+
signingMode:'unsigned-local-test',signingProfileId:null
|
|
31
|
+
}),
|
|
32
|
+
'linux-x64':Object.freeze({
|
|
33
|
+
platforms:new Set(['linux']),architectures:new Set(['x64']),format:'deb',
|
|
34
|
+
signingMode:'unsigned-local-test',signingProfileId:null
|
|
35
|
+
}),
|
|
36
|
+
'linux-arm64':Object.freeze({
|
|
37
|
+
platforms:new Set(['linux']),architectures:new Set(['arm64']),format:'deb',
|
|
38
|
+
signingMode:'unsigned-local-test',signingProfileId:null
|
|
39
|
+
}),
|
|
40
|
+
'android-arm64':Object.freeze({
|
|
41
|
+
platforms:new Set(['android']),architectures:new Set(['arm64']),format:'apk',
|
|
42
|
+
signingMode:'development',signingProfileId:'arcane-android-development-v1'
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
|
|
46
|
+
const issuedPlans=new WeakMap();
|
|
47
|
+
const attemptedPlans=new WeakSet();
|
|
48
|
+
|
|
49
|
+
function fail(message,code=ERROR_CODES.policyDenied,details){
|
|
50
|
+
throw new ArcaneError(code,message,{details});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isObject(value){
|
|
54
|
+
return value!==null&&typeof value==='object'&&!Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertOnlyKeys(value,allowed,label){
|
|
58
|
+
if(!isObject(value))fail(`${label} must be an object.`);
|
|
59
|
+
for(const key of Object.keys(value)){
|
|
60
|
+
if(!allowed.has(key))fail(`${label} contains an unsupported key: ${key}.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function immutable(value){
|
|
65
|
+
if(Array.isArray(value))return Object.freeze(value.map(immutable));
|
|
66
|
+
if(isObject(value))return Object.freeze(Object.fromEntries(
|
|
67
|
+
Object.entries(value).map(([key,item])=>[key,immutable(item)])
|
|
68
|
+
));
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function compareSemver(leftValue,rightValue){
|
|
73
|
+
const left=parseSemver(leftValue);
|
|
74
|
+
const right=parseSemver(rightValue);
|
|
75
|
+
for(const key of ['major','minor','patch']){
|
|
76
|
+
if(left[key]!==right[key])return left[key]<right[key]?-1:1;
|
|
77
|
+
}
|
|
78
|
+
if(!left.prerelease.length&&!right.prerelease.length)return 0;
|
|
79
|
+
if(!left.prerelease.length)return 1;
|
|
80
|
+
if(!right.prerelease.length)return -1;
|
|
81
|
+
const length=Math.max(left.prerelease.length,right.prerelease.length);
|
|
82
|
+
for(let index=0;index<length;index+=1){
|
|
83
|
+
const leftPart=left.prerelease[index];
|
|
84
|
+
const rightPart=right.prerelease[index];
|
|
85
|
+
if(leftPart===undefined)return -1;
|
|
86
|
+
if(rightPart===undefined)return 1;
|
|
87
|
+
if(leftPart===rightPart)continue;
|
|
88
|
+
const leftNumeric=/^\d+$/u.test(leftPart);
|
|
89
|
+
const rightNumeric=/^\d+$/u.test(rightPart);
|
|
90
|
+
if(leftNumeric&&rightNumeric){
|
|
91
|
+
if(leftPart.length!==rightPart.length)return leftPart.length<rightPart.length?-1:1;
|
|
92
|
+
return leftPart<rightPart?-1:1;
|
|
93
|
+
}
|
|
94
|
+
if(leftNumeric!==rightNumeric)return leftNumeric?-1:1;
|
|
95
|
+
return leftPart<rightPart?-1:1;
|
|
96
|
+
}
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function canonicalContractList(value,label,{maximum=4096}={}){
|
|
101
|
+
if(!Array.isArray(value)||value.length>maximum||value.some(item=>typeof item!=='string'||!item)){
|
|
102
|
+
fail(`${label} must be a bounded, unique, sorted string array.`,ERROR_CODES.integrityFailed);
|
|
103
|
+
}
|
|
104
|
+
const sorted=[...value].sort((left,right)=>left.localeCompare(right,'en'));
|
|
105
|
+
if(new Set(value).size!==value.length||value.some((item,index)=>item!==sorted[index])){
|
|
106
|
+
fail(`${label} must be a bounded, unique, sorted string array.`,ERROR_CODES.integrityFailed);
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assertRequirementsAvailable(required,available,label){
|
|
112
|
+
if(!Array.isArray(available)){
|
|
113
|
+
fail(`The native toolchain receipt does not bind supported ${label}.`,ERROR_CODES.integrityFailed);
|
|
114
|
+
}
|
|
115
|
+
const missing=required.filter(value=>!available.includes(value));
|
|
116
|
+
if(missing.length){
|
|
117
|
+
fail(
|
|
118
|
+
`The native toolchain does not provide required ${label}: ${missing.join(', ')}.`,
|
|
119
|
+
ERROR_CODES.targetUnavailable
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function assertNativeToolchainCompatibility({
|
|
125
|
+
appDescriptor,
|
|
126
|
+
toolchainReceipt,
|
|
127
|
+
minimumCoreVersion
|
|
128
|
+
}={}){
|
|
129
|
+
const descriptor=validateAppDescriptor(appDescriptor,{appId:appDescriptor?.id});
|
|
130
|
+
const requirements=descriptor.requirements;
|
|
131
|
+
if(!isObject(toolchainReceipt)||typeof toolchainReceipt.version!=='string'){
|
|
132
|
+
fail('Native compatibility inputs are incomplete.',ERROR_CODES.integrityFailed);
|
|
133
|
+
}
|
|
134
|
+
if(toolchainReceipt.protocolVersion!==requirements.arcaneProtocol){
|
|
135
|
+
fail(
|
|
136
|
+
`The app requires ${requirements.arcaneProtocol}, but the native toolchain provides ${String(toolchainReceipt.protocolVersion??'unknown')}.`,
|
|
137
|
+
ERROR_CODES.targetUnavailable
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const requiredVersions=[requirements.minimumCoreVersion];
|
|
141
|
+
if(minimumCoreVersion!==undefined)requiredVersions.push(minimumCoreVersion);
|
|
142
|
+
for(const requiredVersion of requiredVersions){
|
|
143
|
+
try{
|
|
144
|
+
if(typeof requiredVersion!=='string'||compareSemver(toolchainReceipt.version,requiredVersion)<0){
|
|
145
|
+
fail(
|
|
146
|
+
`Arcane Core ${toolchainReceipt.version} does not meet the required minimum ${String(requiredVersion??'unknown')}.`,
|
|
147
|
+
ERROR_CODES.targetUnavailable
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}catch(error){
|
|
151
|
+
if(error instanceof ArcaneError)throw error;
|
|
152
|
+
fail('Native compatibility contains an invalid Arcane version.',ERROR_CODES.integrityFailed);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
assertRequirementsAvailable(requirements.features,toolchainReceipt.features,'features');
|
|
156
|
+
assertRequirementsAvailable(
|
|
157
|
+
descriptor.permissions.capabilities,
|
|
158
|
+
toolchainReceipt.supportedCapabilities,
|
|
159
|
+
'capabilities'
|
|
160
|
+
);
|
|
161
|
+
assertRequirementsAvailable(
|
|
162
|
+
descriptor.permissions.methods,
|
|
163
|
+
toolchainReceipt.supportedMethods,
|
|
164
|
+
'methods'
|
|
165
|
+
);
|
|
166
|
+
return descriptor;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function highestCoreMinimum(versions){
|
|
170
|
+
let highest=null;
|
|
171
|
+
for(const version of versions){
|
|
172
|
+
if(version===undefined)continue;
|
|
173
|
+
try{
|
|
174
|
+
parseSemver(version);
|
|
175
|
+
}catch(error){
|
|
176
|
+
fail('Native compatibility contains an invalid minimum Core version.',ERROR_CODES.integrityFailed);
|
|
177
|
+
}
|
|
178
|
+
if(highest===null||compareSemver(version,highest)>0)highest=version;
|
|
179
|
+
}
|
|
180
|
+
if(highest===null)fail('Native compatibility has no minimum Core version.',ERROR_CODES.integrityFailed);
|
|
181
|
+
return highest;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function sameOrDescendant(parent,candidate){
|
|
185
|
+
const relative=path.relative(parent,candidate);
|
|
186
|
+
return relative===''||Boolean(relative&&!relative.startsWith('..')&&!path.isAbsolute(relative));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function overlaps(left,right){
|
|
190
|
+
return sameOrDescendant(left,right)||sameOrDescendant(right,left);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function canonicalDirectory(value,label){
|
|
194
|
+
if(typeof value!=='string'||!value.trim())fail(`${label} is required.`,ERROR_CODES.usage);
|
|
195
|
+
const requested=path.resolve(value);
|
|
196
|
+
let info;
|
|
197
|
+
try{info=await lstat(requested);}
|
|
198
|
+
catch(error){
|
|
199
|
+
if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} must be a real directory.`);
|
|
203
|
+
return realpath(requested);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function canonicalFutureDirectory(value,label){
|
|
207
|
+
if(typeof value!=='string'||!value.trim())fail(`${label} is required.`,ERROR_CODES.usage);
|
|
208
|
+
const requested=path.resolve(value);
|
|
209
|
+
let cursor=requested;
|
|
210
|
+
const missing=[];
|
|
211
|
+
while(true){
|
|
212
|
+
try{
|
|
213
|
+
const info=await lstat(cursor);
|
|
214
|
+
if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} has a linked or non-directory ancestor.`);
|
|
215
|
+
const canonical=await realpath(cursor);
|
|
216
|
+
return path.join(canonical,...missing.reverse());
|
|
217
|
+
}catch(error){
|
|
218
|
+
if(error?.code!=='ENOENT')throw error;
|
|
219
|
+
const parent=path.dirname(cursor);
|
|
220
|
+
if(parent===cursor)throw error;
|
|
221
|
+
missing.push(path.basename(cursor));
|
|
222
|
+
cursor=parent;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function validateNativeBuilder(provider){
|
|
228
|
+
const methods=['describe','doctor','prepare','authenticateToolchainReceipt','build','verify','run'];
|
|
229
|
+
if(!isObject(provider)||provider.protocol!==NATIVE_BUILDER_PROTOCOL
|
|
230
|
+
||methods.some(method=>typeof provider[method]!=='function')){
|
|
231
|
+
fail(`Native builder must implement ${NATIVE_BUILDER_PROTOCOL} and its complete operation contract.`,ERROR_CODES.targetUnavailable);
|
|
232
|
+
}
|
|
233
|
+
return provider;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function validateTargetRequest(value){
|
|
237
|
+
assertOnlyKeys(value,new Set(['target','platform','architecture','format','signing']),'targetRequest');
|
|
238
|
+
const definition=NATIVE_TARGETS[value.target];
|
|
239
|
+
if(!definition)fail(`Unsupported native target: ${String(value.target)}.`,ERROR_CODES.targetUnavailable);
|
|
240
|
+
if(!definition.platforms.has(value.platform)||!definition.architectures.has(value.architecture)
|
|
241
|
+
||value.format!==definition.format){
|
|
242
|
+
fail(`Target ${value.target} does not support ${value.platform}/${value.architecture}/${value.format}.`,ERROR_CODES.targetUnavailable);
|
|
243
|
+
}
|
|
244
|
+
assertOnlyKeys(value.signing,new Set(['mode','profileId']),'targetRequest.signing');
|
|
245
|
+
if(!Object.hasOwn(value.signing,'mode')||!Object.hasOwn(value.signing,'profileId')
|
|
246
|
+
||value.signing.mode!==definition.signingMode
|
|
247
|
+
||value.signing.profileId!==definition.signingProfileId){
|
|
248
|
+
const profileRequirement=definition.signingProfileId===null
|
|
249
|
+
?'a null profileId'
|
|
250
|
+
:`profileId ${definition.signingProfileId}`;
|
|
251
|
+
fail(
|
|
252
|
+
`Target ${value.target} requires ${definition.format} with ${definition.signingMode} signing and ${profileRequirement}.`,
|
|
253
|
+
ERROR_CODES.targetUnavailable
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return immutable({
|
|
257
|
+
target:value.target,
|
|
258
|
+
platform:value.platform,
|
|
259
|
+
architecture:value.architecture,
|
|
260
|
+
format:value.format,
|
|
261
|
+
signing:{mode:value.signing.mode,profileId:value.signing.profileId}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function releaseApp(receipt){
|
|
266
|
+
return isObject(receipt?.app)?receipt.app:null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function releaseSummary(receipt){
|
|
270
|
+
const app=releaseApp(receipt);
|
|
271
|
+
if(!app||typeof app.id!=='string'||typeof app.version!=='string'
|
|
272
|
+
||!SHA256_PATTERN.test(receipt.policySha256)
|
|
273
|
+
||!SHA256_PATTERN.test(receipt.contentSha256)){
|
|
274
|
+
fail('The app release receipt is missing its verified identity.',ERROR_CODES.integrityFailed);
|
|
275
|
+
}
|
|
276
|
+
return immutable({
|
|
277
|
+
canonicalLocation:receipt.canonicalLocation,
|
|
278
|
+
policySha256:receipt.policySha256,
|
|
279
|
+
contentSha256:receipt.contentSha256,
|
|
280
|
+
fileCount:receipt.fileCount,
|
|
281
|
+
totalBytes:receipt.totalBytes
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function authenticateReleaseAuthority(
|
|
286
|
+
receipt,
|
|
287
|
+
releaseRoot,
|
|
288
|
+
signal,
|
|
289
|
+
expectedPackageConfig,
|
|
290
|
+
expectedDescriptor
|
|
291
|
+
){
|
|
292
|
+
try{
|
|
293
|
+
return await authenticateAppReleaseAuthority(receipt,{
|
|
294
|
+
releaseRoot,
|
|
295
|
+
expectedPackageConfig,
|
|
296
|
+
expectedDescriptor,
|
|
297
|
+
signal
|
|
298
|
+
});
|
|
299
|
+
}catch(error){
|
|
300
|
+
if(error?.code===ERROR_CODES.cancelled)throw error;
|
|
301
|
+
throw new ArcaneError(
|
|
302
|
+
ERROR_CODES.integrityFailed,
|
|
303
|
+
`App release authentication failed: ${error instanceof Error?error.message:String(error)}`,
|
|
304
|
+
{cause:error}
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function authenticatedRelease(item,targetRequest,{signal}){
|
|
310
|
+
assertOnlyKeys(
|
|
311
|
+
item,
|
|
312
|
+
new Set(['appDescriptor','appReleaseRoot','appReleaseReceipt']),
|
|
313
|
+
'application release input'
|
|
314
|
+
);
|
|
315
|
+
const presentedDescriptor=validateAppDescriptor(
|
|
316
|
+
item.appDescriptor,
|
|
317
|
+
{appId:item.appDescriptor?.id}
|
|
318
|
+
);
|
|
319
|
+
const root=await canonicalDirectory(
|
|
320
|
+
item.appReleaseRoot,
|
|
321
|
+
`Release root for ${presentedDescriptor.id}`
|
|
322
|
+
);
|
|
323
|
+
const authority=await authenticateReleaseAuthority(
|
|
324
|
+
item.appReleaseReceipt,
|
|
325
|
+
root,
|
|
326
|
+
signal,
|
|
327
|
+
projectPackageManifest(presentedDescriptor),
|
|
328
|
+
presentedDescriptor
|
|
329
|
+
);
|
|
330
|
+
const receipt=authority.receipt;
|
|
331
|
+
const descriptor=validateAppDescriptor(authority.descriptor,{
|
|
332
|
+
appId:presentedDescriptor.id
|
|
333
|
+
});
|
|
334
|
+
if(appDescriptorSha256(descriptor)!==authority.descriptorSha256){
|
|
335
|
+
fail('App release descriptor authority hash is invalid.',ERROR_CODES.integrityFailed);
|
|
336
|
+
}
|
|
337
|
+
projectNativeDescriptor(descriptor);
|
|
338
|
+
if(!descriptor.targets.includes(targetRequest.target)){
|
|
339
|
+
fail(`App ${descriptor.id} does not declare target ${targetRequest.target}.`);
|
|
340
|
+
}
|
|
341
|
+
const app=releaseApp(receipt);
|
|
342
|
+
if(app?.id!==descriptor.id||app?.version!==descriptor.version){
|
|
343
|
+
fail(`Release identity for ${descriptor.id} does not match its canonical descriptor.`,ERROR_CODES.integrityFailed);
|
|
344
|
+
}
|
|
345
|
+
return Object.freeze({
|
|
346
|
+
descriptor,
|
|
347
|
+
descriptorSha256:authority.descriptorSha256,
|
|
348
|
+
releaseRoot:root,
|
|
349
|
+
releaseReceipt:receipt,
|
|
350
|
+
readFile:(relativePath,{signal}={})=>readVerifiedAppReleaseFile(receipt,{
|
|
351
|
+
releaseRoot:root,
|
|
352
|
+
relativePath,
|
|
353
|
+
signal
|
|
354
|
+
}),
|
|
355
|
+
summary:immutable({
|
|
356
|
+
id:descriptor.id,
|
|
357
|
+
version:descriptor.version,
|
|
358
|
+
descriptorSha256:authority.descriptorSha256,
|
|
359
|
+
release:releaseSummary(receipt)
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function assertDependencyClosure(application,dependencies){
|
|
365
|
+
const byId=new Map(dependencies.map(item=>[item.descriptor.id,item]));
|
|
366
|
+
if(byId.size!==dependencies.length)fail('Dependency releases must have unique app ids.');
|
|
367
|
+
if(byId.has(application.descriptor.id))fail('The selected app must not also be supplied as a dependency.');
|
|
368
|
+
const reachable=new Set();
|
|
369
|
+
const visiting=new Set();
|
|
370
|
+
function visit(descriptor){
|
|
371
|
+
if(visiting.has(descriptor.id))fail(`Bundled app dependency cycle includes ${descriptor.id}.`);
|
|
372
|
+
visiting.add(descriptor.id);
|
|
373
|
+
for(const id of descriptor.native.bundledApps){
|
|
374
|
+
const dependency=byId.get(id);
|
|
375
|
+
if(!dependency)fail(`Bundled app dependency ${id} is missing its verified release.`);
|
|
376
|
+
if(visiting.has(id))fail(`Bundled app dependency cycle includes ${id}.`);
|
|
377
|
+
if(!reachable.has(id)){
|
|
378
|
+
reachable.add(id);
|
|
379
|
+
visit(dependency.descriptor);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
visiting.delete(descriptor.id);
|
|
383
|
+
}
|
|
384
|
+
visit(application.descriptor);
|
|
385
|
+
const extra=[...byId.keys()].filter(id=>!reachable.has(id));
|
|
386
|
+
if(extra.length)fail(`Unrequested dependency releases were supplied: ${extra.sort().join(', ')}.`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function toolchainSummary(receipt,toolchainRoot){
|
|
390
|
+
if(!isObject(receipt)||receipt.canonicalLocation!==toolchainRoot
|
|
391
|
+
||typeof receipt.kind!=='string'||!receipt.kind
|
|
392
|
+
||typeof receipt.version!=='string'||!receipt.version
|
|
393
|
+
||typeof receipt.protocolVersion!=='string'||!/^arcane\/[1-9][0-9]*$/u.test(receipt.protocolVersion)
|
|
394
|
+
||!SHA256_PATTERN.test(receipt.contentSha256)){
|
|
395
|
+
fail('The native builder returned an invalid authenticated toolchain receipt.',ERROR_CODES.integrityFailed);
|
|
396
|
+
}
|
|
397
|
+
const features=canonicalContractList(receipt.features,'toolchainReceipt.features',{maximum:256});
|
|
398
|
+
const supportedCapabilities=canonicalContractList(
|
|
399
|
+
receipt.supportedCapabilities,
|
|
400
|
+
'toolchainReceipt.supportedCapabilities'
|
|
401
|
+
);
|
|
402
|
+
const supportedMethods=canonicalContractList(receipt.supportedMethods,'toolchainReceipt.supportedMethods');
|
|
403
|
+
return immutable({
|
|
404
|
+
kind:receipt.kind,
|
|
405
|
+
version:receipt.version,
|
|
406
|
+
protocolVersion:receipt.protocolVersion,
|
|
407
|
+
features,
|
|
408
|
+
supportedCapabilities,
|
|
409
|
+
supportedMethods,
|
|
410
|
+
canonicalLocation:receipt.canonicalLocation,
|
|
411
|
+
contentSha256:receipt.contentSha256
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function providerGenerationSummary(value,{optional=false}={}){
|
|
416
|
+
if(value===undefined&&optional)return null;
|
|
417
|
+
if(!isObject(value)
|
|
418
|
+
||value.kind!=='arcane-native-provider-generation'
|
|
419
|
+
||!SHA256_PATTERN.test(value.generationSha256)
|
|
420
|
+
||!SHA256_PATTERN.test(value.contentSha256)
|
|
421
|
+
||!Number.isInteger(value.moduleCount)
|
|
422
|
+
||value.moduleCount<1
|
|
423
|
+
||value.moduleCount>128){
|
|
424
|
+
fail(
|
|
425
|
+
'Native provider generation evidence is invalid.',
|
|
426
|
+
ERROR_CODES.integrityFailed
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return immutable({
|
|
430
|
+
kind:'arcane-native-provider-generation',
|
|
431
|
+
generationSha256:value.generationSha256,
|
|
432
|
+
contentSha256:value.contentSha256,
|
|
433
|
+
moduleCount:value.moduleCount
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function assertProviderGenerationBinding(summary,provider,label){
|
|
438
|
+
if(!summary)return;
|
|
439
|
+
const actual=providerGenerationSummary(provider?.providerGeneration,{optional:true});
|
|
440
|
+
if(!actual||JSON.stringify(actual)!==JSON.stringify(summary)){
|
|
441
|
+
fail(
|
|
442
|
+
`${label} belongs to a different native provider module generation.`,
|
|
443
|
+
ERROR_CODES.integrityFailed
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function assertExpectedPlanBinding(state,{expectedNativeBuilder,expectedTarget}={}){
|
|
449
|
+
assertProviderGenerationBinding(
|
|
450
|
+
state.providerGeneration,
|
|
451
|
+
state.provider,
|
|
452
|
+
'Native build plan'
|
|
453
|
+
);
|
|
454
|
+
if(expectedNativeBuilder!==undefined){
|
|
455
|
+
assertProviderGenerationBinding(
|
|
456
|
+
state.providerGeneration,
|
|
457
|
+
expectedNativeBuilder,
|
|
458
|
+
'Expected native builder'
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if(expectedNativeBuilder!==undefined&&state.provider!==expectedNativeBuilder){
|
|
462
|
+
fail('Native build plan belongs to a different paired provider.',ERROR_CODES.integrityFailed);
|
|
463
|
+
}
|
|
464
|
+
if(expectedTarget!==undefined&&state.plan.targetRequest.target!==expectedTarget){
|
|
465
|
+
fail(
|
|
466
|
+
`Native build plan target ${state.plan.targetRequest.target} does not match ${expectedTarget}.`,
|
|
467
|
+
ERROR_CODES.integrityFailed
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent}={}){
|
|
473
|
+
throwIfAborted(signal);
|
|
474
|
+
assertExpectedPlanBinding(state,{expectedNativeBuilder,expectedTarget});
|
|
475
|
+
const authenticatedToolchain=await state.provider.authenticateToolchainReceipt(
|
|
476
|
+
state.toolchainReceipt,
|
|
477
|
+
{toolchainRoot:state.toolchainRoot,signal,onEvent}
|
|
478
|
+
);
|
|
479
|
+
const summary=toolchainSummary(authenticatedToolchain,state.toolchainRoot);
|
|
480
|
+
if(JSON.stringify(summary)!==JSON.stringify(state.plan.toolchain)){
|
|
481
|
+
fail('The native toolchain identity changed after plan creation.',ERROR_CODES.integrityFailed);
|
|
482
|
+
}
|
|
483
|
+
for(const item of [state.application,...state.dependencies]){
|
|
484
|
+
assertNativeToolchainCompatibility({
|
|
485
|
+
appDescriptor:item.descriptor,
|
|
486
|
+
toolchainReceipt:summary,
|
|
487
|
+
minimumCoreVersion:state.plan.minimumCoreVersion
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
await authenticateReleaseAuthority(
|
|
491
|
+
state.application.releaseReceipt,
|
|
492
|
+
state.application.releaseRoot,
|
|
493
|
+
signal,
|
|
494
|
+
projectPackageManifest(state.application.descriptor),
|
|
495
|
+
state.application.descriptor
|
|
496
|
+
);
|
|
497
|
+
for(const dependency of state.dependencies){
|
|
498
|
+
await authenticateReleaseAuthority(
|
|
499
|
+
dependency.releaseReceipt,
|
|
500
|
+
dependency.releaseRoot,
|
|
501
|
+
signal,
|
|
502
|
+
projectPackageManifest(dependency.descriptor),
|
|
503
|
+
dependency.descriptor
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const outputRoot=await canonicalFutureDirectory(state.outputRoot,'Native output root');
|
|
507
|
+
if(outputRoot!==state.outputRoot)fail('The native output root identity changed after plan creation.',ERROR_CODES.integrityFailed);
|
|
508
|
+
return state.plan;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export async function createNativeBuildPlan({
|
|
512
|
+
nativeBuilder,
|
|
513
|
+
toolchainRoot,
|
|
514
|
+
toolchainReceipt,
|
|
515
|
+
appReleaseRoot,
|
|
516
|
+
appReleaseReceipt,
|
|
517
|
+
appDescriptor,
|
|
518
|
+
dependencyReleases=[],
|
|
519
|
+
providerGeneration,
|
|
520
|
+
minimumCoreVersion,
|
|
521
|
+
protectedRoots=[],
|
|
522
|
+
outputRoot,
|
|
523
|
+
targetRequest,
|
|
524
|
+
signal,
|
|
525
|
+
onEvent
|
|
526
|
+
}={}){
|
|
527
|
+
throwIfAborted(signal);
|
|
528
|
+
await onEvent?.(Object.freeze({type:'native.plan.started'}));
|
|
529
|
+
const request=validateTargetRequest(targetRequest);
|
|
530
|
+
const provider=validateNativeBuilder(nativeBuilder);
|
|
531
|
+
const generation=providerGenerationSummary(
|
|
532
|
+
providerGeneration===undefined?provider.providerGeneration:providerGeneration,
|
|
533
|
+
{optional:true}
|
|
534
|
+
);
|
|
535
|
+
assertProviderGenerationBinding(generation,provider,'Native build plan');
|
|
536
|
+
const canonicalToolchainRoot=await canonicalDirectory(toolchainRoot,'Native toolchain root');
|
|
537
|
+
const authenticatedToolchain=await provider.authenticateToolchainReceipt(
|
|
538
|
+
toolchainReceipt,
|
|
539
|
+
{toolchainRoot:canonicalToolchainRoot,signal,onEvent}
|
|
540
|
+
);
|
|
541
|
+
const authenticatedToolchainSummary=toolchainSummary(authenticatedToolchain,canonicalToolchainRoot);
|
|
542
|
+
const application=await authenticatedRelease({
|
|
543
|
+
appDescriptor,
|
|
544
|
+
appReleaseRoot,
|
|
545
|
+
appReleaseReceipt
|
|
546
|
+
},request,{signal});
|
|
547
|
+
if(!Array.isArray(dependencyReleases)||dependencyReleases.length>64){
|
|
548
|
+
fail('dependencyReleases must be an array with at most 64 entries.');
|
|
549
|
+
}
|
|
550
|
+
const dependencies=[];
|
|
551
|
+
for(const dependency of dependencyReleases){
|
|
552
|
+
dependencies.push(await authenticatedRelease(dependency,request,{signal}));
|
|
553
|
+
}
|
|
554
|
+
assertDependencyClosure(application,dependencies);
|
|
555
|
+
dependencies.sort((left,right)=>left.descriptor.id.localeCompare(right.descriptor.id,'en'));
|
|
556
|
+
const requiredCoreVersion=highestCoreMinimum([
|
|
557
|
+
minimumCoreVersion,
|
|
558
|
+
application.descriptor.requirements.minimumCoreVersion,
|
|
559
|
+
...dependencies.map(item=>item.descriptor.requirements.minimumCoreVersion)
|
|
560
|
+
]);
|
|
561
|
+
for(const item of [application,...dependencies]){
|
|
562
|
+
assertNativeToolchainCompatibility({
|
|
563
|
+
appDescriptor:item.descriptor,
|
|
564
|
+
toolchainReceipt:authenticatedToolchainSummary,
|
|
565
|
+
minimumCoreVersion:requiredCoreVersion
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const canonicalOutputRoot=await canonicalFutureDirectory(outputRoot,'Native output root');
|
|
570
|
+
const protectedPaths=[
|
|
571
|
+
canonicalToolchainRoot,
|
|
572
|
+
application.releaseRoot,
|
|
573
|
+
...dependencies.map(item=>item.releaseRoot)
|
|
574
|
+
];
|
|
575
|
+
if(!Array.isArray(protectedRoots))fail('protectedRoots must be an array.');
|
|
576
|
+
for(const [index,root] of protectedRoots.entries()){
|
|
577
|
+
protectedPaths.push(await canonicalDirectory(root,`Protected source root ${index}`));
|
|
578
|
+
}
|
|
579
|
+
const conflict=protectedPaths.find(root=>overlaps(root,canonicalOutputRoot));
|
|
580
|
+
if(conflict)fail('Native output root must not overlap a toolchain, source, or verified release root.');
|
|
581
|
+
|
|
582
|
+
const plan=immutable({
|
|
583
|
+
protocol:NATIVE_BUILD_PLAN_PROTOCOL,
|
|
584
|
+
generation:randomBytes(16).toString('hex'),
|
|
585
|
+
...(generation?{providerGeneration:generation}:{}),
|
|
586
|
+
toolchain:authenticatedToolchainSummary,
|
|
587
|
+
minimumCoreVersion:requiredCoreVersion,
|
|
588
|
+
app:application.summary,
|
|
589
|
+
dependencies:dependencies.map(item=>item.summary),
|
|
590
|
+
targetRequest:request,
|
|
591
|
+
outputRoot:canonicalOutputRoot,
|
|
592
|
+
operations:[
|
|
593
|
+
'authenticate-toolchain',
|
|
594
|
+
'authenticate-app-release',
|
|
595
|
+
'authenticate-dependency-releases',
|
|
596
|
+
'build-selected-target',
|
|
597
|
+
'verify-native-artifact'
|
|
598
|
+
]
|
|
599
|
+
});
|
|
600
|
+
issuedPlans.set(plan,Object.freeze({
|
|
601
|
+
plan,
|
|
602
|
+
provider,
|
|
603
|
+
providerGeneration:generation,
|
|
604
|
+
toolchainRoot:canonicalToolchainRoot,
|
|
605
|
+
toolchainReceipt,
|
|
606
|
+
minimumCoreVersion:requiredCoreVersion,
|
|
607
|
+
application,
|
|
608
|
+
dependencies:Object.freeze(dependencies),
|
|
609
|
+
outputRoot:canonicalOutputRoot,
|
|
610
|
+
targetRequest:request
|
|
611
|
+
}));
|
|
612
|
+
await onEvent?.(Object.freeze({type:'native.plan.completed',appId:application.descriptor.id,target:request.target}));
|
|
613
|
+
return plan;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export async function authenticateNativeBuildPlan(plan,{
|
|
617
|
+
expectedNativeBuilder,
|
|
618
|
+
expectedTarget,
|
|
619
|
+
signal,
|
|
620
|
+
onEvent
|
|
621
|
+
}={}){
|
|
622
|
+
const state=issuedPlans.get(plan);
|
|
623
|
+
if(!state)fail('Native build plan was not issued by this SDK process.',ERROR_CODES.integrityFailed);
|
|
624
|
+
return authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export async function executeNativeBuildPlan(plan,{
|
|
628
|
+
expectedNativeBuilder,
|
|
629
|
+
expectedTarget,
|
|
630
|
+
signal,
|
|
631
|
+
onEvent
|
|
632
|
+
}={}){
|
|
633
|
+
const state=issuedPlans.get(plan);
|
|
634
|
+
if(!state)fail('Native build plan was not issued by this SDK process.',ERROR_CODES.integrityFailed);
|
|
635
|
+
throwIfAborted(signal);
|
|
636
|
+
if(attemptedPlans.has(plan)){
|
|
637
|
+
fail(
|
|
638
|
+
'Native build plan generation has already been attempted; create a new plan before retrying.',
|
|
639
|
+
ERROR_CODES.integrityFailed
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
attemptedPlans.add(plan);
|
|
643
|
+
await onEvent?.(Object.freeze({type:'native.build.started',appId:plan.app.id,target:plan.targetRequest.target}));
|
|
644
|
+
await authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent});
|
|
645
|
+
const result=await state.provider.build({
|
|
646
|
+
toolchainRoot:state.toolchainRoot,
|
|
647
|
+
toolchainReceipt:state.toolchainReceipt,
|
|
648
|
+
appReleaseRoot:state.application.releaseRoot,
|
|
649
|
+
appReleaseReceipt:state.application.releaseReceipt,
|
|
650
|
+
readAppReleaseFile:(relativePath)=>state.application.readFile(relativePath,{signal}),
|
|
651
|
+
appDescriptor:state.application.descriptor,
|
|
652
|
+
descriptorSha256:state.application.descriptorSha256,
|
|
653
|
+
dependencyReleases:state.dependencies.map(item=>Object.freeze({
|
|
654
|
+
appReleaseRoot:item.releaseRoot,
|
|
655
|
+
appReleaseReceipt:item.releaseReceipt,
|
|
656
|
+
readFile:(relativePath)=>item.readFile(relativePath,{signal}),
|
|
657
|
+
appDescriptor:item.descriptor,
|
|
658
|
+
descriptorSha256:item.descriptorSha256
|
|
659
|
+
})),
|
|
660
|
+
outputRoot:state.outputRoot,
|
|
661
|
+
targetRequest:state.targetRequest,
|
|
662
|
+
signal,
|
|
663
|
+
onEvent
|
|
664
|
+
});
|
|
665
|
+
if(!isObject(result)||!result.artifactReceipt){
|
|
666
|
+
fail('Native builder did not return an artifact receipt.',ERROR_CODES.integrityFailed);
|
|
667
|
+
}
|
|
668
|
+
await onEvent?.(Object.freeze({
|
|
669
|
+
type:'native.build.committed',
|
|
670
|
+
appId:plan.app.id,
|
|
671
|
+
target:plan.targetRequest.target
|
|
672
|
+
}));
|
|
673
|
+
await onEvent?.(Object.freeze({
|
|
674
|
+
type:'native.verify.started',
|
|
675
|
+
appId:plan.app.id,
|
|
676
|
+
target:plan.targetRequest.target
|
|
677
|
+
}));
|
|
678
|
+
const artifactVerification=await state.provider.verify({
|
|
679
|
+
toolchainRoot:state.toolchainRoot,
|
|
680
|
+
toolchainReceipt:state.toolchainReceipt,
|
|
681
|
+
artifactReceipt:result.artifactReceipt,
|
|
682
|
+
outputRoot:state.outputRoot,
|
|
683
|
+
targetRequest:state.targetRequest,
|
|
684
|
+
signal,
|
|
685
|
+
onEvent
|
|
686
|
+
});
|
|
687
|
+
if(!isObject(artifactVerification)||artifactVerification.verified!==true){
|
|
688
|
+
fail('Native builder did not verify the built artifact.',ERROR_CODES.integrityFailed);
|
|
689
|
+
}
|
|
690
|
+
throwIfAborted(signal);
|
|
691
|
+
await onEvent?.(Object.freeze({
|
|
692
|
+
type:'native.verify.completed',
|
|
693
|
+
appId:plan.app.id,
|
|
694
|
+
target:plan.targetRequest.target
|
|
695
|
+
}));
|
|
696
|
+
await onEvent?.(Object.freeze({type:'native.build.completed',appId:plan.app.id,target:plan.targetRequest.target}));
|
|
697
|
+
return {...result,artifactVerification};
|
|
698
|
+
}
|