arcane-os 0.3.0 → 0.3.1
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 +13 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +11 -11
- package/browser-runtime/ai/browser-speech-artifacts.mjs +50 -32
- package/browser-runtime/ai/browser-speech-providers.mjs +77 -9
- package/browser-runtime/ai/speech-worker-client.mjs +6 -4
- package/browser-runtime/ai/speech-worker-runtime.mjs +20 -8
- package/docs/architecture.md +2 -2
- package/docs/reference/README.md +12 -14
- package/docs/reference/ai/browser-speech-package-authority.json +1 -1
- package/docs/reference/ai/browser-speech.md +113 -70
- package/docs/reference/ai/browser-wasm.md +1 -1
- package/docs/reference/availability-and-normalization.md +2 -2
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +1 -1
- package/docs/reference/mail.md +1 -1
- package/docs/reference/protocols.md +55 -13
- package/docs/reference/runtime-modules.md +6 -1
- package/docs/reference/sdk-api.md +14 -10
- package/package.json +1 -1
- package/runtime/ARCANE_RUNTIME_RELEASE.json +7 -7
- package/runtime/arcane/modules/AI.js +8 -0
- package/runtime/arcane/modules/AIProviderRuntime.js +40 -4
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/installed-sdk-runtime.mjs +46 -28
- package/src/workspace-runtime.mjs +804 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
- Restored ordinary warn-first Browser Speech operation with explicit
|
|
6
|
+
`secure:false` defaults, one concise load warning, and truthful
|
|
7
|
+
`unchecked`/`pending`/`verified`/`failed` integrity telemetry while keeping
|
|
8
|
+
strict authenticated-graph admission behind explicit `secure:true`.
|
|
9
|
+
- Added atomic hydration for exact saved-but-unregistered STT/TTS selections
|
|
10
|
+
without a transient provider fallback, including independent mixed
|
|
11
|
+
browser/Cloud/Core role ownership and mismatch-safe rollback.
|
|
12
|
+
- Continued to resolve speech runtimes, models, voices, and providers from
|
|
13
|
+
their upstream npm/fetch distribution paths; the SDK package redistributes
|
|
14
|
+
none of those third-party payloads or their legal corpora.
|
|
15
|
+
|
|
3
16
|
## 0.3.0
|
|
4
17
|
|
|
5
18
|
- Added the branded, versioned, per-realm `globalThis.arcaneEvents` authority
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.3.
|
|
22
|
+
This checkout defines the `0.3.1` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into immutable
|
|
24
24
|
application artifacts.
|
|
25
25
|
|
|
@@ -132,7 +132,7 @@ uses the same controller for automatic memory extraction.
|
|
|
132
132
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
|
-
npx arcane-os@0.3.
|
|
135
|
+
npx arcane-os@0.3.1 new my-app --path ./my-app --target portable --git
|
|
136
136
|
cd my-app
|
|
137
137
|
npm install
|
|
138
138
|
npm run check
|
|
@@ -143,7 +143,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
143
143
|
missing Arcane files:
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
|
-
npm install --save-dev --save-exact arcane-os@0.3.
|
|
146
|
+
npm install --save-dev --save-exact arcane-os@0.3.1
|
|
147
147
|
npm exec -- arcane init my-app --target portable
|
|
148
148
|
```
|
|
149
149
|
|
|
@@ -159,7 +159,7 @@ npm exec -- arcane-os targets
|
|
|
159
159
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
160
160
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
161
161
|
|
|
162
|
-
Use `npx arcane-os@0.3.
|
|
162
|
+
Use `npx arcane-os@0.3.1` for the initial bootstrap because it names this npm
|
|
163
163
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
164
164
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
165
165
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -180,7 +180,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
180
180
|
|
|
181
181
|
# From the generated app repository
|
|
182
182
|
cd ../local-app
|
|
183
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.
|
|
183
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.1.tgz
|
|
184
184
|
npm run check
|
|
185
185
|
npm ci
|
|
186
186
|
```
|
|
@@ -191,7 +191,7 @@ still verifies the installed package name and exact version, the locked runtime
|
|
|
191
191
|
identity, and the runtime bytes. Local directory `file:` dependencies are not
|
|
192
192
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
193
193
|
runner also needs that tarball at the locked path. After publication, replace
|
|
194
|
-
the local declaration with the exact `arcane-os@0.3.
|
|
194
|
+
the local declaration with the exact `arcane-os@0.3.1` registry package and
|
|
195
195
|
commit the regenerated lock.
|
|
196
196
|
|
|
197
197
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -304,7 +304,7 @@ recorded length through a final identity check; an appended byte, concurrent
|
|
|
304
304
|
growth, path replacement, or hard link fails closed. NFC paths use defined
|
|
305
305
|
UTF-8 byte ordering, covered by one pinned golden bundle digest on every
|
|
306
306
|
supported Node/runner combination. This SDK accepts only the explicitly listed
|
|
307
|
-
`0.3.
|
|
307
|
+
`0.3.1` bundle generation; structural validity does not imply cross-SDK
|
|
308
308
|
compatibility, and a release with zero payload bytes cannot be created. Portable
|
|
309
309
|
path validation rejects file/directory prefix conflicts, case-colliding prefix
|
|
310
310
|
spellings, and Windows device aliases including superscript COM/LPT digits.
|
|
@@ -350,7 +350,7 @@ package installation, or assertions.
|
|
|
350
350
|
|
|
351
351
|
## Current target support
|
|
352
352
|
|
|
353
|
-
Version `0.3.
|
|
353
|
+
Version `0.3.1` exposes one browser target and five explicitly paired
|
|
354
354
|
native development targets: a verified non-runnable portable directory, a
|
|
355
355
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
356
356
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"builder": "arcane-sdk-browser-runtime-v1",
|
|
4
|
-
"sdkVersion": "0.3.
|
|
4
|
+
"sdkVersion": "0.3.1",
|
|
5
5
|
"source": {
|
|
6
6
|
"authority": "arcane-os-sdk",
|
|
7
7
|
"repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
31
|
"fileCount": 26,
|
|
32
|
-
"totalBytes":
|
|
33
|
-
"contentSha256": "
|
|
32
|
+
"totalBytes": 9554968,
|
|
33
|
+
"contentSha256": "1493497265c330507abed847e52e65dc2ce22c15efaf5646ed7ae544b107ad6f",
|
|
34
34
|
"files": [
|
|
35
35
|
{
|
|
36
36
|
"path": "ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json",
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"path": "ai/browser-speech-artifacts.mjs",
|
|
58
58
|
"sourcePath": "browser-runtime/ai/browser-speech-artifacts.mjs",
|
|
59
59
|
"provenance": "sdk-source-identity",
|
|
60
|
-
"bytes":
|
|
61
|
-
"sha256": "
|
|
60
|
+
"bytes": 135988,
|
|
61
|
+
"sha256": "2587922a708f014ae8b4e819a607b1a5b322fc4eb969e470cd6c068b33b3adcd"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"path": "ai/browser-speech-providers.mjs",
|
|
65
65
|
"sourcePath": "browser-runtime/ai/browser-speech-providers.mjs",
|
|
66
66
|
"provenance": "sdk-source-identity",
|
|
67
|
-
"bytes":
|
|
68
|
-
"sha256": "
|
|
67
|
+
"bytes": 59278,
|
|
68
|
+
"sha256": "84802cba366c3a54013214240ebaf3ae2896322efc965aab4054a61ca9d06aab"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"path": "ai/browser-speech.mjs",
|
|
@@ -120,15 +120,15 @@
|
|
|
120
120
|
"path": "ai/speech-worker-client.mjs",
|
|
121
121
|
"sourcePath": "browser-runtime/ai/speech-worker-client.mjs",
|
|
122
122
|
"provenance": "sdk-source-identity",
|
|
123
|
-
"bytes":
|
|
124
|
-
"sha256": "
|
|
123
|
+
"bytes": 14247,
|
|
124
|
+
"sha256": "2e5580a86d5adc4f44a0d680e1820437b8d22b5555bf73a36b361ee50d24b3f5"
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"path": "ai/speech-worker-runtime.mjs",
|
|
128
128
|
"sourcePath": "browser-runtime/ai/speech-worker-runtime.mjs",
|
|
129
129
|
"provenance": "sdk-source-identity",
|
|
130
|
-
"bytes":
|
|
131
|
-
"sha256": "
|
|
130
|
+
"bytes": 91125,
|
|
131
|
+
"sha256": "c865ad9a84df30ea1d14c81e7bb5c38666afdb65d5237472cdabfb11e7c66de4"
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
"path": "ai/wllama/LICENCE",
|
|
@@ -2863,13 +2863,6 @@ function assertArtifactGraphStaticImportClosure(metadata) {
|
|
|
2863
2863
|
}
|
|
2864
2864
|
|
|
2865
2865
|
async function inspectArtifactGraphRuntime(admitted, metadata, signal, security) {
|
|
2866
|
-
if (security?.secure !== true) {
|
|
2867
|
-
return Object.freeze({
|
|
2868
|
-
plans: new Map(),
|
|
2869
|
-
order: Object.freeze([]),
|
|
2870
|
-
warnings: Object.freeze(["artifact-graph-runtime-unchecked"]),
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2873
2866
|
const admittedByPath = new Map(admitted.files.map((entry) => [entry.descriptor.path, entry]));
|
|
2874
2867
|
const plans = new Map();
|
|
2875
2868
|
for (const descriptor of metadata.runtimeFiles) {
|
|
@@ -2903,15 +2896,25 @@ async function inspectArtifactGraphRuntime(admitted, metadata, signal, security)
|
|
|
2903
2896
|
const order = assertArtifactGraphStaticImportClosure(metadata);
|
|
2904
2897
|
const warnings = [...new Set([...plans.values()].flatMap((plan) => plan.warnings))]
|
|
2905
2898
|
.sort();
|
|
2906
|
-
if (security?.secure !== true && warnings.length > 0) {
|
|
2907
|
-
globalThis.console?.warn?.(
|
|
2908
|
-
`Arcane browser speech warn-first mode allowed runtime capabilities: ${warnings.join(", ")}.`,
|
|
2909
|
-
);
|
|
2910
|
-
}
|
|
2911
2899
|
throwIfAborted(signal);
|
|
2912
2900
|
return Object.freeze({ plans, order, warnings: Object.freeze(warnings) });
|
|
2913
2901
|
}
|
|
2914
2902
|
|
|
2903
|
+
function assertArtifactGraphRuntimeInspection(inspection) {
|
|
2904
|
+
if (
|
|
2905
|
+
!inspection
|
|
2906
|
+
|| !(inspection.plans instanceof Map)
|
|
2907
|
+
|| !Array.isArray(inspection.order)
|
|
2908
|
+
|| !Array.isArray(inspection.warnings)
|
|
2909
|
+
) {
|
|
2910
|
+
throw artifactGraphError(
|
|
2911
|
+
"artifact-graph-runtime-inspection-missing",
|
|
2912
|
+
"Artifact graph runtime inspection is required before executable materialization.",
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
return inspection;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2915
2918
|
function applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapability) {
|
|
2916
2919
|
const replacements = [];
|
|
2917
2920
|
for (let index = 0; index < plan.staticImports.length; index += 1) {
|
|
@@ -3033,6 +3036,7 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3033
3036
|
const created = [];
|
|
3034
3037
|
const createdIdentities = new Set();
|
|
3035
3038
|
const guardCapability = artifactGraphGuardCapability();
|
|
3039
|
+
const runtimeInspection = assertArtifactGraphRuntimeInspection(inspection);
|
|
3036
3040
|
async function materialize(descriptor, body) {
|
|
3037
3041
|
const blob = body instanceof Blob && body.type === descriptor.mediaType
|
|
3038
3042
|
? body
|
|
@@ -3131,20 +3135,42 @@ async function createArtifactGraphObjectUrls(admitted, metadata, inspection, sec
|
|
|
3131
3135
|
try {
|
|
3132
3136
|
for (const descriptor of metadata.files) {
|
|
3133
3137
|
if (ARTIFACT_GRAPH_JAVASCRIPT_KINDS.has(descriptor.kind)) continue;
|
|
3134
|
-
|
|
3138
|
+
const admittedFile = admittedByPath.get(descriptor.path)?.file;
|
|
3139
|
+
if (!admittedFile) {
|
|
3140
|
+
throw artifactGraphError(
|
|
3141
|
+
"artifact-graph-materialized-source-file-missing",
|
|
3142
|
+
`Artifact graph file ${descriptor.path} is unavailable for materialization.`,
|
|
3143
|
+
);
|
|
3144
|
+
}
|
|
3145
|
+
await materialize(descriptor, admittedFile);
|
|
3135
3146
|
}
|
|
3136
|
-
for (const path of
|
|
3147
|
+
for (const path of runtimeInspection.order) {
|
|
3137
3148
|
const descriptor = metadata.filesByPath.get(path);
|
|
3138
|
-
const plan =
|
|
3149
|
+
const plan = runtimeInspection.plans.get(path);
|
|
3150
|
+
if (!descriptor || !plan) {
|
|
3151
|
+
throw artifactGraphError(
|
|
3152
|
+
"artifact-graph-runtime-inspection-incomplete",
|
|
3153
|
+
`Artifact graph runtime inspection is incomplete for ${path}.`,
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3139
3156
|
await materialize(
|
|
3140
3157
|
descriptor,
|
|
3141
3158
|
applyArtifactGraphModuleTransforms(plan, materializedByPath, guardCapability),
|
|
3142
3159
|
);
|
|
3143
3160
|
}
|
|
3161
|
+
const files = metadata.files.map((descriptor) => {
|
|
3162
|
+
const materialized = materializedByPath.get(descriptor.path);
|
|
3163
|
+
if (!materialized) {
|
|
3164
|
+
throw artifactGraphError(
|
|
3165
|
+
"artifact-graph-materialization-incomplete",
|
|
3166
|
+
`Artifact graph file ${descriptor.path} was not materialized.`,
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
return materialized;
|
|
3170
|
+
});
|
|
3144
3171
|
return Object.freeze({
|
|
3145
3172
|
guardCapability,
|
|
3146
|
-
files: Object.freeze(
|
|
3147
|
-
materializedByPath.get(descriptor.path))),
|
|
3173
|
+
files: Object.freeze(files),
|
|
3148
3174
|
release() {
|
|
3149
3175
|
for (const url of created.splice(0).reverse()) {
|
|
3150
3176
|
try {
|
|
@@ -3436,11 +3462,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3436
3462
|
files.push({ descriptor, file });
|
|
3437
3463
|
}
|
|
3438
3464
|
try {
|
|
3439
|
-
const inspection = graph
|
|
3465
|
+
const inspection = graph
|
|
3440
3466
|
? await inspectArtifactGraphRuntime({ files }, metadata, signal, security)
|
|
3441
|
-
:
|
|
3442
|
-
? null
|
|
3443
|
-
: (await assertSelfContainedRuntime({ files }, metadata, security), null);
|
|
3467
|
+
: (await assertSelfContainedRuntime({ files }, metadata, security), null);
|
|
3444
3468
|
throwIfAborted(signal);
|
|
3445
3469
|
return Object.freeze({
|
|
3446
3470
|
files: Object.freeze(files),
|
|
@@ -3734,11 +3758,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3734
3758
|
}
|
|
3735
3759
|
installed.push({ descriptor, file });
|
|
3736
3760
|
}
|
|
3737
|
-
const inspection = graph
|
|
3761
|
+
const inspection = graph
|
|
3738
3762
|
? await inspectArtifactGraphRuntime({ files: installed }, metadata, signal, security)
|
|
3739
|
-
:
|
|
3740
|
-
? null
|
|
3741
|
-
: (await assertSelfContainedRuntime({ files: installed }, metadata, security), null);
|
|
3763
|
+
: (await assertSelfContainedRuntime({ files: installed }, metadata, security), null);
|
|
3742
3764
|
throwIfAborted(signal);
|
|
3743
3765
|
const manifest = Object.freeze({
|
|
3744
3766
|
schema: graph ? ARTIFACT_GRAPH_MANIFEST_SCHEMA : MANIFEST_SCHEMA,
|
|
@@ -3789,12 +3811,6 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3789
3811
|
throw error;
|
|
3790
3812
|
}
|
|
3791
3813
|
const metadata = artifactMetadata(authority);
|
|
3792
|
-
if (graph && effectiveSecurity.secure !== true) {
|
|
3793
|
-
throw artifactGraphError(
|
|
3794
|
-
"artifact-graph-secure-mode-required",
|
|
3795
|
-
"Browser speech artifact graphs require explicit secure:true.",
|
|
3796
|
-
);
|
|
3797
|
-
}
|
|
3798
3814
|
assertSecurityDescriptors(metadata.files, effectiveSecurity);
|
|
3799
3815
|
const cached = await openCached(authority, {
|
|
3800
3816
|
signal,
|
|
@@ -3812,7 +3828,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3812
3828
|
if (graph) {
|
|
3813
3829
|
throw artifactGraphError(
|
|
3814
3830
|
"artifact-graph-offline-cache-miss",
|
|
3815
|
-
"No complete
|
|
3831
|
+
"No complete admitted offline artifact graph cache is available.",
|
|
3816
3832
|
);
|
|
3817
3833
|
}
|
|
3818
3834
|
throw speechError("ARCANE_AI_ARTIFACT_OFFLINE_MISS", "No admitted offline speech cache is available.");
|
|
@@ -3837,12 +3853,14 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
3837
3853
|
cache: artifactGraphAdmission,
|
|
3838
3854
|
artifactGraphId: authority.identitySha256,
|
|
3839
3855
|
artifactGraphAdmission,
|
|
3856
|
+
warnings: admitted.inspection.warnings,
|
|
3840
3857
|
security: effectiveSecurity,
|
|
3841
3858
|
runtime: Object.freeze({
|
|
3842
3859
|
...metadata.runtime,
|
|
3843
3860
|
files: runtimeFiles,
|
|
3844
3861
|
edges: metadata.edges,
|
|
3845
3862
|
transforms: metadata.transforms,
|
|
3863
|
+
warnings: admitted.inspection.warnings,
|
|
3846
3864
|
guardCapability: materialized.guardCapability,
|
|
3847
3865
|
artifactGraphId: authority.identitySha256,
|
|
3848
3866
|
artifactGraphAdmission,
|
|
@@ -27,6 +27,9 @@ const ROLE_REQUEST_REASON = Object.freeze({
|
|
|
27
27
|
tts: "tts-synthesis-cancelled",
|
|
28
28
|
});
|
|
29
29
|
const UNMAPPED_PROVIDER_REASON = "browser-speech-provider-error-reason-unmapped";
|
|
30
|
+
const WARN_FIRST_WARNING = "browser-speech-warn-first-secure-mode-disabled";
|
|
31
|
+
const NO_PROVIDER_WARNINGS = Object.freeze([]);
|
|
32
|
+
const WARN_FIRST_PROVIDER_WARNINGS = Object.freeze([WARN_FIRST_WARNING]);
|
|
30
33
|
const WORKER_FAILURE_CODES = new Set([
|
|
31
34
|
"ARCANE_AI_WORKER_CRASHED",
|
|
32
35
|
"ARCANE_AI_WORKER_MESSAGE_ERROR",
|
|
@@ -298,6 +301,34 @@ function graphSecurity(scope, label) {
|
|
|
298
301
|
return normalizeModelSecurity(scope, label);
|
|
299
302
|
}
|
|
300
303
|
|
|
304
|
+
function providerIntegrity(security, outcome = "pending") {
|
|
305
|
+
const enabledCount = Number(security.checks.byteLength)
|
|
306
|
+
+ Number(security.checks.sha256);
|
|
307
|
+
const state = enabledCount === 0 ? "unchecked" : outcome;
|
|
308
|
+
if (!["unchecked", "pending", "verified", "failed"].includes(state)) {
|
|
309
|
+
throw new TypeError("Browser speech integrity outcome is invalid.");
|
|
310
|
+
}
|
|
311
|
+
return Object.freeze({
|
|
312
|
+
state,
|
|
313
|
+
byteLength: Object.freeze({
|
|
314
|
+
enabled: security.checks.byteLength,
|
|
315
|
+
state: security.checks.byteLength ? state : "unchecked",
|
|
316
|
+
}),
|
|
317
|
+
sha256: Object.freeze({
|
|
318
|
+
enabled: security.checks.sha256,
|
|
319
|
+
state: security.checks.sha256 ? state : "unchecked",
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function providerWarnings(security, runtimeWarnings = NO_PROVIDER_WARNINGS) {
|
|
325
|
+
const warnings = security.secure === true
|
|
326
|
+
? runtimeWarnings
|
|
327
|
+
: [...WARN_FIRST_PROVIDER_WARNINGS, ...runtimeWarnings];
|
|
328
|
+
if (warnings.length === 0) return NO_PROVIDER_WARNINGS;
|
|
329
|
+
return Object.freeze([...new Set(warnings)]);
|
|
330
|
+
}
|
|
331
|
+
|
|
301
332
|
function publicStatus({
|
|
302
333
|
role,
|
|
303
334
|
id,
|
|
@@ -311,6 +342,8 @@ function publicStatus({
|
|
|
311
342
|
activeOperation,
|
|
312
343
|
artifactGraphAdmission,
|
|
313
344
|
security,
|
|
345
|
+
integrity,
|
|
346
|
+
warnings,
|
|
314
347
|
}) {
|
|
315
348
|
return Object.freeze({
|
|
316
349
|
role,
|
|
@@ -326,6 +359,8 @@ function publicStatus({
|
|
|
326
359
|
errorCode,
|
|
327
360
|
cache,
|
|
328
361
|
security,
|
|
362
|
+
integrity,
|
|
363
|
+
warnings,
|
|
329
364
|
artifactGraphId: authority.artifactGraphId ?? null,
|
|
330
365
|
artifactGraphAdmission,
|
|
331
366
|
});
|
|
@@ -1101,8 +1136,22 @@ function createBrowserSpeechProvider({
|
|
|
1101
1136
|
let unloadOperation = null;
|
|
1102
1137
|
let disposeOperation = null;
|
|
1103
1138
|
let requestOperation = null;
|
|
1139
|
+
let lastWarnings = NO_PROVIDER_WARNINGS;
|
|
1140
|
+
let lastIntegrity = Object.freeze({
|
|
1141
|
+
security: defaultSecurity,
|
|
1142
|
+
integrity: providerIntegrity(defaultSecurity),
|
|
1143
|
+
});
|
|
1104
1144
|
|
|
1105
1145
|
function status() {
|
|
1146
|
+
const effectiveSecurity = active?.security
|
|
1147
|
+
?? loadOperation?.security
|
|
1148
|
+
?? lastIntegrity.security;
|
|
1149
|
+
const integrity = active?.integrity
|
|
1150
|
+
?? loadOperation?.integrity
|
|
1151
|
+
?? lastIntegrity.integrity;
|
|
1152
|
+
const runtimeWarnings = active?.warnings
|
|
1153
|
+
?? loadOperation?.warnings
|
|
1154
|
+
?? lastWarnings;
|
|
1106
1155
|
return publicStatus({
|
|
1107
1156
|
role,
|
|
1108
1157
|
id: providerId,
|
|
@@ -1115,7 +1164,9 @@ function createBrowserSpeechProvider({
|
|
|
1115
1164
|
lifecycleReason,
|
|
1116
1165
|
activeOperation,
|
|
1117
1166
|
artifactGraphAdmission,
|
|
1118
|
-
security:
|
|
1167
|
+
security: effectiveSecurity,
|
|
1168
|
+
integrity,
|
|
1169
|
+
warnings: providerWarnings(effectiveSecurity, runtimeWarnings),
|
|
1119
1170
|
});
|
|
1120
1171
|
}
|
|
1121
1172
|
|
|
@@ -1270,14 +1321,6 @@ function createBrowserSpeechProvider({
|
|
|
1270
1321
|
"Browser speech graph load security",
|
|
1271
1322
|
),
|
|
1272
1323
|
});
|
|
1273
|
-
if (effectiveSecurity.secure !== true) {
|
|
1274
|
-
throw providerError(
|
|
1275
|
-
"ARCANE_AI_SECURE_MODE_REQUIRED",
|
|
1276
|
-
"Authenticated browser speech artifact graphs require explicit secure:true.",
|
|
1277
|
-
undefined,
|
|
1278
|
-
`${role}-artifact-graph-secure-mode-required`,
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
1324
|
} else {
|
|
1282
1325
|
effectiveSecurity = resolveModelSecurity({
|
|
1283
1326
|
app: configuredAppSecurity,
|
|
@@ -1320,6 +1363,8 @@ function createBrowserSpeechProvider({
|
|
|
1320
1363
|
const record = {
|
|
1321
1364
|
promise: null,
|
|
1322
1365
|
security: effectiveSecurity,
|
|
1366
|
+
integrity: providerIntegrity(effectiveSecurity),
|
|
1367
|
+
warnings: NO_PROVIDER_WARNINGS,
|
|
1323
1368
|
observers: new Set(),
|
|
1324
1369
|
settled: false,
|
|
1325
1370
|
hasProgress: false,
|
|
@@ -1334,6 +1379,11 @@ function createBrowserSpeechProvider({
|
|
|
1334
1379
|
for (const observer of [...record.observers]) observer.progress(progress);
|
|
1335
1380
|
},
|
|
1336
1381
|
};
|
|
1382
|
+
lastIntegrity = Object.freeze({
|
|
1383
|
+
security: effectiveSecurity,
|
|
1384
|
+
integrity: record.integrity,
|
|
1385
|
+
});
|
|
1386
|
+
lastWarnings = NO_PROVIDER_WARNINGS;
|
|
1337
1387
|
const promise = Promise.resolve().then(async () => {
|
|
1338
1388
|
let prepared = null;
|
|
1339
1389
|
let slot = null;
|
|
@@ -1344,6 +1394,15 @@ function createBrowserSpeechProvider({
|
|
|
1344
1394
|
offline,
|
|
1345
1395
|
security: effectiveSecurity,
|
|
1346
1396
|
});
|
|
1397
|
+
record.integrity = providerIntegrity(effectiveSecurity, "verified");
|
|
1398
|
+
record.warnings = Array.isArray(prepared.warnings)
|
|
1399
|
+
? Object.freeze([...prepared.warnings])
|
|
1400
|
+
: NO_PROVIDER_WARNINGS;
|
|
1401
|
+
lastIntegrity = Object.freeze({
|
|
1402
|
+
security: effectiveSecurity,
|
|
1403
|
+
integrity: record.integrity,
|
|
1404
|
+
});
|
|
1405
|
+
lastWarnings = record.warnings;
|
|
1347
1406
|
throwIfAborted(
|
|
1348
1407
|
linked.controller.signal,
|
|
1349
1408
|
() => linked.reason(`${role}-load-cancelled`),
|
|
@@ -1363,6 +1422,8 @@ function createBrowserSpeechProvider({
|
|
|
1363
1422
|
released: false,
|
|
1364
1423
|
client: null,
|
|
1365
1424
|
security: effectiveSecurity,
|
|
1425
|
+
integrity: record.integrity,
|
|
1426
|
+
warnings: record.warnings,
|
|
1366
1427
|
};
|
|
1367
1428
|
slot.client = createSpeechWorkerClient({
|
|
1368
1429
|
role,
|
|
@@ -1447,6 +1508,13 @@ function createBrowserSpeechProvider({
|
|
|
1447
1508
|
let failure = linked.controller.signal.aborted
|
|
1448
1509
|
? linkedAbortFailure(linked, `${role}-load-cancelled`)
|
|
1449
1510
|
: trustedLoadFailure(error, role);
|
|
1511
|
+
if (!linked.controller.signal.aborted && prepared === null) {
|
|
1512
|
+
record.integrity = providerIntegrity(effectiveSecurity, "failed");
|
|
1513
|
+
lastIntegrity = Object.freeze({
|
|
1514
|
+
security: effectiveSecurity,
|
|
1515
|
+
integrity: record.integrity,
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1450
1518
|
try {
|
|
1451
1519
|
if (slot) await terminateSlot(slot, failure);
|
|
1452
1520
|
else prepared?.release();
|
|
@@ -176,7 +176,7 @@ class SpeechWorkerClient {
|
|
|
176
176
|
if (typeof MessageChannelConstructor !== "function") {
|
|
177
177
|
throw clientError(
|
|
178
178
|
"ARCANE_AI_WORKER_MESSAGE_ERROR",
|
|
179
|
-
"
|
|
179
|
+
"Strict artifact graph loading requires MessageChannel.",
|
|
180
180
|
undefined,
|
|
181
181
|
"artifact-graph-private-message-channel-unavailable",
|
|
182
182
|
);
|
|
@@ -294,18 +294,20 @@ class SpeechWorkerClient {
|
|
|
294
294
|
worker = this.#start();
|
|
295
295
|
const graphLoad = op === "load"
|
|
296
296
|
&& payload?.configuration?.runtime?.moduleGraph === ARTIFACT_GRAPH_MODULE_GRAPH;
|
|
297
|
-
|
|
297
|
+
const strictGraphLoad = graphLoad
|
|
298
|
+
&& payload?.configuration?.security?.secure === true;
|
|
299
|
+
if (strictGraphLoad) {
|
|
298
300
|
initialPrivatePort = this.#establishPrivateTransport();
|
|
299
301
|
} else if (this.#transportMode === null) {
|
|
300
302
|
this.#transportMode = "worker-global-message";
|
|
301
303
|
this.#transport = worker;
|
|
302
304
|
}
|
|
303
305
|
if (op === "load"
|
|
304
|
-
&& !
|
|
306
|
+
&& !strictGraphLoad
|
|
305
307
|
&& this.#transportMode === "private-message-port") {
|
|
306
308
|
throw clientError(
|
|
307
309
|
"ARCANE_AI_ADAPTER_PROTOCOL_MISMATCH",
|
|
308
|
-
"A private artifact graph Worker cannot load
|
|
310
|
+
"A private strict artifact graph Worker cannot load an ordinary runtime descriptor.",
|
|
309
311
|
undefined,
|
|
310
312
|
`${this.#role}-worker-runtime-transport-mode-mismatch`,
|
|
311
313
|
);
|
|
@@ -7,7 +7,7 @@ const ARTIFACT_GRAPH_MODULE_GRAPH =
|
|
|
7
7
|
const GRAPH_GUARD_NAME = "__arcaneBrowserSpeechArtifactGraphGuardsV1";
|
|
8
8
|
const NESTED_WORKER_PROTOCOL =
|
|
9
9
|
"arcane-ai-browser-speech-artifact-module-worker/1";
|
|
10
|
-
const
|
|
10
|
+
const GRAPH_ADMISSIONS = new Set([
|
|
11
11
|
"artifact-graph-network-dbopfs-verified",
|
|
12
12
|
"artifact-graph-dbopfs-cache-verified",
|
|
13
13
|
"artifact-graph-offline-dbopfs-cache-verified",
|
|
@@ -695,14 +695,24 @@ function validateConfiguration(configuration, role) {
|
|
|
695
695
|
paths.add(file.path);
|
|
696
696
|
}
|
|
697
697
|
const runtimePaths = new Set(configuration.runtime.files.map((file) => file.path));
|
|
698
|
+
const security = configuration.security;
|
|
699
|
+
const securityChecks = security?.checks;
|
|
698
700
|
if (
|
|
699
|
-
|
|
701
|
+
!security
|
|
702
|
+
|| typeof security !== "object"
|
|
703
|
+
|| Array.isArray(security)
|
|
704
|
+
|| typeof security.secure !== "boolean"
|
|
705
|
+
|| !securityChecks
|
|
706
|
+
|| typeof securityChecks !== "object"
|
|
707
|
+
|| Array.isArray(securityChecks)
|
|
708
|
+
|| typeof securityChecks.byteLength !== "boolean"
|
|
709
|
+
|| typeof securityChecks.sha256 !== "boolean"
|
|
700
710
|
|| typeof configuration.runtime.artifactGraphId !== "string"
|
|
701
711
|
|| !/^[a-f0-9]{64}$/u.test(configuration.runtime.artifactGraphId)
|
|
702
712
|
|| (
|
|
703
713
|
typeof configuration.runtime.guardCapability !== "string"
|
|
704
714
|
|| !/^[a-f0-9]{64}$/u.test(configuration.runtime.guardCapability)
|
|
705
|
-
|| !
|
|
715
|
+
|| !GRAPH_ADMISSIONS.has(configuration.runtime.artifactGraphAdmission)
|
|
706
716
|
|| !configuration.runtime.edges
|
|
707
717
|
|| typeof configuration.runtime.edges !== "object"
|
|
708
718
|
|| !Array.isArray(configuration.runtime.transforms)
|
|
@@ -710,7 +720,7 @@ function validateConfiguration(configuration, role) {
|
|
|
710
720
|
) {
|
|
711
721
|
throw workerError(
|
|
712
722
|
"ARCANE_AI_ARTIFACT_GRAPH_CONFIGURATION_INVALID",
|
|
713
|
-
"
|
|
723
|
+
"Artifact graph security, identity, admission, edges, and transforms are required.",
|
|
714
724
|
undefined,
|
|
715
725
|
"artifact-graph-worker-configuration-incomplete",
|
|
716
726
|
);
|
|
@@ -2293,7 +2303,7 @@ export function createSpeechWorkerRuntime({ role, scope = globalThis, send } = {
|
|
|
2293
2303
|
configuration = validateConfiguration(request.payload?.configuration, role);
|
|
2294
2304
|
const entry = configuration.runtime.files.find((file) =>
|
|
2295
2305
|
file.path === configuration.runtime.entry);
|
|
2296
|
-
if (graphConfiguration(configuration)
|
|
2306
|
+
if (graphConfiguration(configuration)) {
|
|
2297
2307
|
environment = installArtifactGraphEnvironment(scope, configuration, role);
|
|
2298
2308
|
} else if (configuration.security?.secure === true) {
|
|
2299
2309
|
const legacyFetch = installLegacyAuthorizedFetch(scope, configuration);
|
|
@@ -2561,8 +2571,10 @@ export function installBrowserSpeechWorker(role, scope = globalThis) {
|
|
|
2561
2571
|
const requestedPort = privatePort(event.data?.privatePort);
|
|
2562
2572
|
const isGraphLoad = event.data?.op === "load"
|
|
2563
2573
|
&& graphConfiguration(event.data?.payload?.configuration);
|
|
2574
|
+
const isStrictGraphLoad = isGraphLoad
|
|
2575
|
+
&& event.data?.payload?.configuration?.security?.secure === true;
|
|
2564
2576
|
if (requestedPort) {
|
|
2565
|
-
if (!
|
|
2577
|
+
if (!isStrictGraphLoad || transportMode !== null) return;
|
|
2566
2578
|
transportMode = "private-message-port";
|
|
2567
2579
|
transport = requestedPort;
|
|
2568
2580
|
requestedPort.addEventListener("message", (portEvent) => receive(portEvent.data));
|
|
@@ -2572,14 +2584,14 @@ export function installBrowserSpeechWorker(role, scope = globalThis) {
|
|
|
2572
2584
|
receive(request);
|
|
2573
2585
|
return;
|
|
2574
2586
|
}
|
|
2575
|
-
if (
|
|
2587
|
+
if (isStrictGraphLoad) {
|
|
2576
2588
|
scope.postMessage({
|
|
2577
2589
|
protocol: SPEECH_WORKER_PROTOCOL,
|
|
2578
2590
|
id: event.data.id,
|
|
2579
2591
|
ok: false,
|
|
2580
2592
|
error: serializedError(workerError(
|
|
2581
2593
|
"ARCANE_AI_ARTIFACT_GRAPH_ISOLATION_UNAVAILABLE",
|
|
2582
|
-
"
|
|
2594
|
+
"Strict artifact graph loading requires a private MessagePort.",
|
|
2583
2595
|
undefined,
|
|
2584
2596
|
"artifact-graph-private-message-port-missing",
|
|
2585
2597
|
), role, "load"),
|
package/docs/architecture.md
CHANGED
|
@@ -200,7 +200,7 @@ length with an EOF growth probe, and rechecked by handle and pathname identity.
|
|
|
200
200
|
Every cumulative path prefix has one case-folded spelling and one file/directory
|
|
201
201
|
kind; prefix topology conflicts and the complete portable Windows device-name
|
|
202
202
|
set fail before creation or admission.
|
|
203
|
-
The current SDK admits only the explicitly compatible `0.3.
|
|
203
|
+
The current SDK admits only the explicitly compatible `0.3.1` bundle
|
|
204
204
|
generation and rejects zero-byte payload releases.
|
|
205
205
|
|
|
206
206
|
Promotion retains any prior output as an identity-bound backup until the new
|
|
@@ -320,7 +320,7 @@ through SDK-bound verified readers rather than accepting a mutable source path
|
|
|
320
320
|
as authority. Build completion requires provider verification, and later
|
|
321
321
|
verify/run calls receive the exact artifact receipt.
|
|
322
322
|
|
|
323
|
-
The SDK `0.3.
|
|
323
|
+
The SDK `0.3.1` runtime requires Arcane `0.8.12` or newer. Compatibility
|
|
324
324
|
is contractual rather than exact-version pinning: the prepared Core must meet
|
|
325
325
|
the highest minimum declared by the runtime, selected app, and bundled app
|
|
326
326
|
dependencies; keep each app's Arcane protocol generation; and provide every
|
package/docs/reference/README.md
CHANGED
|
@@ -58,25 +58,23 @@ See [Core reference provenance](core/README.md) for the imported inventory and
|
|
|
58
58
|
the exact distinction between a documentation snapshot and shipped runtime
|
|
59
59
|
bytes.
|
|
60
60
|
|
|
61
|
-
## Published 0.
|
|
61
|
+
## Published 0.3.0
|
|
62
62
|
|
|
63
|
-
The
|
|
64
|
-
|
|
65
|
-
`
|
|
66
|
-
to `0.2.3`; the separate `dev` dist-tag remains `0.1.0-dev.5`.
|
|
63
|
+
The verified published package is exactly `arcane-os@0.3.0` from source commit
|
|
64
|
+
`72239d0221279b461f1be08b02a70105f4cf7364`. The npm `latest` dist-tag resolves
|
|
65
|
+
to `0.3.0`; the separate `dev` dist-tag remains `0.1.0-dev.5`.
|
|
67
66
|
|
|
68
|
-
The singleton-event, Mail, warn-first Browser Speech, and mixed-route
|
|
69
|
-
documented below are
|
|
70
|
-
`0.2.3`; consumers require the numeric 0.3.0 publication before relying on them.
|
|
67
|
+
The singleton-event, Mail, warn-first Browser Speech, and independent mixed-route
|
|
68
|
+
contracts documented below are included in this exact package authority.
|
|
71
69
|
|
|
72
70
|
| Evidence | Exact value |
|
|
73
71
|
| --- | --- |
|
|
74
|
-
| npm integrity | `sha512-
|
|
75
|
-
| npm shasum | `
|
|
76
|
-
| Immutable release tarball | `arcane-os-0.
|
|
77
|
-
| GitHub release | [`0.
|
|
78
|
-
| Hosted source/artifact gate | [Check run
|
|
79
|
-
| Trusted publication | [Run
|
|
72
|
+
| npm integrity | `sha512-dh7QfM1ur48hOyMez1XWjym/LJcOAYy+Xl3KcvVQ6ewwX5N7ER82PQx6LE+zSb5sHVtz4TOU2AuARsrFNSwuVw==` |
|
|
73
|
+
| npm shasum | `94075dad9c3974b0e069ecfe09f8d612d8a30ea7` |
|
|
74
|
+
| Immutable release tarball | `arcane-os-0.3.0.tgz`; 7,230,292 bytes; SHA-256 `4afdb8113d8408b03917b73ce45e25bf4d302577fc6f199165d4931dda8ca081` |
|
|
75
|
+
| GitHub release | [`0.3.0`](https://github.com/TheWizardNexus/arcane-os-sdk/releases/tag/0.3.0) (tag and title are both exactly `0.3.0`) |
|
|
76
|
+
| Hosted source/artifact gate | [Check run 33130699235](https://github.com/TheWizardNexus/arcane-os-sdk/actions/runs/33130699235) |
|
|
77
|
+
| Trusted publication | [Run 33131083176](https://github.com/TheWizardNexus/arcane-os-sdk/actions/runs/33131083176) |
|
|
80
78
|
|
|
81
79
|
The npm SLSA provenance binds the published package to that exact source commit
|
|
82
80
|
and `.github/workflows/publish-dev.yml`. Hashes prove byte identity or
|
|
@@ -822,7 +822,7 @@
|
|
|
822
822
|
"closureStatus": "browser-speech-runtime-resolved-from-upstream-packages-and-provider-downloads",
|
|
823
823
|
"publicationStatus": "browser-speech-public-runtime-ready-warn-first-secure-opt-in"
|
|
824
824
|
},
|
|
825
|
-
"deferredReleaseBoundary": "The
|
|
825
|
+
"deferredReleaseBoundary": "The additive numeric 0.3.1 package, runtime/browser receipts, npm integrity/provenance, tag, and GitHub release remain assigned to the shared release owner after the warn-first and exact selected-route corrections are reconciled."
|
|
826
826
|
},
|
|
827
827
|
"omittedUntilVerified": [
|
|
828
828
|
"Final composite SPDX license expressions for the bundled JavaScript entrypoints.",
|