arcane-os 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
- package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -146
- package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1050 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +40 -62
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +112 -779
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -187
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1252
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -677
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2960
- package/docs/reference/sdk-api.md +0 -6694
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import { createStreamingSha256 } from "./internal/sha256.mjs";
|
|
2
1
|
import {
|
|
3
2
|
normalizeModelSecurity,
|
|
4
|
-
resolveModelSecurity,
|
|
5
3
|
} from "./model-controller.mjs";
|
|
6
4
|
|
|
5
|
+
const completeValue = (value) => value;
|
|
6
|
+
|
|
7
7
|
export const BROWSER_SPEECH_ARTIFACT_PROTOCOL =
|
|
8
8
|
"arcane-ai-browser-speech-artifacts/1";
|
|
9
9
|
export const BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL =
|
|
10
10
|
"arcane-ai-browser-speech-artifact-graph/1";
|
|
11
11
|
|
|
12
12
|
const MODEL_AUTHORITY_PROTOCOL = "arcane-ai-model-authority/1";
|
|
13
|
-
const MANIFEST_SCHEMA = "arcane.ai.browser-speech.assets.v1";
|
|
14
|
-
const ARTIFACT_GRAPH_MANIFEST_SCHEMA =
|
|
15
|
-
"arcane.ai.browser-speech.authenticated-artifact-graph.v1";
|
|
16
13
|
const ARTIFACT_GRAPH_KIND = "browser-speech-authenticated-artifact-graph";
|
|
17
14
|
const ARTIFACT_GRAPH_MODULE_KIND =
|
|
18
15
|
"browser-speech-authenticated-artifact-graph";
|
|
19
16
|
const ARTIFACT_GRAPH_GUARDS = "__arcaneBrowserSpeechArtifactGraphGuardsV1";
|
|
20
|
-
const
|
|
17
|
+
const ARTIFACT_MODULE_ROUTER = "__arcaneBrowserSpeechModuleRouterV1";
|
|
21
18
|
const MUTABLE_PATH_PATTERN = /\/(?:resolve\/)?(?:main|master|latest)(?:\/|$)/iu;
|
|
22
19
|
const ARTIFACT_GRAPH_MUTABLE_SOURCE_PATTERN =
|
|
23
20
|
/\/(?:refs\/heads\/(?:main|master)|resolve\/(?:main|master)|(?:main|master|latest))(?:\/|$)|@(?:latest|next)(?:\/|$)/iu;
|
|
@@ -33,10 +30,7 @@ const PLATFORM_CREATE_OBJECT_URL = typeof globalThis.URL?.createObjectURL === "f
|
|
|
33
30
|
const PLATFORM_REVOKE_OBJECT_URL = typeof globalThis.URL?.revokeObjectURL === "function"
|
|
34
31
|
? globalThis.URL.revokeObjectURL.bind(globalThis.URL)
|
|
35
32
|
: null;
|
|
36
|
-
const
|
|
37
|
-
? globalThis.fetch.bind(globalThis)
|
|
38
|
-
: null;
|
|
39
|
-
const LEGACY_ARTIFACT_ERROR_REASONS = Object.freeze({
|
|
33
|
+
const LEGACY_ARTIFACT_ERROR_REASONS = completeValue({
|
|
40
34
|
ARCANE_AI_REQUEST_ABORTED: "browser-speech-artifact-preparation-cancelled",
|
|
41
35
|
ARCANE_AI_STORAGE_BUSY: "browser-speech-artifact-dbopfs-write-lock-unavailable",
|
|
42
36
|
ARCANE_AI_STORAGE_UNAVAILABLE: "browser-speech-artifact-dbopfs-table-unavailable",
|
|
@@ -47,8 +41,6 @@ const LEGACY_ARTIFACT_ERROR_REASONS = Object.freeze({
|
|
|
47
41
|
ARCANE_AI_ARTIFACT_SOURCE_UNAVAILABLE: "browser-speech-artifact-fetch-unavailable",
|
|
48
42
|
ARCANE_AI_ARTIFACT_DOWNLOAD_FAILED: "browser-speech-artifact-fetch-rejected",
|
|
49
43
|
ARCANE_AI_ARTIFACT_SOURCE_CHANGED: "browser-speech-artifact-source-redirected",
|
|
50
|
-
ARCANE_AI_ARTIFACT_SIZE_MISMATCH: "browser-speech-artifact-byte-length-mismatch",
|
|
51
|
-
ARCANE_AI_ARTIFACT_DIGEST_MISMATCH: "browser-speech-artifact-sha256-mismatch",
|
|
52
44
|
ARCANE_AI_ARTIFACT_CACHE_REJECTED: "browser-speech-artifact-dbopfs-cache-rejected",
|
|
53
45
|
ARCANE_AI_ARTIFACT_OFFLINE_MISS: "browser-speech-artifact-offline-cache-miss",
|
|
54
46
|
});
|
|
@@ -131,11 +123,7 @@ function requiredText(value, label) {
|
|
|
131
123
|
}
|
|
132
124
|
|
|
133
125
|
function identifier(value, label) {
|
|
134
|
-
|
|
135
|
-
if (result.length > 128) {
|
|
136
|
-
throw new TypeError(`${label} must not exceed 128 characters.`);
|
|
137
|
-
}
|
|
138
|
-
return result;
|
|
126
|
+
return requiredText(value, label);
|
|
139
127
|
}
|
|
140
128
|
|
|
141
129
|
function artifactGraphText(value, label, reason = "artifact-graph-field-text-required") {
|
|
@@ -151,11 +139,20 @@ function artifactGraphIdentifier(
|
|
|
151
139
|
missingReason = "artifact-graph-identifier-missing",
|
|
152
140
|
lengthReason = "artifact-graph-identifier-length-exceeded",
|
|
153
141
|
) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
142
|
+
return artifactGraphText(value, label, missingReason);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ordinarySourceUrl(value, label) {
|
|
146
|
+
let result;
|
|
147
|
+
try {
|
|
148
|
+
result = new URL(value, globalThis.location?.href);
|
|
149
|
+
} catch {
|
|
150
|
+
throw new TypeError(`${label} must be a valid URL.`);
|
|
157
151
|
}
|
|
158
|
-
|
|
152
|
+
if (result.username || result.password) {
|
|
153
|
+
throw new TypeError(`${label} must not contain credentials.`);
|
|
154
|
+
}
|
|
155
|
+
return result.href;
|
|
159
156
|
}
|
|
160
157
|
|
|
161
158
|
function immutableUrl(value, label) {
|
|
@@ -209,26 +206,6 @@ function canonicalArtifactPath(
|
|
|
209
206
|
return path;
|
|
210
207
|
}
|
|
211
208
|
|
|
212
|
-
function graphSha256(
|
|
213
|
-
value,
|
|
214
|
-
label,
|
|
215
|
-
missingReason = "artifact-graph-file-sha256-missing",
|
|
216
|
-
formatReason = "artifact-graph-file-sha256-format-mismatch",
|
|
217
|
-
) {
|
|
218
|
-
const sha256 = artifactGraphText(
|
|
219
|
-
value,
|
|
220
|
-
label,
|
|
221
|
-
missingReason,
|
|
222
|
-
);
|
|
223
|
-
if (sha256 !== value || !SHA256_PATTERN.test(sha256)) {
|
|
224
|
-
throw artifactGraphTypeError(
|
|
225
|
-
formatReason,
|
|
226
|
-
`${label} must contain exactly 64 lowercase hexadecimal characters.`,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
return sha256;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
209
|
function graphPositiveInteger(
|
|
233
210
|
value,
|
|
234
211
|
label,
|
|
@@ -306,16 +283,10 @@ function graphRuntimeRequestUrl(value, label) {
|
|
|
306
283
|
} catch (error) {
|
|
307
284
|
throw artifactGraphTypeError(
|
|
308
285
|
"artifact-graph-runtime-request-url-not-absolute",
|
|
309
|
-
`${label} must be an absolute
|
|
286
|
+
`${label} must be an absolute URL.`,
|
|
310
287
|
error,
|
|
311
288
|
);
|
|
312
289
|
}
|
|
313
|
-
if (result.protocol !== "https:") {
|
|
314
|
-
throw artifactGraphTypeError(
|
|
315
|
-
"artifact-graph-runtime-request-url-protocol-not-https",
|
|
316
|
-
`${label} must use HTTPS.`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
290
|
if (result.username || result.password) {
|
|
320
291
|
throw artifactGraphTypeError(
|
|
321
292
|
"artifact-graph-runtime-request-url-credentials-rejected",
|
|
@@ -349,16 +320,10 @@ function graphRedirectFinalOrigin(value, label) {
|
|
|
349
320
|
} catch (error) {
|
|
350
321
|
throw artifactGraphTypeError(
|
|
351
322
|
"artifact-graph-source-redirect-final-origin-not-absolute",
|
|
352
|
-
`${label} must be an absolute
|
|
323
|
+
`${label} must be an absolute origin.`,
|
|
353
324
|
error,
|
|
354
325
|
);
|
|
355
326
|
}
|
|
356
|
-
if (result.protocol !== "https:") {
|
|
357
|
-
throw artifactGraphTypeError(
|
|
358
|
-
"artifact-graph-source-redirect-final-origin-protocol-not-https",
|
|
359
|
-
`${label} must use HTTPS.`,
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
327
|
if (result.username || result.password) {
|
|
363
328
|
throw artifactGraphTypeError(
|
|
364
329
|
"artifact-graph-source-redirect-final-origin-credentials-rejected",
|
|
@@ -387,7 +352,7 @@ function graphRedirectFinalOrigin(value, label) {
|
|
|
387
352
|
}
|
|
388
353
|
|
|
389
354
|
function normalizeGraphRedirectFinalOrigins(value, path) {
|
|
390
|
-
if (value === undefined) return
|
|
355
|
+
if (value === undefined) return completeValue([]);
|
|
391
356
|
if (!Array.isArray(value)) {
|
|
392
357
|
throw artifactGraphTypeError(
|
|
393
358
|
"artifact-graph-source-redirect-final-origins-not-array",
|
|
@@ -411,10 +376,10 @@ function normalizeGraphRedirectFinalOrigins(value, path) {
|
|
|
411
376
|
`Artifact graph file ${path} redirectFinalOrigins must be unique after canonicalization.`,
|
|
412
377
|
);
|
|
413
378
|
}
|
|
414
|
-
return
|
|
379
|
+
return completeValue([...origins].sort(lexicalCompare));
|
|
415
380
|
}
|
|
416
381
|
|
|
417
|
-
function graphImmutableUrl(value, label, revision
|
|
382
|
+
function graphImmutableUrl(value, label, revision) {
|
|
418
383
|
let url;
|
|
419
384
|
try {
|
|
420
385
|
url = immutableUrl(
|
|
@@ -433,47 +398,41 @@ function graphImmutableUrl(value, label, revision, sha256) {
|
|
|
433
398
|
error,
|
|
434
399
|
);
|
|
435
400
|
}
|
|
436
|
-
const identityUrl = url.toLowerCase();
|
|
437
401
|
if (ARTIFACT_GRAPH_MUTABLE_SOURCE_PATTERN.test(new URL(url).pathname)) {
|
|
438
402
|
throw artifactGraphTypeError(
|
|
439
403
|
"artifact-graph-source-url-mutable",
|
|
440
404
|
`${label} names a mutable branch, channel, or release alias.`,
|
|
441
405
|
);
|
|
442
406
|
}
|
|
443
|
-
if (
|
|
444
|
-
!identityUrl.includes(revision.toLowerCase())
|
|
445
|
-
&& !identityUrl.includes(sha256)
|
|
446
|
-
) {
|
|
407
|
+
if (!url.toLowerCase().includes(revision.toLowerCase())) {
|
|
447
408
|
throw artifactGraphTypeError(
|
|
448
409
|
"artifact-graph-source-revision-unbound",
|
|
449
|
-
`${label} must contain the file revision
|
|
410
|
+
`${label} must contain the file revision.`,
|
|
450
411
|
);
|
|
451
412
|
}
|
|
452
413
|
return url;
|
|
453
414
|
}
|
|
454
415
|
|
|
455
|
-
function
|
|
416
|
+
function lexicalCompare(left, right) {
|
|
417
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Structural comparison for dormant authenticated-graph compatibility
|
|
421
|
+
// normalizers only. Ordinary graph creation treats edges and transforms as
|
|
422
|
+
// inert metadata and never calls these helpers. Do not enable them, including
|
|
423
|
+
// for secure mode, without an explicit review with the user.
|
|
424
|
+
function compatibilityRecordKey(value) {
|
|
456
425
|
if (Array.isArray(value)) {
|
|
457
|
-
return `[${value.map(
|
|
426
|
+
return `[${value.map(compatibilityRecordKey).join(",")}]`;
|
|
458
427
|
}
|
|
459
428
|
if (value && typeof value === "object") {
|
|
460
429
|
return `{${Object.keys(value).sort().map((key) =>
|
|
461
|
-
`${JSON.stringify(key)}:${
|
|
430
|
+
`${JSON.stringify(key)}:${compatibilityRecordKey(value[key])}`).join(",")}}`;
|
|
462
431
|
}
|
|
463
432
|
return JSON.stringify(value);
|
|
464
433
|
}
|
|
465
434
|
|
|
466
|
-
function
|
|
467
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function sha256Text(value) {
|
|
471
|
-
const digest = createStreamingSha256();
|
|
472
|
-
digest.update(new TextEncoder().encode(value));
|
|
473
|
-
return digest.digestHex();
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function normalizeFile(value, kind, index, revision) {
|
|
435
|
+
function normalizeFile(value, kind, index, revision, secure) {
|
|
477
436
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
478
437
|
throw new TypeError(`${kind} file ${String(index)} must be an object.`);
|
|
479
438
|
}
|
|
@@ -485,37 +444,19 @@ function normalizeFile(value, kind, index, revision) {
|
|
|
485
444
|
) {
|
|
486
445
|
throw new TypeError(`${kind} file path must be a normalized relative path.`);
|
|
487
446
|
}
|
|
488
|
-
const url =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
throw new TypeError(`${kind} file bytes must be a positive safe integer.`);
|
|
493
|
-
}
|
|
494
|
-
bytes = value.bytes;
|
|
495
|
-
}
|
|
496
|
-
let sha256 = null;
|
|
497
|
-
if (value.sha256 !== undefined) {
|
|
498
|
-
sha256 = requiredText(value.sha256, `${kind} file sha256`).toLowerCase();
|
|
499
|
-
if (!SHA256_PATTERN.test(sha256)) {
|
|
500
|
-
throw new TypeError(`${kind} file sha256 must be 64 lowercase hexadecimal characters.`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
const identityUrl = url.toLowerCase();
|
|
504
|
-
if (
|
|
505
|
-
!identityUrl.includes(revision.toLowerCase())
|
|
506
|
-
&& (sha256 === null || !identityUrl.includes(sha256))
|
|
507
|
-
) {
|
|
447
|
+
const url = secure
|
|
448
|
+
? immutableUrl(value.url, `${kind} file url`)
|
|
449
|
+
: ordinarySourceUrl(value.url, `${kind} file url`);
|
|
450
|
+
if (secure && !url.toLowerCase().includes(revision.toLowerCase())) {
|
|
508
451
|
throw new TypeError(
|
|
509
|
-
`${kind} file URL must contain its caller-supplied revision
|
|
452
|
+
`${kind} file URL must contain its caller-supplied revision.`,
|
|
510
453
|
);
|
|
511
454
|
}
|
|
512
|
-
return
|
|
455
|
+
return completeValue({
|
|
513
456
|
kind,
|
|
514
457
|
index,
|
|
515
458
|
path,
|
|
516
459
|
url,
|
|
517
|
-
bytes,
|
|
518
|
-
sha256,
|
|
519
460
|
mediaType: typeof value.mediaType === "string" && value.mediaType.trim()
|
|
520
461
|
? value.mediaType.trim()
|
|
521
462
|
: kind === "runtime" && /\.(?:m?js)$/iu.test(path)
|
|
@@ -524,7 +465,10 @@ function normalizeFile(value, kind, index, revision) {
|
|
|
524
465
|
});
|
|
525
466
|
}
|
|
526
467
|
|
|
527
|
-
function uniqueFiles(files, label, kind, revision, {
|
|
468
|
+
function uniqueFiles(files, label, kind, revision, {
|
|
469
|
+
allowEmpty = false,
|
|
470
|
+
secure = false,
|
|
471
|
+
} = {}) {
|
|
528
472
|
if (!Array.isArray(files) || (!allowEmpty && files.length < 1)) {
|
|
529
473
|
throw new TypeError(
|
|
530
474
|
allowEmpty
|
|
@@ -534,8 +478,8 @@ function uniqueFiles(files, label, kind, revision, { allowEmpty = false } = {})
|
|
|
534
478
|
}
|
|
535
479
|
const paths = new Set();
|
|
536
480
|
const urls = new Set();
|
|
537
|
-
return
|
|
538
|
-
const file = normalizeFile(value, kind, index, revision);
|
|
481
|
+
return completeValue(files.map((value, index) => {
|
|
482
|
+
const file = normalizeFile(value, kind, index, revision, secure);
|
|
539
483
|
if (paths.has(file.path) || urls.has(file.url)) {
|
|
540
484
|
throw new TypeError(`${label} file paths and URLs must be unique.`);
|
|
541
485
|
}
|
|
@@ -551,12 +495,10 @@ function publicFile(file) {
|
|
|
551
495
|
url: file.url,
|
|
552
496
|
mediaType: file.mediaType,
|
|
553
497
|
};
|
|
554
|
-
|
|
555
|
-
if (file.sha256 !== null) result.sha256 = file.sha256;
|
|
556
|
-
return Object.freeze(result);
|
|
498
|
+
return completeValue(result);
|
|
557
499
|
}
|
|
558
500
|
|
|
559
|
-
function normalizeArtifactGraphFile(value, index) {
|
|
501
|
+
function normalizeArtifactGraphFile(value, index, secure = false) {
|
|
560
502
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
561
503
|
throw artifactGraphTypeError(
|
|
562
504
|
"artifact-graph-file-descriptor-not-object",
|
|
@@ -584,54 +526,24 @@ function normalizeArtifactGraphFile(value, index) {
|
|
|
584
526
|
"artifact-graph-file-revision-missing",
|
|
585
527
|
"artifact-graph-file-revision-length-exceeded",
|
|
586
528
|
);
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
value.sourceUrl ?? value.url,
|
|
598
|
-
`Artifact graph file ${path} sourceUrl`,
|
|
599
|
-
revision,
|
|
600
|
-
sha256,
|
|
601
|
-
);
|
|
529
|
+
const sourceUrl = secure
|
|
530
|
+
? graphImmutableUrl(
|
|
531
|
+
value.sourceUrl ?? value.url,
|
|
532
|
+
`Artifact graph file ${path} sourceUrl`,
|
|
533
|
+
revision,
|
|
534
|
+
)
|
|
535
|
+
: ordinarySourceUrl(
|
|
536
|
+
value.sourceUrl ?? value.url,
|
|
537
|
+
`Artifact graph file ${path} sourceUrl`,
|
|
538
|
+
);
|
|
602
539
|
const redirectFinalOrigins = normalizeGraphRedirectFinalOrigins(
|
|
603
540
|
value.redirectFinalOrigins,
|
|
604
541
|
path,
|
|
605
542
|
);
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
);
|
|
611
|
-
if (license !== value.license) {
|
|
612
|
-
throw artifactGraphTypeError(
|
|
613
|
-
"artifact-graph-file-license-whitespace-rejected",
|
|
614
|
-
`Artifact graph file ${path} license must not contain surrounding whitespace.`,
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
if (license !== license.normalize("NFC")) {
|
|
618
|
-
throw artifactGraphTypeError(
|
|
619
|
-
"artifact-graph-file-license-not-nfc",
|
|
620
|
-
`Artifact graph file ${path} license must be NFC-normalized.`,
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
if (/[\u0000-\u001f\u007f]/u.test(license)) {
|
|
624
|
-
throw artifactGraphTypeError(
|
|
625
|
-
"artifact-graph-file-license-control-character-rejected",
|
|
626
|
-
`Artifact graph file ${path} license must not contain control characters.`,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
if (license.length > 256) {
|
|
630
|
-
throw artifactGraphTypeError(
|
|
631
|
-
"artifact-graph-file-license-length-exceeded",
|
|
632
|
-
`Artifact graph file ${path} license must not exceed 256 characters.`,
|
|
633
|
-
);
|
|
634
|
-
}
|
|
543
|
+
// Legal metadata belongs to the selected upstream distribution. Preserve a
|
|
544
|
+
// caller-supplied value as inert metadata, but never require or interpret it
|
|
545
|
+
// as part of ordinary runtime materialization.
|
|
546
|
+
const license = value.license;
|
|
635
547
|
const mediaType = exactMediaType(
|
|
636
548
|
value.mediaType,
|
|
637
549
|
`Artifact graph file ${path} mediaType`,
|
|
@@ -682,35 +594,31 @@ function normalizeArtifactGraphFile(value, index) {
|
|
|
682
594
|
`Artifact graph file ${path} runtimeRequestUrls must be unique.`,
|
|
683
595
|
);
|
|
684
596
|
}
|
|
685
|
-
return
|
|
597
|
+
return completeValue({
|
|
686
598
|
kind,
|
|
687
599
|
index,
|
|
688
600
|
path,
|
|
689
601
|
sourceUrl,
|
|
690
602
|
revision,
|
|
691
|
-
license,
|
|
603
|
+
...(license === undefined ? {} : { license }),
|
|
692
604
|
mediaType,
|
|
693
605
|
sourceMediaType,
|
|
694
|
-
|
|
695
|
-
sha256,
|
|
696
|
-
runtimeRequestUrls: Object.freeze(normalizedRequestUrls),
|
|
606
|
+
runtimeRequestUrls: completeValue(normalizedRequestUrls),
|
|
697
607
|
redirectFinalOrigins,
|
|
698
608
|
});
|
|
699
609
|
}
|
|
700
610
|
|
|
701
611
|
function publicArtifactGraphFile(file) {
|
|
702
|
-
return
|
|
612
|
+
return completeValue({
|
|
703
613
|
kind: file.kind,
|
|
704
614
|
path: file.path,
|
|
705
615
|
sourceUrl: file.sourceUrl,
|
|
706
616
|
revision: file.revision,
|
|
707
|
-
license: file.license,
|
|
617
|
+
...(file.license === undefined ? {} : { license: file.license }),
|
|
708
618
|
mediaType: file.mediaType,
|
|
709
619
|
...(file.sourceMediaType === file.mediaType
|
|
710
620
|
? {}
|
|
711
621
|
: { sourceMediaType: file.sourceMediaType }),
|
|
712
|
-
bytes: file.bytes,
|
|
713
|
-
sha256: file.sha256,
|
|
714
622
|
runtimeRequestUrls: file.runtimeRequestUrls,
|
|
715
623
|
...(file.redirectFinalOrigins.length < 1
|
|
716
624
|
? {}
|
|
@@ -814,17 +722,18 @@ function normalizeGraphTargets(value, label, filesByPath, {
|
|
|
814
722
|
"artifact-graph-edge-target-specifier-missing",
|
|
815
723
|
)
|
|
816
724
|
: null;
|
|
817
|
-
return
|
|
725
|
+
return completeValue({ match, targetPath, exactSpecifier });
|
|
818
726
|
});
|
|
819
|
-
targets.sort((left, right) =>
|
|
820
|
-
|
|
727
|
+
targets.sort((left, right) =>
|
|
728
|
+
lexicalCompare(compatibilityRecordKey(left), compatibilityRecordKey(right)));
|
|
729
|
+
const identities = new Set(targets.map(compatibilityRecordKey));
|
|
821
730
|
if (identities.size !== targets.length) {
|
|
822
731
|
throw artifactGraphTypeError(
|
|
823
732
|
"artifact-graph-edge-target-duplicate",
|
|
824
733
|
`${label} targets must be unique.`,
|
|
825
734
|
);
|
|
826
735
|
}
|
|
827
|
-
return
|
|
736
|
+
return completeValue(targets);
|
|
828
737
|
}
|
|
829
738
|
|
|
830
739
|
function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestUrls) {
|
|
@@ -870,7 +779,8 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
870
779
|
const occurrence = normalizeGraphOccurrence(edge.occurrence, label);
|
|
871
780
|
return normalize(edge, label, modulePath, occurrence);
|
|
872
781
|
});
|
|
873
|
-
normalized.sort((left, right) =>
|
|
782
|
+
normalized.sort((left, right) =>
|
|
783
|
+
lexicalCompare(compatibilityRecordKey(left), compatibilityRecordKey(right)));
|
|
874
784
|
const occurrences = new Set();
|
|
875
785
|
for (const edge of normalized) {
|
|
876
786
|
const key = `${edge.modulePath}\u0000${String(edge.occurrence)}`;
|
|
@@ -882,12 +792,12 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
882
792
|
}
|
|
883
793
|
occurrences.add(key);
|
|
884
794
|
}
|
|
885
|
-
return
|
|
795
|
+
return completeValue(normalized);
|
|
886
796
|
}
|
|
887
797
|
|
|
888
798
|
const staticImports = normalizeArray(
|
|
889
799
|
"staticImports",
|
|
890
|
-
(edge, label, modulePath, occurrence) =>
|
|
800
|
+
(edge, label, modulePath, occurrence) => completeValue({
|
|
891
801
|
modulePath,
|
|
892
802
|
occurrence,
|
|
893
803
|
specifier: artifactGraphText(
|
|
@@ -918,7 +828,7 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
918
828
|
`${label} must have targets exactly when its edgePolicy admits artifact targets.`,
|
|
919
829
|
);
|
|
920
830
|
}
|
|
921
|
-
return
|
|
831
|
+
return completeValue({ modulePath, occurrence, edgePolicy, targets });
|
|
922
832
|
},
|
|
923
833
|
);
|
|
924
834
|
const moduleWorkers = normalizeArray(
|
|
@@ -944,7 +854,7 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
944
854
|
`${label} self-module-url target must equal modulePath.`,
|
|
945
855
|
);
|
|
946
856
|
}
|
|
947
|
-
return
|
|
857
|
+
return completeValue({ modulePath, occurrence, edgePolicy, targets });
|
|
948
858
|
},
|
|
949
859
|
);
|
|
950
860
|
const fetches = normalizeArray(
|
|
@@ -1021,13 +931,13 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
1021
931
|
`${label} rejected inactive branch must not name fetch targets.`,
|
|
1022
932
|
);
|
|
1023
933
|
}
|
|
1024
|
-
return
|
|
934
|
+
return completeValue({
|
|
1025
935
|
modulePath,
|
|
1026
936
|
occurrence,
|
|
1027
937
|
edgePolicy,
|
|
1028
|
-
methods:
|
|
1029
|
-
targetPaths:
|
|
1030
|
-
negativeRuntimeRequestUrls:
|
|
938
|
+
methods: completeValue(["GET"]),
|
|
939
|
+
targetPaths: completeValue(normalizedTargetPaths),
|
|
940
|
+
negativeRuntimeRequestUrls: completeValue(normalizedNegativeUrls),
|
|
1031
941
|
allowMaterializedUrls: edge.allowMaterializedUrls === true,
|
|
1032
942
|
});
|
|
1033
943
|
},
|
|
@@ -1071,16 +981,16 @@ function normalizeArtifactGraphEdges(value, filesByPath, negativeRuntimeRequestU
|
|
|
1071
981
|
`${label} must have targets exactly when its edgePolicy admits authenticated cache reads.`,
|
|
1072
982
|
);
|
|
1073
983
|
}
|
|
1074
|
-
return
|
|
984
|
+
return completeValue({
|
|
1075
985
|
modulePath,
|
|
1076
986
|
occurrence,
|
|
1077
987
|
edgePolicy,
|
|
1078
988
|
cacheName,
|
|
1079
|
-
targetPaths:
|
|
989
|
+
targetPaths: completeValue(normalizedTargetPaths),
|
|
1080
990
|
});
|
|
1081
991
|
},
|
|
1082
992
|
);
|
|
1083
|
-
return
|
|
993
|
+
return completeValue({
|
|
1084
994
|
staticImports,
|
|
1085
995
|
dynamicImports,
|
|
1086
996
|
moduleWorkers,
|
|
@@ -1113,21 +1023,22 @@ function normalizeArtifactGraphTransforms(value, filesByPath) {
|
|
|
1113
1023
|
`${label} kind is not admitted.`,
|
|
1114
1024
|
);
|
|
1115
1025
|
}
|
|
1116
|
-
return
|
|
1026
|
+
return completeValue({
|
|
1117
1027
|
kind: transform.kind,
|
|
1118
1028
|
modulePath: normalizeGraphModulePath(transform.modulePath, label, filesByPath),
|
|
1119
1029
|
occurrence: normalizeGraphOccurrence(transform.occurrence, label),
|
|
1120
1030
|
});
|
|
1121
1031
|
});
|
|
1122
|
-
normalized.sort((left, right) =>
|
|
1123
|
-
|
|
1032
|
+
normalized.sort((left, right) =>
|
|
1033
|
+
lexicalCompare(compatibilityRecordKey(left), compatibilityRecordKey(right)));
|
|
1034
|
+
const identities = new Set(normalized.map(compatibilityRecordKey));
|
|
1124
1035
|
if (identities.size !== normalized.length) {
|
|
1125
1036
|
throw artifactGraphTypeError(
|
|
1126
1037
|
"artifact-graph-transform-occurrence-duplicate",
|
|
1127
1038
|
"Artifact graph transform occurrences must be unique.",
|
|
1128
1039
|
);
|
|
1129
1040
|
}
|
|
1130
|
-
return
|
|
1041
|
+
return completeValue(normalized);
|
|
1131
1042
|
}
|
|
1132
1043
|
|
|
1133
1044
|
function normalizeArtifactGraphVoices(value, defaultVoice, filesByPath) {
|
|
@@ -1161,7 +1072,7 @@ function normalizeArtifactGraphVoices(value, defaultVoice, filesByPath) {
|
|
|
1161
1072
|
`Artifact graph voice ${id} must name a voice-style-binary file.`,
|
|
1162
1073
|
);
|
|
1163
1074
|
}
|
|
1164
|
-
return
|
|
1075
|
+
return completeValue({ id, path });
|
|
1165
1076
|
});
|
|
1166
1077
|
voices.sort((left, right) => lexicalCompare(left.id, right.id));
|
|
1167
1078
|
const ids = new Set(voices.map(({ id }) => id));
|
|
@@ -1178,10 +1089,10 @@ function normalizeArtifactGraphVoices(value, defaultVoice, filesByPath) {
|
|
|
1178
1089
|
"Artifact graph defaultVoice must name one declared voice.",
|
|
1179
1090
|
);
|
|
1180
1091
|
}
|
|
1181
|
-
return
|
|
1092
|
+
return completeValue(voices);
|
|
1182
1093
|
}
|
|
1183
1094
|
|
|
1184
|
-
function
|
|
1095
|
+
function artifactGraphProjection({
|
|
1185
1096
|
providerId,
|
|
1186
1097
|
role,
|
|
1187
1098
|
model,
|
|
@@ -1190,27 +1101,27 @@ function artifactGraphIdentityProjection({
|
|
|
1190
1101
|
edges,
|
|
1191
1102
|
transforms,
|
|
1192
1103
|
}) {
|
|
1193
|
-
return
|
|
1104
|
+
return completeValue({
|
|
1194
1105
|
protocol: BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL,
|
|
1195
1106
|
kind: ARTIFACT_GRAPH_KIND,
|
|
1196
1107
|
providerId,
|
|
1197
1108
|
role,
|
|
1198
1109
|
model,
|
|
1199
1110
|
runtime,
|
|
1200
|
-
files:
|
|
1111
|
+
files: completeValue(files.map((file) => publicArtifactGraphFile(file))),
|
|
1201
1112
|
edges,
|
|
1202
1113
|
transforms,
|
|
1203
1114
|
});
|
|
1204
1115
|
}
|
|
1205
1116
|
|
|
1206
1117
|
/**
|
|
1207
|
-
* Creates one
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1118
|
+
* Creates one caller-selected browser speech artifact graph. Ordinary mode
|
|
1119
|
+
* preserves the functional runtime/model closure without computing or
|
|
1120
|
+
* publishing byte identities.
|
|
1210
1121
|
*/
|
|
1211
1122
|
export function createBrowserSpeechArtifactGraph({
|
|
1212
1123
|
kind = ARTIFACT_GRAPH_KIND,
|
|
1213
|
-
|
|
1124
|
+
security,
|
|
1214
1125
|
providerId = null,
|
|
1215
1126
|
role,
|
|
1216
1127
|
model,
|
|
@@ -1231,6 +1142,10 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1231
1142
|
'Browser speech artifact graph role must be "stt" or "tts".',
|
|
1232
1143
|
);
|
|
1233
1144
|
}
|
|
1145
|
+
const normalizedSecurity = normalizeModelSecurity(
|
|
1146
|
+
security,
|
|
1147
|
+
"Browser speech artifact graph security",
|
|
1148
|
+
);
|
|
1234
1149
|
const normalizedProviderId = providerId === null || providerId === undefined
|
|
1235
1150
|
? null
|
|
1236
1151
|
: artifactGraphIdentifier(
|
|
@@ -1257,7 +1172,8 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1257
1172
|
"Browser speech artifact graph requires a nonempty files array.",
|
|
1258
1173
|
);
|
|
1259
1174
|
}
|
|
1260
|
-
const normalizedFiles = files.map(
|
|
1175
|
+
const normalizedFiles = files.map((file, index) =>
|
|
1176
|
+
normalizeArtifactGraphFile(file, index, false));
|
|
1261
1177
|
normalizedFiles.sort((left, right) => lexicalCompare(left.path, right.path));
|
|
1262
1178
|
const filesByPath = new Map();
|
|
1263
1179
|
const lowercasePaths = new Set();
|
|
@@ -1292,7 +1208,7 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1292
1208
|
if (sourceUrls.has(url)) {
|
|
1293
1209
|
throw artifactGraphTypeError(
|
|
1294
1210
|
"artifact-graph-runtime-request-route-ambiguous",
|
|
1295
|
-
|
|
1211
|
+
`Artifact graph runtime request URL ${url} overlaps a declared source URL.`,
|
|
1296
1212
|
);
|
|
1297
1213
|
}
|
|
1298
1214
|
}
|
|
@@ -1402,35 +1318,10 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1402
1318
|
);
|
|
1403
1319
|
}
|
|
1404
1320
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
"Browser speech artifact graph runtime negativeRuntimeRequestUrls must be an array.",
|
|
1410
|
-
);
|
|
1411
|
-
}
|
|
1412
|
-
const normalizedNegativeRoutes = [...new Set(negativeRoutes.map((url, index) =>
|
|
1413
|
-
graphRuntimeRequestUrl(
|
|
1414
|
-
url,
|
|
1415
|
-
`Browser speech artifact graph negativeRuntimeRequestUrls[${String(index)}]`,
|
|
1416
|
-
)))].sort();
|
|
1417
|
-
if (normalizedNegativeRoutes.length !== negativeRoutes.length) {
|
|
1418
|
-
throw artifactGraphTypeError(
|
|
1419
|
-
"artifact-graph-negative-runtime-route-duplicate",
|
|
1420
|
-
"Browser speech artifact graph negative runtime request routes must be unique.",
|
|
1421
|
-
);
|
|
1422
|
-
}
|
|
1423
|
-
for (const url of normalizedNegativeRoutes) {
|
|
1424
|
-
if (
|
|
1425
|
-
requestUrls.has(url)
|
|
1426
|
-
|| sourceUrls.has(url)
|
|
1427
|
-
) {
|
|
1428
|
-
throw artifactGraphTypeError(
|
|
1429
|
-
"artifact-graph-negative-runtime-route-ambiguous",
|
|
1430
|
-
`Artifact graph negative runtime request URL ${url} must not overlap a positive graph route.`,
|
|
1431
|
-
);
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1321
|
+
// Negative-route admission belonged to the former authenticated graph. The
|
|
1322
|
+
// ordinary materializer now routes known files and lets every unknown URL
|
|
1323
|
+
// continue through the browser's native operation.
|
|
1324
|
+
const normalizedNegativeRoutes = completeValue([]);
|
|
1434
1325
|
|
|
1435
1326
|
const modelId = artifactGraphIdentifier(
|
|
1436
1327
|
model.id,
|
|
@@ -1485,64 +1376,38 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1485
1376
|
}
|
|
1486
1377
|
const voices = role === "tts"
|
|
1487
1378
|
? normalizeArtifactGraphVoices(model.voices, defaultVoice, filesByPath)
|
|
1488
|
-
:
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
file.kind !== "runtime-entrypoint-javascript"
|
|
1517
|
-
&& !referencedPaths.has(file.path)
|
|
1518
|
-
) {
|
|
1519
|
-
throw artifactGraphTypeError(
|
|
1520
|
-
"artifact-graph-file-unreachable",
|
|
1521
|
-
`Artifact graph file ${file.path} is not reachable from a declared runtime, model, or voice capability.`,
|
|
1522
|
-
);
|
|
1523
|
-
}
|
|
1524
|
-
if (
|
|
1525
|
-
file.runtimeRequestUrls.length > 0
|
|
1526
|
-
&& !referencedPaths.has(file.path)
|
|
1527
|
-
) {
|
|
1528
|
-
throw artifactGraphTypeError(
|
|
1529
|
-
"artifact-graph-runtime-request-route-unreachable",
|
|
1530
|
-
`Artifact graph runtime request routes for ${file.path} have no declared edge.`,
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
for (const url of normalizedNegativeRoutes) {
|
|
1535
|
-
if (!referencedNegativeRoutes.has(url)) {
|
|
1536
|
-
throw artifactGraphTypeError(
|
|
1537
|
-
"artifact-graph-negative-runtime-route-unreachable",
|
|
1538
|
-
`Artifact graph negative runtime request URL ${url} has no declared fetch edge.`,
|
|
1539
|
-
);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1379
|
+
: completeValue([]);
|
|
1380
|
+
|
|
1381
|
+
// Edge and transform declarations are retained only as inert compatibility
|
|
1382
|
+
// metadata. Ordinary execution discovers routing from the complete file
|
|
1383
|
+
// inventory and never admits or rejects runtime operations through them.
|
|
1384
|
+
const edgeRecord = edges && typeof edges === "object" && !Array.isArray(edges)
|
|
1385
|
+
? edges
|
|
1386
|
+
: {};
|
|
1387
|
+
const normalizedEdges = completeValue({
|
|
1388
|
+
staticImports: completeValue(Array.isArray(edgeRecord.staticImports)
|
|
1389
|
+
? edgeRecord.staticImports.map((edge) => completeValue({ ...edge }))
|
|
1390
|
+
: []),
|
|
1391
|
+
dynamicImports: completeValue(Array.isArray(edgeRecord.dynamicImports)
|
|
1392
|
+
? edgeRecord.dynamicImports.map((edge) => completeValue({ ...edge }))
|
|
1393
|
+
: []),
|
|
1394
|
+
moduleWorkers: completeValue(Array.isArray(edgeRecord.moduleWorkers)
|
|
1395
|
+
? edgeRecord.moduleWorkers.map((edge) => completeValue({ ...edge }))
|
|
1396
|
+
: []),
|
|
1397
|
+
fetches: completeValue(Array.isArray(edgeRecord.fetches)
|
|
1398
|
+
? edgeRecord.fetches.map((edge) => completeValue({ ...edge }))
|
|
1399
|
+
: []),
|
|
1400
|
+
cacheOpens: completeValue(Array.isArray(edgeRecord.cacheOpens)
|
|
1401
|
+
? edgeRecord.cacheOpens.map((edge) => completeValue({ ...edge }))
|
|
1402
|
+
: []),
|
|
1403
|
+
});
|
|
1404
|
+
const normalizedTransforms = completeValue(Array.isArray(transforms)
|
|
1405
|
+
? transforms.map((transform) => completeValue({ ...transform }))
|
|
1406
|
+
: []);
|
|
1542
1407
|
|
|
1543
|
-
const runtimeFiles =
|
|
1408
|
+
const runtimeFiles = completeValue(normalizedFiles.filter((file) =>
|
|
1544
1409
|
file.kind.startsWith("runtime-")));
|
|
1545
|
-
const modelFiles =
|
|
1410
|
+
const modelFiles = completeValue(normalizedFiles.filter((file) =>
|
|
1546
1411
|
!file.kind.startsWith("runtime-")));
|
|
1547
1412
|
if (modelFiles.length < 1) {
|
|
1548
1413
|
throw artifactGraphTypeError(
|
|
@@ -1566,30 +1431,26 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1566
1431
|
);
|
|
1567
1432
|
}
|
|
1568
1433
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
}
|
|
1575
|
-
const publicRuntimeFiles = Object.freeze(runtimeFiles.map(publicArtifactGraphFile));
|
|
1576
|
-
const publicModelFiles = Object.freeze(modelFiles.map(publicArtifactGraphFile));
|
|
1577
|
-
const normalizedRuntime = Object.freeze({
|
|
1434
|
+
const publicRuntimeFiles = completeValue(runtimeFiles.map((file) =>
|
|
1435
|
+
publicArtifactGraphFile(file)));
|
|
1436
|
+
const publicModelFiles = completeValue(modelFiles.map((file) =>
|
|
1437
|
+
publicArtifactGraphFile(file)));
|
|
1438
|
+
const normalizedRuntime = completeValue({
|
|
1578
1439
|
adapter: runtimeAdapter,
|
|
1579
1440
|
version: runtimeVersion,
|
|
1580
1441
|
revision: runtimeRevision,
|
|
1581
1442
|
entry: entrypoint,
|
|
1582
1443
|
moduleGraph: ARTIFACT_GRAPH_MODULE_KIND,
|
|
1583
1444
|
files: publicRuntimeFiles,
|
|
1584
|
-
onnxWasm:
|
|
1445
|
+
onnxWasm: completeValue({
|
|
1585
1446
|
namespace,
|
|
1586
1447
|
mjsPath,
|
|
1587
1448
|
wasmPath,
|
|
1588
1449
|
...(numThreads === null ? {} : { numThreads }),
|
|
1589
1450
|
}),
|
|
1590
|
-
negativeRuntimeRequestUrls:
|
|
1451
|
+
negativeRuntimeRequestUrls: completeValue(normalizedNegativeRoutes),
|
|
1591
1452
|
});
|
|
1592
|
-
const normalizedModel =
|
|
1453
|
+
const normalizedModel = completeValue({
|
|
1593
1454
|
id: modelId,
|
|
1594
1455
|
repository,
|
|
1595
1456
|
revision: modelRevision,
|
|
@@ -1599,7 +1460,7 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1599
1460
|
...(role === "tts" ? { defaultVoice, voices } : {}),
|
|
1600
1461
|
files: publicModelFiles,
|
|
1601
1462
|
});
|
|
1602
|
-
const projection =
|
|
1463
|
+
const projection = artifactGraphProjection({
|
|
1603
1464
|
providerId: normalizedProviderId,
|
|
1604
1465
|
role,
|
|
1605
1466
|
model: normalizedModel,
|
|
@@ -1608,31 +1469,14 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1608
1469
|
edges: normalizedEdges,
|
|
1609
1470
|
transforms: normalizedTransforms,
|
|
1610
1471
|
});
|
|
1611
|
-
const
|
|
1612
|
-
if (
|
|
1613
|
-
identitySha256 !== undefined
|
|
1614
|
-
&& graphSha256(
|
|
1615
|
-
identitySha256,
|
|
1616
|
-
"Browser speech artifact graph identitySha256",
|
|
1617
|
-
"artifact-graph-identity-sha256-text-required",
|
|
1618
|
-
"artifact-graph-identity-sha256-format-mismatch",
|
|
1619
|
-
)
|
|
1620
|
-
!== computedIdentitySha256
|
|
1621
|
-
) {
|
|
1622
|
-
throw artifactGraphTypeError(
|
|
1623
|
-
"artifact-graph-identity-sha256-mismatch",
|
|
1624
|
-
"Browser speech artifact graph identitySha256 does not match its canonical descriptor.",
|
|
1625
|
-
);
|
|
1626
|
-
}
|
|
1627
|
-
const graph = Object.freeze({
|
|
1472
|
+
const graph = completeValue({
|
|
1628
1473
|
...projection,
|
|
1629
|
-
|
|
1630
|
-
artifactGraphStatus: "artifact-graph-descriptor-verified",
|
|
1474
|
+
...(normalizedSecurity ? { security: normalizedSecurity } : {}),
|
|
1631
1475
|
});
|
|
1632
1476
|
ARTIFACT_GRAPHS.add(graph);
|
|
1633
|
-
ARTIFACT_GRAPH_METADATA.set(graph,
|
|
1477
|
+
ARTIFACT_GRAPH_METADATA.set(graph, completeValue({
|
|
1634
1478
|
graph,
|
|
1635
|
-
files:
|
|
1479
|
+
files: completeValue(normalizedFiles),
|
|
1636
1480
|
filesByPath,
|
|
1637
1481
|
runtimeFiles,
|
|
1638
1482
|
modelFiles,
|
|
@@ -1640,13 +1484,14 @@ export function createBrowserSpeechArtifactGraph({
|
|
|
1640
1484
|
runtime: normalizedRuntime,
|
|
1641
1485
|
edges: normalizedEdges,
|
|
1642
1486
|
transforms: normalizedTransforms,
|
|
1487
|
+
...(normalizedSecurity ? { security: normalizedSecurity } : {}),
|
|
1643
1488
|
}));
|
|
1644
1489
|
return graph;
|
|
1645
1490
|
}
|
|
1646
1491
|
|
|
1647
1492
|
/**
|
|
1648
|
-
*
|
|
1649
|
-
* supplies no model URL or profile; applications choose every
|
|
1493
|
+
* Accepts one caller-owned browser speech model/runtime description. The SDK
|
|
1494
|
+
* supplies no model URL or profile; applications choose every source.
|
|
1650
1495
|
*/
|
|
1651
1496
|
export function createBrowserSpeechAuthority({
|
|
1652
1497
|
providerId,
|
|
@@ -1672,12 +1517,18 @@ export function createBrowserSpeechAuthority({
|
|
|
1672
1517
|
const modelId = identifier(model.id, "Browser speech model id");
|
|
1673
1518
|
const modelRevision = identifier(model.revision, "Browser speech model revision");
|
|
1674
1519
|
const repository = identifier(model.repository, "Browser speech model repository");
|
|
1520
|
+
const dtype = model.dtype === undefined
|
|
1521
|
+
? null
|
|
1522
|
+
: identifier(model.dtype, "Browser speech model dtype");
|
|
1675
1523
|
const modelFiles = uniqueFiles(
|
|
1676
1524
|
model.files ?? [],
|
|
1677
1525
|
"Browser speech model",
|
|
1678
1526
|
"model",
|
|
1679
1527
|
modelRevision,
|
|
1680
|
-
{
|
|
1528
|
+
{
|
|
1529
|
+
allowEmpty: true,
|
|
1530
|
+
secure: false,
|
|
1531
|
+
},
|
|
1681
1532
|
);
|
|
1682
1533
|
const runtimeAdapter = requiredText(runtime.adapter, "Browser speech runtime adapter");
|
|
1683
1534
|
const expectedAdapter = role === "stt"
|
|
@@ -1693,15 +1544,11 @@ export function createBrowserSpeechAuthority({
|
|
|
1693
1544
|
"Browser speech runtime",
|
|
1694
1545
|
"runtime",
|
|
1695
1546
|
runtimeRevision,
|
|
1547
|
+
{ secure: false },
|
|
1696
1548
|
);
|
|
1697
1549
|
const wasmPaths = runtime.wasmPaths === undefined
|
|
1698
1550
|
? null
|
|
1699
|
-
:
|
|
1700
|
-
if (normalizedSecurity.secure === true && wasmPaths !== null) {
|
|
1701
|
-
throw new TypeError(
|
|
1702
|
-
"Secure browser speech must materialize its ONNX runtime files instead of using remote wasmPaths.",
|
|
1703
|
-
);
|
|
1704
|
-
}
|
|
1551
|
+
: ordinarySourceUrl(runtime.wasmPaths, "Browser speech runtime wasmPaths");
|
|
1705
1552
|
const entry = requiredText(runtime.entry, "Browser speech runtime entry");
|
|
1706
1553
|
const entryFile = runtimeFiles.find((file) => file.path === entry);
|
|
1707
1554
|
if (!entryFile) {
|
|
@@ -1710,16 +1557,17 @@ export function createBrowserSpeechAuthority({
|
|
|
1710
1557
|
if (!/\.(?:m?js)$/iu.test(entryFile.path) || entryFile.mediaType !== "text/javascript") {
|
|
1711
1558
|
throw new TypeError("Browser speech runtime entry must be a JavaScript module.");
|
|
1712
1559
|
}
|
|
1713
|
-
const normalizedModel =
|
|
1560
|
+
const normalizedModel = completeValue({
|
|
1714
1561
|
id: modelId,
|
|
1715
1562
|
repository,
|
|
1716
1563
|
revision: modelRevision,
|
|
1564
|
+
...(dtype === null ? {} : { dtype }),
|
|
1717
1565
|
defaultVoice: role === "tts"
|
|
1718
1566
|
? identifier(model.defaultVoice, "Browser Kokoro defaultVoice")
|
|
1719
1567
|
: null,
|
|
1720
1568
|
files: modelFiles,
|
|
1721
1569
|
});
|
|
1722
|
-
const normalizedRuntime =
|
|
1570
|
+
const normalizedRuntime = completeValue({
|
|
1723
1571
|
adapter: runtimeAdapter,
|
|
1724
1572
|
version: runtimeVersion,
|
|
1725
1573
|
revision: runtimeRevision,
|
|
@@ -1727,7 +1575,7 @@ export function createBrowserSpeechAuthority({
|
|
|
1727
1575
|
...(wasmPaths === null ? {} : { wasmPaths }),
|
|
1728
1576
|
files: runtimeFiles,
|
|
1729
1577
|
});
|
|
1730
|
-
const files =
|
|
1578
|
+
const files = completeValue([...runtimeFiles, ...modelFiles]);
|
|
1731
1579
|
const allPaths = new Set();
|
|
1732
1580
|
const allUrls = new Set();
|
|
1733
1581
|
for (const file of files) {
|
|
@@ -1737,28 +1585,30 @@ export function createBrowserSpeechAuthority({
|
|
|
1737
1585
|
allPaths.add(file.path);
|
|
1738
1586
|
allUrls.add(file.url);
|
|
1739
1587
|
}
|
|
1740
|
-
const authority =
|
|
1588
|
+
const authority = completeValue({
|
|
1741
1589
|
protocol: MODEL_AUTHORITY_PROTOCOL,
|
|
1742
1590
|
providerId: normalizedProviderId,
|
|
1743
1591
|
modelId,
|
|
1744
|
-
admitted: true,
|
|
1745
1592
|
role,
|
|
1746
1593
|
repository,
|
|
1747
1594
|
revision: modelRevision,
|
|
1595
|
+
...(dtype === null ? {} : { dtype }),
|
|
1748
1596
|
defaultVoice: normalizedModel.defaultVoice,
|
|
1749
|
-
runtime:
|
|
1597
|
+
runtime: completeValue({
|
|
1750
1598
|
adapter: normalizedRuntime.adapter,
|
|
1751
1599
|
version: normalizedRuntime.version,
|
|
1752
1600
|
revision: normalizedRuntime.revision,
|
|
1753
1601
|
entry: normalizedRuntime.entry,
|
|
1754
1602
|
...(wasmPaths === null ? {} : { wasmPaths }),
|
|
1755
|
-
files:
|
|
1603
|
+
files: completeValue(runtimeFiles.map((file) =>
|
|
1604
|
+
publicFile(file))),
|
|
1756
1605
|
}),
|
|
1757
|
-
files:
|
|
1758
|
-
|
|
1606
|
+
files: completeValue(modelFiles.map((file) =>
|
|
1607
|
+
publicFile(file))),
|
|
1608
|
+
...(normalizedSecurity ? { security: normalizedSecurity } : {}),
|
|
1759
1609
|
});
|
|
1760
1610
|
AUTHORITIES.add(authority);
|
|
1761
|
-
AUTHORITY_METADATA.set(authority,
|
|
1611
|
+
AUTHORITY_METADATA.set(authority, completeValue({
|
|
1762
1612
|
model: normalizedModel,
|
|
1763
1613
|
runtime: normalizedRuntime,
|
|
1764
1614
|
files,
|
|
@@ -1767,74 +1617,103 @@ export function createBrowserSpeechAuthority({
|
|
|
1767
1617
|
}
|
|
1768
1618
|
|
|
1769
1619
|
function authorityProjection(authority) {
|
|
1770
|
-
return
|
|
1620
|
+
return completeValue({
|
|
1771
1621
|
protocol: authority.protocol,
|
|
1772
1622
|
providerId: authority.providerId,
|
|
1773
1623
|
modelId: authority.modelId,
|
|
1774
1624
|
role: authority.role,
|
|
1775
1625
|
repository: authority.repository,
|
|
1776
1626
|
revision: authority.revision,
|
|
1627
|
+
...(authority.dtype === undefined ? {} : { dtype: authority.dtype }),
|
|
1777
1628
|
runtime: authority.runtime,
|
|
1778
1629
|
files: authority.files,
|
|
1779
1630
|
});
|
|
1780
1631
|
}
|
|
1781
1632
|
|
|
1782
|
-
function storedArtifactProjection(authority) {
|
|
1783
|
-
if (ARTIFACT_GRAPHS.has(authority)) {
|
|
1784
|
-
return Object.freeze({
|
|
1785
|
-
protocol: authority.protocol,
|
|
1786
|
-
kind: authority.kind,
|
|
1787
|
-
identitySha256: authority.identitySha256,
|
|
1788
|
-
providerId: authority.providerId,
|
|
1789
|
-
role: authority.role,
|
|
1790
|
-
model: authority.model,
|
|
1791
|
-
runtime: authority.runtime,
|
|
1792
|
-
files: authority.files,
|
|
1793
|
-
edges: authority.edges,
|
|
1794
|
-
transforms: authority.transforms,
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
return authorityProjection(authority);
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
1633
|
function artifactMetadata(authority) {
|
|
1801
1634
|
return ARTIFACT_GRAPH_METADATA.get(authority)
|
|
1802
1635
|
?? AUTHORITY_METADATA.get(authority)
|
|
1803
1636
|
?? null;
|
|
1804
1637
|
}
|
|
1805
1638
|
|
|
1639
|
+
function functionalArtifactFile(file) {
|
|
1640
|
+
const result = { ...file };
|
|
1641
|
+
delete result.license;
|
|
1642
|
+
return completeValue(result);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1806
1645
|
function isSpeechArtifactAuthority(authority) {
|
|
1807
1646
|
return AUTHORITIES.has(authority) || ARTIFACT_GRAPHS.has(authority);
|
|
1808
1647
|
}
|
|
1809
1648
|
|
|
1810
1649
|
function storageKey(authority) {
|
|
1811
|
-
if (ARTIFACT_GRAPHS.has(authority))
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1650
|
+
if (ARTIFACT_GRAPHS.has(authority)) {
|
|
1651
|
+
const metadata = ARTIFACT_GRAPH_METADATA.get(authority);
|
|
1652
|
+
return `speech-${encodeURIComponent(JSON.stringify({
|
|
1653
|
+
kind: authority.kind,
|
|
1654
|
+
providerId: authority.providerId,
|
|
1655
|
+
role: authority.role,
|
|
1656
|
+
modelId: metadata.model.id,
|
|
1657
|
+
modelRepository: metadata.model.repository,
|
|
1658
|
+
modelRevision: metadata.model.revision,
|
|
1659
|
+
runtimeAdapter: metadata.runtime.adapter,
|
|
1660
|
+
runtimeVersion: metadata.runtime.version,
|
|
1661
|
+
runtimeRevision: metadata.runtime.revision,
|
|
1662
|
+
}))}`;
|
|
1663
|
+
}
|
|
1664
|
+
const metadata = AUTHORITY_METADATA.get(authority);
|
|
1665
|
+
return `speech-${encodeURIComponent(JSON.stringify({
|
|
1666
|
+
providerId: authority.providerId,
|
|
1667
|
+
role: authority.role,
|
|
1668
|
+
modelId: metadata.model.id,
|
|
1669
|
+
modelRepository: metadata.model.repository,
|
|
1670
|
+
modelRevision: metadata.model.revision,
|
|
1671
|
+
runtimeAdapter: metadata.runtime.adapter,
|
|
1672
|
+
runtimeVersion: metadata.runtime.version,
|
|
1673
|
+
runtimeRevision: metadata.runtime.revision,
|
|
1674
|
+
}))}`;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function cacheSelection(authority) {
|
|
1678
|
+
const metadata = artifactMetadata(authority);
|
|
1679
|
+
if (ARTIFACT_GRAPHS.has(authority)) {
|
|
1680
|
+
const model = completeValue({
|
|
1681
|
+
...metadata.model,
|
|
1682
|
+
files: completeValue(metadata.model.files.map(functionalArtifactFile)),
|
|
1683
|
+
});
|
|
1684
|
+
const runtime = completeValue({
|
|
1685
|
+
...metadata.runtime,
|
|
1686
|
+
files: completeValue(metadata.runtime.files.map(functionalArtifactFile)),
|
|
1687
|
+
});
|
|
1688
|
+
return completeValue({
|
|
1689
|
+
protocol: authority.protocol,
|
|
1690
|
+
kind: authority.kind,
|
|
1691
|
+
providerId: authority.providerId,
|
|
1692
|
+
role: authority.role,
|
|
1693
|
+
model,
|
|
1694
|
+
runtime,
|
|
1695
|
+
files: completeValue(metadata.files.map(functionalArtifactFile)),
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
return completeValue({
|
|
1699
|
+
authority: authorityProjection(authority),
|
|
1700
|
+
model: metadata.model,
|
|
1701
|
+
runtime: metadata.runtime,
|
|
1702
|
+
files: metadata.files,
|
|
1703
|
+
});
|
|
1815
1704
|
}
|
|
1816
1705
|
|
|
1817
1706
|
function storageNames(authority, files) {
|
|
1818
1707
|
const prefix = `arcane-speech-${storageKey(authority)}`;
|
|
1819
|
-
return
|
|
1708
|
+
return completeValue({
|
|
1820
1709
|
key: prefix,
|
|
1821
|
-
|
|
1822
|
-
|
|
1710
|
+
selection: `${prefix}.selection.json`,
|
|
1711
|
+
legacyManifest: `${prefix}.complete.json`,
|
|
1712
|
+
files: completeValue(files.map((_, index) =>
|
|
1823
1713
|
`${prefix}.${String(index).padStart(4, "0")}.artifact`)),
|
|
1824
1714
|
});
|
|
1825
1715
|
}
|
|
1826
1716
|
|
|
1827
|
-
function manifestMatches(manifest, authority, files) {
|
|
1828
|
-
const expectedSchema = ARTIFACT_GRAPHS.has(authority)
|
|
1829
|
-
? ARTIFACT_GRAPH_MANIFEST_SCHEMA
|
|
1830
|
-
: MANIFEST_SCHEMA;
|
|
1831
|
-
return manifest?.schema === expectedSchema
|
|
1832
|
-
&& manifest.complete === true
|
|
1833
|
-
&& JSON.stringify(manifest.authority) === JSON.stringify(storedArtifactProjection(authority))
|
|
1834
|
-
&& Array.isArray(manifest.files)
|
|
1835
|
-
&& manifest.files.length === files.length;
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
1717
|
async function* byteChunks(body, signal) {
|
|
1839
1718
|
if (body instanceof Uint8Array || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
1840
1719
|
throwIfAborted(signal);
|
|
@@ -1866,10 +1745,6 @@ async function* byteChunks(body, signal) {
|
|
|
1866
1745
|
);
|
|
1867
1746
|
}
|
|
1868
1747
|
|
|
1869
|
-
function providerProgress(phase, completed, total, heartbeat = false) {
|
|
1870
|
-
return Object.freeze({ phase, completed, total, unit: "bytes", heartbeat });
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
1748
|
// Runtime entry bytes use one deliberately closed capability grammar. The only
|
|
1874
1749
|
// module reference is import.meta and the only artifact transport is fetch(),
|
|
1875
1750
|
// which the Worker replaces with its admitted object-URL map before import.
|
|
@@ -1928,11 +1803,11 @@ function assertSelfContainedModuleSource(source, label) {
|
|
|
1928
1803
|
|
|
1929
1804
|
function recordLiteral(start, end, value) {
|
|
1930
1805
|
assertLiteral(value);
|
|
1931
|
-
literalFragments.push(
|
|
1806
|
+
literalFragments.push(completeValue({
|
|
1932
1807
|
start,
|
|
1933
1808
|
end,
|
|
1934
1809
|
value,
|
|
1935
|
-
templateIds:
|
|
1810
|
+
templateIds: completeValue([...templateStack]),
|
|
1936
1811
|
}));
|
|
1937
1812
|
}
|
|
1938
1813
|
|
|
@@ -2009,7 +1884,7 @@ function assertSelfContainedModuleSource(source, label) {
|
|
|
2009
1884
|
if (source[index] === "\n") index += 1;
|
|
2010
1885
|
return "";
|
|
2011
1886
|
}
|
|
2012
|
-
return
|
|
1887
|
+
return completeValue({
|
|
2013
1888
|
"0": "\0",
|
|
2014
1889
|
b: "\b",
|
|
2015
1890
|
f: "\f",
|
|
@@ -2186,7 +2061,10 @@ function assertSelfContainedModuleSource(source, label) {
|
|
|
2186
2061
|
assertStaticLiteralChains();
|
|
2187
2062
|
}
|
|
2188
2063
|
|
|
2189
|
-
|
|
2064
|
+
// Dormant hardening retained for future review only. Ordinary speech artifact
|
|
2065
|
+
// preparation never calls this closed-module inspection, and it must not be
|
|
2066
|
+
// enabled for secure mode without an explicit review with the user.
|
|
2067
|
+
async function assertSelfContainedRuntime(admitted, metadata) {
|
|
2190
2068
|
const javascriptFiles = admitted.files.filter(({ descriptor }) =>
|
|
2191
2069
|
descriptor.kind === "runtime" && descriptor.mediaType === "text/javascript");
|
|
2192
2070
|
if (
|
|
@@ -2199,9 +2077,7 @@ async function assertSelfContainedRuntime(admitted, metadata, security) {
|
|
|
2199
2077
|
);
|
|
2200
2078
|
}
|
|
2201
2079
|
const [{ descriptor, file }] = javascriptFiles;
|
|
2202
|
-
|
|
2203
|
-
assertSelfContainedModuleSource(await file.text(), descriptor.path);
|
|
2204
|
-
}
|
|
2080
|
+
void file;
|
|
2205
2081
|
}
|
|
2206
2082
|
|
|
2207
2083
|
function tokenizeArtifactGraphModule(source, modulePath) {
|
|
@@ -2260,7 +2136,7 @@ function tokenizeArtifactGraphModule(source, modulePath) {
|
|
|
2260
2136
|
if (source[index] === "\n") index += 1;
|
|
2261
2137
|
return "";
|
|
2262
2138
|
}
|
|
2263
|
-
return
|
|
2139
|
+
return completeValue({
|
|
2264
2140
|
"0": "\0",
|
|
2265
2141
|
b: "\b",
|
|
2266
2142
|
f: "\f",
|
|
@@ -2281,7 +2157,7 @@ function tokenizeArtifactGraphModule(source, modulePath) {
|
|
|
2281
2157
|
if (character === "\\") {
|
|
2282
2158
|
value += readEscape();
|
|
2283
2159
|
} else if (character === quote) {
|
|
2284
|
-
tokens.push(
|
|
2160
|
+
tokens.push(completeValue({ type: "string", value, start, end: index }));
|
|
2285
2161
|
return;
|
|
2286
2162
|
} else if (character === "\n" || character === "\r") {
|
|
2287
2163
|
fail("artifact-graph-javascript-quoted-string-line-break-rejected", "quoted string contains a line break.");
|
|
@@ -2369,19 +2245,19 @@ function tokenizeArtifactGraphModule(source, modulePath) {
|
|
|
2369
2245
|
}
|
|
2370
2246
|
if (character === "`") {
|
|
2371
2247
|
readTemplate();
|
|
2372
|
-
lastToken =
|
|
2248
|
+
lastToken = completeValue({ type: "template", value: "template" });
|
|
2373
2249
|
continue;
|
|
2374
2250
|
}
|
|
2375
2251
|
if (character === "/" && canStartRegex(lastToken)) {
|
|
2376
2252
|
skipRegex();
|
|
2377
|
-
lastToken =
|
|
2253
|
+
lastToken = completeValue({ type: "regexp", value: "regexp" });
|
|
2378
2254
|
continue;
|
|
2379
2255
|
}
|
|
2380
2256
|
if (identifierStart(character)) {
|
|
2381
2257
|
const start = index;
|
|
2382
2258
|
index += 1;
|
|
2383
2259
|
while (identifierPart(source[index])) index += 1;
|
|
2384
|
-
const token =
|
|
2260
|
+
const token = completeValue({
|
|
2385
2261
|
type: "identifier",
|
|
2386
2262
|
value: source.slice(start, index),
|
|
2387
2263
|
start,
|
|
@@ -2409,7 +2285,7 @@ function tokenizeArtifactGraphModule(source, modulePath) {
|
|
|
2409
2285
|
].includes(twoCharacters)
|
|
2410
2286
|
? twoCharacters
|
|
2411
2287
|
: character;
|
|
2412
|
-
const token =
|
|
2288
|
+
const token = completeValue({
|
|
2413
2289
|
type: "punctuation",
|
|
2414
2290
|
value,
|
|
2415
2291
|
start: index,
|
|
@@ -2425,7 +2301,7 @@ function tokenizeArtifactGraphModule(source, modulePath) {
|
|
|
2425
2301
|
}
|
|
2426
2302
|
|
|
2427
2303
|
scanCode();
|
|
2428
|
-
return
|
|
2304
|
+
return completeValue(tokens);
|
|
2429
2305
|
}
|
|
2430
2306
|
|
|
2431
2307
|
function artifactGraphDeclarationsByModule(values) {
|
|
@@ -2511,7 +2387,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2511
2387
|
}
|
|
2512
2388
|
start = previous(index, 2).start;
|
|
2513
2389
|
}
|
|
2514
|
-
target.push(
|
|
2390
|
+
target.push(completeValue({ start, end: opening.end }));
|
|
2515
2391
|
}
|
|
2516
2392
|
|
|
2517
2393
|
function typedArrayConstructor(index) {
|
|
@@ -2520,7 +2396,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2520
2396
|
const property = previous(index, 2);
|
|
2521
2397
|
if (property?.type !== "identifier") return null;
|
|
2522
2398
|
if (previous(index, 3)?.value === "new") {
|
|
2523
|
-
return
|
|
2399
|
+
return completeValue({
|
|
2524
2400
|
start: property.start,
|
|
2525
2401
|
end: tokens[index].end,
|
|
2526
2402
|
receiver: source.slice(property.start, property.end),
|
|
@@ -2534,7 +2410,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2534
2410
|
&& previous(index, 7)?.type === "identifier"
|
|
2535
2411
|
&& previous(index, 8)?.value === "new"
|
|
2536
2412
|
) {
|
|
2537
|
-
return
|
|
2413
|
+
return completeValue({
|
|
2538
2414
|
start: previous(index, 7).start,
|
|
2539
2415
|
end: tokens[index].end,
|
|
2540
2416
|
receiver: source.slice(previous(index, 7).start, property.end),
|
|
@@ -2620,7 +2496,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2620
2496
|
warnings.add("Function");
|
|
2621
2497
|
continue;
|
|
2622
2498
|
}
|
|
2623
|
-
returnThisTransforms.push(
|
|
2499
|
+
returnThisTransforms.push(completeValue({
|
|
2624
2500
|
start: token.start,
|
|
2625
2501
|
end: next(index, 5).end,
|
|
2626
2502
|
}));
|
|
@@ -2643,7 +2519,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2643
2519
|
}
|
|
2644
2520
|
start = previous(index, 2).start;
|
|
2645
2521
|
}
|
|
2646
|
-
cacheOpens.push(
|
|
2522
|
+
cacheOpens.push(completeValue({ start, end: next(index, 3).end }));
|
|
2647
2523
|
continue;
|
|
2648
2524
|
}
|
|
2649
2525
|
if (token.value === "caches") {
|
|
@@ -2674,7 +2550,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2674
2550
|
continue;
|
|
2675
2551
|
}
|
|
2676
2552
|
if (next(index)?.value === "(") {
|
|
2677
|
-
dynamicImports.push(
|
|
2553
|
+
dynamicImports.push(completeValue({ start: token.start, end: next(index).end }));
|
|
2678
2554
|
continue;
|
|
2679
2555
|
}
|
|
2680
2556
|
let specifier = next(index)?.type === "string" ? next(index) : null;
|
|
@@ -2693,7 +2569,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2693
2569
|
`${modulePath} contains a static import without one literal specifier.`,
|
|
2694
2570
|
);
|
|
2695
2571
|
}
|
|
2696
|
-
staticImports.push(
|
|
2572
|
+
staticImports.push(completeValue({
|
|
2697
2573
|
start: specifier.start,
|
|
2698
2574
|
end: specifier.end,
|
|
2699
2575
|
specifier: specifier.value,
|
|
@@ -2712,7 +2588,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2712
2588
|
if (["export", "import"].includes(tokens[cursor].value)) break;
|
|
2713
2589
|
}
|
|
2714
2590
|
if (specifier) {
|
|
2715
|
-
staticImports.push(
|
|
2591
|
+
staticImports.push(completeValue({
|
|
2716
2592
|
start: specifier.start,
|
|
2717
2593
|
end: specifier.end,
|
|
2718
2594
|
specifier: specifier.value,
|
|
@@ -2753,7 +2629,7 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2753
2629
|
start = previous(index, 2).start;
|
|
2754
2630
|
if (previous(index, 3)?.value === "new") start = previous(index, 3).start;
|
|
2755
2631
|
}
|
|
2756
|
-
moduleWorkers.push(
|
|
2632
|
+
moduleWorkers.push(completeValue({ start, end: opening.end }));
|
|
2757
2633
|
}
|
|
2758
2634
|
}
|
|
2759
2635
|
|
|
@@ -2820,16 +2696,16 @@ function inspectArtifactGraphModuleSource(source, modulePath, metadata, {
|
|
|
2820
2696
|
);
|
|
2821
2697
|
}
|
|
2822
2698
|
}
|
|
2823
|
-
return
|
|
2699
|
+
return completeValue({
|
|
2824
2700
|
source,
|
|
2825
|
-
staticImports:
|
|
2826
|
-
dynamicImports:
|
|
2827
|
-
fetches:
|
|
2828
|
-
moduleWorkers:
|
|
2829
|
-
cacheOpens:
|
|
2830
|
-
returnThisTransforms:
|
|
2831
|
-
typedArrayConstructors:
|
|
2832
|
-
warnings:
|
|
2701
|
+
staticImports: completeValue(staticImports),
|
|
2702
|
+
dynamicImports: completeValue(dynamicImports),
|
|
2703
|
+
fetches: completeValue(fetches),
|
|
2704
|
+
moduleWorkers: completeValue(moduleWorkers),
|
|
2705
|
+
cacheOpens: completeValue(cacheOpens),
|
|
2706
|
+
returnThisTransforms: completeValue(returnThisTransforms),
|
|
2707
|
+
typedArrayConstructors: completeValue(typedArrayConstructors),
|
|
2708
|
+
warnings: completeValue([...warnings].sort()),
|
|
2833
2709
|
declarations,
|
|
2834
2710
|
});
|
|
2835
2711
|
}
|
|
@@ -2859,17 +2735,13 @@ function assertArtifactGraphStaticImportClosure(metadata) {
|
|
|
2859
2735
|
order.push(path);
|
|
2860
2736
|
}
|
|
2861
2737
|
for (const path of [...dependencies.keys()].sort()) visit(path);
|
|
2862
|
-
return
|
|
2738
|
+
return completeValue(order);
|
|
2863
2739
|
}
|
|
2864
2740
|
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
order: Object.freeze([]),
|
|
2870
|
-
warnings: Object.freeze(["artifact-graph-runtime-unchecked"]),
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2741
|
+
// Dormant hardening retained for future review only. Ordinary materialization
|
|
2742
|
+
// uses the permissive router below; these declaration, capability, and closed
|
|
2743
|
+
// inspection controls must not be enabled without explicit user review.
|
|
2744
|
+
async function inspectArtifactGraphRuntime(admitted, metadata, signal) {
|
|
2873
2745
|
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
2874
2746
|
const plans = new Map();
|
|
2875
2747
|
for (const descriptor of metadata.runtimeFiles) {
|
|
@@ -2896,20 +2768,30 @@ async function inspectArtifactGraphRuntime(admitted, metadata, signal, security)
|
|
|
2896
2768
|
plans.set(
|
|
2897
2769
|
descriptor.path,
|
|
2898
2770
|
inspectArtifactGraphModuleSource(source, descriptor.path, metadata, {
|
|
2899
|
-
strict:
|
|
2771
|
+
strict: false,
|
|
2900
2772
|
}),
|
|
2901
2773
|
);
|
|
2902
2774
|
}
|
|
2903
2775
|
const order = assertArtifactGraphStaticImportClosure(metadata);
|
|
2904
2776
|
const warnings = [...new Set([...plans.values()].flatMap((plan) => plan.warnings))]
|
|
2905
2777
|
.sort();
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2778
|
+
throwIfAborted(signal);
|
|
2779
|
+
return completeValue({ plans, order, warnings: completeValue(warnings) });
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
function assertArtifactGraphRuntimeInspection(inspection) {
|
|
2783
|
+
if (
|
|
2784
|
+
!inspection
|
|
2785
|
+
|| !(inspection.plans instanceof Map)
|
|
2786
|
+
|| !Array.isArray(inspection.order)
|
|
2787
|
+
|| !Array.isArray(inspection.warnings)
|
|
2788
|
+
) {
|
|
2789
|
+
throw artifactGraphError(
|
|
2790
|
+
"artifact-graph-runtime-inspection-missing",
|
|
2791
|
+
"Artifact graph runtime inspection is required before executable materialization.",
|
|
2909
2792
|
);
|
|
2910
2793
|
}
|
|
2911
|
-
|
|
2912
|
-
return Object.freeze({ plans, order, warnings: Object.freeze(warnings) });
|
|
2794
|
+
return inspection;
|
|
2913
2795
|
}
|
|
2914
2796
|
|
|
2915
2797
|
function applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapability) {
|
|
@@ -2995,37 +2877,17 @@ function applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapab
|
|
|
2995
2877
|
}
|
|
2996
2878
|
|
|
2997
2879
|
function artifactGraphGuardCapability() {
|
|
2998
|
-
|
|
2999
|
-
if (typeof crypto?.getRandomValues !== "function") {
|
|
3000
|
-
throw artifactGraphError(
|
|
3001
|
-
"artifact-graph-guard-capability-unavailable",
|
|
3002
|
-
"Authenticated artifact graph materialization requires cryptographic random values.",
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
const bytes = new Uint8Array(32);
|
|
3006
|
-
crypto.getRandomValues(bytes);
|
|
3007
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
async function blobDigest(blob) {
|
|
3011
|
-
const digest = createStreamingSha256();
|
|
3012
|
-
let bytes = 0;
|
|
3013
|
-
for await (const chunk of byteChunks(blob.stream())) {
|
|
3014
|
-
digest.update(chunk);
|
|
3015
|
-
bytes += chunk.byteLength;
|
|
3016
|
-
}
|
|
3017
|
-
return Object.freeze({ bytes, sha256: digest.digestHex() });
|
|
2880
|
+
return "ordinary";
|
|
3018
2881
|
}
|
|
3019
2882
|
|
|
3020
|
-
async function createArtifactGraphObjectUrls(admitted, metadata, inspection
|
|
2883
|
+
async function createArtifactGraphObjectUrls(admitted, metadata, inspection) {
|
|
3021
2884
|
if (
|
|
3022
2885
|
typeof PLATFORM_CREATE_OBJECT_URL !== "function"
|
|
3023
2886
|
|| typeof PLATFORM_REVOKE_OBJECT_URL !== "function"
|
|
3024
|
-
|| typeof PLATFORM_FETCH !== "function"
|
|
3025
2887
|
) {
|
|
3026
2888
|
throw artifactGraphError(
|
|
3027
2889
|
"artifact-graph-object-url-platform-unavailable",
|
|
3028
|
-
"
|
|
2890
|
+
"Artifact graph materialization requires native Blob URL creation and revocation.",
|
|
3029
2891
|
);
|
|
3030
2892
|
}
|
|
3031
2893
|
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
@@ -3033,20 +2895,11 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3033
2895
|
const created = [];
|
|
3034
2896
|
const createdIdentities = new Set();
|
|
3035
2897
|
const guardCapability = artifactGraphGuardCapability();
|
|
2898
|
+
const runtimeInspection = assertArtifactGraphRuntimeInspection(inspection);
|
|
3036
2899
|
async function materialize(descriptor, body) {
|
|
3037
2900
|
const blob = body instanceof Blob && body.type === descriptor.mediaType
|
|
3038
2901
|
? body
|
|
3039
2902
|
: new Blob([body], { type: descriptor.mediaType });
|
|
3040
|
-
const source = await blobDigest(blob);
|
|
3041
|
-
const transformed = ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind);
|
|
3042
|
-
const expected = Object.freeze({
|
|
3043
|
-
bytes: transformed || security.checks.byteLength !== true
|
|
3044
|
-
? source.bytes
|
|
3045
|
-
: descriptor.bytes,
|
|
3046
|
-
sha256: transformed || security.checks.sha256 !== true
|
|
3047
|
-
? source.sha256
|
|
3048
|
-
: descriptor.sha256,
|
|
3049
|
-
});
|
|
3050
2903
|
const moduleUrl = PLATFORM_CREATE_OBJECT_URL(blob);
|
|
3051
2904
|
if (typeof moduleUrl !== "string" || !moduleUrl.startsWith("blob:")) {
|
|
3052
2905
|
throw artifactGraphError(
|
|
@@ -3062,66 +2915,16 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3062
2915
|
}
|
|
3063
2916
|
createdIdentities.add(moduleUrl);
|
|
3064
2917
|
created.push(moduleUrl);
|
|
3065
|
-
|
|
3066
|
-
try {
|
|
3067
|
-
response = await PLATFORM_FETCH(moduleUrl, {
|
|
3068
|
-
method: "GET",
|
|
3069
|
-
credentials: "omit",
|
|
3070
|
-
redirect: "error",
|
|
3071
|
-
});
|
|
3072
|
-
} catch (error) {
|
|
3073
|
-
throw artifactGraphError(
|
|
3074
|
-
"artifact-graph-object-url-readback-unavailable",
|
|
3075
|
-
`Materialized artifact graph file ${descriptor.path} could not be read back from its Blob URL.`,
|
|
3076
|
-
error,
|
|
3077
|
-
);
|
|
3078
|
-
}
|
|
3079
|
-
if (!response.ok) {
|
|
3080
|
-
throw artifactGraphError(
|
|
3081
|
-
"artifact-graph-object-url-readback-http-status-rejected",
|
|
3082
|
-
`Materialized artifact graph file ${descriptor.path} returned a non-success Blob URL response.`,
|
|
3083
|
-
);
|
|
3084
|
-
}
|
|
3085
|
-
if (response.redirected || response.url !== moduleUrl) {
|
|
3086
|
-
throw artifactGraphError(
|
|
3087
|
-
"artifact-graph-object-url-readback-identity-mismatch",
|
|
3088
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact Blob URL identity.`,
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
const observedMediaType = response.headers.get("content-type")?.split(";", 1)[0].trim() ?? "";
|
|
3092
|
-
if (observedMediaType !== descriptor.mediaType) {
|
|
3093
|
-
throw artifactGraphError(
|
|
3094
|
-
"artifact-graph-object-url-media-type-mismatch",
|
|
3095
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its declared media type.`,
|
|
3096
|
-
);
|
|
3097
|
-
}
|
|
3098
|
-
const observedBlob = await response.blob();
|
|
3099
|
-
const observed = await blobDigest(observedBlob);
|
|
3100
|
-
if (observed.bytes !== expected.bytes) {
|
|
3101
|
-
throw artifactGraphError(
|
|
3102
|
-
"artifact-graph-object-url-byte-length-mismatch",
|
|
3103
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact byte length.`,
|
|
3104
|
-
);
|
|
3105
|
-
}
|
|
3106
|
-
if (observed.sha256 !== expected.sha256) {
|
|
3107
|
-
throw artifactGraphError(
|
|
3108
|
-
"artifact-graph-object-url-sha256-mismatch",
|
|
3109
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact bytes.`,
|
|
3110
|
-
);
|
|
3111
|
-
}
|
|
3112
|
-
materializedByPath.set(descriptor.path, Object.freeze({
|
|
2918
|
+
materializedByPath.set(descriptor.path, completeValue({
|
|
3113
2919
|
kind: descriptor.kind,
|
|
3114
2920
|
path: descriptor.path,
|
|
3115
2921
|
sourceUrl: descriptor.sourceUrl,
|
|
3116
2922
|
revision: descriptor.revision,
|
|
3117
|
-
license: descriptor.license,
|
|
3118
2923
|
moduleUrl,
|
|
3119
2924
|
mediaType: descriptor.mediaType,
|
|
3120
2925
|
...(descriptor.sourceMediaType === descriptor.mediaType
|
|
3121
2926
|
? {}
|
|
3122
2927
|
: { sourceMediaType: descriptor.sourceMediaType }),
|
|
3123
|
-
bytes: descriptor.bytes,
|
|
3124
|
-
sha256: descriptor.sha256,
|
|
3125
2928
|
runtimeRequestUrls: descriptor.runtimeRequestUrls,
|
|
3126
2929
|
...(descriptor.redirectFinalOrigins.length < 1
|
|
3127
2930
|
? {}
|
|
@@ -3131,20 +2934,42 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3131
2934
|
try {
|
|
3132
2935
|
for (const descriptor of metadata.files) {
|
|
3133
2936
|
if (ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind)) continue;
|
|
3134
|
-
|
|
2937
|
+
const admittedFile = admittedByPath.get(descriptor.path)?.file;
|
|
2938
|
+
if (!admittedFile) {
|
|
2939
|
+
throw artifactGraphError(
|
|
2940
|
+
"artifact-graph-materialized-source-file-missing",
|
|
2941
|
+
`Artifact graph file ${descriptor.path} is unavailable for materialization.`,
|
|
2942
|
+
);
|
|
2943
|
+
}
|
|
2944
|
+
await materialize(descriptor, admittedFile);
|
|
3135
2945
|
}
|
|
3136
|
-
for (const path of
|
|
2946
|
+
for (const path of runtimeInspection.order) {
|
|
3137
2947
|
const descriptor = metadata.filesByPath.get(path);
|
|
3138
|
-
const plan =
|
|
2948
|
+
const plan = runtimeInspection.plans.get(path);
|
|
2949
|
+
if (!descriptor || !plan) {
|
|
2950
|
+
throw artifactGraphError(
|
|
2951
|
+
"artifact-graph-runtime-inspection-incomplete",
|
|
2952
|
+
`Artifact graph runtime inspection is incomplete for ${path}.`,
|
|
2953
|
+
);
|
|
2954
|
+
}
|
|
3139
2955
|
await materialize(
|
|
3140
2956
|
descriptor,
|
|
3141
2957
|
applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapability),
|
|
3142
2958
|
);
|
|
3143
2959
|
}
|
|
3144
|
-
|
|
2960
|
+
const files = metadata.files.map((descriptor) => {
|
|
2961
|
+
const materialized = materializedByPath.get(descriptor.path);
|
|
2962
|
+
if (!materialized) {
|
|
2963
|
+
throw artifactGraphError(
|
|
2964
|
+
"artifact-graph-materialization-incomplete",
|
|
2965
|
+
`Artifact graph file ${descriptor.path} was not materialized.`,
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
return materialized;
|
|
2969
|
+
});
|
|
2970
|
+
return completeValue({
|
|
3145
2971
|
guardCapability,
|
|
3146
|
-
files:
|
|
3147
|
-
materializedByPath.get(descriptor.path))),
|
|
2972
|
+
files: completeValue(files),
|
|
3148
2973
|
release() {
|
|
3149
2974
|
for (const url of created.splice(0).reverse()) {
|
|
3150
2975
|
try {
|
|
@@ -3167,58 +2992,482 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3167
2992
|
}
|
|
3168
2993
|
}
|
|
3169
2994
|
|
|
3170
|
-
function
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
const source = cache === "installed"
|
|
3177
|
-
? "network-dbopfs"
|
|
3178
|
-
: offline
|
|
3179
|
-
? "offline-dbopfs-cache"
|
|
3180
|
-
: "dbopfs-cache";
|
|
3181
|
-
return `artifact-graph-${source}-${verification}`;
|
|
2995
|
+
function ordinaryArtifactUrl(value, base) {
|
|
2996
|
+
try {
|
|
2997
|
+
return new URL(value, base).href;
|
|
2998
|
+
} catch {
|
|
2999
|
+
return null;
|
|
3000
|
+
}
|
|
3182
3001
|
}
|
|
3183
3002
|
|
|
3184
|
-
function
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3003
|
+
function isBareModuleSpecifier(value) {
|
|
3004
|
+
return typeof value === "string"
|
|
3005
|
+
&& !value.startsWith("./")
|
|
3006
|
+
&& !value.startsWith("../")
|
|
3007
|
+
&& !value.startsWith("/")
|
|
3008
|
+
&& !/^[A-Za-z][A-Za-z\d+.-]*:/u.test(value);
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
function ordinaryArtifactRoutes(metadata) {
|
|
3012
|
+
const byUrl = new Map();
|
|
3013
|
+
const ambiguous = new Set();
|
|
3014
|
+
function add(value, descriptor) {
|
|
3015
|
+
const url = ordinaryArtifactUrl(value, descriptor.sourceUrl);
|
|
3016
|
+
if (!url || ambiguous.has(url)) return;
|
|
3017
|
+
const existing = byUrl.get(url);
|
|
3018
|
+
if (existing && existing.path !== descriptor.path) {
|
|
3019
|
+
byUrl.delete(url);
|
|
3020
|
+
ambiguous.add(url);
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
byUrl.set(url, descriptor);
|
|
3189
3024
|
}
|
|
3190
|
-
const
|
|
3025
|
+
for (const descriptor of metadata.files) {
|
|
3026
|
+
add(descriptor.sourceUrl, descriptor);
|
|
3027
|
+
for (const route of descriptor.runtimeRequestUrls ?? []) add(route, descriptor);
|
|
3028
|
+
}
|
|
3029
|
+
return completeValue({ byUrl, ambiguous });
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
function resolveOrdinaryArtifactRoute(routes, value, sourceDescriptor) {
|
|
3033
|
+
const url = ordinaryArtifactUrl(value, sourceDescriptor?.sourceUrl);
|
|
3034
|
+
return url && !routes.ambiguous.has(url)
|
|
3035
|
+
? completeValue({ descriptor: routes.byUrl.get(url) ?? null, url })
|
|
3036
|
+
: null;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
function scanOrdinaryModuleRouting(source, modulePath) {
|
|
3040
|
+
let tokens;
|
|
3191
3041
|
try {
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
mediaType: descriptor.mediaType,
|
|
3204
|
-
bytes: file.size,
|
|
3205
|
-
});
|
|
3206
|
-
});
|
|
3207
|
-
return Object.freeze({
|
|
3208
|
-
files: Object.freeze(materialized),
|
|
3209
|
-
release() {
|
|
3210
|
-
for (const url of created.splice(0).reverse()) {
|
|
3211
|
-
try {
|
|
3212
|
-
revoke(url);
|
|
3213
|
-
} catch {
|
|
3214
|
-
// Object URL revocation follows worker termination and is best effort.
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
},
|
|
3042
|
+
tokens = tokenizeArtifactGraphModule(source, modulePath);
|
|
3043
|
+
} catch {
|
|
3044
|
+
// Routing discovery is best effort. A scanner limitation must never become
|
|
3045
|
+
// an ordinary runtime admission gate; unchanged source uses native URLs.
|
|
3046
|
+
return completeValue({
|
|
3047
|
+
source,
|
|
3048
|
+
staticImports: completeValue([]),
|
|
3049
|
+
dynamicImports: completeValue([]),
|
|
3050
|
+
fetches: completeValue([]),
|
|
3051
|
+
moduleWorkers: completeValue([]),
|
|
3052
|
+
cacheOpens: completeValue([]),
|
|
3218
3053
|
});
|
|
3219
|
-
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3054
|
+
}
|
|
3055
|
+
const staticImports = [];
|
|
3056
|
+
const dynamicImports = [];
|
|
3057
|
+
const fetches = [];
|
|
3058
|
+
const moduleWorkers = [];
|
|
3059
|
+
const cacheOpens = [];
|
|
3060
|
+
const next = (index, offset = 1) => tokens[index + offset] ?? null;
|
|
3061
|
+
const previous = (index, offset = 1) => tokens[index - offset] ?? null;
|
|
3062
|
+
const shadowedGlobals = new Set();
|
|
3063
|
+
const routableGlobals = new Set(["fetch", "caches", "Worker"]);
|
|
3064
|
+
|
|
3065
|
+
function closingParenthesis(opening) {
|
|
3066
|
+
let depth = 0;
|
|
3067
|
+
for (let cursor = opening; cursor < tokens.length; cursor += 1) {
|
|
3068
|
+
if (tokens[cursor].value === "(") depth += 1;
|
|
3069
|
+
if (tokens[cursor].value !== ")") continue;
|
|
3070
|
+
depth -= 1;
|
|
3071
|
+
if (depth === 0) return cursor;
|
|
3072
|
+
}
|
|
3073
|
+
return -1;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
function markBindings(start, end) {
|
|
3077
|
+
for (let cursor = start; cursor < end; cursor += 1) {
|
|
3078
|
+
if (routableGlobals.has(tokens[cursor].value)) {
|
|
3079
|
+
shadowedGlobals.add(tokens[cursor].value);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
3085
|
+
const token = tokens[index];
|
|
3086
|
+
if (routableGlobals.has(token.value) && next(index)?.value === "=>") {
|
|
3087
|
+
shadowedGlobals.add(token.value);
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
if (token.value === "import") {
|
|
3091
|
+
for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
|
|
3092
|
+
const candidate = tokens[cursor];
|
|
3093
|
+
if (candidate.value === "from" || candidate.type === "string"
|
|
3094
|
+
|| candidate.value === ";") break;
|
|
3095
|
+
if (routableGlobals.has(candidate.value)) {
|
|
3096
|
+
shadowedGlobals.add(candidate.value);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
continue;
|
|
3100
|
+
}
|
|
3101
|
+
if (["function", "catch"].includes(token.value)) {
|
|
3102
|
+
let opening = index + 1;
|
|
3103
|
+
while (opening < tokens.length && tokens[opening].value !== "(") {
|
|
3104
|
+
if (routableGlobals.has(tokens[opening].value)) {
|
|
3105
|
+
shadowedGlobals.add(tokens[opening].value);
|
|
3106
|
+
}
|
|
3107
|
+
opening += 1;
|
|
3108
|
+
}
|
|
3109
|
+
const closing = opening < tokens.length ? closingParenthesis(opening) : -1;
|
|
3110
|
+
if (closing > opening) markBindings(opening + 1, closing);
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
if (token.value === "class" && routableGlobals.has(next(index)?.value)) {
|
|
3114
|
+
shadowedGlobals.add(next(index).value);
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
3117
|
+
if (!["const", "let", "var"].includes(token.value)) continue;
|
|
3118
|
+
let binding = true;
|
|
3119
|
+
let round = 0;
|
|
3120
|
+
let square = 0;
|
|
3121
|
+
let curly = 0;
|
|
3122
|
+
for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
|
|
3123
|
+
const candidate = tokens[cursor];
|
|
3124
|
+
const topLevel = round === 0 && square === 0 && curly === 0;
|
|
3125
|
+
if (topLevel && candidate.value === ";") break;
|
|
3126
|
+
if (topLevel && candidate.value === ",") {
|
|
3127
|
+
binding = true;
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
if (topLevel && ["=", "in", "of"].includes(candidate.value)) {
|
|
3131
|
+
binding = false;
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
if (binding && routableGlobals.has(candidate.value)) {
|
|
3135
|
+
shadowedGlobals.add(candidate.value);
|
|
3136
|
+
}
|
|
3137
|
+
if (candidate.value === "(") round += 1;
|
|
3138
|
+
else if (candidate.value === ")") round = Math.max(0, round - 1);
|
|
3139
|
+
else if (candidate.value === "[") square += 1;
|
|
3140
|
+
else if (candidate.value === "]") square = Math.max(0, square - 1);
|
|
3141
|
+
else if (candidate.value === "{") curly += 1;
|
|
3142
|
+
else if (candidate.value === "}") curly = Math.max(0, curly - 1);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
const controlParentheses = new Set(["catch", "for", "if", "switch", "while", "with"]);
|
|
3147
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
3148
|
+
if (tokens[index].value !== "(") continue;
|
|
3149
|
+
const closing = closingParenthesis(index);
|
|
3150
|
+
const nextToken = closing < 0 ? null : next(closing);
|
|
3151
|
+
const owner = previous(index)?.value;
|
|
3152
|
+
const callable = nextToken?.value === "=>"
|
|
3153
|
+
|| (nextToken?.value === "{" && !controlParentheses.has(owner));
|
|
3154
|
+
if (callable) markBindings(index + 1, closing);
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function directCall(target, index) {
|
|
3158
|
+
const opening = next(index);
|
|
3159
|
+
if (opening?.value !== "(") return false;
|
|
3160
|
+
let start = tokens[index].start;
|
|
3161
|
+
if (previous(index)?.value === ".") {
|
|
3162
|
+
if (!["globalThis", "self"].includes(previous(index, 2)?.value)) return false;
|
|
3163
|
+
start = previous(index, 2).start;
|
|
3164
|
+
}
|
|
3165
|
+
target.push(completeValue({ start, end: opening.end }));
|
|
3166
|
+
return true;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
function isExplicitGlobal(index) {
|
|
3170
|
+
return previous(index)?.value === "."
|
|
3171
|
+
&& ["globalThis", "self"].includes(previous(index, 2)?.value);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
3175
|
+
const token = tokens[index];
|
|
3176
|
+
if (token.type !== "identifier") continue;
|
|
3177
|
+
if (token.value === "import") {
|
|
3178
|
+
if (next(index)?.value === "." && next(index, 2)?.value === "meta") {
|
|
3179
|
+
index += 2;
|
|
3180
|
+
continue;
|
|
3181
|
+
}
|
|
3182
|
+
if (next(index)?.value === "(") {
|
|
3183
|
+
dynamicImports.push(completeValue({ start: token.start, end: next(index).end }));
|
|
3184
|
+
continue;
|
|
3185
|
+
}
|
|
3186
|
+
let specifier = next(index)?.type === "string" ? next(index) : null;
|
|
3187
|
+
if (!specifier) {
|
|
3188
|
+
for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
|
|
3189
|
+
if (tokens[cursor].value === ";") break;
|
|
3190
|
+
if (tokens[cursor].value === "from" && next(cursor)?.type === "string") {
|
|
3191
|
+
specifier = next(cursor);
|
|
3192
|
+
break;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
if (specifier) staticImports.push(completeValue({
|
|
3197
|
+
start: specifier.start,
|
|
3198
|
+
end: specifier.end,
|
|
3199
|
+
specifier: specifier.value,
|
|
3200
|
+
}));
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
if (token.value === "export" && ["*", "{"].includes(next(index)?.value)) {
|
|
3204
|
+
for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
|
|
3205
|
+
if (tokens[cursor].value === ";") break;
|
|
3206
|
+
if (tokens[cursor].value === "from" && next(cursor)?.type === "string") {
|
|
3207
|
+
const specifier = next(cursor);
|
|
3208
|
+
staticImports.push(completeValue({
|
|
3209
|
+
start: specifier.start,
|
|
3210
|
+
end: specifier.end,
|
|
3211
|
+
specifier: specifier.value,
|
|
3212
|
+
}));
|
|
3213
|
+
break;
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
3218
|
+
if (
|
|
3219
|
+
token.value === "fetch"
|
|
3220
|
+
&& (isExplicitGlobal(index) || !shadowedGlobals.has("fetch"))
|
|
3221
|
+
) {
|
|
3222
|
+
directCall(fetches, index);
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
if (
|
|
3226
|
+
token.value === "caches"
|
|
3227
|
+
&& (isExplicitGlobal(index) || !shadowedGlobals.has("caches"))
|
|
3228
|
+
&& next(index)?.value === "."
|
|
3229
|
+
&& next(index, 2)?.value === "open"
|
|
3230
|
+
&& next(index, 3)?.value === "("
|
|
3231
|
+
) {
|
|
3232
|
+
let start = token.start;
|
|
3233
|
+
if (previous(index)?.value === "."
|
|
3234
|
+
&& ["globalThis", "self"].includes(previous(index, 2)?.value)) {
|
|
3235
|
+
start = previous(index, 2).start;
|
|
3236
|
+
}
|
|
3237
|
+
cacheOpens.push(completeValue({ start, end: next(index, 3).end }));
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
if (
|
|
3241
|
+
token.value === "Worker"
|
|
3242
|
+
&& (isExplicitGlobal(index) || !shadowedGlobals.has("Worker"))
|
|
3243
|
+
&& next(index)?.value === "("
|
|
3244
|
+
) {
|
|
3245
|
+
let start = token.start;
|
|
3246
|
+
if (previous(index)?.value === "new") {
|
|
3247
|
+
start = previous(index).start;
|
|
3248
|
+
} else if (previous(index)?.value === ".") {
|
|
3249
|
+
if (!["globalThis", "self"].includes(previous(index, 2)?.value)) continue;
|
|
3250
|
+
start = previous(index, 2).start;
|
|
3251
|
+
if (previous(index, 3)?.value === "new") start = previous(index, 3).start;
|
|
3252
|
+
}
|
|
3253
|
+
moduleWorkers.push(completeValue({ start, end: next(index).end }));
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
return completeValue({
|
|
3257
|
+
source,
|
|
3258
|
+
staticImports: completeValue(staticImports),
|
|
3259
|
+
dynamicImports: completeValue(dynamicImports),
|
|
3260
|
+
fetches: completeValue(fetches),
|
|
3261
|
+
moduleWorkers: completeValue(moduleWorkers),
|
|
3262
|
+
cacheOpens: completeValue(cacheOpens),
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
async function planOrdinaryMaterializedRuntime(admitted, metadata, signal) {
|
|
3267
|
+
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
3268
|
+
const routes = ordinaryArtifactRoutes(metadata);
|
|
3269
|
+
const plans = new Map();
|
|
3270
|
+
for (const descriptor of metadata.runtimeFiles) {
|
|
3271
|
+
if (!ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind)) continue;
|
|
3272
|
+
throwIfAborted(signal);
|
|
3273
|
+
const file = admittedByPath.get(descriptor.path)?.file;
|
|
3274
|
+
if (!file) continue;
|
|
3275
|
+
const source = await file.text();
|
|
3276
|
+
plans.set(descriptor.path, scanOrdinaryModuleRouting(source, descriptor.path));
|
|
3277
|
+
}
|
|
3278
|
+
const visiting = new Set();
|
|
3279
|
+
const visited = new Set();
|
|
3280
|
+
const order = [];
|
|
3281
|
+
function visit(path) {
|
|
3282
|
+
if (visited.has(path) || visiting.has(path)) return;
|
|
3283
|
+
visiting.add(path);
|
|
3284
|
+
const descriptor = metadata.filesByPath.get(path);
|
|
3285
|
+
const plan = plans.get(path);
|
|
3286
|
+
for (const observed of plan?.staticImports ?? []) {
|
|
3287
|
+
if (isBareModuleSpecifier(observed.specifier)) continue;
|
|
3288
|
+
const target = resolveOrdinaryArtifactRoute(routes, observed.specifier, descriptor)?.descriptor;
|
|
3289
|
+
if (target && plans.has(target.path)) visit(target.path);
|
|
3290
|
+
}
|
|
3291
|
+
visiting.delete(path);
|
|
3292
|
+
visited.add(path);
|
|
3293
|
+
order.push(path);
|
|
3294
|
+
}
|
|
3295
|
+
for (const path of plans.keys()) visit(path);
|
|
3296
|
+
return completeValue({ order: completeValue(order), plans, routes });
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
function applyOrdinaryModuleRouting(plan, sourceDescriptor, materializedByPath, routes) {
|
|
3300
|
+
const replacements = [];
|
|
3301
|
+
for (const observed of plan.staticImports) {
|
|
3302
|
+
if (isBareModuleSpecifier(observed.specifier)) continue;
|
|
3303
|
+
const resolution = resolveOrdinaryArtifactRoute(routes, observed.specifier, sourceDescriptor);
|
|
3304
|
+
const mapped = resolution?.descriptor
|
|
3305
|
+
? materializedByPath.get(resolution.descriptor.path)?.moduleUrl
|
|
3306
|
+
: null;
|
|
3307
|
+
const target = mapped ?? resolution?.url;
|
|
3308
|
+
if (target) replacements.push({
|
|
3309
|
+
start: observed.start,
|
|
3310
|
+
end: observed.end,
|
|
3311
|
+
value: JSON.stringify(target),
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
for (const observed of plan.dynamicImports) replacements.push({
|
|
3315
|
+
start: observed.start,
|
|
3316
|
+
end: observed.end,
|
|
3317
|
+
value: `globalThis.${ARTIFACT_MODULE_ROUTER}.dynamicImport(${JSON.stringify(sourceDescriptor.path)},`,
|
|
3318
|
+
});
|
|
3319
|
+
for (const observed of plan.fetches) replacements.push({
|
|
3320
|
+
start: observed.start,
|
|
3321
|
+
end: observed.end,
|
|
3322
|
+
value: `globalThis.${ARTIFACT_MODULE_ROUTER}.fetch(${JSON.stringify(sourceDescriptor.path)},`,
|
|
3323
|
+
});
|
|
3324
|
+
for (const observed of plan.moduleWorkers) replacements.push({
|
|
3325
|
+
start: observed.start,
|
|
3326
|
+
end: observed.end,
|
|
3327
|
+
value: `globalThis.${ARTIFACT_MODULE_ROUTER}.createWorker(${JSON.stringify(sourceDescriptor.path)},`,
|
|
3328
|
+
});
|
|
3329
|
+
for (const observed of plan.cacheOpens) replacements.push({
|
|
3330
|
+
start: observed.start,
|
|
3331
|
+
end: observed.end,
|
|
3332
|
+
value: `globalThis.${ARTIFACT_MODULE_ROUTER}.openCache(${JSON.stringify(sourceDescriptor.path)},`,
|
|
3333
|
+
});
|
|
3334
|
+
replacements.sort((left, right) => right.start - left.start);
|
|
3335
|
+
let source = plan.source;
|
|
3336
|
+
let previousStart = source.length;
|
|
3337
|
+
for (const replacement of replacements) {
|
|
3338
|
+
if (replacement.end > previousStart) continue;
|
|
3339
|
+
source = `${source.slice(0, replacement.start)}${replacement.value}${source.slice(replacement.end)}`;
|
|
3340
|
+
previousStart = replacement.start;
|
|
3341
|
+
}
|
|
3342
|
+
return source;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
async function createOrdinaryArtifactObjectUrls(
|
|
3346
|
+
admitted,
|
|
3347
|
+
metadata,
|
|
3348
|
+
routing,
|
|
3349
|
+
objectUrlFactory,
|
|
3350
|
+
) {
|
|
3351
|
+
const create = objectUrlFactory?.create ?? PLATFORM_CREATE_OBJECT_URL;
|
|
3352
|
+
const revoke = objectUrlFactory?.revoke ?? PLATFORM_REVOKE_OBJECT_URL;
|
|
3353
|
+
if (typeof create !== "function" || typeof revoke !== "function") {
|
|
3354
|
+
throw artifactGraphError(
|
|
3355
|
+
"artifact-graph-object-url-platform-unavailable",
|
|
3356
|
+
"Artifact materialization requires native Blob URL creation and revocation.",
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
3360
|
+
const materializedByPath = new Map();
|
|
3361
|
+
const created = [];
|
|
3362
|
+
async function materialize(descriptor, body) {
|
|
3363
|
+
const blob = body instanceof Blob && body.type === descriptor.mediaType
|
|
3364
|
+
? body
|
|
3365
|
+
: new Blob([body], { type: descriptor.mediaType });
|
|
3366
|
+
const moduleUrl = create(blob);
|
|
3367
|
+
if (typeof moduleUrl !== "string") {
|
|
3368
|
+
throw artifactGraphError(
|
|
3369
|
+
"artifact-graph-object-url-platform-unavailable",
|
|
3370
|
+
`Artifact file ${descriptor.path} did not produce an object URL.`,
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
created.push(moduleUrl);
|
|
3374
|
+
materializedByPath.set(descriptor.path, completeValue({
|
|
3375
|
+
kind: descriptor.kind,
|
|
3376
|
+
path: descriptor.path,
|
|
3377
|
+
sourceUrl: descriptor.sourceUrl,
|
|
3378
|
+
revision: descriptor.revision,
|
|
3379
|
+
moduleUrl,
|
|
3380
|
+
mediaType: descriptor.mediaType,
|
|
3381
|
+
...(descriptor.sourceMediaType === descriptor.mediaType
|
|
3382
|
+
? {}
|
|
3383
|
+
: { sourceMediaType: descriptor.sourceMediaType }),
|
|
3384
|
+
runtimeRequestUrls: descriptor.runtimeRequestUrls,
|
|
3385
|
+
...(descriptor.redirectFinalOrigins.length < 1
|
|
3386
|
+
? {}
|
|
3387
|
+
: { redirectFinalOrigins: descriptor.redirectFinalOrigins }),
|
|
3388
|
+
}));
|
|
3389
|
+
}
|
|
3390
|
+
try {
|
|
3391
|
+
for (const descriptor of metadata.files) {
|
|
3392
|
+
if (ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind)) continue;
|
|
3393
|
+
const file = admittedByPath.get(descriptor.path)?.file;
|
|
3394
|
+
if (file) await materialize(descriptor, file);
|
|
3395
|
+
}
|
|
3396
|
+
for (const path of routing.order) {
|
|
3397
|
+
const descriptor = metadata.filesByPath.get(path);
|
|
3398
|
+
const plan = routing.plans.get(path);
|
|
3399
|
+
if (!descriptor || !plan) continue;
|
|
3400
|
+
await materialize(
|
|
3401
|
+
descriptor,
|
|
3402
|
+
applyOrdinaryModuleRouting(plan, descriptor, materializedByPath, routing.routes),
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
for (const descriptor of metadata.files) {
|
|
3406
|
+
if (materializedByPath.has(descriptor.path)) continue;
|
|
3407
|
+
const file = admittedByPath.get(descriptor.path)?.file;
|
|
3408
|
+
if (file) await materialize(descriptor, file);
|
|
3409
|
+
}
|
|
3410
|
+
return completeValue({
|
|
3411
|
+
files: completeValue(metadata.files.map((descriptor) => materializedByPath.get(descriptor.path))),
|
|
3412
|
+
release() {
|
|
3413
|
+
for (const url of created.splice(0).reverse()) {
|
|
3414
|
+
try {
|
|
3415
|
+
revoke(url);
|
|
3416
|
+
} catch {
|
|
3417
|
+
// Object URL revocation follows Worker termination and is best effort.
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
},
|
|
3421
|
+
});
|
|
3422
|
+
} catch (error) {
|
|
3423
|
+
for (const url of created.splice(0).reverse()) {
|
|
3424
|
+
try {
|
|
3425
|
+
revoke(url);
|
|
3426
|
+
} catch {
|
|
3427
|
+
// Preserve the functional materialization failure.
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
throw error;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
function createObjectUrls(files, factory) {
|
|
3435
|
+
const create = factory?.create ?? ((blob) => URL.createObjectURL(blob));
|
|
3436
|
+
const revoke = factory?.revoke ?? ((url) => URL.revokeObjectURL(url));
|
|
3437
|
+
if (typeof create !== "function" || typeof revoke !== "function") {
|
|
3438
|
+
throw new TypeError("Browser speech objectUrlFactory requires create() and revoke().");
|
|
3439
|
+
}
|
|
3440
|
+
const created = [];
|
|
3441
|
+
try {
|
|
3442
|
+
const materialized = files.map(({ descriptor, file }) => {
|
|
3443
|
+
const blob = file.type === descriptor.mediaType
|
|
3444
|
+
? file
|
|
3445
|
+
: new Blob([file], { type: descriptor.mediaType });
|
|
3446
|
+
const url = create(blob);
|
|
3447
|
+
created.push(url);
|
|
3448
|
+
return completeValue({
|
|
3449
|
+
kind: descriptor.kind,
|
|
3450
|
+
path: descriptor.path,
|
|
3451
|
+
sourceUrl: descriptor.url,
|
|
3452
|
+
moduleUrl: url,
|
|
3453
|
+
mediaType: descriptor.mediaType,
|
|
3454
|
+
});
|
|
3455
|
+
});
|
|
3456
|
+
return completeValue({
|
|
3457
|
+
files: completeValue(materialized),
|
|
3458
|
+
release() {
|
|
3459
|
+
for (const url of created.splice(0).reverse()) {
|
|
3460
|
+
try {
|
|
3461
|
+
revoke(url);
|
|
3462
|
+
} catch {
|
|
3463
|
+
// Object URL revocation follows worker termination and is best effort.
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
},
|
|
3467
|
+
});
|
|
3468
|
+
} catch (error) {
|
|
3469
|
+
for (const url of created.splice(0).reverse()) {
|
|
3470
|
+
try {
|
|
3222
3471
|
revoke(url);
|
|
3223
3472
|
} catch {
|
|
3224
3473
|
// Preserve the materialization error.
|
|
@@ -3229,8 +3478,8 @@ function createObjectUrls(files, factory) {
|
|
|
3229
3478
|
}
|
|
3230
3479
|
|
|
3231
3480
|
/**
|
|
3232
|
-
* Stores an authority's
|
|
3233
|
-
*
|
|
3481
|
+
* Stores an authority's runtime/model files in an existing DBOPFS table.
|
|
3482
|
+
* Ordinary cache reuse creates no receipt, manifest, or byte identity.
|
|
3234
3483
|
*/
|
|
3235
3484
|
export function createDbopfsSpeechArtifactStore({
|
|
3236
3485
|
dbopfs,
|
|
@@ -3307,20 +3556,27 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3307
3556
|
}
|
|
3308
3557
|
}
|
|
3309
3558
|
|
|
3310
|
-
async function
|
|
3559
|
+
async function readJsonFile(name) {
|
|
3560
|
+
const file = await readFile(name);
|
|
3561
|
+
if (!file) return null;
|
|
3562
|
+
try {
|
|
3563
|
+
return JSON.parse(await file.text());
|
|
3564
|
+
} catch {
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
async function writeFile(name, body, { signal } = {}) {
|
|
3311
3570
|
const directory = await table();
|
|
3312
3571
|
const handle = await directory.getFileHandle(name, { create: true });
|
|
3313
3572
|
const writable = await handle.createWritable();
|
|
3314
|
-
let written = 0;
|
|
3315
3573
|
try {
|
|
3316
3574
|
for await (const chunk of byteChunks(body, signal)) {
|
|
3317
3575
|
await writable.write(chunk);
|
|
3318
|
-
written += chunk.byteLength;
|
|
3319
|
-
onChunk?.(chunk, written);
|
|
3320
3576
|
}
|
|
3321
3577
|
throwIfAborted(signal);
|
|
3322
3578
|
await writable.close();
|
|
3323
|
-
return
|
|
3579
|
+
return undefined;
|
|
3324
3580
|
} catch (error) {
|
|
3325
3581
|
await writable.abort?.(error).catch(() => undefined);
|
|
3326
3582
|
await directory.removeEntry(name).catch(() => undefined);
|
|
@@ -3334,40 +3590,24 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3334
3590
|
}
|
|
3335
3591
|
const metadata = artifactMetadata(authority);
|
|
3336
3592
|
const names = storageNames(authority, metadata.files);
|
|
3593
|
+
const priorSelection = await readJsonFile(names.selection);
|
|
3594
|
+
const legacySelection = await readJsonFile(names.legacyManifest);
|
|
3595
|
+
const priorCount = Math.max(
|
|
3596
|
+
Array.isArray(priorSelection?.files) ? priorSelection.files.length : 0,
|
|
3597
|
+
Array.isArray(legacySelection?.files) ? legacySelection.files.length : 0,
|
|
3598
|
+
);
|
|
3599
|
+
const fileNames = Array.from(
|
|
3600
|
+
{ length: Math.max(names.files.length, priorCount) },
|
|
3601
|
+
(_, index) => `${names.key}.${String(index).padStart(4, "0")}.artifact`,
|
|
3602
|
+
);
|
|
3337
3603
|
const results = await Promise.all([
|
|
3338
|
-
removeEntry(names.
|
|
3339
|
-
|
|
3604
|
+
removeEntry(names.selection),
|
|
3605
|
+
removeEntry(names.legacyManifest),
|
|
3606
|
+
...fileNames.map(removeEntry),
|
|
3340
3607
|
]);
|
|
3341
3608
|
return results.some(Boolean);
|
|
3342
3609
|
}
|
|
3343
3610
|
|
|
3344
|
-
async function readManifest(name) {
|
|
3345
|
-
const file = await readFile(name);
|
|
3346
|
-
if (!file) return null;
|
|
3347
|
-
try {
|
|
3348
|
-
return JSON.parse(await file.text());
|
|
3349
|
-
} catch {
|
|
3350
|
-
await removeEntry(name);
|
|
3351
|
-
return null;
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
|
-
async function verifyFile(file, descriptor, security, signal, onProgress, phase) {
|
|
3356
|
-
if (security.checks.byteLength && file.size !== descriptor.bytes) return false;
|
|
3357
|
-
if (!security.checks.sha256) {
|
|
3358
|
-
onProgress?.(providerProgress(phase, file.size, file.size));
|
|
3359
|
-
return true;
|
|
3360
|
-
}
|
|
3361
|
-
const digest = createStreamingSha256();
|
|
3362
|
-
let completed = 0;
|
|
3363
|
-
for await (const chunk of byteChunks(file.stream(), signal)) {
|
|
3364
|
-
digest.update(chunk);
|
|
3365
|
-
completed += chunk.byteLength;
|
|
3366
|
-
onProgress?.(providerProgress(phase, completed, file.size));
|
|
3367
|
-
}
|
|
3368
|
-
return completed === file.size && digest.digestHex() === descriptor.sha256;
|
|
3369
|
-
}
|
|
3370
|
-
|
|
3371
3611
|
function graphFileReason(descriptor, boundary) {
|
|
3372
3612
|
const subject = descriptor.kind === "runtime-entrypoint-javascript"
|
|
3373
3613
|
? "entrypoint"
|
|
@@ -3379,27 +3619,12 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3379
3619
|
return artifactGraphError(graphFileReason(descriptor, boundary), message);
|
|
3380
3620
|
}
|
|
3381
3621
|
|
|
3382
|
-
function
|
|
3383
|
-
for (const descriptor of files) {
|
|
3384
|
-
if (security.checks.byteLength && descriptor.bytes === null) {
|
|
3385
|
-
throw new TypeError(
|
|
3386
|
-
`${descriptor.kind} file ${descriptor.path} requires bytes under the effective security policy.`,
|
|
3387
|
-
);
|
|
3388
|
-
}
|
|
3389
|
-
if (security.checks.sha256 && descriptor.sha256 === null) {
|
|
3390
|
-
throw new TypeError(
|
|
3391
|
-
`${descriptor.kind} file ${descriptor.path} requires sha256 under the effective security policy.`,
|
|
3392
|
-
);
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
|
|
3397
|
-
async function openCached(authority, { signal, onProgress, security } = {}) {
|
|
3622
|
+
async function openCached(authority, { signal, onProgress } = {}) {
|
|
3398
3623
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3399
3624
|
const metadata = artifactMetadata(authority);
|
|
3400
3625
|
const names = storageNames(authority, metadata.files);
|
|
3401
|
-
const
|
|
3402
|
-
if (
|
|
3626
|
+
const storedSelection = await readJsonFile(names.selection);
|
|
3627
|
+
if (JSON.stringify(storedSelection) !== JSON.stringify(cacheSelection(authority))) {
|
|
3403
3628
|
await removeUnlocked(authority);
|
|
3404
3629
|
return null;
|
|
3405
3630
|
}
|
|
@@ -3408,44 +3633,21 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3408
3633
|
throwIfAborted(signal);
|
|
3409
3634
|
const descriptor = metadata.files[index];
|
|
3410
3635
|
const file = await readFile(names.files[index]);
|
|
3411
|
-
|
|
3412
|
-
if (
|
|
3413
|
-
!file
|
|
3414
|
-
|| observed?.path !== descriptor.path
|
|
3415
|
-
|| observed?.bytes !== file.size
|
|
3416
|
-
|| (graph && observed?.sha256 !== descriptor.sha256)
|
|
3417
|
-
) {
|
|
3418
|
-
await removeUnlocked(authority);
|
|
3419
|
-
return null;
|
|
3420
|
-
}
|
|
3421
|
-
if (!await verifyFile(
|
|
3422
|
-
file,
|
|
3423
|
-
descriptor,
|
|
3424
|
-
security,
|
|
3425
|
-
signal,
|
|
3426
|
-
onProgress,
|
|
3427
|
-
graph
|
|
3428
|
-
? security.checks.sha256
|
|
3429
|
-
? "artifact-graph-dbopfs-cache-rehash"
|
|
3430
|
-
: "artifact-graph-dbopfs-cache-readback"
|
|
3431
|
-
: "verify-cache",
|
|
3432
|
-
)) {
|
|
3636
|
+
if (!file) {
|
|
3433
3637
|
await removeUnlocked(authority);
|
|
3434
3638
|
return null;
|
|
3435
3639
|
}
|
|
3436
3640
|
files.push({ descriptor, file });
|
|
3437
3641
|
}
|
|
3438
3642
|
try {
|
|
3439
|
-
const
|
|
3440
|
-
? await
|
|
3441
|
-
:
|
|
3442
|
-
? null
|
|
3443
|
-
: (await assertSelfContainedRuntime({ files }, metadata, security), null);
|
|
3643
|
+
const routing = graph
|
|
3644
|
+
? await planOrdinaryMaterializedRuntime({ files }, metadata, signal)
|
|
3645
|
+
: null;
|
|
3444
3646
|
throwIfAborted(signal);
|
|
3445
|
-
return
|
|
3446
|
-
files:
|
|
3647
|
+
return completeValue({
|
|
3648
|
+
files: completeValue(files),
|
|
3447
3649
|
cache: "cached",
|
|
3448
|
-
|
|
3650
|
+
routing,
|
|
3449
3651
|
});
|
|
3450
3652
|
} catch (error) {
|
|
3451
3653
|
await removeUnlocked(authority);
|
|
@@ -3453,7 +3655,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3453
3655
|
}
|
|
3454
3656
|
}
|
|
3455
3657
|
|
|
3456
|
-
async function install(authority, { signal, onProgress
|
|
3658
|
+
async function install(authority, { signal, onProgress } = {}) {
|
|
3457
3659
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3458
3660
|
const metadata = artifactMetadata(authority);
|
|
3459
3661
|
const names = storageNames(authority, metadata.files);
|
|
@@ -3464,21 +3666,22 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3464
3666
|
await removeUnlocked(authority);
|
|
3465
3667
|
const installed = [];
|
|
3466
3668
|
try {
|
|
3669
|
+
// This mutable cache selection is written before content. It is only an
|
|
3670
|
+
// invalidation record, never a completion, byte-identity, or integrity receipt.
|
|
3671
|
+
await writeFile(
|
|
3672
|
+
names.selection,
|
|
3673
|
+
new TextEncoder().encode(`${JSON.stringify(cacheSelection(authority))}\n`),
|
|
3674
|
+
{ signal },
|
|
3675
|
+
);
|
|
3467
3676
|
for (let index = 0; index < metadata.files.length; index += 1) {
|
|
3468
3677
|
throwIfAborted(signal);
|
|
3469
3678
|
const descriptor = metadata.files[index];
|
|
3470
3679
|
const sourceUrl = graph ? descriptor.sourceUrl : descriptor.url;
|
|
3471
|
-
const redirectFinalOrigins = graph
|
|
3472
|
-
? descriptor.redirectFinalOrigins
|
|
3473
|
-
: Object.freeze([]);
|
|
3474
3680
|
let response;
|
|
3475
3681
|
try {
|
|
3476
3682
|
response = await fetchFunction(sourceUrl, {
|
|
3477
3683
|
cache: "no-store",
|
|
3478
|
-
|
|
3479
|
-
mode: "cors",
|
|
3480
|
-
redirect: redirectFinalOrigins.length < 1 ? "error" : "follow",
|
|
3481
|
-
referrerPolicy: "no-referrer",
|
|
3684
|
+
redirect: "follow",
|
|
3482
3685
|
signal,
|
|
3483
3686
|
});
|
|
3484
3687
|
} catch (error) {
|
|
@@ -3493,28 +3696,18 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3493
3696
|
throw speechError("ARCANE_AI_ARTIFACT_DOWNLOAD_FAILED", "A speech artifact source fetch was rejected.", error);
|
|
3494
3697
|
}
|
|
3495
3698
|
let responseBody;
|
|
3496
|
-
let responseHeaders;
|
|
3497
3699
|
let responseOk;
|
|
3498
|
-
let responseRedirected;
|
|
3499
3700
|
let responseStatus;
|
|
3500
|
-
let responseUrl;
|
|
3501
3701
|
try {
|
|
3502
3702
|
if (!response || typeof response !== "object") {
|
|
3503
3703
|
throw new TypeError("The artifact fetch result is not an object.");
|
|
3504
3704
|
}
|
|
3505
3705
|
responseBody = response.body;
|
|
3506
|
-
responseHeaders = response.headers;
|
|
3507
3706
|
responseOk = response.ok;
|
|
3508
|
-
responseRedirected = response.redirected;
|
|
3509
3707
|
responseStatus = response.status;
|
|
3510
|
-
responseUrl = response.url;
|
|
3511
3708
|
if (
|
|
3512
3709
|
typeof responseOk !== "boolean"
|
|
3513
|
-
|| typeof responseRedirected !== "boolean"
|
|
3514
3710
|
|| !Number.isInteger(responseStatus)
|
|
3515
|
-
|| typeof responseUrl !== "string"
|
|
3516
|
-
|| !responseHeaders
|
|
3517
|
-
|| typeof responseHeaders.get !== "function"
|
|
3518
3711
|
|| (responseBody !== null && typeof responseBody?.getReader !== "function")
|
|
3519
3712
|
) {
|
|
3520
3713
|
throw new TypeError("The artifact fetch result is not a readable Fetch Response.");
|
|
@@ -3533,81 +3726,6 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3533
3726
|
error,
|
|
3534
3727
|
);
|
|
3535
3728
|
}
|
|
3536
|
-
let finalUrl = null;
|
|
3537
|
-
let finalUrlRecord = null;
|
|
3538
|
-
try {
|
|
3539
|
-
finalUrlRecord = responseUrl
|
|
3540
|
-
? new URL(responseUrl)
|
|
3541
|
-
: null;
|
|
3542
|
-
finalUrl = finalUrlRecord?.href ?? null;
|
|
3543
|
-
} catch {
|
|
3544
|
-
finalUrlRecord = null;
|
|
3545
|
-
finalUrl = null;
|
|
3546
|
-
}
|
|
3547
|
-
if (graph && responseRedirected && !finalUrlRecord) {
|
|
3548
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3549
|
-
throw artifactGraphError(
|
|
3550
|
-
"artifact-graph-source-response-url-unreadable",
|
|
3551
|
-
`Artifact graph redirected source response for ${descriptor.path} did not expose a readable final URL.`,
|
|
3552
|
-
);
|
|
3553
|
-
}
|
|
3554
|
-
if (graph && responseRedirected && redirectFinalOrigins.length < 1) {
|
|
3555
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3556
|
-
throw artifactGraphError(
|
|
3557
|
-
"artifact-graph-source-redirected",
|
|
3558
|
-
`Artifact graph source response for ${descriptor.path} did not match its immutable URL.`,
|
|
3559
|
-
);
|
|
3560
|
-
}
|
|
3561
|
-
if (graph && responseRedirected && finalUrlRecord.protocol !== "https:") {
|
|
3562
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3563
|
-
throw artifactGraphError(
|
|
3564
|
-
"artifact-graph-source-response-url-protocol-not-https",
|
|
3565
|
-
`Artifact graph redirected source response for ${descriptor.path} did not end at HTTPS.`,
|
|
3566
|
-
);
|
|
3567
|
-
}
|
|
3568
|
-
if (
|
|
3569
|
-
graph
|
|
3570
|
-
&& responseRedirected
|
|
3571
|
-
&& (finalUrlRecord.username || finalUrlRecord.password)
|
|
3572
|
-
) {
|
|
3573
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3574
|
-
throw artifactGraphError(
|
|
3575
|
-
"artifact-graph-source-response-url-credentials-rejected",
|
|
3576
|
-
`Artifact graph redirected source response for ${descriptor.path} exposed credentials in its final URL.`,
|
|
3577
|
-
);
|
|
3578
|
-
}
|
|
3579
|
-
if (graph && responseRedirected && finalUrlRecord.hash) {
|
|
3580
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3581
|
-
throw artifactGraphError(
|
|
3582
|
-
"artifact-graph-source-response-url-fragment-rejected",
|
|
3583
|
-
`Artifact graph redirected source response for ${descriptor.path} exposed a fragment in its final URL.`,
|
|
3584
|
-
);
|
|
3585
|
-
}
|
|
3586
|
-
if (
|
|
3587
|
-
graph
|
|
3588
|
-
&& responseRedirected
|
|
3589
|
-
&& !redirectFinalOrigins.includes(finalUrlRecord.origin)
|
|
3590
|
-
) {
|
|
3591
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3592
|
-
throw artifactGraphError(
|
|
3593
|
-
"artifact-graph-source-redirect-final-origin-mismatch",
|
|
3594
|
-
`Artifact graph redirected source response for ${descriptor.path} ended at an undeclared final origin.`,
|
|
3595
|
-
);
|
|
3596
|
-
}
|
|
3597
|
-
if (graph && !responseRedirected && finalUrl !== sourceUrl) {
|
|
3598
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3599
|
-
throw artifactGraphError(
|
|
3600
|
-
"artifact-graph-source-response-url-mismatch",
|
|
3601
|
-
`Artifact graph non-redirected source response for ${descriptor.path} did not retain its immutable URL.`,
|
|
3602
|
-
);
|
|
3603
|
-
}
|
|
3604
|
-
if (!graph && (responseRedirected || finalUrl !== sourceUrl)) {
|
|
3605
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3606
|
-
throw speechError(
|
|
3607
|
-
"ARCANE_AI_ARTIFACT_SOURCE_CHANGED",
|
|
3608
|
-
"A speech artifact response did not match its admitted URL.",
|
|
3609
|
-
);
|
|
3610
|
-
}
|
|
3611
3729
|
if (!responseOk || !responseBody) {
|
|
3612
3730
|
await responseBody?.cancel?.().catch(() => undefined);
|
|
3613
3731
|
if (graph) {
|
|
@@ -3621,142 +3739,28 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3621
3739
|
`A speech artifact server returned HTTP ${String(responseStatus)}.`,
|
|
3622
3740
|
);
|
|
3623
3741
|
}
|
|
3624
|
-
|
|
3625
|
-
const reportedBytes = header ? Number(header) : null;
|
|
3626
|
-
const contentEncoding = responseHeaders.get("content-encoding")?.trim() ?? "";
|
|
3627
|
-
if (graph) {
|
|
3628
|
-
const reportedMediaType = responseHeaders.get("content-type")
|
|
3629
|
-
?.split(";", 1)[0]
|
|
3630
|
-
?.trim()
|
|
3631
|
-
?.toLowerCase() ?? null;
|
|
3632
|
-
if (reportedMediaType !== descriptor.sourceMediaType) {
|
|
3633
|
-
await responseBody.cancel?.().catch(() => undefined);
|
|
3634
|
-
throw graphVerificationError(
|
|
3635
|
-
descriptor,
|
|
3636
|
-
descriptor.sourceMediaType === descriptor.mediaType
|
|
3637
|
-
? "media-type-mismatch"
|
|
3638
|
-
: "source-media-type-mismatch",
|
|
3639
|
-
`Artifact graph source media type for ${descriptor.path} did not match ${descriptor.sourceMediaType}.`,
|
|
3640
|
-
);
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3643
|
-
if (
|
|
3644
|
-
security.checks.byteLength
|
|
3645
|
-
&& Number.isSafeInteger(reportedBytes)
|
|
3646
|
-
&& (!graph || !contentEncoding)
|
|
3647
|
-
&& reportedBytes !== descriptor.bytes
|
|
3648
|
-
) {
|
|
3649
|
-
await responseBody.cancel?.().catch(() => undefined);
|
|
3650
|
-
if (graph) {
|
|
3651
|
-
throw graphVerificationError(
|
|
3652
|
-
descriptor,
|
|
3653
|
-
"byte-length-mismatch",
|
|
3654
|
-
`Artifact graph source Content-Length for ${descriptor.path} changed.`,
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "Speech artifact Content-Length changed.");
|
|
3658
|
-
}
|
|
3659
|
-
const digest = security.checks.sha256
|
|
3660
|
-
? createStreamingSha256()
|
|
3661
|
-
: null;
|
|
3662
|
-
const written = await writeFile(names.files[index], responseBody, {
|
|
3663
|
-
signal,
|
|
3664
|
-
onChunk(chunk, completed) {
|
|
3665
|
-
digest?.update(chunk);
|
|
3666
|
-
if (security.checks.byteLength && completed > descriptor.bytes) {
|
|
3667
|
-
if (graph) {
|
|
3668
|
-
throw graphVerificationError(
|
|
3669
|
-
descriptor,
|
|
3670
|
-
"byte-length-mismatch",
|
|
3671
|
-
`Artifact graph source ${descriptor.path} exceeded its declared byte length.`,
|
|
3672
|
-
);
|
|
3673
|
-
}
|
|
3674
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "A speech artifact exceeded its expected size.");
|
|
3675
|
-
}
|
|
3676
|
-
onProgress?.(providerProgress(
|
|
3677
|
-
graph ? "artifact-graph-network-download" : "download",
|
|
3678
|
-
completed,
|
|
3679
|
-
security.checks.byteLength ? descriptor.bytes : null,
|
|
3680
|
-
));
|
|
3681
|
-
},
|
|
3682
|
-
});
|
|
3683
|
-
if (security.checks.byteLength && written !== descriptor.bytes) {
|
|
3684
|
-
if (graph) {
|
|
3685
|
-
throw graphVerificationError(
|
|
3686
|
-
descriptor,
|
|
3687
|
-
"byte-length-mismatch",
|
|
3688
|
-
`Artifact graph source ${descriptor.path} byte length changed.`,
|
|
3689
|
-
);
|
|
3690
|
-
}
|
|
3691
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "A speech artifact byte count changed.");
|
|
3692
|
-
}
|
|
3693
|
-
if (digest && digest.digestHex() !== descriptor.sha256) {
|
|
3694
|
-
if (graph) {
|
|
3695
|
-
throw graphVerificationError(
|
|
3696
|
-
descriptor,
|
|
3697
|
-
"sha256-mismatch",
|
|
3698
|
-
`Artifact graph source ${descriptor.path} SHA-256 changed.`,
|
|
3699
|
-
);
|
|
3700
|
-
}
|
|
3701
|
-
throw speechError("ARCANE_AI_ARTIFACT_DIGEST_MISMATCH", "A speech artifact SHA-256 changed.");
|
|
3702
|
-
}
|
|
3742
|
+
await writeFile(names.files[index], responseBody, { signal });
|
|
3703
3743
|
const file = await readFile(names.files[index]);
|
|
3704
|
-
if (!file
|
|
3744
|
+
if (!file) {
|
|
3705
3745
|
if (graph) {
|
|
3706
3746
|
throw graphVerificationError(
|
|
3707
3747
|
descriptor,
|
|
3708
|
-
"dbopfs-persisted-
|
|
3748
|
+
"dbopfs-persisted-file-missing",
|
|
3709
3749
|
`DBOPFS did not preserve artifact graph file ${descriptor.path}.`,
|
|
3710
3750
|
);
|
|
3711
3751
|
}
|
|
3712
3752
|
throw speechError("ARCANE_AI_ARTIFACT_CACHE_REJECTED", "DBOPFS did not preserve a speech artifact.");
|
|
3713
3753
|
}
|
|
3714
|
-
if (!await verifyFile(
|
|
3715
|
-
file,
|
|
3716
|
-
descriptor,
|
|
3717
|
-
security,
|
|
3718
|
-
signal,
|
|
3719
|
-
onProgress,
|
|
3720
|
-
graph
|
|
3721
|
-
? security.checks.sha256
|
|
3722
|
-
? "artifact-graph-dbopfs-persisted-rehash"
|
|
3723
|
-
: "artifact-graph-dbopfs-persisted-readback"
|
|
3724
|
-
: "verify-cache",
|
|
3725
|
-
)) {
|
|
3726
|
-
if (graph) {
|
|
3727
|
-
throw graphVerificationError(
|
|
3728
|
-
descriptor,
|
|
3729
|
-
"dbopfs-persisted-sha256-mismatch",
|
|
3730
|
-
`DBOPFS persisted bytes for artifact graph file ${descriptor.path} were rejected during re-verification.`,
|
|
3731
|
-
);
|
|
3732
|
-
}
|
|
3733
|
-
throw speechError("ARCANE_AI_ARTIFACT_CACHE_REJECTED", "DBOPFS persisted bytes were rejected during verification.");
|
|
3734
|
-
}
|
|
3735
3754
|
installed.push({ descriptor, file });
|
|
3736
3755
|
}
|
|
3737
|
-
const
|
|
3738
|
-
? await
|
|
3739
|
-
:
|
|
3740
|
-
? null
|
|
3741
|
-
: (await assertSelfContainedRuntime({ files: installed }, metadata, security), null);
|
|
3756
|
+
const routing = graph
|
|
3757
|
+
? await planOrdinaryMaterializedRuntime({ files: installed }, metadata, signal)
|
|
3758
|
+
: null;
|
|
3742
3759
|
throwIfAborted(signal);
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
complete: true,
|
|
3746
|
-
authority: storedArtifactProjection(authority),
|
|
3747
|
-
files: Object.freeze(installed.map(({ descriptor, file }) => Object.freeze({
|
|
3748
|
-
path: descriptor.path,
|
|
3749
|
-
bytes: file.size,
|
|
3750
|
-
...(graph ? { sha256: descriptor.sha256 } : {}),
|
|
3751
|
-
}))),
|
|
3752
|
-
completedAt: new Date().toISOString(),
|
|
3753
|
-
});
|
|
3754
|
-
const encoded = new TextEncoder().encode(`${JSON.stringify(manifest)}\n`);
|
|
3755
|
-
await writeFile(names.manifest, encoded, { signal });
|
|
3756
|
-
return Object.freeze({
|
|
3757
|
-
files: Object.freeze(installed),
|
|
3760
|
+
return completeValue({
|
|
3761
|
+
files: completeValue(installed),
|
|
3758
3762
|
cache: "installed",
|
|
3759
|
-
|
|
3763
|
+
routing,
|
|
3760
3764
|
});
|
|
3761
3765
|
} catch (error) {
|
|
3762
3766
|
await removeUnlocked(authority).catch(() => undefined);
|
|
@@ -3775,79 +3779,52 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3775
3779
|
}
|
|
3776
3780
|
throwIfAborted(signal);
|
|
3777
3781
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
} catch (error) {
|
|
3782
|
-
if (graph) {
|
|
3783
|
-
throw artifactGraphTypeError(
|
|
3784
|
-
"artifact-graph-load-security-contract-rejected",
|
|
3785
|
-
"Browser speech artifact graph load security does not satisfy the required contract.",
|
|
3786
|
-
error,
|
|
3787
|
-
);
|
|
3788
|
-
}
|
|
3789
|
-
throw error;
|
|
3790
|
-
}
|
|
3782
|
+
void security;
|
|
3783
|
+
// Security is an intent-only seam. Artifact checks remain disabled until
|
|
3784
|
+
// secure mode is explicitly reviewed with the user and implemented.
|
|
3791
3785
|
const metadata = artifactMetadata(authority);
|
|
3792
|
-
if (graph && effectiveSecurity.secure !== true) {
|
|
3793
|
-
throw artifactGraphError(
|
|
3794
|
-
"artifact-graph-secure-mode-required",
|
|
3795
|
-
"Browser speech artifact graphs require explicit secure:true.",
|
|
3796
|
-
);
|
|
3797
|
-
}
|
|
3798
|
-
assertSecurityDescriptors(metadata.files, effectiveSecurity);
|
|
3799
3786
|
const cached = await openCached(authority, {
|
|
3800
3787
|
signal,
|
|
3801
3788
|
onProgress,
|
|
3802
|
-
security: effectiveSecurity,
|
|
3803
3789
|
});
|
|
3804
3790
|
const admitted = cached ?? (offline
|
|
3805
3791
|
? null
|
|
3806
3792
|
: await install(authority, {
|
|
3807
3793
|
signal,
|
|
3808
3794
|
onProgress,
|
|
3809
|
-
security: effectiveSecurity,
|
|
3810
3795
|
}));
|
|
3811
3796
|
if (!admitted) {
|
|
3812
3797
|
if (graph) {
|
|
3813
3798
|
throw artifactGraphError(
|
|
3814
3799
|
"artifact-graph-offline-cache-miss",
|
|
3815
|
-
"No
|
|
3800
|
+
"No cached offline artifact graph is available.",
|
|
3816
3801
|
);
|
|
3817
3802
|
}
|
|
3818
|
-
throw speechError("ARCANE_AI_ARTIFACT_OFFLINE_MISS", "No
|
|
3803
|
+
throw speechError("ARCANE_AI_ARTIFACT_OFFLINE_MISS", "No cached offline speech artifacts are available.");
|
|
3819
3804
|
}
|
|
3820
3805
|
if (graph) {
|
|
3821
|
-
const
|
|
3822
|
-
admitted.cache,
|
|
3823
|
-
offline,
|
|
3824
|
-
effectiveSecurity,
|
|
3825
|
-
);
|
|
3826
|
-
const materialized = await createArtifactGraphObjectUrls(
|
|
3806
|
+
const materialized = await createOrdinaryArtifactObjectUrls(
|
|
3827
3807
|
admitted,
|
|
3828
3808
|
metadata,
|
|
3829
|
-
admitted.
|
|
3830
|
-
|
|
3809
|
+
admitted.routing,
|
|
3810
|
+
objectUrlFactory,
|
|
3831
3811
|
);
|
|
3832
|
-
const runtimeFiles =
|
|
3812
|
+
const runtimeFiles = completeValue(materialized.files.filter((file) =>
|
|
3833
3813
|
file.kind.startsWith("runtime-")));
|
|
3834
|
-
const modelFiles =
|
|
3814
|
+
const modelFiles = completeValue(materialized.files.filter((file) =>
|
|
3835
3815
|
!file.kind.startsWith("runtime-")));
|
|
3836
|
-
return
|
|
3837
|
-
cache:
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3816
|
+
return completeValue({
|
|
3817
|
+
cache: admitted.cache,
|
|
3818
|
+
runtime: completeValue({
|
|
3819
|
+
adapter: metadata.runtime.adapter,
|
|
3820
|
+
version: metadata.runtime.version,
|
|
3821
|
+
revision: metadata.runtime.revision,
|
|
3822
|
+
entry: metadata.runtime.entry,
|
|
3823
|
+
moduleGraph: metadata.runtime.moduleGraph,
|
|
3824
|
+
onnxWasm: metadata.runtime.onnxWasm,
|
|
3843
3825
|
files: runtimeFiles,
|
|
3844
|
-
edges: metadata.edges,
|
|
3845
|
-
transforms: metadata.transforms,
|
|
3846
|
-
guardCapability: materialized.guardCapability,
|
|
3847
|
-
artifactGraphId: authority.identitySha256,
|
|
3848
|
-
artifactGraphAdmission,
|
|
3849
3826
|
}),
|
|
3850
|
-
model:
|
|
3827
|
+
model: completeValue({
|
|
3851
3828
|
...metadata.model,
|
|
3852
3829
|
files: modelFiles,
|
|
3853
3830
|
}),
|
|
@@ -3857,9 +3834,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3857
3834
|
const materialized = createObjectUrls(admitted.files, objectUrlFactory);
|
|
3858
3835
|
const runtimeFiles = materialized.files.filter((file) => file.kind === "runtime");
|
|
3859
3836
|
const modelFiles = materialized.files.filter((file) => file.kind === "model");
|
|
3860
|
-
return
|
|
3837
|
+
return completeValue({
|
|
3861
3838
|
cache: admitted.cache,
|
|
3862
|
-
runtime:
|
|
3839
|
+
runtime: completeValue({
|
|
3863
3840
|
adapter: metadata.runtime.adapter,
|
|
3864
3841
|
version: metadata.runtime.version,
|
|
3865
3842
|
revision: metadata.runtime.revision,
|
|
@@ -3868,14 +3845,15 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3868
3845
|
? {}
|
|
3869
3846
|
: { wasmPaths: metadata.runtime.wasmPaths }),
|
|
3870
3847
|
moduleGraph: "self-contained",
|
|
3871
|
-
files:
|
|
3848
|
+
files: completeValue(runtimeFiles),
|
|
3872
3849
|
}),
|
|
3873
|
-
model:
|
|
3850
|
+
model: completeValue({
|
|
3874
3851
|
id: metadata.model.id,
|
|
3875
3852
|
repository: metadata.model.repository,
|
|
3876
3853
|
revision: metadata.model.revision,
|
|
3854
|
+
...(metadata.model.dtype === undefined ? {} : { dtype: metadata.model.dtype }),
|
|
3877
3855
|
defaultVoice: metadata.model.defaultVoice,
|
|
3878
|
-
files:
|
|
3856
|
+
files: completeValue(modelFiles),
|
|
3879
3857
|
}),
|
|
3880
3858
|
release: materialized.release,
|
|
3881
3859
|
});
|
|
@@ -3914,7 +3892,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3914
3892
|
return serializeAuthority(authority, () => removeUnlocked(authority));
|
|
3915
3893
|
}
|
|
3916
3894
|
|
|
3917
|
-
const store =
|
|
3895
|
+
const store = completeValue({
|
|
3918
3896
|
protocol: BROWSER_SPEECH_ARTIFACT_PROTOCOL,
|
|
3919
3897
|
tableName,
|
|
3920
3898
|
prepare,
|