@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,239 @@
1
+ 'use strict';
2
+
3
+ const CANONICAL_PROVIDER_CONTRACT_VERSION = 'pulse.canonical-provider-contract.v1';
4
+ const CANONICAL_PROVIDER_PLAN_VERSION = 'pulse.canonical-provider-plan.v1';
5
+
6
+ const CANONICAL_PROVIDER_CAPABILITIES = Object.freeze([
7
+ 'request',
8
+ 'response.json',
9
+ 'response.text',
10
+ 'response.custom',
11
+ 'fetch',
12
+ 'config.get',
13
+ 'secret.get',
14
+ 'kv.get',
15
+ 'kv.put',
16
+ 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap',
17
+ 'event.ingress',
18
+ 'event.emit',
19
+ 'assets.lookup',
20
+ 'grip.channel',
21
+ 'grip.hold',
22
+ 'grip.publish',
23
+ 'grip.broadcast',
24
+ 'jwt.verify',
25
+ 's3.head',
26
+ 's3.getText',
27
+ 's3.putText',
28
+ 'opaque.pass-through'
29
+ ]);
30
+
31
+ class CanonicalProviderContractError extends Error {
32
+ constructor(code, message, detail = {}) {
33
+ super(message);
34
+ this.name = 'CanonicalProviderContractError';
35
+ this.code = code;
36
+ this.detail = Object.freeze({ ...detail });
37
+ }
38
+ }
39
+
40
+ function unique(values) {
41
+ return [...new Set((values || []).filter((value) => value !== undefined).map(String))];
42
+ }
43
+
44
+ function deepFreeze(value) {
45
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
46
+ for (const child of Object.values(value)) deepFreeze(child);
47
+ return Object.freeze(value);
48
+ }
49
+
50
+ function normalizeDescriptor(input = {}) {
51
+ const id = String(input.id || input.name || '').trim().toLowerCase();
52
+ if (!id) throw new CanonicalProviderContractError('PULSE_PROVIDER_ID_REQUIRED', 'Canonical provider descriptor requires an id.');
53
+ const capabilities = unique(input.capabilities || input.operations).sort();
54
+ const unsupported = capabilities.filter((capability) => !CANONICAL_PROVIDER_CAPABILITIES.includes(capability));
55
+ if (unsupported.length > 0) {
56
+ throw new CanonicalProviderContractError(
57
+ 'PULSE_PROVIDER_CAPABILITY_UNKNOWN',
58
+ `Provider ${id} declares unknown capabilities: ${unsupported.join(', ')}`,
59
+ { id, unsupported }
60
+ );
61
+ }
62
+ const lowering = { ...(input.lowering || {}) };
63
+ for (const capability of capabilities) {
64
+ if (!lowering[capability]) {
65
+ throw new CanonicalProviderContractError(
66
+ 'PULSE_PROVIDER_LOWERING_MISSING',
67
+ `Provider ${id} does not define lowering for ${capability}.`,
68
+ { id, capability }
69
+ );
70
+ }
71
+ }
72
+ return deepFreeze({
73
+ version: CANONICAL_PROVIDER_CONTRACT_VERSION,
74
+ id,
75
+ name: id,
76
+ package: String(input.package || ''),
77
+ runtime: String(input.runtime || ''),
78
+ providerVersion: String(input.providerVersion || input.version || '0.0.0'),
79
+ buildTarget: String(input.buildTarget || id),
80
+ capabilities,
81
+ operations: capabilities,
82
+ lowering,
83
+ providerSpecificUserland: false,
84
+ providerSdkUserland: false,
85
+ capabilityDiscoveryFromUserland: false,
86
+ localExecution: input.localExecution !== false,
87
+ deployable: input.deployable === true
88
+ });
89
+ }
90
+
91
+ function capabilitiesForOperation(operation) {
92
+ if (!operation || typeof operation !== 'object') return Object.freeze([]);
93
+ if (operation.kind === 'fetch') {
94
+ return Object.freeze(operation.opaqueReturn ? ['fetch', 'opaque.pass-through'] : ['fetch']);
95
+ }
96
+ if (operation.kind === 'config') return Object.freeze(['config.get']);
97
+ if (operation.kind === 'secret') return Object.freeze(['secret.get']);
98
+ if (operation.kind === 'kv') return Object.freeze([`kv.${operation.operation || 'get'}`]);
99
+ if (operation.kind === 'grip') {
100
+ const capability = `grip.${operation.operation || ''}`;
101
+ return Object.freeze(operation.operation === 'hold' ? [capability, 'opaque.pass-through'] : [capability]);
102
+ }
103
+ if (operation.capability) {
104
+ const direct = String(operation.capability);
105
+ if (direct === 'kv') return Object.freeze([`kv.${operation.operation || 'get'}`]);
106
+ return Object.freeze([direct]);
107
+ }
108
+ return Object.freeze([]);
109
+ }
110
+
111
+ function capabilityForOperation(operation) {
112
+ return capabilitiesForOperation(operation)[0];
113
+ }
114
+
115
+ function capabilityFromCompilerCapability(capability) {
116
+ const value = String(capability);
117
+ if (value === 'request.text' || value === 'request.json') return 'request';
118
+ if (value === 'response.response') return 'response.custom';
119
+ if (value === 'kv') return undefined;
120
+ return CANONICAL_PROVIDER_CAPABILITIES.includes(value) ? value : undefined;
121
+ }
122
+
123
+ function requirementsFromMetadata(metadata = {}) {
124
+ const requirements = [];
125
+ for (const operation of Array.isArray(metadata.providerOperations) ? metadata.providerOperations : []) {
126
+ requirements.push(...capabilitiesForOperation(operation));
127
+ }
128
+ for (const capability of metadata.capabilities || []) {
129
+ const normalized = capabilityFromCompilerCapability(capability);
130
+ if (normalized) requirements.push(normalized);
131
+ }
132
+ const eventCatalog = metadata.events && metadata.events.catalog;
133
+ for (const event of eventCatalog && Array.isArray(eventCatalog.events) ? eventCatalog.events : []) {
134
+ for (const requirement of event.hostRequirements || []) {
135
+ if (CANONICAL_PROVIDER_CAPABILITIES.includes(String(requirement))) requirements.push(String(requirement));
136
+ }
137
+ }
138
+ if ((metadata.opaqueReturnCount || 0) > 0) requirements.push('fetch', 'opaque.pass-through');
139
+ return Object.freeze(unique(requirements).sort());
140
+ }
141
+
142
+ function bindingForOperation(operation, bindings = {}) {
143
+ if (!operation || typeof operation !== 'object') return undefined;
144
+ if (operation.kind === 'config') return bindings.configStore || 'pulse_config';
145
+ if (operation.kind === 'secret') return bindings.secretStore || 'pulse_secrets';
146
+ if (operation.kind === 'kv') {
147
+ const logical = operation.resource && operation.resource.kind === 'literal' ? operation.resource.value : undefined;
148
+ return logical && bindings.kv && bindings.kv[logical] ? bindings.kv[logical] : logical;
149
+ }
150
+ if (operation.kind === 'fetch') {
151
+ const origin = operation.resource && operation.resource.kind === 'literal' ? operation.resource.origin : undefined;
152
+ return origin && bindings.backends && bindings.backends[origin] ? bindings.backends[origin] : origin;
153
+ }
154
+ if (operation.kind === 'grip') return bindings.grip;
155
+ return undefined;
156
+ }
157
+
158
+ function assertProviderSupports(metadata = {}, descriptorInput) {
159
+ const descriptor = normalizeDescriptor(descriptorInput);
160
+ const requirements = requirementsFromMetadata(metadata);
161
+ const missing = requirements.filter((capability) => !descriptor.capabilities.includes(capability));
162
+ if (missing.length > 0) {
163
+ throw new CanonicalProviderContractError(
164
+ 'PULSE_PROVIDER_CAPABILITY_UNSUPPORTED',
165
+ `Provider ${descriptor.id} does not implement required capabilities: ${missing.join(', ')}`,
166
+ { provider: descriptor.id, missing, required: requirements, available: descriptor.capabilities }
167
+ );
168
+ }
169
+ return descriptor;
170
+ }
171
+
172
+ function createProviderLoweringPlan(metadata = {}, descriptorInput, bindings = {}) {
173
+ const descriptor = assertProviderSupports(metadata, descriptorInput);
174
+ const requirements = requirementsFromMetadata(metadata);
175
+ const operations = (metadata.providerOperations || []).map((operation) => {
176
+ const operationCapabilities = capabilitiesForOperation(operation);
177
+ const primaryCapability = operationCapabilities[0];
178
+ const resultCapability = operationCapabilities[1];
179
+ return deepFreeze({
180
+ id: String(operation.id),
181
+ kind: String(operation.kind),
182
+ operation: operation.operation ? String(operation.operation) : undefined,
183
+ capability: primaryCapability,
184
+ capabilities: operationCapabilities,
185
+ lowering: primaryCapability ? descriptor.lowering[primaryCapability] : undefined,
186
+ resultCapability,
187
+ resultLowering: resultCapability ? descriptor.lowering[resultCapability] : undefined,
188
+ binding: bindingForOperation(operation, bindings),
189
+ resource: operation.resource,
190
+ package: operation.package ? String(operation.package) : undefined,
191
+ contractId: operation.contractId ? String(operation.contractId) : undefined,
192
+ payload: operation.payload && typeof operation.payload === 'object' ? deepFreeze({ ...operation.payload }) : undefined,
193
+ result: operation.result ? String(operation.result) : undefined,
194
+ opaqueReturn: Boolean(operation.opaqueReturn || operation.result === 'opaque-response'),
195
+ position: operation.position
196
+ });
197
+ });
198
+ return deepFreeze({
199
+ version: CANONICAL_PROVIDER_PLAN_VERSION,
200
+ contractVersion: CANONICAL_PROVIDER_CONTRACT_VERSION,
201
+ provider: descriptor.id,
202
+ providerVersion: descriptor.providerVersion,
203
+ package: descriptor.package,
204
+ runtime: descriptor.runtime,
205
+ buildTarget: descriptor.buildTarget,
206
+ deployable: descriptor.deployable,
207
+ localExecution: descriptor.localExecution,
208
+ requirements,
209
+ bindings: {
210
+ configStore: bindings.configStore || undefined,
211
+ secretStore: bindings.secretStore || undefined,
212
+ kv: { ...(bindings.kv || {}) },
213
+ backends: { ...(bindings.backends || {}) },
214
+ grip: bindings.grip && typeof bindings.grip === 'object' ? { ...bindings.grip } : undefined
215
+ },
216
+ operations,
217
+ providerSpecificUserland: false,
218
+ providerSdkUserland: false,
219
+ capabilityDiscoveryFromUserland: false
220
+ });
221
+ }
222
+
223
+ module.exports = Object.freeze({
224
+ CANONICAL_PROVIDER_CONTRACT_VERSION,
225
+ CANONICAL_PROVIDER_PLAN_VERSION,
226
+ CANONICAL_PROVIDER_CAPABILITIES,
227
+ CanonicalProviderContractError,
228
+ normalizeDescriptor,
229
+ createProviderDescriptor: normalizeDescriptor,
230
+ requirementsFromMetadata,
231
+ assertProviderSupports,
232
+ createProviderLoweringPlan,
233
+ createProviderPlan: createProviderLoweringPlan,
234
+ capabilitiesForOperation,
235
+ capabilityForOperation,
236
+ capabilityFromCompilerCapability,
237
+ bindingForOperation,
238
+ deepFreeze
239
+ });
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ const FINAL_WASM_POLICY_VERSION = 'pulse.final-wasm-policy.v1';
6
+
7
+ function stableObject(value) {
8
+ if (Array.isArray(value)) return value.map(stableObject);
9
+ if (!value || typeof value !== 'object') return value;
10
+ return Object.fromEntries(Object.keys(value).sort()
11
+ .filter((key) => value[key] !== undefined)
12
+ .map((key) => [key, stableObject(value[key])]));
13
+ }
14
+
15
+ function sha256(value) {
16
+ return crypto.createHash('sha256').update(JSON.stringify(stableObject(value))).digest('hex');
17
+ }
18
+
19
+ function string(value, field) {
20
+ if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${field} must be a non-empty string.`);
21
+ return value.trim();
22
+ }
23
+
24
+ function surface(values, field) {
25
+ if (!Array.isArray(values)) throw new TypeError(`${field} must be an array.`);
26
+ const seen = new Set();
27
+ return Object.freeze(values.map((entry, index) => {
28
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new TypeError(`${field}[${index}] must be an object.`);
29
+ const normalized = Object.freeze({
30
+ module: entry.module === undefined ? undefined : string(entry.module, `${field}[${index}].module`),
31
+ name: string(entry.name, `${field}[${index}].name`),
32
+ kind: string(entry.kind, `${field}[${index}].kind`)
33
+ });
34
+ const key = `${normalized.module || ''}\0${normalized.name}\0${normalized.kind}`;
35
+ if (seen.has(key)) throw new TypeError(`${field} contains duplicate surface ${normalized.name}.`);
36
+ seen.add(key);
37
+ return normalized;
38
+ }).sort((left, right) => (
39
+ String(left.module || '').localeCompare(String(right.module || ''))
40
+ || left.name.localeCompare(right.name)
41
+ || left.kind.localeCompare(right.kind)
42
+ )));
43
+ }
44
+
45
+ function defineFinalWasmPolicy(input) {
46
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('Final Wasm policy must be an object.');
47
+ if (input.version !== FINAL_WASM_POLICY_VERSION) throw new TypeError(`Final Wasm policy must use ${FINAL_WASM_POLICY_VERSION}.`);
48
+ const descriptor = Object.freeze({
49
+ version: FINAL_WASM_POLICY_VERSION,
50
+ descriptorOwner: string(input.descriptorOwner, 'descriptorOwner'),
51
+ toolchainVersion: string(input.toolchainVersion, 'toolchainVersion'),
52
+ descriptorIdentity: string(input.descriptorIdentity, 'descriptorIdentity'),
53
+ permittedImports: surface(input.permittedImports, 'permittedImports'),
54
+ requiredExports: surface(input.requiredExports, 'requiredExports'),
55
+ featureBaseline: 'mvp',
56
+ allowedFeatures: Object.freeze([]),
57
+ memory: Object.freeze({ minimum: 1, maximum: 1, imported: 0, growable: false }),
58
+ start: 'forbidden'
59
+ });
60
+ return Object.freeze({ ...descriptor, descriptorSha256: sha256(descriptor) });
61
+ }
62
+
63
+ function projectFinalWasmPolicy(policy, imports) {
64
+ const descriptor = defineFinalWasmPolicy(policy);
65
+ const allowedImports = surface(imports, 'observed final Wasm imports');
66
+ const permitted = new Set(descriptor.permittedImports.map((entry) => `${entry.module}\0${entry.name}\0${entry.kind}`));
67
+ const rejected = allowedImports.filter((entry) => !permitted.has(`${entry.module}\0${entry.name}\0${entry.kind}`));
68
+ if (rejected.length > 0) throw new TypeError(`Final Wasm imports exceed the ${descriptor.descriptorIdentity} policy ceiling.`);
69
+ return Object.freeze({
70
+ descriptorOwner: descriptor.descriptorOwner,
71
+ toolchainVersion: descriptor.toolchainVersion,
72
+ descriptorIdentity: descriptor.descriptorIdentity,
73
+ descriptorSha256: descriptor.descriptorSha256,
74
+ allowedImports,
75
+ requiredExports: descriptor.requiredExports.map(({ name, kind }) => Object.freeze({ name, kind })),
76
+ featureBaseline: descriptor.featureBaseline,
77
+ allowedFeatures: descriptor.allowedFeatures,
78
+ memory: descriptor.memory,
79
+ start: descriptor.start
80
+ });
81
+ }
82
+
83
+ module.exports = Object.freeze({
84
+ FINAL_WASM_POLICY_VERSION,
85
+ defineFinalWasmPolicy,
86
+ projectFinalWasmPolicy
87
+ });