@pulse-compute/wasm-contracts 0.0.0 → 1.0.0-beta.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.
Files changed (97) hide show
  1. package/README.md +35 -2
  2. package/package.json +124 -6
  3. package/src/assets/contracts.js +296 -0
  4. package/src/config/provider-resolution.js +54 -0
  5. package/src/config/runtime-provider-kv.js +73 -0
  6. package/src/config/symbolic-refs.js +192 -0
  7. package/src/crypto/contracts.js +647 -0
  8. package/src/definitions/config-schema.js +30 -0
  9. package/src/definitions/handler-roles.js +66 -0
  10. package/src/definitions/path-grammar.js +11 -0
  11. package/src/definitions/router-api.js +149 -0
  12. package/src/diagnostics/codes.js +42 -0
  13. package/src/diagnostics/reporter.js +20 -0
  14. package/src/diagnostics.js +139 -0
  15. package/src/dispatch-table.js +23 -0
  16. package/src/entities/catalog.d.ts +7 -0
  17. package/src/entities/catalog.js +87 -0
  18. package/src/entities/contracts.d.ts +95 -0
  19. package/src/entities/contracts.js +122 -0
  20. package/src/entities/json-rpc.d.ts +27 -0
  21. package/src/entities/json-rpc.js +69 -0
  22. package/src/entities/runtime.d.ts +67 -0
  23. package/src/entities/runtime.js +288 -0
  24. package/src/events/contracts.js +456 -0
  25. package/src/execution-plan.js +42 -0
  26. package/src/grip/contracts.js +106 -0
  27. package/src/handler/backend-json-request-body.js +58 -0
  28. package/src/handler/beta-lifecycle-golden.js +97 -0
  29. package/src/handler/canonical-native-plan.js +143 -0
  30. package/src/handler/canonical-native-runtime.js +168 -0
  31. package/src/handler/canonical-runtime.js +13 -0
  32. package/src/handler/context.js +131 -0
  33. package/src/handler/ctx-contract.js +71 -0
  34. package/src/handler/doctor-readiness-aggregator.js +67 -0
  35. package/src/handler/effects.js +446 -0
  36. package/src/handler/io-lifecycle.js +134 -0
  37. package/src/handler/lifecycle-authoring-alignment.js +56 -0
  38. package/src/handler/lifecycle-readiness-aggregate.js +93 -0
  39. package/src/handler/reference-node-lifecycle-golden.js +73 -0
  40. package/src/handler/request-json-body.js +147 -0
  41. package/src/handler/runtime-api.js +471 -0
  42. package/src/handler/schema-decode-result.js +135 -0
  43. package/src/handler/schema-response-codec.js +141 -0
  44. package/src/handler/surface-contract.js +601 -0
  45. package/src/handler/types-contract.js +51 -0
  46. package/src/handler-table.js +28 -0
  47. package/src/host/backend-capabilities.js +24 -0
  48. package/src/host/capabilities.js +273 -0
  49. package/src/host/channel-broadcaster.js +60 -0
  50. package/src/host/json-body.js +80 -0
  51. package/src/host/request-result-headers.js +38 -0
  52. package/src/host/streaming-passthrough.js +90 -0
  53. package/src/host/wasm-host-abi.js +154 -0
  54. package/src/index.js +75 -0
  55. package/src/jwt/contracts.js +427 -0
  56. package/src/library/api-surface.js +99 -0
  57. package/src/library/contracts.js +273 -0
  58. package/src/library/manifest.js +317 -0
  59. package/src/library/second-package-owned-lowering.js +39 -0
  60. package/src/library/sidecars.js +147 -0
  61. package/src/logging.js +89 -0
  62. package/src/package/package-contract.js +975 -0
  63. package/src/package/package-contract.schema.json +303 -0
  64. package/src/package/runtime-pulse-live.js +100 -0
  65. package/src/path-table.js +12 -0
  66. package/src/path.js +275 -0
  67. package/src/project/config-factory.js +48 -0
  68. package/src/project/config-plan.js +409 -0
  69. package/src/project/config-runtime.js +46 -0
  70. package/src/project/javascript-application.js +212 -0
  71. package/src/project/javascript-core-capabilities.js +44 -0
  72. package/src/project/native-optimization.js +9 -0
  73. package/src/project/package-ownership.js +242 -0
  74. package/src/project/package-product-projection.js +171 -0
  75. package/src/project/reachable-graph-identity.js +219 -0
  76. package/src/project/reachable-graph-projections.js +279 -0
  77. package/src/project/reachable-graph-resolver.js +303 -0
  78. package/src/project/reachable-graph-v2.js +430 -0
  79. package/src/project/reachable-graph-v2.schema.json +285 -0
  80. package/src/project/reachable-graph.js +199 -0
  81. package/src/project/reachable-graph.schema.json +222 -0
  82. package/src/project/target-support-evidence.js +559 -0
  83. package/src/provider/canonical-provider.js +239 -0
  84. package/src/provider/final-wasm-policy.js +87 -0
  85. package/src/provider/toolchain.js +771 -0
  86. package/src/route-plan.js +43 -0
  87. package/src/s3/contracts.js +12 -0
  88. package/src/schema-json/compile.js +63 -0
  89. package/src/schema-json/contracts.js +10 -0
  90. package/src/schema-json/generic-parser.js +62 -0
  91. package/src/schema-json/registry.js +359 -0
  92. package/src/schema-json/semantic-trace.js +241 -0
  93. package/src/schema-json/v1.js +65 -0
  94. package/src/schema-json/v2.js +110 -0
  95. package/src/sha256.js +104 -0
  96. package/src/spine.js +381 -0
  97. package/src/stable-id.js +142 -0
@@ -0,0 +1,647 @@
1
+ 'use strict';
2
+
3
+ const { stableStringify, sha256Hex } = require('../stable-id.js');
4
+
5
+ const CRYPTO_CONFIGURATION_VERSION = 'pulse.crypto-configuration.v1';
6
+ const CRYPTO_TARGET_CAPABILITIES_VERSION = 'pulse.crypto-target-capabilities.v1';
7
+ const CRYPTO_REQUIREMENTS_VERSION = 'pulse.crypto-requirements.v1';
8
+ const CRYPTO_REALIZATION_PLAN_VERSION = 'pulse.crypto-realization-plan.v1';
9
+ const CRYPTO_NATIVE_GUEST_SOURCE_PLAN_VERSION = 'pulse.crypto-native-guest-source-plan.v1';
10
+ const CRYPTO_SEMANTIC_OWNER = '@pulse-compute/crypto';
11
+ const CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION = 'webcrypto.subtle.hmac-sha-256.v1';
12
+ const CRYPTO_GUEST_SOURCE_IMPLEMENTATION = 'pulse-hmac-as.v1';
13
+ const CRYPTO_ES256_CONTRACT_VERSION = 'pulse.crypto.es256-verification.v1';
14
+ const CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION = 'webcrypto.subtle.ecdsa-p256-sha-256.v1';
15
+ const CRYPTO_ES256_GUEST_LINKED_REALIZATION = 'guest-linked:pulse-es256-rustcrypto-p256';
16
+ const CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION =
17
+ 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1';
18
+ const CRYPTO_VERIFICATION_STATUSES = Object.freeze([
19
+ 'valid',
20
+ 'invalid-authenticator',
21
+ 'invalid-key',
22
+ 'invalid-input',
23
+ 'realization-failure'
24
+ ]);
25
+
26
+ const CRYPTO_ALGORITHMS = Object.freeze(['HS256', 'ES256', 'SHA-256', 'HMAC-SHA256']);
27
+ const CRYPTO_REALIZATIONS = Object.freeze([
28
+ ...['SHA-256', 'HMAC-SHA256'].map((algorithm) => Object.freeze({
29
+ id: 'runtime-builtin', kind: 'runtime-builtin', algorithm, targets: Object.freeze(['javascript']),
30
+ implementation: algorithm === 'SHA-256' ? 'webcrypto.subtle.sha-256-bytes.v1' : 'webcrypto.subtle.hmac-sha-256-bytes.v1'
31
+ })),
32
+ ...['SHA-256', 'HMAC-SHA256'].map((algorithm) => Object.freeze({
33
+ id: 'guest-source:pulse-hmac-as', kind: 'guest-source', backend: 'pulse-hmac-as',
34
+ implementation: CRYPTO_GUEST_SOURCE_IMPLEMENTATION, algorithm, targets: Object.freeze(['native']),
35
+ source: Object.freeze({ package: '@pulse-compute/crypto', export: './pulsewasm-native',
36
+ contractVersion: 'pulse.crypto-guest-source.v1', contribution: 'pulse-hmac-as' })
37
+ })),
38
+ Object.freeze({
39
+ id: 'runtime-builtin',
40
+ kind: 'runtime-builtin',
41
+ implementation: CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION,
42
+ algorithm: 'HS256',
43
+ targets: Object.freeze(['javascript'])
44
+ }),
45
+ Object.freeze({
46
+ id: 'guest-source:pulse-hmac-as',
47
+ kind: 'guest-source',
48
+ backend: 'pulse-hmac-as',
49
+ implementation: CRYPTO_GUEST_SOURCE_IMPLEMENTATION,
50
+ algorithm: 'HS256',
51
+ targets: Object.freeze(['native']),
52
+ source: Object.freeze({
53
+ package: '@pulse-compute/crypto',
54
+ export: './pulsewasm-native',
55
+ contractVersion: 'pulse.crypto-guest-source.v1',
56
+ contribution: 'pulse-hmac-as'
57
+ })
58
+ }),
59
+ Object.freeze({
60
+ id: 'runtime-builtin',
61
+ kind: 'runtime-builtin',
62
+ implementation: CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
63
+ algorithm: 'ES256',
64
+ targets: Object.freeze(['javascript'])
65
+ }),
66
+ Object.freeze({
67
+ id: CRYPTO_ES256_GUEST_LINKED_REALIZATION,
68
+ kind: 'guest-linked',
69
+ backend: 'pulse-es256-rustcrypto-p256',
70
+ implementation: CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
71
+ algorithm: 'ES256',
72
+ targets: Object.freeze(['native']),
73
+ source: Object.freeze({
74
+ package: '@pulse-compute/crypto',
75
+ export: './pulsewasm-native',
76
+ contractVersion: 'pulse.guest-unit-contribution.v1',
77
+ contribution: 'pulseEs256GuestUnit'
78
+ })
79
+ })
80
+ ]);
81
+
82
+ const algorithmSet = new Set(CRYPTO_ALGORITHMS);
83
+ const realizationsById = new Map();
84
+ for (const entry of CRYPTO_REALIZATIONS) {
85
+ const records = realizationsById.get(entry.id) || [];
86
+ records.push(entry);
87
+ realizationsById.set(entry.id, records);
88
+ }
89
+
90
+ function cryptoError(code, message, details = {}) {
91
+ const error = new TypeError(message);
92
+ error.code = code;
93
+ error.details = Object.freeze({ ...details });
94
+ return error;
95
+ }
96
+
97
+ function isPlainObject(value) {
98
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
99
+ const prototype = Object.getPrototypeOf(value);
100
+ return prototype === Object.prototype || prototype === null;
101
+ }
102
+
103
+ function deepFreeze(value) {
104
+ if (Array.isArray(value)) {
105
+ for (const child of value) deepFreeze(child);
106
+ return Object.freeze(value);
107
+ }
108
+ if (isPlainObject(value)) {
109
+ for (const child of Object.values(value)) deepFreeze(child);
110
+ return Object.freeze(value);
111
+ }
112
+ return value;
113
+ }
114
+
115
+ const CRYPTO_ES256_CONTRACT = deepFreeze({
116
+ version: CRYPTO_ES256_CONTRACT_VERSION,
117
+ status: 'executable-g3',
118
+ semanticOwner: CRYPTO_SEMANTIC_OWNER,
119
+ algorithm: 'ES256',
120
+ primitive: {
121
+ kind: 'ecdsa',
122
+ curve: 'P-256',
123
+ hash: 'SHA-256'
124
+ },
125
+ operation: 'verify',
126
+ request: {
127
+ function: 'crypto.signature.verify',
128
+ keyType: 'p256-public-key-bytes',
129
+ publicKeyEncoding: 'raw-x-y-big-endian',
130
+ publicKeyBytes: 64,
131
+ dataBytesMaximum: 16340,
132
+ signatureEncoding: 'jose-r-s-big-endian',
133
+ signatureBytes: 64,
134
+ derAccepted: false
135
+ },
136
+ runtimeBuiltin: {
137
+ realization: 'runtime-builtin',
138
+ implementation: CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
139
+ eligibleTargets: ['node-javascript', 'fastly-javascript']
140
+ },
141
+ native: {
142
+ realization: CRYPTO_ES256_GUEST_LINKED_REALIZATION,
143
+ implementation: CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
144
+ eligibleTargets: ['node-native', 'fastly-native']
145
+ },
146
+ guest: {
147
+ unitVersion: 'pulse.guest-unit.v2',
148
+ unitId: 'pulse.crypto.es256.rustcrypto-p256.v1',
149
+ module: 'pulse_crypto_es256',
150
+ owner: '@pulse-compute/crypto',
151
+ abi: 'pulse.crypto.es256.verify.v1',
152
+ export: {
153
+ name: 'pulse_crypto_es256_verify',
154
+ parameters: ['i32', 'i32'],
155
+ results: ['i32']
156
+ },
157
+ origin: 'package-prebuilt',
158
+ sourceBuildDuringApplicationBuild: false,
159
+ memory: {
160
+ identity: 'pulse.guest-memory.invocation-frame.v2',
161
+ import: 'env.memory',
162
+ minimumPages: 32,
163
+ maximumPages: 32,
164
+ growable: false
165
+ },
166
+ start: 'forbidden',
167
+ hostCallbacks: false,
168
+ allocator: false
169
+ },
170
+ frame: {
171
+ identity: 'pulse.crypto.es256.invocation-frame.v2',
172
+ byteOrder: 'little-endian',
173
+ pointerWidthBits: 32,
174
+ magic: 0x32534550,
175
+ version: 2,
176
+ headerBytes: 64,
177
+ capacityBytes: 16640,
178
+ alignmentBytes: 16,
179
+ signingInputBytesMaximum: 16340,
180
+ publicKeyBytes: 64,
181
+ signatureBytes: 64,
182
+ rawPayloadBytesMaximum: 16468,
183
+ reservedHeaderAndAlignmentBytes: 172,
184
+ canonicalOffsets: {
185
+ publicKey: 64,
186
+ signature: 128,
187
+ signingInput: 192
188
+ },
189
+ header: {
190
+ magic: 0,
191
+ version: 4,
192
+ headerLength: 8,
193
+ totalLength: 12,
194
+ algorithm: 16,
195
+ flags: 20,
196
+ signingInputOffset: 24,
197
+ signingInputLength: 28,
198
+ publicKeyOffset: 32,
199
+ publicKeyLength: 36,
200
+ signatureOffset: 40,
201
+ signatureLength: 44,
202
+ reserved0: 48,
203
+ reserved1: 52,
204
+ reserved2: 56,
205
+ reserved3: 60
206
+ },
207
+ algorithmCode: 1,
208
+ flags: 0,
209
+ reservedWordCount: 4,
210
+ reservedWordValue: 0,
211
+ callerOwned: true,
212
+ synchronous: true,
213
+ retainable: false,
214
+ clearCapacityAfterReturnOrTrap: true
215
+ },
216
+ resultStatuses: CRYPTO_VERIFICATION_STATUSES,
217
+ resultCodes: {
218
+ valid: 1,
219
+ invalidAuthenticator: 0,
220
+ invalidKey: -1,
221
+ invalidInput: -2,
222
+ realizationFailure: -3
223
+ },
224
+ automaticFallback: false
225
+ });
226
+
227
+ function requireAlgorithm(value, location) {
228
+ if (typeof value !== 'string' || !algorithmSet.has(value)) {
229
+ throw cryptoError(
230
+ 'PULSE_CRYPTO_ALGORITHM_UNKNOWN',
231
+ `${location} must be a known canonical crypto algorithm.`,
232
+ { location, algorithm: value, supportedAlgorithms: CRYPTO_ALGORITHMS }
233
+ );
234
+ }
235
+ return value;
236
+ }
237
+
238
+ function requireKnownRealization(value, location) {
239
+ if (typeof value !== 'string' || !realizationsById.has(value)) {
240
+ throw cryptoError(
241
+ 'PULSE_CRYPTO_REALIZATION_UNKNOWN',
242
+ `${location} must name an exact known crypto realization.`,
243
+ {
244
+ location,
245
+ realization: value,
246
+ knownRealizations: CRYPTO_REALIZATIONS.map((entry) => entry.id)
247
+ }
248
+ );
249
+ }
250
+ return value;
251
+ }
252
+
253
+ function knownRealization(realization, algorithm, target) {
254
+ const records = realizationsById.get(realization) || [];
255
+ return records.find((entry) => (
256
+ entry.algorithm === algorithm
257
+ && (target === undefined || entry.targets.includes(target))
258
+ ));
259
+ }
260
+
261
+ function canonicalConfiguration(form, algorithms) {
262
+ return deepFreeze({
263
+ version: CRYPTO_CONFIGURATION_VERSION,
264
+ form,
265
+ algorithms: algorithms
266
+ .map((entry) => ({ algorithm: entry.algorithm, realization: entry.realization || null }))
267
+ .sort((left, right) => left.algorithm.localeCompare(right.algorithm))
268
+ });
269
+ }
270
+
271
+ function normalizeCryptoConfiguration(value, options = {}) {
272
+ const location = options.location || 'crypto';
273
+ if (Array.isArray(value)) {
274
+ const seen = new Set();
275
+ const algorithms = value.map((entry, index) => {
276
+ const algorithm = requireAlgorithm(entry, `${location}[${index}]`);
277
+ if (seen.has(algorithm)) {
278
+ throw cryptoError(
279
+ 'PULSE_CRYPTO_ALGORITHM_DUPLICATE',
280
+ `${location} contains duplicate algorithm ${algorithm}.`,
281
+ { location, algorithm }
282
+ );
283
+ }
284
+ seen.add(algorithm);
285
+ return { algorithm, realization: null };
286
+ });
287
+ return canonicalConfiguration('array', algorithms);
288
+ }
289
+ if (isPlainObject(value)) {
290
+ const algorithms = Object.keys(value).map((algorithm) => {
291
+ requireAlgorithm(algorithm, `${location}.${algorithm}`);
292
+ const selection = value[algorithm];
293
+ if (!isPlainObject(selection)) {
294
+ throw cryptoError(
295
+ 'PULSE_CRYPTO_REALIZATION_SELECTION_OBJECT_REQUIRED',
296
+ `${location}.${algorithm} must be an object.`,
297
+ { location: `${location}.${algorithm}`, valueType: Array.isArray(selection) ? 'array' : typeof selection }
298
+ );
299
+ }
300
+ const keys = Object.keys(selection);
301
+ const unsupported = keys.filter((key) => key !== 'realization').sort();
302
+ if (unsupported.length > 0) {
303
+ throw cryptoError(
304
+ 'PULSE_CRYPTO_REALIZATION_SELECTION_KEY_UNSUPPORTED',
305
+ `${location}.${algorithm} contains unsupported selection keys.`,
306
+ { location: `${location}.${algorithm}`, keys: unsupported, allowed: Object.freeze(['realization']) }
307
+ );
308
+ }
309
+ if (Object.prototype.hasOwnProperty.call(selection, 'realization') && selection.realization === undefined) {
310
+ throw cryptoError(
311
+ 'PULSE_CRYPTO_REALIZATION_UNKNOWN',
312
+ `${location}.${algorithm}.realization must name an exact known crypto realization.`,
313
+ { location: `${location}.${algorithm}.realization`, realization: selection.realization }
314
+ );
315
+ }
316
+ return {
317
+ algorithm,
318
+ realization: Object.prototype.hasOwnProperty.call(selection, 'realization')
319
+ ? requireKnownRealization(selection.realization, `${location}.${algorithm}.realization`)
320
+ : null
321
+ };
322
+ });
323
+ return canonicalConfiguration('object', algorithms);
324
+ }
325
+ throw cryptoError(
326
+ 'PULSE_CRYPTO_CONFIG_SHAPE_INVALID',
327
+ `${location} must use the array or object form.`,
328
+ { location, valueType: value === null ? 'null' : typeof value }
329
+ );
330
+ }
331
+
332
+ function emptyCryptoConfiguration() {
333
+ return canonicalConfiguration('implicit', []);
334
+ }
335
+
336
+ function normalizeCryptoConfigurationDocument(value) {
337
+ if (!isPlainObject(value) || value.version !== CRYPTO_CONFIGURATION_VERSION) {
338
+ throw cryptoError('PULSE_CRYPTO_CONFIG_SHAPE_INVALID', 'Crypto configuration document is invalid.');
339
+ }
340
+ if (!['array', 'object', 'implicit'].includes(value.form) || !Array.isArray(value.algorithms)) {
341
+ throw cryptoError('PULSE_CRYPTO_CONFIG_SHAPE_INVALID', 'Crypto configuration document has an invalid canonical shape.');
342
+ }
343
+ const seen = new Set();
344
+ const algorithms = value.algorithms.map((entry, index) => {
345
+ if (!isPlainObject(entry)) {
346
+ throw cryptoError('PULSE_CRYPTO_CONFIG_SHAPE_INVALID', 'Crypto configuration algorithm entries must be objects.', { index });
347
+ }
348
+ const algorithm = requireAlgorithm(entry.algorithm, `crypto configuration.algorithms[${index}].algorithm`);
349
+ if (seen.has(algorithm)) {
350
+ throw cryptoError('PULSE_CRYPTO_ALGORITHM_DUPLICATE', `Crypto configuration contains duplicate algorithm ${algorithm}.`, { algorithm });
351
+ }
352
+ seen.add(algorithm);
353
+ return {
354
+ algorithm,
355
+ realization: entry.realization === null
356
+ ? null
357
+ : requireKnownRealization(entry.realization, `crypto configuration.algorithms[${index}].realization`)
358
+ };
359
+ });
360
+ return canonicalConfiguration(value.form, algorithms);
361
+ }
362
+
363
+ function normalizeCryptoRequirements(inputs = []) {
364
+ if (!Array.isArray(inputs)) {
365
+ throw cryptoError('PULSE_CRYPTO_REQUIREMENTS_ARRAY_REQUIRED', 'Crypto requirements must be an array.');
366
+ }
367
+ const requestedByAlgorithm = new Map();
368
+ for (const [index, input] of inputs.entries()) {
369
+ if (!isPlainObject(input)) {
370
+ throw cryptoError(
371
+ 'PULSE_CRYPTO_REQUIREMENT_OBJECT_REQUIRED',
372
+ `Crypto requirement ${index + 1} must be an object.`,
373
+ { index }
374
+ );
375
+ }
376
+ const algorithm = requireAlgorithm(input.algorithm, `crypto requirements[${index}].algorithm`);
377
+ if (
378
+ input.semanticOwner !== undefined
379
+ && (
380
+ typeof input.semanticOwner !== 'string'
381
+ || input.semanticOwner.trim() !== CRYPTO_SEMANTIC_OWNER
382
+ )
383
+ ) {
384
+ throw cryptoError(
385
+ 'PULSE_CRYPTO_REQUIREMENT_SEMANTIC_OWNER_INVALID',
386
+ `Crypto requirement ${index + 1} must name ${CRYPTO_SEMANTIC_OWNER} as semantic owner.`,
387
+ { index, algorithm, semanticOwner: input.semanticOwner }
388
+ );
389
+ }
390
+ if (typeof input.requestedBy !== 'string' || input.requestedBy.trim().length === 0) {
391
+ throw cryptoError(
392
+ 'PULSE_CRYPTO_REQUIREMENT_OWNER_REQUIRED',
393
+ `Crypto requirement ${index + 1} must identify its requesting package.`,
394
+ { index, algorithm }
395
+ );
396
+ }
397
+ const owners = requestedByAlgorithm.get(algorithm) || new Set();
398
+ owners.add(input.requestedBy.trim());
399
+ requestedByAlgorithm.set(algorithm, owners);
400
+ }
401
+ const algorithms = Array.from(requestedByAlgorithm, ([algorithm, owners]) => deepFreeze({
402
+ algorithm,
403
+ requestedBy: Array.from(owners).sort()
404
+ })).sort((left, right) => left.algorithm.localeCompare(right.algorithm));
405
+ const document = {
406
+ version: CRYPTO_REQUIREMENTS_VERSION,
407
+ semanticOwner: CRYPTO_SEMANTIC_OWNER,
408
+ algorithms
409
+ };
410
+ return deepFreeze({
411
+ ...document,
412
+ requirementsHash: sha256Hex(stableStringify(document))
413
+ });
414
+ }
415
+
416
+ function normalizeCryptoRequirementsDocument(value) {
417
+ if (!isPlainObject(value) || value.version !== CRYPTO_REQUIREMENTS_VERSION || !Array.isArray(value.algorithms)) {
418
+ throw cryptoError('PULSE_CRYPTO_REQUIREMENTS_ARRAY_REQUIRED', 'Crypto requirements document is invalid.');
419
+ }
420
+ const flattened = [];
421
+ if (
422
+ value.semanticOwner !== undefined
423
+ && value.semanticOwner !== CRYPTO_SEMANTIC_OWNER
424
+ ) {
425
+ throw cryptoError(
426
+ 'PULSE_CRYPTO_REQUIREMENT_SEMANTIC_OWNER_INVALID',
427
+ `Crypto requirements must name ${CRYPTO_SEMANTIC_OWNER} as semantic owner.`,
428
+ { semanticOwner: value.semanticOwner }
429
+ );
430
+ }
431
+ for (const [index, entry] of value.algorithms.entries()) {
432
+ if (!isPlainObject(entry) || !Array.isArray(entry.requestedBy)) {
433
+ throw cryptoError('PULSE_CRYPTO_REQUIREMENT_OBJECT_REQUIRED', 'Crypto requirement document entry is invalid.', { index });
434
+ }
435
+ for (const requestedBy of entry.requestedBy) flattened.push({
436
+ algorithm: entry.algorithm,
437
+ requestedBy,
438
+ semanticOwner: CRYPTO_SEMANTIC_OWNER
439
+ });
440
+ }
441
+ return normalizeCryptoRequirements(flattened);
442
+ }
443
+
444
+ function defineCryptoTargetCapabilities(input) {
445
+ if (!isPlainObject(input)) {
446
+ throw cryptoError('PULSE_CRYPTO_TARGET_CAPABILITIES_OBJECT_REQUIRED', 'Crypto target capabilities must be an object.');
447
+ }
448
+ const target = typeof input.target === 'string' && input.target.trim() ? input.target.trim() : null;
449
+ if (!target) throw cryptoError('PULSE_CRYPTO_TARGET_REQUIRED', 'Crypto target capabilities must identify a target.');
450
+ if (!Array.isArray(input.algorithms)) {
451
+ throw cryptoError('PULSE_CRYPTO_TARGET_ALGORITHMS_ARRAY_REQUIRED', 'Crypto target algorithms must be an array.', { target });
452
+ }
453
+ const seen = new Set();
454
+ const algorithms = input.algorithms.map((entry, index) => {
455
+ if (!isPlainObject(entry)) {
456
+ throw cryptoError('PULSE_CRYPTO_TARGET_ALGORITHM_OBJECT_REQUIRED', 'Crypto target algorithm entries must be objects.', { target, index });
457
+ }
458
+ const algorithm = requireAlgorithm(entry.algorithm, `crypto target ${target}.algorithms[${index}]`);
459
+ if (seen.has(algorithm)) {
460
+ throw cryptoError('PULSE_CRYPTO_TARGET_ALGORITHM_DUPLICATE', `Crypto target ${target} declares ${algorithm} more than once.`, { target, algorithm });
461
+ }
462
+ seen.add(algorithm);
463
+ const realization = requireKnownRealization(entry.realization, `crypto target ${target}.${algorithm}.realization`);
464
+ const known = knownRealization(realization, algorithm, target);
465
+ if (!known) {
466
+ throw cryptoError(
467
+ 'PULSE_CRYPTO_REALIZATION_PIN_INVALID',
468
+ `Crypto realization ${realization} is incompatible with ${algorithm} on target ${target}.`,
469
+ { target, algorithm, realization }
470
+ );
471
+ }
472
+ return {
473
+ algorithm,
474
+ realization,
475
+ kind: known.kind,
476
+ ...(known.backend ? { backend: known.backend } : {}),
477
+ implementation: known.implementation,
478
+ implemented: entry.implemented === true,
479
+ status: typeof entry.status === 'string' ? entry.status : (entry.implemented === true ? 'implemented' : 'planned')
480
+ };
481
+ }).sort((left, right) => left.algorithm.localeCompare(right.algorithm));
482
+ return deepFreeze({
483
+ version: CRYPTO_TARGET_CAPABILITIES_VERSION,
484
+ target,
485
+ automaticFallback: false,
486
+ algorithms
487
+ });
488
+ }
489
+
490
+ function targetCryptoCapabilities(targetDescriptor, target) {
491
+ const capabilities = targetDescriptor && targetDescriptor.crypto;
492
+ if (!capabilities || capabilities.version !== CRYPTO_TARGET_CAPABILITIES_VERSION) {
493
+ throw cryptoError(
494
+ 'PULSE_CRYPTO_REALIZATION_UNAVAILABLE',
495
+ `Selected target ${target} does not declare crypto realization capabilities.`,
496
+ { target }
497
+ );
498
+ }
499
+ if (capabilities.target !== target) {
500
+ throw cryptoError(
501
+ 'PULSE_CRYPTO_REALIZATION_UNAVAILABLE',
502
+ `Selected target ${target} has a mismatched crypto capability declaration.`,
503
+ { target, declaredTarget: capabilities.target }
504
+ );
505
+ }
506
+ return defineCryptoTargetCapabilities(capabilities);
507
+ }
508
+
509
+ function planCryptoRealizations(input = {}) {
510
+ const declaration = input.declaration && input.declaration.version === CRYPTO_CONFIGURATION_VERSION
511
+ ? normalizeCryptoConfigurationDocument(input.declaration)
512
+ : emptyCryptoConfiguration();
513
+ const requirements = input.requirements && input.requirements.version === CRYPTO_REQUIREMENTS_VERSION
514
+ ? normalizeCryptoRequirementsDocument(input.requirements)
515
+ : normalizeCryptoRequirements(input.requirements || []);
516
+ const target = typeof input.target === 'string' && input.target.trim() ? input.target.trim() : null;
517
+ const profile = typeof input.profile === 'string' && input.profile.trim() ? input.profile.trim() : null;
518
+ const profileProvenance = deepFreeze({
519
+ selectedProfile: profile,
520
+ selectionSource: typeof input.profileSelectionSource === 'string' && input.profileSelectionSource.trim()
521
+ ? input.profileSelectionSource.trim()
522
+ : null,
523
+ cryptoSource: typeof input.configurationSource === 'string' && input.configurationSource.trim()
524
+ ? input.configurationSource.trim()
525
+ : 'implicit'
526
+ });
527
+ const declaredByAlgorithm = new Map(declaration.algorithms.map((entry) => [entry.algorithm, entry]));
528
+
529
+ for (const requirement of requirements.algorithms) {
530
+ if (!declaredByAlgorithm.has(requirement.algorithm)) {
531
+ throw cryptoError(
532
+ declaration.algorithms.length === 0 ? 'PULSE_CRYPTO_CONFIG_REQUIRED' : 'PULSE_CRYPTO_ALGORITHM_UNDECLARED',
533
+ `Crypto algorithm ${requirement.algorithm}, requested by ${requirement.requestedBy.join(', ')}, is not declared by profile ${profile || '<unknown>'}.`,
534
+ {
535
+ profile,
536
+ target,
537
+ algorithm: requirement.algorithm,
538
+ requestedBy: requirement.requestedBy,
539
+ semanticOwner: requirements.semanticOwner,
540
+ profileProvenance,
541
+ declaredAlgorithms: declaration.algorithms.map((entry) => entry.algorithm)
542
+ }
543
+ );
544
+ }
545
+ }
546
+
547
+ if (declaration.algorithms.length === 0) {
548
+ const document = {
549
+ version: CRYPTO_REALIZATION_PLAN_VERSION,
550
+ semanticOwner: requirements.semanticOwner,
551
+ requirementsHash: requirements.requirementsHash,
552
+ profile,
553
+ profileProvenance,
554
+ target,
555
+ algorithms: Object.freeze([]),
556
+ automaticFallback: false
557
+ };
558
+ return deepFreeze({ ...document, planHash: sha256Hex(stableStringify(document)) });
559
+ }
560
+
561
+ const targetCapabilities = targetCryptoCapabilities(input.targetDescriptor, target);
562
+ const targetByAlgorithm = new Map(targetCapabilities.algorithms.map((entry) => [entry.algorithm, entry]));
563
+ const planned = declaration.algorithms.map((configured) => {
564
+ const targetCapability = targetByAlgorithm.get(configured.algorithm);
565
+ if (!targetCapability) {
566
+ throw cryptoError(
567
+ 'PULSE_CRYPTO_REALIZATION_UNAVAILABLE',
568
+ `Selected target ${target} has no ${configured.algorithm} realization.`,
569
+ { profile, target, algorithm: configured.algorithm }
570
+ );
571
+ }
572
+ const realization = configured.realization || targetCapability.realization;
573
+ const known = knownRealization(
574
+ realization,
575
+ configured.algorithm,
576
+ target
577
+ );
578
+ if (
579
+ !known
580
+ || targetCapability.realization !== realization
581
+ ) {
582
+ throw cryptoError(
583
+ 'PULSE_CRYPTO_REALIZATION_PIN_INVALID',
584
+ `Pinned realization ${realization} is unavailable for ${configured.algorithm} on target ${target}.`,
585
+ {
586
+ profile,
587
+ target,
588
+ algorithm: configured.algorithm,
589
+ realization,
590
+ targetRealization: targetCapability.realization
591
+ }
592
+ );
593
+ }
594
+ const requirement = requirements.algorithms.find((entry) => entry.algorithm === configured.algorithm);
595
+ return {
596
+ algorithm: configured.algorithm,
597
+ primitive: configured.algorithm === 'ES256'
598
+ ? Object.freeze({ kind: 'ecdsa', curve: 'P-256', hash: 'SHA-256' })
599
+ : Object.freeze({ kind: configured.algorithm === 'SHA-256' ? 'digest' : 'hmac', hash: 'SHA-256' }),
600
+ requestedBy: requirement ? requirement.requestedBy : Object.freeze([]),
601
+ semanticOwner: requirements.semanticOwner,
602
+ realization,
603
+ kind: known.kind,
604
+ ...(known.backend ? { backend: known.backend } : {}),
605
+ implementation: known.implementation,
606
+ targetStatus: targetCapability.status,
607
+ targetImplemented: targetCapability.implemented,
608
+ pinned: configured.realization !== null,
609
+ automaticFallback: false
610
+ };
611
+ });
612
+ const document = {
613
+ version: CRYPTO_REALIZATION_PLAN_VERSION,
614
+ semanticOwner: requirements.semanticOwner,
615
+ requirementsHash: requirements.requirementsHash,
616
+ profile,
617
+ profileProvenance,
618
+ target,
619
+ algorithms: planned,
620
+ automaticFallback: false
621
+ };
622
+ return deepFreeze({ ...document, planHash: sha256Hex(stableStringify(document)) });
623
+ }
624
+
625
+ module.exports = Object.freeze({
626
+ CRYPTO_CONFIGURATION_VERSION,
627
+ CRYPTO_TARGET_CAPABILITIES_VERSION,
628
+ CRYPTO_REQUIREMENTS_VERSION,
629
+ CRYPTO_REALIZATION_PLAN_VERSION,
630
+ CRYPTO_NATIVE_GUEST_SOURCE_PLAN_VERSION,
631
+ CRYPTO_SEMANTIC_OWNER,
632
+ CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION,
633
+ CRYPTO_GUEST_SOURCE_IMPLEMENTATION,
634
+ CRYPTO_ES256_CONTRACT_VERSION,
635
+ CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
636
+ CRYPTO_ES256_GUEST_LINKED_REALIZATION,
637
+ CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
638
+ CRYPTO_VERIFICATION_STATUSES,
639
+ CRYPTO_ES256_CONTRACT,
640
+ CRYPTO_ALGORITHMS,
641
+ CRYPTO_REALIZATIONS,
642
+ normalizeCryptoConfiguration,
643
+ emptyCryptoConfiguration,
644
+ normalizeCryptoRequirements,
645
+ defineCryptoTargetCapabilities,
646
+ planCryptoRealizations
647
+ });
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const CONFIG_SCHEMA_VERSION = 'pulsewasm.config-schema.v1';
4
+ const ENV_PROFILE_NAME = 'PULSE_PROFILE';
5
+ const SEMANTIC_ENV_KEYS = new Set([
6
+ 'entry',
7
+ 'rootRouter',
8
+ 'router',
9
+ 'routers',
10
+ 'route',
11
+ 'routes',
12
+ 'handler',
13
+ 'handlers',
14
+ 'middleware',
15
+ 'mount',
16
+ 'mounts'
17
+ ]);
18
+ const ALLOWED_ENV_METHODS = new Set(['number', 'boolean', 'define']);
19
+
20
+ function isSemanticConfigKey(key) {
21
+ return SEMANTIC_ENV_KEYS.has(key);
22
+ }
23
+
24
+ module.exports = {
25
+ CONFIG_SCHEMA_VERSION,
26
+ ENV_PROFILE_NAME,
27
+ SEMANTIC_ENV_KEYS,
28
+ ALLOWED_ENV_METHODS,
29
+ isSemanticConfigKey
30
+ };