@pulse-compute/wasm-host-runtime 1.0.0-beta.1 → 1.0.0-beta.3

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/README.md CHANGED
@@ -6,9 +6,10 @@
6
6
  > **Install directly:** No for application projects; it is installed transitively.<br>
7
7
  > **Supported entry points:** `None for application authors.`<br>
8
8
  > **Stability:** Internal compiler/runtime interface synchronized with this release set.<br>
9
- > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.1/packages/implementation-packages/)
9
+ > **npm:** [`@pulse-compute/wasm-host-runtime`](https://www.npmjs.com/package/@pulse-compute/wasm-host-runtime)<br>
10
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.3/packages/implementation-packages/)
10
11
  >
11
- > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.1` package policy.
12
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.3` package policy.
12
13
  <!-- pulse-package-status:end -->
13
14
 
14
15
  Package-owned PulseWasm host-runtime build-time builders and local runtime helpers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-compute/wasm-host-runtime",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": "^22.14.0 || ^24.0.0"
@@ -28,9 +28,11 @@
28
28
  "./runtime/canonical-native-host": "./src/runtime/canonical-native-host.js"
29
29
  },
30
30
  "dependencies": {
31
- "@pulse-compute/wasm-build-support": "1.0.0-beta.1",
32
- "@pulse-compute/wasm-contracts": "1.0.0-beta.1",
33
- "@pulse-compute/wasm-schema-json": "1.0.0-beta.1"
31
+ "@pulse-compute/crypto": "1.0.0-beta.3",
32
+ "@pulse-compute/runtime": "1.0.0-beta.3",
33
+ "@pulse-compute/wasm-build-support": "1.0.0-beta.3",
34
+ "@pulse-compute/wasm-contracts": "1.0.0-beta.3",
35
+ "@pulse-compute/wasm-schema-json": "1.0.0-beta.3"
34
36
  },
35
37
  "publishConfig": {
36
38
  "access": "public"
@@ -46,7 +48,7 @@
46
48
  "url": "git+https://github.com/pulse-compute/pulse.git",
47
49
  "directory": "wasm/packages/host-runtime"
48
50
  },
49
- "homepage": "https://pulsecompute.io/v1.0.0-beta.1/packages/implementation-packages/",
51
+ "homepage": "https://pulsecompute.io/v1.0.0-beta.3/packages/implementation-packages/",
50
52
  "bugs": {
51
53
  "url": "https://github.com/pulse-compute/pulse/issues"
52
54
  }
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const portableKv = require('@pulse-compute/runtime/host');
2
3
 
3
4
 
4
5
  function loadRuntimeApi() {
@@ -886,6 +887,7 @@ function normalizeProviderEffect(effect, schemaCodecs, options = {}) {
886
887
  if (!effect || typeof effect !== 'object') {
887
888
  throw new CanonicalRuntimeError('CanonicalEffectProtocolError', 'PULSE_CANONICAL_EFFECT_PROTOCOL', 'Canonical provider effect must be an object.', { effect });
888
889
  }
890
+ if (portableKv.isConditionalKv(effect.kind)) return Object.freeze({ ...portableKv.admitConditionalKv(effect, options), id: effect.id, groupKey: effect.groupKey, source: effect.source });
889
891
  if (effect.kind === 'fetch') return normalizeFetchEffect(effect, schemaCodecs, options);
890
892
  const id = String(effect.id || '');
891
893
  if (!id) throw new CanonicalRuntimeError('CanonicalEffectProtocolError', 'PULSE_CANONICAL_EFFECT_PROTOCOL', 'Canonical provider effect requires an id.', { effect });
@@ -980,6 +982,7 @@ function effectTraceStart(adapterId, executionId, normalized, sensitiveValues) {
980
982
  headers: redactedHeaders(normalized.init.headers, sensitiveValues)
981
983
  });
982
984
  if (normalized.kind === 'config.get' || normalized.kind === 'secret.get') return Object.freeze({ ...base, name: redactString(normalized.name, sensitiveValues) });
985
+ if (portableKv.isConditionalKv(normalized.kind)) return Object.freeze({ ...base, key: '<redacted>', generation: '<redacted>', value: '<redacted>' });
983
986
  if (normalized.kind === 'kv.get' || normalized.kind === 'kv.put') return Object.freeze({ ...base, store: redactString(normalized.store, sensitiveValues), key: redactString(normalized.key, sensitiveValues) });
984
987
  if (normalized.kind === 'event.emit') return Object.freeze({ ...base, eventType: redactString(normalized.frame.type, sensitiveValues), schemaId: normalized.frame.schemaId });
985
988
  if (normalized.package) return Object.freeze({ ...base, package: normalized.package, contractId: normalized.contractId, operation: normalized.operation });
@@ -991,6 +994,7 @@ function effectTraceResolved(adapterId, executionId, normalized, value) {
991
994
  if (normalized.kind === 'fetch') return Object.freeze({ ...base, status: value.status, bodyClass: value.bodyClass });
992
995
  if (normalized.kind === 'secret.get') return Object.freeze({ ...base, hit: value !== undefined, value: '<redacted>' });
993
996
  if (normalized.kind === 'config.get') return Object.freeze({ ...base, hit: value !== undefined });
997
+ if (portableKv.isConditionalKv(normalized.kind)) return Object.freeze({ ...base, status: value.status });
994
998
  if (normalized.kind === 'kv.get') return Object.freeze({ ...base, hit: value !== undefined });
995
999
  if (normalized.kind === 'kv.put') return Object.freeze({ ...base, stored: Boolean(value) });
996
1000
  if (normalized.kind === 'event.emit') return Object.freeze({ ...base, accepted: value === undefined });
@@ -1288,6 +1292,7 @@ function normalizeProviderAdapter(input = {}) {
1288
1292
  version: String(input.version || `pulse.canonical-${id}-provider.v1`),
1289
1293
  createCapabilities: typeof input.createCapabilities === 'function' ? input.createCapabilities : () => ({}),
1290
1294
  dispatchEffect,
1295
+ prepareConditionalKv: typeof input.prepareConditionalKv === 'function' ? input.prepareConditionalKv.bind(input) : undefined,
1291
1296
  resultMetadata: typeof input.resultMetadata === 'function' ? input.resultMetadata : () => undefined,
1292
1297
  disposeExecution: typeof input.disposeExecution === 'function' ? input.disposeExecution : () => undefined
1293
1298
  });
@@ -1361,10 +1366,15 @@ function createCanonicalHostRuntime(options = {}) {
1361
1366
 
1362
1367
  async function dispatchOne(effect) {
1363
1368
  const normalized = normalizeProviderEffect(effect, schemaCodecs, runtimeOptions);
1369
+ const conditional = portableKv.isConditionalKv(normalized.kind);
1370
+ if (conditional) portableKv.registerKvRedactions(normalized, (value) => sensitiveValues.add(value));
1364
1371
  recordTrace(effectTraceStart(adapter.id, executionId, normalized, sensitiveValues));
1365
1372
  dispatchedEffects += 1;
1366
1373
  try {
1367
- let snapshot = await adapter.dispatchEffect(normalized, { ...options, ...executionOptions, metadata, executionId, trace: traceSink });
1374
+ const effectExecution = { ...options, ...executionOptions, metadata, executionId, trace: traceSink, registerRedactionValue: (value) => sensitiveValues.add(value), onKvObservation: recordTrace };
1375
+ let snapshot = conditional
1376
+ ? await portableKv.executeConditionalKv(normalized, adapter.prepareConditionalKv || ((_admitted, kvExecution) => () => adapter.dispatchEffect(normalized, kvExecution)), effectExecution, runtimeOptions)
1377
+ : await adapter.dispatchEffect(normalized, effectExecution);
1368
1378
  if (normalized.kind === 'config.get' || normalized.kind === 'secret.get') {
1369
1379
  if (snapshot !== undefined && typeof snapshot !== 'string') {
1370
1380
  throw new CanonicalRuntimeError(
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const portableKv = require('@pulse-compute/runtime/host');
2
3
 
3
4
  const crypto = require('node:crypto');
4
5
  const {
@@ -81,6 +82,7 @@ function normalizeProviderAdapter(input) {
81
82
  if (!input || typeof input !== 'object') {
82
83
  return Object.freeze({
83
84
  id: 'unconfigured',
85
+ prepareConditionalKv() { return undefined; },
84
86
  version: 'pulse.canonical-native-unconfigured-provider.v1',
85
87
  async dispatchEffect(effect) {
86
88
  throw new CanonicalNativeHostError(`No provider adapter is configured for native effect ${effect.kind}.`, 'PULSE_PROVIDER_CAPABILITY_MISSING', { effectId: effect.id, kind: effect.kind });
@@ -104,6 +106,7 @@ function normalizeProviderAdapter(input) {
104
106
  id,
105
107
  version: String(input.version || `pulse.canonical-native-${id}-provider.v1`),
106
108
  dispatchEffect,
109
+ prepareConditionalKv: typeof input.prepareConditionalKv === 'function' ? input.prepareConditionalKv.bind(input) : undefined,
107
110
  resultMetadata: typeof input.resultMetadata === 'function' ? input.resultMetadata.bind(input) : () => undefined,
108
111
  disposeExecution: typeof input.disposeExecution === 'function' ? input.disposeExecution.bind(input) : () => undefined
109
112
  });
@@ -247,6 +250,7 @@ function nativeEffect(planEffect, payload) {
247
250
  }
248
251
  if (planEffect.kind === 'config.get' || planEffect.kind === 'secret.get') return { ...base, name: payload.name };
249
252
  if (planEffect.kind === 'kv.get' || planEffect.kind === 'kv.put') return { ...base, store: kvStoreName(payload.store), key: payload.key, value: planEffect.kind === 'kv.put' ? payload.value : undefined };
253
+ if (portableKv.isConditionalKv(planEffect.kind)) return { ...base, store: kvStoreName(payload.store), key: payload.key, value: payload.value, generation: payload.generation };
250
254
  if (planEffect.kind === 'event.emit') {
251
255
  return {
252
256
  ...base,
@@ -724,7 +728,9 @@ function instantiateCanonicalNativeModule(compiled, options = {}) {
724
728
  const effect = plan.effects && plan.effects[index];
725
729
  if (!effect) throw new CanonicalNativeHostError(`Native module requested unknown effect index ${index}.`, 'PULSE_CANONICAL_NATIVE_EFFECT_INDEX_INVALID', { effectIndex: index });
726
730
  const payload = value(payloadHandle);
727
- pending.push(Object.freeze({ index, effect, payload }));
731
+ const kvAdmission = portableKv.isConditionalKv(effect.kind) ? portableKv.admitConditionalKv(nativeEffect(effect, payload), options) : undefined;
732
+ if (kvAdmission) portableKv.registerKvRedactions(kvAdmission, (value) => sensitiveValues.add(value));
733
+ pending.push(Object.freeze({ index, effect, payload, ...(kvAdmission ? { kvAdmission } : {}) }));
728
734
  }
729
735
  };
730
736
 
@@ -782,7 +788,7 @@ function instantiateCanonicalNativeModule(compiled, options = {}) {
782
788
  throw new CanonicalNativeHostError(`Native host cannot prepare an unknown effect index ${effectIndex}.`, 'PULSE_CANONICAL_NATIVE_EFFECT_INDEX_INVALID', { effectIndex });
783
789
  }
784
790
 
785
- let result = rawResult;
791
+ let result = portableKv.isConditionalKv(effect.kind) ? portableKv.normalizeConditionalKvResult(effect, rawResult, options) : rawResult;
786
792
  if (effect.kind === 'event.emit' && result !== undefined) {
787
793
  throw new canonicalRuntime.CanonicalRuntimeError(
788
794
  'EventAcceptanceError',
@@ -992,7 +998,11 @@ async function executeCanonicalNativeInvocation(compiled, options = {}, invocati
992
998
  if (eventMode && options.signal && options.signal.aborted) {
993
999
  throw nativeEventCancelled('Native event execution was cancelled before effect dispatch.');
994
1000
  }
995
- const rawEffect = nativeEffect(entry.effect, entry.payload);
1001
+ const rawEffect = { ...nativeEffect(entry.effect, entry.payload), ...entry.kvAdmission };
1002
+ const conditional = portableKv.isConditionalKv(rawEffect.kind);
1003
+ const privateEffect = conditional || ((controller.plan.packages && controller.plan.packages.effects) || []).some((item) =>
1004
+ item.package === entry.effect.package && item.contractId === entry.effect.contractId && item.operation === entry.effect.operation
1005
+ && item.redaction && Object.keys(item.redaction).length > 0);
996
1006
  const normalized = canonicalRuntime.normalizeProviderEffect(rawEffect, controller.schemaCodecs, {
997
1007
  ...options,
998
1008
  strict: options.strict === undefined
@@ -1002,8 +1012,8 @@ async function executeCanonicalNativeInvocation(compiled, options = {}, invocati
1002
1012
  target: 'native',
1003
1013
  provider: adapter.id
1004
1014
  });
1005
- controller.trace.push(Object.freeze(redactValue({ type: 'native-effect-start', executionId, provider: adapter.id, effectId: normalized.id, kind: normalized.kind, payload: entry.payload }, controller.sensitiveValues)));
1006
- const rawResult = await raceNativeEventSignal(adapter.dispatchEffect(normalized, {
1015
+ controller.trace.push(Object.freeze(redactValue({ type: 'native-effect-start', executionId, provider: adapter.id, effectId: normalized.id, kind: normalized.kind, payload: privateEffect ? '<redacted>' : entry.payload }, controller.sensitiveValues)));
1016
+ const effectExecution = {
1007
1017
  ...options,
1008
1018
  executionId,
1009
1019
  metadata: compiled && compiled.plan ? compiled.plan.canonical : controller.plan.canonical,
@@ -1018,9 +1028,13 @@ async function executeCanonicalNativeInvocation(compiled, options = {}, invocati
1018
1028
  validateSchemaValue(schemaId, value, context = {}) {
1019
1029
  return controller.schemaCodecs.decode(String(schemaId), value, context.source || 'package-effect');
1020
1030
  }
1021
- }), eventMode ? options.signal : undefined);
1031
+ };
1032
+ const rawResult = conditional
1033
+ ? await portableKv.executeConditionalKv(normalized, adapter.prepareConditionalKv || ((_admitted, kvExecution) => () => adapter.dispatchEffect(normalized, kvExecution)),
1034
+ { ...effectExecution, onKvObservation: (event) => controller.trace.push(event) }, options)
1035
+ : await raceNativeEventSignal(adapter.dispatchEffect(normalized, effectExecution), eventMode ? options.signal : undefined);
1022
1036
  const result = controller.prepareEffectResult(entry.index, rawResult);
1023
- controller.trace.push(Object.freeze(redactValue({ type: 'native-effect-resolved', executionId, provider: adapter.id, effectId: normalized.id, kind: normalized.kind, result: entry.effect.kind === 'secret.get' ? '<redacted>' : result && typeof result.toJSON === 'function' ? result.toJSON() : result }, controller.sensitiveValues)));
1037
+ controller.trace.push(Object.freeze(redactValue({ type: 'native-effect-resolved', executionId, provider: adapter.id, effectId: normalized.id, kind: normalized.kind, result: privateEffect || entry.effect.kind === 'secret.get' ? '<redacted>' : result && typeof result.toJSON === 'function' ? result.toJSON() : result }, controller.sensitiveValues)));
1024
1038
  resolutionOrder.push(entry.effect.id);
1025
1039
  return { entry, result };
1026
1040
  }));
@@ -244,7 +244,13 @@ function createNativeGuestSourceCryptoVerifier(input = {}) {
244
244
  && memory.buffer.byteLength === ES256_MEMORY_BYTES
245
245
  && typeof verifyEs256 === 'function'
246
246
  );
247
+ const byteAlgorithms = ['SHA-256', 'HMAC-SHA256'].filter((algorithm) =>
248
+ selectedAlgorithm(plan, algorithm, HS256_REALIZATION, 'guest-source', CRYPTO_GUEST_SOURCE_IMPLEMENTATION));
249
+ const digestMac = byteAlgorithms.length > 0
250
+ ? require('@pulse-compute/crypto/pulsewasm-native').bindNativeDigestMac(moduleExports) : undefined;
247
251
  const selectedRealizations = Object.freeze([
252
+ ...byteAlgorithms.map((algorithm) => Object.freeze({ algorithm, realization: HS256_REALIZATION,
253
+ implementation: CRYPTO_GUEST_SOURCE_IMPLEMENTATION, guestUnitRequired: false, available: Boolean(digestMac) })),
248
254
  ...(selected ? [Object.freeze({
249
255
  algorithm: 'HS256',
250
256
  realization: HS256_REALIZATION,
@@ -271,6 +277,10 @@ function createNativeGuestSourceCryptoVerifier(input = {}) {
271
277
  });
272
278
 
273
279
  const verifier = Object.freeze({
280
+ ...(digestMac ? { bytes: Object.freeze({
281
+ ...(byteAlgorithms.includes('SHA-256') ? { sha256: digestMac.sha256 } : {}),
282
+ ...(byteAlgorithms.includes('HMAC-SHA256') ? { hmacSha256: digestMac.hmacSha256 } : {})
283
+ }) } : {}),
274
284
  mac: Object.freeze({
275
285
  verify(request) {
276
286
  const normalized = normalizeRequest(request);