arcane-os 0.3.1 → 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 +14 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
- package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
- 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 -148
- package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
- 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 +1042 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
- 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 +27 -67
- 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 +109 -1558
- 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 -185
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1295
- 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 -719
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2965
- package/docs/reference/sdk-api.md +0 -6698
- 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,10 +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
|
-
|
|
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) {
|
|
2866
2745
|
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
2867
2746
|
const plans = new Map();
|
|
2868
2747
|
for (const descriptor of metadata.runtimeFiles) {
|
|
@@ -2889,7 +2768,7 @@ async function inspectArtifactGraphRuntime(admitted, metadata, signal, security)
|
|
|
2889
2768
|
plans.set(
|
|
2890
2769
|
descriptor.path,
|
|
2891
2770
|
inspectArtifactGraphModuleSource(source, descriptor.path, metadata, {
|
|
2892
|
-
strict:
|
|
2771
|
+
strict: false,
|
|
2893
2772
|
}),
|
|
2894
2773
|
);
|
|
2895
2774
|
}
|
|
@@ -2897,7 +2776,7 @@ async function inspectArtifactGraphRuntime(admitted, metadata, signal, security)
|
|
|
2897
2776
|
const warnings = [...new Set([...plans.values()].flatMap((plan) => plan.warnings))]
|
|
2898
2777
|
.sort();
|
|
2899
2778
|
throwIfAborted(signal);
|
|
2900
|
-
return
|
|
2779
|
+
return completeValue({ plans, order, warnings: completeValue(warnings) });
|
|
2901
2780
|
}
|
|
2902
2781
|
|
|
2903
2782
|
function assertArtifactGraphRuntimeInspection(inspection) {
|
|
@@ -2998,37 +2877,17 @@ function applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapab
|
|
|
2998
2877
|
}
|
|
2999
2878
|
|
|
3000
2879
|
function artifactGraphGuardCapability() {
|
|
3001
|
-
|
|
3002
|
-
if (typeof crypto?.getRandomValues !== "function") {
|
|
3003
|
-
throw artifactGraphError(
|
|
3004
|
-
"artifact-graph-guard-capability-unavailable",
|
|
3005
|
-
"Authenticated artifact graph materialization requires cryptographic random values.",
|
|
3006
|
-
);
|
|
3007
|
-
}
|
|
3008
|
-
const bytes = new Uint8Array(32);
|
|
3009
|
-
crypto.getRandomValues(bytes);
|
|
3010
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
async function blobDigest(blob) {
|
|
3014
|
-
const digest = createStreamingSha256();
|
|
3015
|
-
let bytes = 0;
|
|
3016
|
-
for await (const chunk of byteChunks(blob.stream())) {
|
|
3017
|
-
digest.update(chunk);
|
|
3018
|
-
bytes += chunk.byteLength;
|
|
3019
|
-
}
|
|
3020
|
-
return Object.freeze({ bytes, sha256: digest.digestHex() });
|
|
2880
|
+
return "ordinary";
|
|
3021
2881
|
}
|
|
3022
2882
|
|
|
3023
|
-
async function createArtifactGraphObjectUrls(admitted, metadata, inspection
|
|
2883
|
+
async function createArtifactGraphObjectUrls(admitted, metadata, inspection) {
|
|
3024
2884
|
if (
|
|
3025
2885
|
typeof PLATFORM_CREATE_OBJECT_URL !== "function"
|
|
3026
2886
|
|| typeof PLATFORM_REVOKE_OBJECT_URL !== "function"
|
|
3027
|
-
|| typeof PLATFORM_FETCH !== "function"
|
|
3028
2887
|
) {
|
|
3029
2888
|
throw artifactGraphError(
|
|
3030
2889
|
"artifact-graph-object-url-platform-unavailable",
|
|
3031
|
-
"
|
|
2890
|
+
"Artifact graph materialization requires native Blob URL creation and revocation.",
|
|
3032
2891
|
);
|
|
3033
2892
|
}
|
|
3034
2893
|
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
@@ -3041,16 +2900,6 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3041
2900
|
const blob = body instanceof Blob && body.type === descriptor.mediaType
|
|
3042
2901
|
? body
|
|
3043
2902
|
: new Blob([body], { type: descriptor.mediaType });
|
|
3044
|
-
const source = await blobDigest(blob);
|
|
3045
|
-
const transformed = ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind);
|
|
3046
|
-
const expected = Object.freeze({
|
|
3047
|
-
bytes: transformed || security.checks.byteLength !== true
|
|
3048
|
-
? source.bytes
|
|
3049
|
-
: descriptor.bytes,
|
|
3050
|
-
sha256: transformed || security.checks.sha256 !== true
|
|
3051
|
-
? source.sha256
|
|
3052
|
-
: descriptor.sha256,
|
|
3053
|
-
});
|
|
3054
2903
|
const moduleUrl = PLATFORM_CREATE_OBJECT_URL(blob);
|
|
3055
2904
|
if (typeof moduleUrl !== "string" || !moduleUrl.startsWith("blob:")) {
|
|
3056
2905
|
throw artifactGraphError(
|
|
@@ -3066,66 +2915,16 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3066
2915
|
}
|
|
3067
2916
|
createdIdentities.add(moduleUrl);
|
|
3068
2917
|
created.push(moduleUrl);
|
|
3069
|
-
|
|
3070
|
-
try {
|
|
3071
|
-
response = await PLATFORM_FETCH(moduleUrl, {
|
|
3072
|
-
method: "GET",
|
|
3073
|
-
credentials: "omit",
|
|
3074
|
-
redirect: "error",
|
|
3075
|
-
});
|
|
3076
|
-
} catch (error) {
|
|
3077
|
-
throw artifactGraphError(
|
|
3078
|
-
"artifact-graph-object-url-readback-unavailable",
|
|
3079
|
-
`Materialized artifact graph file ${descriptor.path} could not be read back from its Blob URL.`,
|
|
3080
|
-
error,
|
|
3081
|
-
);
|
|
3082
|
-
}
|
|
3083
|
-
if (!response.ok) {
|
|
3084
|
-
throw artifactGraphError(
|
|
3085
|
-
"artifact-graph-object-url-readback-http-status-rejected",
|
|
3086
|
-
`Materialized artifact graph file ${descriptor.path} returned a non-success Blob URL response.`,
|
|
3087
|
-
);
|
|
3088
|
-
}
|
|
3089
|
-
if (response.redirected || response.url !== moduleUrl) {
|
|
3090
|
-
throw artifactGraphError(
|
|
3091
|
-
"artifact-graph-object-url-readback-identity-mismatch",
|
|
3092
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact Blob URL identity.`,
|
|
3093
|
-
);
|
|
3094
|
-
}
|
|
3095
|
-
const observedMediaType = response.headers.get("content-type")?.split(";", 1)[0].trim() ?? "";
|
|
3096
|
-
if (observedMediaType !== descriptor.mediaType) {
|
|
3097
|
-
throw artifactGraphError(
|
|
3098
|
-
"artifact-graph-object-url-media-type-mismatch",
|
|
3099
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its declared media type.`,
|
|
3100
|
-
);
|
|
3101
|
-
}
|
|
3102
|
-
const observedBlob = await response.blob();
|
|
3103
|
-
const observed = await blobDigest(observedBlob);
|
|
3104
|
-
if (observed.bytes !== expected.bytes) {
|
|
3105
|
-
throw artifactGraphError(
|
|
3106
|
-
"artifact-graph-object-url-byte-length-mismatch",
|
|
3107
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact byte length.`,
|
|
3108
|
-
);
|
|
3109
|
-
}
|
|
3110
|
-
if (observed.sha256 !== expected.sha256) {
|
|
3111
|
-
throw artifactGraphError(
|
|
3112
|
-
"artifact-graph-object-url-sha256-mismatch",
|
|
3113
|
-
`Materialized artifact graph file ${descriptor.path} did not retain its exact bytes.`,
|
|
3114
|
-
);
|
|
3115
|
-
}
|
|
3116
|
-
materializedByPath.set(descriptor.path, Object.freeze({
|
|
2918
|
+
materializedByPath.set(descriptor.path, completeValue({
|
|
3117
2919
|
kind: descriptor.kind,
|
|
3118
2920
|
path: descriptor.path,
|
|
3119
2921
|
sourceUrl: descriptor.sourceUrl,
|
|
3120
2922
|
revision: descriptor.revision,
|
|
3121
|
-
license: descriptor.license,
|
|
3122
2923
|
moduleUrl,
|
|
3123
2924
|
mediaType: descriptor.mediaType,
|
|
3124
2925
|
...(descriptor.sourceMediaType === descriptor.mediaType
|
|
3125
2926
|
? {}
|
|
3126
2927
|
: { sourceMediaType: descriptor.sourceMediaType }),
|
|
3127
|
-
bytes: descriptor.bytes,
|
|
3128
|
-
sha256: descriptor.sha256,
|
|
3129
2928
|
runtimeRequestUrls: descriptor.runtimeRequestUrls,
|
|
3130
2929
|
...(descriptor.redirectFinalOrigins.length < 1
|
|
3131
2930
|
? {}
|
|
@@ -3168,9 +2967,9 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3168
2967
|
}
|
|
3169
2968
|
return materialized;
|
|
3170
2969
|
});
|
|
3171
|
-
return
|
|
2970
|
+
return completeValue({
|
|
3172
2971
|
guardCapability,
|
|
3173
|
-
files:
|
|
2972
|
+
files: completeValue(files),
|
|
3174
2973
|
release() {
|
|
3175
2974
|
for (const url of created.splice(0).reverse()) {
|
|
3176
2975
|
try {
|
|
@@ -3193,73 +2992,497 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3193
2992
|
}
|
|
3194
2993
|
}
|
|
3195
2994
|
|
|
3196
|
-
function
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
const source = cache === "installed"
|
|
3203
|
-
? "network-dbopfs"
|
|
3204
|
-
: offline
|
|
3205
|
-
? "offline-dbopfs-cache"
|
|
3206
|
-
: "dbopfs-cache";
|
|
3207
|
-
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
|
+
}
|
|
3208
3001
|
}
|
|
3209
3002
|
|
|
3210
|
-
function
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
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);
|
|
3215
3024
|
}
|
|
3216
|
-
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;
|
|
3217
3041
|
try {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
mediaType: descriptor.mediaType,
|
|
3230
|
-
bytes: file.size,
|
|
3231
|
-
});
|
|
3232
|
-
});
|
|
3233
|
-
return Object.freeze({
|
|
3234
|
-
files: Object.freeze(materialized),
|
|
3235
|
-
release() {
|
|
3236
|
-
for (const url of created.splice(0).reverse()) {
|
|
3237
|
-
try {
|
|
3238
|
-
revoke(url);
|
|
3239
|
-
} catch {
|
|
3240
|
-
// Object URL revocation follows worker termination and is best effort.
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
|
-
},
|
|
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([]),
|
|
3244
3053
|
});
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
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);
|
|
3251
3080
|
}
|
|
3252
3081
|
}
|
|
3253
|
-
throw error;
|
|
3254
3082
|
}
|
|
3255
|
-
}
|
|
3256
3083
|
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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 {
|
|
3471
|
+
revoke(url);
|
|
3472
|
+
} catch {
|
|
3473
|
+
// Preserve the materialization error.
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
throw error;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
/**
|
|
3481
|
+
* Stores an authority's runtime/model files in an existing DBOPFS table.
|
|
3482
|
+
* Ordinary cache reuse creates no receipt, manifest, or byte identity.
|
|
3483
|
+
*/
|
|
3484
|
+
export function createDbopfsSpeechArtifactStore({
|
|
3485
|
+
dbopfs,
|
|
3263
3486
|
tableName = "arcane_ai_browser_speech",
|
|
3264
3487
|
fetchImpl = null,
|
|
3265
3488
|
objectUrlFactory = null,
|
|
@@ -3333,20 +3556,27 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3333
3556
|
}
|
|
3334
3557
|
}
|
|
3335
3558
|
|
|
3336
|
-
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 } = {}) {
|
|
3337
3570
|
const directory = await table();
|
|
3338
3571
|
const handle = await directory.getFileHandle(name, { create: true });
|
|
3339
3572
|
const writable = await handle.createWritable();
|
|
3340
|
-
let written = 0;
|
|
3341
3573
|
try {
|
|
3342
3574
|
for await (const chunk of byteChunks(body, signal)) {
|
|
3343
3575
|
await writable.write(chunk);
|
|
3344
|
-
written += chunk.byteLength;
|
|
3345
|
-
onChunk?.(chunk, written);
|
|
3346
3576
|
}
|
|
3347
3577
|
throwIfAborted(signal);
|
|
3348
3578
|
await writable.close();
|
|
3349
|
-
return
|
|
3579
|
+
return undefined;
|
|
3350
3580
|
} catch (error) {
|
|
3351
3581
|
await writable.abort?.(error).catch(() => undefined);
|
|
3352
3582
|
await directory.removeEntry(name).catch(() => undefined);
|
|
@@ -3360,40 +3590,24 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3360
3590
|
}
|
|
3361
3591
|
const metadata = artifactMetadata(authority);
|
|
3362
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
|
+
);
|
|
3363
3603
|
const results = await Promise.all([
|
|
3364
|
-
removeEntry(names.
|
|
3365
|
-
|
|
3604
|
+
removeEntry(names.selection),
|
|
3605
|
+
removeEntry(names.legacyManifest),
|
|
3606
|
+
...fileNames.map(removeEntry),
|
|
3366
3607
|
]);
|
|
3367
3608
|
return results.some(Boolean);
|
|
3368
3609
|
}
|
|
3369
3610
|
|
|
3370
|
-
async function readManifest(name) {
|
|
3371
|
-
const file = await readFile(name);
|
|
3372
|
-
if (!file) return null;
|
|
3373
|
-
try {
|
|
3374
|
-
return JSON.parse(await file.text());
|
|
3375
|
-
} catch {
|
|
3376
|
-
await removeEntry(name);
|
|
3377
|
-
return null;
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
async function verifyFile(file, descriptor, security, signal, onProgress, phase) {
|
|
3382
|
-
if (security.checks.byteLength && file.size !== descriptor.bytes) return false;
|
|
3383
|
-
if (!security.checks.sha256) {
|
|
3384
|
-
onProgress?.(providerProgress(phase, file.size, file.size));
|
|
3385
|
-
return true;
|
|
3386
|
-
}
|
|
3387
|
-
const digest = createStreamingSha256();
|
|
3388
|
-
let completed = 0;
|
|
3389
|
-
for await (const chunk of byteChunks(file.stream(), signal)) {
|
|
3390
|
-
digest.update(chunk);
|
|
3391
|
-
completed += chunk.byteLength;
|
|
3392
|
-
onProgress?.(providerProgress(phase, completed, file.size));
|
|
3393
|
-
}
|
|
3394
|
-
return completed === file.size && digest.digestHex() === descriptor.sha256;
|
|
3395
|
-
}
|
|
3396
|
-
|
|
3397
3611
|
function graphFileReason(descriptor, boundary) {
|
|
3398
3612
|
const subject = descriptor.kind === "runtime-entrypoint-javascript"
|
|
3399
3613
|
? "entrypoint"
|
|
@@ -3405,27 +3619,12 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3405
3619
|
return artifactGraphError(graphFileReason(descriptor, boundary), message);
|
|
3406
3620
|
}
|
|
3407
3621
|
|
|
3408
|
-
function
|
|
3409
|
-
for (const descriptor of files) {
|
|
3410
|
-
if (security.checks.byteLength && descriptor.bytes === null) {
|
|
3411
|
-
throw new TypeError(
|
|
3412
|
-
`${descriptor.kind} file ${descriptor.path} requires bytes under the effective security policy.`,
|
|
3413
|
-
);
|
|
3414
|
-
}
|
|
3415
|
-
if (security.checks.sha256 && descriptor.sha256 === null) {
|
|
3416
|
-
throw new TypeError(
|
|
3417
|
-
`${descriptor.kind} file ${descriptor.path} requires sha256 under the effective security policy.`,
|
|
3418
|
-
);
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
async function openCached(authority, { signal, onProgress, security } = {}) {
|
|
3622
|
+
async function openCached(authority, { signal, onProgress } = {}) {
|
|
3424
3623
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3425
3624
|
const metadata = artifactMetadata(authority);
|
|
3426
3625
|
const names = storageNames(authority, metadata.files);
|
|
3427
|
-
const
|
|
3428
|
-
if (
|
|
3626
|
+
const storedSelection = await readJsonFile(names.selection);
|
|
3627
|
+
if (JSON.stringify(storedSelection) !== JSON.stringify(cacheSelection(authority))) {
|
|
3429
3628
|
await removeUnlocked(authority);
|
|
3430
3629
|
return null;
|
|
3431
3630
|
}
|
|
@@ -3434,42 +3633,21 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3434
3633
|
throwIfAborted(signal);
|
|
3435
3634
|
const descriptor = metadata.files[index];
|
|
3436
3635
|
const file = await readFile(names.files[index]);
|
|
3437
|
-
|
|
3438
|
-
if (
|
|
3439
|
-
!file
|
|
3440
|
-
|| observed?.path !== descriptor.path
|
|
3441
|
-
|| observed?.bytes !== file.size
|
|
3442
|
-
|| (graph && observed?.sha256 !== descriptor.sha256)
|
|
3443
|
-
) {
|
|
3444
|
-
await removeUnlocked(authority);
|
|
3445
|
-
return null;
|
|
3446
|
-
}
|
|
3447
|
-
if (!await verifyFile(
|
|
3448
|
-
file,
|
|
3449
|
-
descriptor,
|
|
3450
|
-
security,
|
|
3451
|
-
signal,
|
|
3452
|
-
onProgress,
|
|
3453
|
-
graph
|
|
3454
|
-
? security.checks.sha256
|
|
3455
|
-
? "artifact-graph-dbopfs-cache-rehash"
|
|
3456
|
-
: "artifact-graph-dbopfs-cache-readback"
|
|
3457
|
-
: "verify-cache",
|
|
3458
|
-
)) {
|
|
3636
|
+
if (!file) {
|
|
3459
3637
|
await removeUnlocked(authority);
|
|
3460
3638
|
return null;
|
|
3461
3639
|
}
|
|
3462
3640
|
files.push({ descriptor, file });
|
|
3463
3641
|
}
|
|
3464
3642
|
try {
|
|
3465
|
-
const
|
|
3466
|
-
? await
|
|
3467
|
-
:
|
|
3643
|
+
const routing = graph
|
|
3644
|
+
? await planOrdinaryMaterializedRuntime({ files }, metadata, signal)
|
|
3645
|
+
: null;
|
|
3468
3646
|
throwIfAborted(signal);
|
|
3469
|
-
return
|
|
3470
|
-
files:
|
|
3647
|
+
return completeValue({
|
|
3648
|
+
files: completeValue(files),
|
|
3471
3649
|
cache: "cached",
|
|
3472
|
-
|
|
3650
|
+
routing,
|
|
3473
3651
|
});
|
|
3474
3652
|
} catch (error) {
|
|
3475
3653
|
await removeUnlocked(authority);
|
|
@@ -3477,7 +3655,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3477
3655
|
}
|
|
3478
3656
|
}
|
|
3479
3657
|
|
|
3480
|
-
async function install(authority, { signal, onProgress
|
|
3658
|
+
async function install(authority, { signal, onProgress } = {}) {
|
|
3481
3659
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3482
3660
|
const metadata = artifactMetadata(authority);
|
|
3483
3661
|
const names = storageNames(authority, metadata.files);
|
|
@@ -3488,21 +3666,22 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3488
3666
|
await removeUnlocked(authority);
|
|
3489
3667
|
const installed = [];
|
|
3490
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
|
+
);
|
|
3491
3676
|
for (let index = 0; index < metadata.files.length; index += 1) {
|
|
3492
3677
|
throwIfAborted(signal);
|
|
3493
3678
|
const descriptor = metadata.files[index];
|
|
3494
3679
|
const sourceUrl = graph ? descriptor.sourceUrl : descriptor.url;
|
|
3495
|
-
const redirectFinalOrigins = graph
|
|
3496
|
-
? descriptor.redirectFinalOrigins
|
|
3497
|
-
: Object.freeze([]);
|
|
3498
3680
|
let response;
|
|
3499
3681
|
try {
|
|
3500
3682
|
response = await fetchFunction(sourceUrl, {
|
|
3501
3683
|
cache: "no-store",
|
|
3502
|
-
|
|
3503
|
-
mode: "cors",
|
|
3504
|
-
redirect: redirectFinalOrigins.length < 1 ? "error" : "follow",
|
|
3505
|
-
referrerPolicy: "no-referrer",
|
|
3684
|
+
redirect: "follow",
|
|
3506
3685
|
signal,
|
|
3507
3686
|
});
|
|
3508
3687
|
} catch (error) {
|
|
@@ -3517,28 +3696,18 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3517
3696
|
throw speechError("ARCANE_AI_ARTIFACT_DOWNLOAD_FAILED", "A speech artifact source fetch was rejected.", error);
|
|
3518
3697
|
}
|
|
3519
3698
|
let responseBody;
|
|
3520
|
-
let responseHeaders;
|
|
3521
3699
|
let responseOk;
|
|
3522
|
-
let responseRedirected;
|
|
3523
3700
|
let responseStatus;
|
|
3524
|
-
let responseUrl;
|
|
3525
3701
|
try {
|
|
3526
3702
|
if (!response || typeof response !== "object") {
|
|
3527
3703
|
throw new TypeError("The artifact fetch result is not an object.");
|
|
3528
3704
|
}
|
|
3529
3705
|
responseBody = response.body;
|
|
3530
|
-
responseHeaders = response.headers;
|
|
3531
3706
|
responseOk = response.ok;
|
|
3532
|
-
responseRedirected = response.redirected;
|
|
3533
3707
|
responseStatus = response.status;
|
|
3534
|
-
responseUrl = response.url;
|
|
3535
3708
|
if (
|
|
3536
3709
|
typeof responseOk !== "boolean"
|
|
3537
|
-
|| typeof responseRedirected !== "boolean"
|
|
3538
3710
|
|| !Number.isInteger(responseStatus)
|
|
3539
|
-
|| typeof responseUrl !== "string"
|
|
3540
|
-
|| !responseHeaders
|
|
3541
|
-
|| typeof responseHeaders.get !== "function"
|
|
3542
3711
|
|| (responseBody !== null && typeof responseBody?.getReader !== "function")
|
|
3543
3712
|
) {
|
|
3544
3713
|
throw new TypeError("The artifact fetch result is not a readable Fetch Response.");
|
|
@@ -3557,81 +3726,6 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3557
3726
|
error,
|
|
3558
3727
|
);
|
|
3559
3728
|
}
|
|
3560
|
-
let finalUrl = null;
|
|
3561
|
-
let finalUrlRecord = null;
|
|
3562
|
-
try {
|
|
3563
|
-
finalUrlRecord = responseUrl
|
|
3564
|
-
? new URL(responseUrl)
|
|
3565
|
-
: null;
|
|
3566
|
-
finalUrl = finalUrlRecord?.href ?? null;
|
|
3567
|
-
} catch {
|
|
3568
|
-
finalUrlRecord = null;
|
|
3569
|
-
finalUrl = null;
|
|
3570
|
-
}
|
|
3571
|
-
if (graph && responseRedirected && !finalUrlRecord) {
|
|
3572
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3573
|
-
throw artifactGraphError(
|
|
3574
|
-
"artifact-graph-source-response-url-unreadable",
|
|
3575
|
-
`Artifact graph redirected source response for ${descriptor.path} did not expose a readable final URL.`,
|
|
3576
|
-
);
|
|
3577
|
-
}
|
|
3578
|
-
if (graph && responseRedirected && redirectFinalOrigins.length < 1) {
|
|
3579
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3580
|
-
throw artifactGraphError(
|
|
3581
|
-
"artifact-graph-source-redirected",
|
|
3582
|
-
`Artifact graph source response for ${descriptor.path} did not match its immutable URL.`,
|
|
3583
|
-
);
|
|
3584
|
-
}
|
|
3585
|
-
if (graph && responseRedirected && finalUrlRecord.protocol !== "https:") {
|
|
3586
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3587
|
-
throw artifactGraphError(
|
|
3588
|
-
"artifact-graph-source-response-url-protocol-not-https",
|
|
3589
|
-
`Artifact graph redirected source response for ${descriptor.path} did not end at HTTPS.`,
|
|
3590
|
-
);
|
|
3591
|
-
}
|
|
3592
|
-
if (
|
|
3593
|
-
graph
|
|
3594
|
-
&& responseRedirected
|
|
3595
|
-
&& (finalUrlRecord.username || finalUrlRecord.password)
|
|
3596
|
-
) {
|
|
3597
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3598
|
-
throw artifactGraphError(
|
|
3599
|
-
"artifact-graph-source-response-url-credentials-rejected",
|
|
3600
|
-
`Artifact graph redirected source response for ${descriptor.path} exposed credentials in its final URL.`,
|
|
3601
|
-
);
|
|
3602
|
-
}
|
|
3603
|
-
if (graph && responseRedirected && finalUrlRecord.hash) {
|
|
3604
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3605
|
-
throw artifactGraphError(
|
|
3606
|
-
"artifact-graph-source-response-url-fragment-rejected",
|
|
3607
|
-
`Artifact graph redirected source response for ${descriptor.path} exposed a fragment in its final URL.`,
|
|
3608
|
-
);
|
|
3609
|
-
}
|
|
3610
|
-
if (
|
|
3611
|
-
graph
|
|
3612
|
-
&& responseRedirected
|
|
3613
|
-
&& !redirectFinalOrigins.includes(finalUrlRecord.origin)
|
|
3614
|
-
) {
|
|
3615
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3616
|
-
throw artifactGraphError(
|
|
3617
|
-
"artifact-graph-source-redirect-final-origin-mismatch",
|
|
3618
|
-
`Artifact graph redirected source response for ${descriptor.path} ended at an undeclared final origin.`,
|
|
3619
|
-
);
|
|
3620
|
-
}
|
|
3621
|
-
if (graph && !responseRedirected && finalUrl !== sourceUrl) {
|
|
3622
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3623
|
-
throw artifactGraphError(
|
|
3624
|
-
"artifact-graph-source-response-url-mismatch",
|
|
3625
|
-
`Artifact graph non-redirected source response for ${descriptor.path} did not retain its immutable URL.`,
|
|
3626
|
-
);
|
|
3627
|
-
}
|
|
3628
|
-
if (!graph && (responseRedirected || finalUrl !== sourceUrl)) {
|
|
3629
|
-
await responseBody?.cancel?.().catch(() => undefined);
|
|
3630
|
-
throw speechError(
|
|
3631
|
-
"ARCANE_AI_ARTIFACT_SOURCE_CHANGED",
|
|
3632
|
-
"A speech artifact response did not match its admitted URL.",
|
|
3633
|
-
);
|
|
3634
|
-
}
|
|
3635
3729
|
if (!responseOk || !responseBody) {
|
|
3636
3730
|
await responseBody?.cancel?.().catch(() => undefined);
|
|
3637
3731
|
if (graph) {
|
|
@@ -3645,140 +3739,28 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3645
3739
|
`A speech artifact server returned HTTP ${String(responseStatus)}.`,
|
|
3646
3740
|
);
|
|
3647
3741
|
}
|
|
3648
|
-
|
|
3649
|
-
const reportedBytes = header ? Number(header) : null;
|
|
3650
|
-
const contentEncoding = responseHeaders.get("content-encoding")?.trim() ?? "";
|
|
3651
|
-
if (graph) {
|
|
3652
|
-
const reportedMediaType = responseHeaders.get("content-type")
|
|
3653
|
-
?.split(";", 1)[0]
|
|
3654
|
-
?.trim()
|
|
3655
|
-
?.toLowerCase() ?? null;
|
|
3656
|
-
if (reportedMediaType !== descriptor.sourceMediaType) {
|
|
3657
|
-
await responseBody.cancel?.().catch(() => undefined);
|
|
3658
|
-
throw graphVerificationError(
|
|
3659
|
-
descriptor,
|
|
3660
|
-
descriptor.sourceMediaType === descriptor.mediaType
|
|
3661
|
-
? "media-type-mismatch"
|
|
3662
|
-
: "source-media-type-mismatch",
|
|
3663
|
-
`Artifact graph source media type for ${descriptor.path} did not match ${descriptor.sourceMediaType}.`,
|
|
3664
|
-
);
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
if (
|
|
3668
|
-
security.checks.byteLength
|
|
3669
|
-
&& Number.isSafeInteger(reportedBytes)
|
|
3670
|
-
&& (!graph || !contentEncoding)
|
|
3671
|
-
&& reportedBytes !== descriptor.bytes
|
|
3672
|
-
) {
|
|
3673
|
-
await responseBody.cancel?.().catch(() => undefined);
|
|
3674
|
-
if (graph) {
|
|
3675
|
-
throw graphVerificationError(
|
|
3676
|
-
descriptor,
|
|
3677
|
-
"byte-length-mismatch",
|
|
3678
|
-
`Artifact graph source Content-Length for ${descriptor.path} changed.`,
|
|
3679
|
-
);
|
|
3680
|
-
}
|
|
3681
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "Speech artifact Content-Length changed.");
|
|
3682
|
-
}
|
|
3683
|
-
const digest = security.checks.sha256
|
|
3684
|
-
? createStreamingSha256()
|
|
3685
|
-
: null;
|
|
3686
|
-
const written = await writeFile(names.files[index], responseBody, {
|
|
3687
|
-
signal,
|
|
3688
|
-
onChunk(chunk, completed) {
|
|
3689
|
-
digest?.update(chunk);
|
|
3690
|
-
if (security.checks.byteLength && completed > descriptor.bytes) {
|
|
3691
|
-
if (graph) {
|
|
3692
|
-
throw graphVerificationError(
|
|
3693
|
-
descriptor,
|
|
3694
|
-
"byte-length-mismatch",
|
|
3695
|
-
`Artifact graph source ${descriptor.path} exceeded its declared byte length.`,
|
|
3696
|
-
);
|
|
3697
|
-
}
|
|
3698
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "A speech artifact exceeded its expected size.");
|
|
3699
|
-
}
|
|
3700
|
-
onProgress?.(providerProgress(
|
|
3701
|
-
graph ? "artifact-graph-network-download" : "download",
|
|
3702
|
-
completed,
|
|
3703
|
-
security.checks.byteLength ? descriptor.bytes : null,
|
|
3704
|
-
));
|
|
3705
|
-
},
|
|
3706
|
-
});
|
|
3707
|
-
if (security.checks.byteLength && written !== descriptor.bytes) {
|
|
3708
|
-
if (graph) {
|
|
3709
|
-
throw graphVerificationError(
|
|
3710
|
-
descriptor,
|
|
3711
|
-
"byte-length-mismatch",
|
|
3712
|
-
`Artifact graph source ${descriptor.path} byte length changed.`,
|
|
3713
|
-
);
|
|
3714
|
-
}
|
|
3715
|
-
throw speechError("ARCANE_AI_ARTIFACT_SIZE_MISMATCH", "A speech artifact byte count changed.");
|
|
3716
|
-
}
|
|
3717
|
-
if (digest && digest.digestHex() !== descriptor.sha256) {
|
|
3718
|
-
if (graph) {
|
|
3719
|
-
throw graphVerificationError(
|
|
3720
|
-
descriptor,
|
|
3721
|
-
"sha256-mismatch",
|
|
3722
|
-
`Artifact graph source ${descriptor.path} SHA-256 changed.`,
|
|
3723
|
-
);
|
|
3724
|
-
}
|
|
3725
|
-
throw speechError("ARCANE_AI_ARTIFACT_DIGEST_MISMATCH", "A speech artifact SHA-256 changed.");
|
|
3726
|
-
}
|
|
3742
|
+
await writeFile(names.files[index], responseBody, { signal });
|
|
3727
3743
|
const file = await readFile(names.files[index]);
|
|
3728
|
-
if (!file
|
|
3744
|
+
if (!file) {
|
|
3729
3745
|
if (graph) {
|
|
3730
3746
|
throw graphVerificationError(
|
|
3731
3747
|
descriptor,
|
|
3732
|
-
"dbopfs-persisted-
|
|
3748
|
+
"dbopfs-persisted-file-missing",
|
|
3733
3749
|
`DBOPFS did not preserve artifact graph file ${descriptor.path}.`,
|
|
3734
3750
|
);
|
|
3735
3751
|
}
|
|
3736
3752
|
throw speechError("ARCANE_AI_ARTIFACT_CACHE_REJECTED", "DBOPFS did not preserve a speech artifact.");
|
|
3737
3753
|
}
|
|
3738
|
-
if (!await verifyFile(
|
|
3739
|
-
file,
|
|
3740
|
-
descriptor,
|
|
3741
|
-
security,
|
|
3742
|
-
signal,
|
|
3743
|
-
onProgress,
|
|
3744
|
-
graph
|
|
3745
|
-
? security.checks.sha256
|
|
3746
|
-
? "artifact-graph-dbopfs-persisted-rehash"
|
|
3747
|
-
: "artifact-graph-dbopfs-persisted-readback"
|
|
3748
|
-
: "verify-cache",
|
|
3749
|
-
)) {
|
|
3750
|
-
if (graph) {
|
|
3751
|
-
throw graphVerificationError(
|
|
3752
|
-
descriptor,
|
|
3753
|
-
"dbopfs-persisted-sha256-mismatch",
|
|
3754
|
-
`DBOPFS persisted bytes for artifact graph file ${descriptor.path} were rejected during re-verification.`,
|
|
3755
|
-
);
|
|
3756
|
-
}
|
|
3757
|
-
throw speechError("ARCANE_AI_ARTIFACT_CACHE_REJECTED", "DBOPFS persisted bytes were rejected during verification.");
|
|
3758
|
-
}
|
|
3759
3754
|
installed.push({ descriptor, file });
|
|
3760
3755
|
}
|
|
3761
|
-
const
|
|
3762
|
-
? await
|
|
3763
|
-
:
|
|
3756
|
+
const routing = graph
|
|
3757
|
+
? await planOrdinaryMaterializedRuntime({ files: installed }, metadata, signal)
|
|
3758
|
+
: null;
|
|
3764
3759
|
throwIfAborted(signal);
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
complete: true,
|
|
3768
|
-
authority: storedArtifactProjection(authority),
|
|
3769
|
-
files: Object.freeze(installed.map(({ descriptor, file }) => Object.freeze({
|
|
3770
|
-
path: descriptor.path,
|
|
3771
|
-
bytes: file.size,
|
|
3772
|
-
...(graph ? { sha256: descriptor.sha256 } : {}),
|
|
3773
|
-
}))),
|
|
3774
|
-
completedAt: new Date().toISOString(),
|
|
3775
|
-
});
|
|
3776
|
-
const encoded = new TextEncoder().encode(`${JSON.stringify(manifest)}\n`);
|
|
3777
|
-
await writeFile(names.manifest, encoded, { signal });
|
|
3778
|
-
return Object.freeze({
|
|
3779
|
-
files: Object.freeze(installed),
|
|
3760
|
+
return completeValue({
|
|
3761
|
+
files: completeValue(installed),
|
|
3780
3762
|
cache: "installed",
|
|
3781
|
-
|
|
3763
|
+
routing,
|
|
3782
3764
|
});
|
|
3783
3765
|
} catch (error) {
|
|
3784
3766
|
await removeUnlocked(authority).catch(() => undefined);
|
|
@@ -3797,75 +3779,52 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3797
3779
|
}
|
|
3798
3780
|
throwIfAborted(signal);
|
|
3799
3781
|
const graph = ARTIFACT_GRAPHS.has(authority);
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
} catch (error) {
|
|
3804
|
-
if (graph) {
|
|
3805
|
-
throw artifactGraphTypeError(
|
|
3806
|
-
"artifact-graph-load-security-contract-rejected",
|
|
3807
|
-
"Browser speech artifact graph load security does not satisfy the required contract.",
|
|
3808
|
-
error,
|
|
3809
|
-
);
|
|
3810
|
-
}
|
|
3811
|
-
throw error;
|
|
3812
|
-
}
|
|
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.
|
|
3813
3785
|
const metadata = artifactMetadata(authority);
|
|
3814
|
-
assertSecurityDescriptors(metadata.files, effectiveSecurity);
|
|
3815
3786
|
const cached = await openCached(authority, {
|
|
3816
3787
|
signal,
|
|
3817
3788
|
onProgress,
|
|
3818
|
-
security: effectiveSecurity,
|
|
3819
3789
|
});
|
|
3820
3790
|
const admitted = cached ?? (offline
|
|
3821
3791
|
? null
|
|
3822
3792
|
: await install(authority, {
|
|
3823
3793
|
signal,
|
|
3824
3794
|
onProgress,
|
|
3825
|
-
security: effectiveSecurity,
|
|
3826
3795
|
}));
|
|
3827
3796
|
if (!admitted) {
|
|
3828
3797
|
if (graph) {
|
|
3829
3798
|
throw artifactGraphError(
|
|
3830
3799
|
"artifact-graph-offline-cache-miss",
|
|
3831
|
-
"No
|
|
3800
|
+
"No cached offline artifact graph is available.",
|
|
3832
3801
|
);
|
|
3833
3802
|
}
|
|
3834
|
-
throw speechError("ARCANE_AI_ARTIFACT_OFFLINE_MISS", "No
|
|
3803
|
+
throw speechError("ARCANE_AI_ARTIFACT_OFFLINE_MISS", "No cached offline speech artifacts are available.");
|
|
3835
3804
|
}
|
|
3836
3805
|
if (graph) {
|
|
3837
|
-
const
|
|
3838
|
-
admitted.cache,
|
|
3839
|
-
offline,
|
|
3840
|
-
effectiveSecurity,
|
|
3841
|
-
);
|
|
3842
|
-
const materialized = await createArtifactGraphObjectUrls(
|
|
3806
|
+
const materialized = await createOrdinaryArtifactObjectUrls(
|
|
3843
3807
|
admitted,
|
|
3844
3808
|
metadata,
|
|
3845
|
-
admitted.
|
|
3846
|
-
|
|
3809
|
+
admitted.routing,
|
|
3810
|
+
objectUrlFactory,
|
|
3847
3811
|
);
|
|
3848
|
-
const runtimeFiles =
|
|
3812
|
+
const runtimeFiles = completeValue(materialized.files.filter((file) =>
|
|
3849
3813
|
file.kind.startsWith("runtime-")));
|
|
3850
|
-
const modelFiles =
|
|
3814
|
+
const modelFiles = completeValue(materialized.files.filter((file) =>
|
|
3851
3815
|
!file.kind.startsWith("runtime-")));
|
|
3852
|
-
return
|
|
3853
|
-
cache:
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
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,
|
|
3860
3825
|
files: runtimeFiles,
|
|
3861
|
-
edges: metadata.edges,
|
|
3862
|
-
transforms: metadata.transforms,
|
|
3863
|
-
warnings: admitted.inspection.warnings,
|
|
3864
|
-
guardCapability: materialized.guardCapability,
|
|
3865
|
-
artifactGraphId: authority.identitySha256,
|
|
3866
|
-
artifactGraphAdmission,
|
|
3867
3826
|
}),
|
|
3868
|
-
model:
|
|
3827
|
+
model: completeValue({
|
|
3869
3828
|
...metadata.model,
|
|
3870
3829
|
files: modelFiles,
|
|
3871
3830
|
}),
|
|
@@ -3875,9 +3834,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3875
3834
|
const materialized = createObjectUrls(admitted.files, objectUrlFactory);
|
|
3876
3835
|
const runtimeFiles = materialized.files.filter((file) => file.kind === "runtime");
|
|
3877
3836
|
const modelFiles = materialized.files.filter((file) => file.kind === "model");
|
|
3878
|
-
return
|
|
3837
|
+
return completeValue({
|
|
3879
3838
|
cache: admitted.cache,
|
|
3880
|
-
runtime:
|
|
3839
|
+
runtime: completeValue({
|
|
3881
3840
|
adapter: metadata.runtime.adapter,
|
|
3882
3841
|
version: metadata.runtime.version,
|
|
3883
3842
|
revision: metadata.runtime.revision,
|
|
@@ -3886,14 +3845,15 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3886
3845
|
? {}
|
|
3887
3846
|
: { wasmPaths: metadata.runtime.wasmPaths }),
|
|
3888
3847
|
moduleGraph: "self-contained",
|
|
3889
|
-
files:
|
|
3848
|
+
files: completeValue(runtimeFiles),
|
|
3890
3849
|
}),
|
|
3891
|
-
model:
|
|
3850
|
+
model: completeValue({
|
|
3892
3851
|
id: metadata.model.id,
|
|
3893
3852
|
repository: metadata.model.repository,
|
|
3894
3853
|
revision: metadata.model.revision,
|
|
3854
|
+
...(metadata.model.dtype === undefined ? {} : { dtype: metadata.model.dtype }),
|
|
3895
3855
|
defaultVoice: metadata.model.defaultVoice,
|
|
3896
|
-
files:
|
|
3856
|
+
files: completeValue(modelFiles),
|
|
3897
3857
|
}),
|
|
3898
3858
|
release: materialized.release,
|
|
3899
3859
|
});
|
|
@@ -3932,7 +3892,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3932
3892
|
return serializeAuthority(authority, () => removeUnlocked(authority));
|
|
3933
3893
|
}
|
|
3934
3894
|
|
|
3935
|
-
const store =
|
|
3895
|
+
const store = completeValue({
|
|
3936
3896
|
protocol: BROWSER_SPEECH_ARTIFACT_PROTOCOL,
|
|
3937
3897
|
tableName,
|
|
3938
3898
|
prepare,
|