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 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.0` SDK contract. Applications pin one exact npm
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.0 new my-app --path ./my-app --target portable --git
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.0
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.0` for the initial bootstrap because it names this npm
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.0.tgz
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.0` registry package and
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.0` bundle generation; structural validity does not imply cross-SDK
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.0` exposes one browser target and five explicitly paired
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.0",
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": 9551253,
33
- "contentSha256": "164c30c71f4de1affaa335e0d435aa3cca31294f2c98be937b219e5e13e35959",
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": 135328,
61
- "sha256": "d5eb7d74000f8d91f6e9d19a1f5bffc12feb292d004605b28fc35d368419427d"
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": 56765,
68
- "sha256": "abe453aad718f9abc1e1b0911ea45a8f07e77f916e48d87cd661397e06084195"
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": 14130,
124
- "sha256": "833565bcad0cb6091bfccc4b5e42542362b149a9ffd6ecc0b1b0440ae70efb58"
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": 90700,
131
- "sha256": "4449fddbc1963f2c51f524b4a44993d88a28124482d96d183cd3e28f4ebdf1c8"
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
- await materialize(descriptor, admittedByPath.get(descriptor.path).file);
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 inspection.order) {
3147
+ for (const path of runtimeInspection.order) {
3137
3148
  const descriptor = metadata.filesByPath.get(path);
3138
- const plan = inspection.plans.get(path);
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(metadata.files.map((descriptor) =>
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 && security.secure
3465
+ const inspection = graph
3440
3466
  ? await inspectArtifactGraphRuntime({ files }, metadata, signal, security)
3441
- : graph
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 && security.secure
3761
+ const inspection = graph
3738
3762
  ? await inspectArtifactGraphRuntime({ files: installed }, metadata, signal, security)
3739
- : graph
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 verified offline artifact graph cache is available.",
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: active?.security ?? loadOperation?.security ?? defaultSecurity,
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
- "Authenticated artifact graph loading requires MessageChannel.",
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
- if (graphLoad) {
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
- && !graphLoad
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 a legacy runtime descriptor.",
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 STRICT_GRAPH_ADMISSIONS = new Set([
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
- configuration.security?.secure !== true
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
- || !STRICT_GRAPH_ADMISSIONS.has(configuration.runtime.artifactGraphAdmission)
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
- "Explicit secure:true plus authenticated artifact graph identity, admission, edges, and transforms are required.",
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) && configuration.security?.secure === true) {
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 (!isGraphLoad || transportMode !== null) return;
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 (isGraphLoad) {
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
- "Authenticated artifact graph loading requires a private MessagePort.",
2594
+ "Strict artifact graph loading requires a private MessagePort.",
2583
2595
  undefined,
2584
2596
  "artifact-graph-private-message-port-missing",
2585
2597
  ), role, "load"),
@@ -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.0` bundle
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.0` runtime requires Arcane `0.8.12` or newer. Compatibility
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
@@ -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.2.3
61
+ ## Published 0.3.0
62
62
 
63
- The most recently verified published package before this 0.3.0 source candidate
64
- is exactly `arcane-os@0.2.3` from source commit
65
- `d717f21d45664d20e4ed6377596db87c47492e11`. The npm `latest` dist-tag resolves
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 contracts
69
- documented below are newer canonical source. They are not package authority in
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-TZewkGM7dh9PdVnOtnkBO7QalJ6qyWWdKruCmsTxoHyeoG5XpqVbkNgiJhtBLhrIzgUV3vydYplxZQkIbIWoHg==` |
75
- | npm shasum | `8e978a23289a41db130253e6475a0c8bb0c0d73f` |
76
- | Immutable release tarball | `arcane-os-0.2.3.tgz`; 6,999,078 bytes; SHA-256 `857f179c2f9d4549e7691b4e6cebc49e5ab5e18600816443b26319c61fc1f85d` |
77
- | GitHub release | [`0.2.3`](https://github.com/TheWizardNexus/arcane-os-sdk/releases/tag/0.2.3) (tag and title are both exactly `0.2.3`) |
78
- | Hosted source/artifact gate | [Check run 33052271534](https://github.com/TheWizardNexus/arcane-os-sdk/actions/runs/33052271534) |
79
- | Trusted publication | [Run 33052383457](https://github.com/TheWizardNexus/arcane-os-sdk/actions/runs/33052383457) |
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 combined numeric 0.3.0 package, runtime/browser receipts, npm integrity/provenance, tag, and GitHub release remain assigned to the shared release owner after Browser Speech, singleton events, and Mail source increments are reconciled."
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.",