@pulse-compute/jwt 0.0.0 → 1.0.0-beta.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.
Files changed (60) hide show
  1. package/README.md +58 -3
  2. package/as/index.as.ts +388 -0
  3. package/conformance/native-contract.json +53 -0
  4. package/dist/bearer.d.ts +4 -0
  5. package/dist/bearer.d.ts.map +1 -0
  6. package/dist/bearer.js +33 -0
  7. package/dist/bearer.js.map +1 -0
  8. package/dist/crypto-verifier.d.ts +19 -0
  9. package/dist/crypto-verifier.d.ts.map +1 -0
  10. package/dist/crypto-verifier.js +225 -0
  11. package/dist/crypto-verifier.js.map +1 -0
  12. package/dist/errors.d.ts +14 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +65 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/index.d.ts +19 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +21 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/internal/clock.d.ts +7 -0
  21. package/dist/internal/clock.d.ts.map +1 -0
  22. package/dist/internal/clock.js +34 -0
  23. package/dist/internal/clock.js.map +1 -0
  24. package/dist/internal/data.d.ts +12 -0
  25. package/dist/internal/data.d.ts.map +1 -0
  26. package/dist/internal/data.js +86 -0
  27. package/dist/internal/data.js.map +1 -0
  28. package/dist/internal/package-runtime.d.ts +9 -0
  29. package/dist/internal/package-runtime.d.ts.map +1 -0
  30. package/dist/internal/package-runtime.js +14 -0
  31. package/dist/internal/package-runtime.js.map +1 -0
  32. package/dist/internal/registered-claims.d.ts +3 -0
  33. package/dist/internal/registered-claims.d.ts.map +1 -0
  34. package/dist/internal/registered-claims.js +84 -0
  35. package/dist/internal/registered-claims.js.map +1 -0
  36. package/dist/internal/verifier-authority.d.ts +24 -0
  37. package/dist/internal/verifier-authority.d.ts.map +1 -0
  38. package/dist/internal/verifier-authority.js +101 -0
  39. package/dist/internal/verifier-authority.js.map +1 -0
  40. package/dist/options.d.ts +113 -0
  41. package/dist/options.d.ts.map +1 -0
  42. package/dist/options.js +382 -0
  43. package/dist/options.js.map +1 -0
  44. package/dist/provider.d.ts +7 -0
  45. package/dist/provider.d.ts.map +1 -0
  46. package/dist/provider.js +7 -0
  47. package/dist/provider.js.map +1 -0
  48. package/dist/result.d.ts +17 -0
  49. package/dist/result.d.ts.map +1 -0
  50. package/dist/result.js +96 -0
  51. package/dist/result.js.map +1 -0
  52. package/dist/token.d.ts +20 -0
  53. package/dist/token.d.ts.map +1 -0
  54. package/dist/token.js +323 -0
  55. package/dist/token.js.map +1 -0
  56. package/package.json +64 -6
  57. package/pulse.package.json +75 -0
  58. package/pulsewasm.compiler.cjs +1258 -0
  59. package/pulsewasm.manifest.cjs +117 -0
  60. package/pulsewasm.native.cjs +60 -0
@@ -0,0 +1,1258 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ const phaseName = 'jwt-lowering-plan';
6
+
7
+ function loadDiagnostics() {
8
+ try {
9
+ return require('@pulse-compute/wasm-contracts/diagnostics');
10
+ } catch (error) {
11
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
12
+ return require('../../wasm/packages/contracts/src/diagnostics.js');
13
+ }
14
+ throw error;
15
+ }
16
+ }
17
+
18
+ function loadJwtContracts() {
19
+ try {
20
+ return require('@pulse-compute/wasm-contracts/jwt/contracts');
21
+ } catch (error) {
22
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
23
+ return require('../../wasm/packages/contracts/src/jwt/contracts.js');
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function loadPackageContracts() {
30
+ try {
31
+ return require('@pulse-compute/wasm-contracts/package/package-contract');
32
+ } catch (error) {
33
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
34
+ return require('../../wasm/packages/contracts/src/package/package-contract.js');
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ function loadCanonicalRuntimeContracts() {
41
+ try {
42
+ return require('@pulse-compute/wasm-contracts/handler/canonical-runtime');
43
+ } catch (error) {
44
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
45
+ return require('../../wasm/packages/contracts/src/handler/canonical-runtime.js');
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ const {
52
+ normalizeArtifact,
53
+ normalizeDiagnostic,
54
+ sourceLoc,
55
+ stableFileName
56
+ } = loadDiagnostics();
57
+ const jwtContracts = loadJwtContracts();
58
+ const packageContracts = loadPackageContracts();
59
+ const canonicalRuntimeContracts = loadCanonicalRuntimeContracts();
60
+
61
+ function loadCryptoGuestContribution() {
62
+ try {
63
+ return require('@pulse-compute/crypto/pulsewasm-native')
64
+ .pulseEs256GuestUnit();
65
+ } catch (error) {
66
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
67
+ return require('../crypto/pulsewasm.native.cjs').pulseEs256GuestUnit();
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+
73
+ const MAX_TOKEN_AGE_SECONDS = 31_536_000;
74
+ const OPTION_MEMBERS = new Set([
75
+ 'algorithms', 'key', 'issuer', 'audience', 'subject', 'typ',
76
+ 'clockToleranceSeconds', 'maxTokenAgeSeconds', 'requiredClaims', 'claimsSchema'
77
+ ]);
78
+
79
+ function clone(value) {
80
+ if (Array.isArray(value)) return value.map(clone);
81
+ if (!value || typeof value !== 'object') return value;
82
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, clone(child)]));
83
+ }
84
+
85
+ function deepFreeze(value) {
86
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
87
+ for (const child of Object.values(value)) deepFreeze(child);
88
+ return Object.freeze(value);
89
+ }
90
+
91
+ function stableObject(value) {
92
+ if (Array.isArray(value)) return value.map(stableObject);
93
+ if (!value || typeof value !== 'object') return value;
94
+ return Object.fromEntries(Object.keys(value).sort()
95
+ .filter((key) => value[key] !== undefined)
96
+ .map((key) => [key, stableObject(value[key])]));
97
+ }
98
+
99
+ function stableStringify(value) {
100
+ return JSON.stringify(stableObject(value));
101
+ }
102
+
103
+ function loadTypeScript(inputs = {}) {
104
+ if (inputs.ts) return inputs.ts;
105
+ try {
106
+ return require('typescript');
107
+ } catch (error) {
108
+ const diagnostic = normalizeDiagnostic({
109
+ phase: phaseName,
110
+ severity: 'error',
111
+ code: 'PULSE_JWT_TYPESCRIPT_REQUIRED',
112
+ message: 'JWT lowering plan extraction requires the TypeScript parser.',
113
+ hint: 'Run the lowerer from the synchronized Pulse workspace toolchain.',
114
+ loc: { file: '<jwt-lowering-plan>' }
115
+ });
116
+ const thrown = new Error(diagnostic.message);
117
+ thrown.diagnostics = [diagnostic];
118
+ throw thrown;
119
+ }
120
+ }
121
+
122
+ function unwrapExpression(ts, node) {
123
+ let current = node;
124
+ while (
125
+ current
126
+ && (
127
+ ts.isParenthesizedExpression(current)
128
+ || ts.isAsExpression(current)
129
+ || ts.isNonNullExpression(current)
130
+ || ts.isTypeAssertionExpression(current)
131
+ || ts.isSatisfiesExpression && ts.isSatisfiesExpression(current)
132
+ )
133
+ ) current = current.expression;
134
+ return current;
135
+ }
136
+
137
+ function locFor(sourceFile, node) {
138
+ return node ? sourceLoc(sourceFile, node) : { file: sourceFile ? sourceFile.fileName : '<jwt-lowering-plan>' };
139
+ }
140
+
141
+ function rangeFor(sourceFile, node) {
142
+ return Object.freeze({
143
+ start: node && typeof node.getStart === 'function' ? node.getStart(sourceFile) : 0,
144
+ end: node && typeof node.getEnd === 'function' ? node.getEnd() : 0
145
+ });
146
+ }
147
+
148
+ function positionFor(sourceFile, node) {
149
+ const offset = node && typeof node.getStart === 'function' ? node.getStart(sourceFile) : 0;
150
+ const point = sourceFile.getLineAndCharacterOfPosition(offset);
151
+ return Object.freeze({ line: point.line + 1, column: point.character + 1, offset });
152
+ }
153
+
154
+ function makeDiagnostic(sourceFile, node, code, message, hint, details) {
155
+ return normalizeDiagnostic({
156
+ phase: phaseName,
157
+ severity: 'error',
158
+ code,
159
+ message,
160
+ hint,
161
+ details,
162
+ loc: locFor(sourceFile, node)
163
+ });
164
+ }
165
+
166
+ function hasErrors(diagnostics) {
167
+ return diagnostics.some((entry) => String(entry.severity || 'error') === 'error');
168
+ }
169
+
170
+ function propertyName(ts, node) {
171
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return String(node.text);
172
+ return undefined;
173
+ }
174
+
175
+ function objectMembers(ts, sourceFile, node, allowed, diagnostics, code, subject) {
176
+ const current = unwrapExpression(ts, node);
177
+ if (!current || !ts.isObjectLiteralExpression(current)) {
178
+ diagnostics.push(makeDiagnostic(
179
+ sourceFile,
180
+ current || node,
181
+ code,
182
+ `${subject} must be a static object literal for Native lowering.`,
183
+ `Inline ${subject.toLowerCase()} without spreads, computed names, methods, or accessors.`
184
+ ));
185
+ return new Map();
186
+ }
187
+ const output = new Map();
188
+ for (const member of current.properties) {
189
+ if (!ts.isPropertyAssignment(member)) {
190
+ diagnostics.push(makeDiagnostic(
191
+ sourceFile,
192
+ member,
193
+ jwtContracts.JWT_DIAGNOSTIC_CODES.OPTIONS_PROPERTY_UNSUPPORTED,
194
+ `${subject} contains a dynamic property form that Native lowering cannot evaluate.`,
195
+ 'Use ordinary static property assignments only.'
196
+ ));
197
+ continue;
198
+ }
199
+ const name = propertyName(ts, member.name);
200
+ if (!name || name === '__proto__' || !allowed.has(name) || output.has(name)) {
201
+ diagnostics.push(makeDiagnostic(
202
+ sourceFile,
203
+ member.name,
204
+ jwtContracts.JWT_DIAGNOSTIC_CODES.OPTIONS_PROPERTY_UNSUPPORTED,
205
+ `${subject} contains an unsupported or duplicate property.`,
206
+ 'Use each documented static property at most once.',
207
+ { category: subject.toLowerCase().replace(/\s+/g, '-'), propertySupported: Boolean(name && allowed.has(name) && name !== '__proto__') }
208
+ ));
209
+ continue;
210
+ }
211
+ output.set(name, member.initializer);
212
+ }
213
+ return output;
214
+ }
215
+
216
+ function literalString(ts, node) {
217
+ const current = unwrapExpression(ts, node);
218
+ return current && (ts.isStringLiteral(current) || ts.isNoSubstitutionTemplateLiteral(current))
219
+ ? current.text
220
+ : undefined;
221
+ }
222
+
223
+ function literalNumber(ts, node) {
224
+ const current = unwrapExpression(ts, node);
225
+ if (!current) return undefined;
226
+ if (ts.isNumericLiteral(current)) return Number(current.text);
227
+ if (ts.isPrefixUnaryExpression(current) && current.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(current.operand)) {
228
+ return -Number(current.operand.text);
229
+ }
230
+ return undefined;
231
+ }
232
+
233
+ function literalStringArray(ts, sourceFile, node, diagnostics, options = {}) {
234
+ const current = unwrapExpression(ts, node);
235
+ if (!current || !ts.isArrayLiteralExpression(current)) {
236
+ diagnostics.push(makeDiagnostic(
237
+ sourceFile,
238
+ current || node,
239
+ options.code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
240
+ `${options.label || 'JWT policy'} must be a static string-literal array.`,
241
+ 'Use a bounded inline array of non-empty string literals.'
242
+ ));
243
+ return [];
244
+ }
245
+ const values = [];
246
+ for (const element of current.elements) {
247
+ if (ts.isSpreadElement(element)) {
248
+ diagnostics.push(makeDiagnostic(
249
+ sourceFile,
250
+ element,
251
+ options.code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
252
+ `${options.label || 'JWT policy'} cannot contain spread elements.`,
253
+ 'List each value explicitly.'
254
+ ));
255
+ continue;
256
+ }
257
+ const value = literalString(ts, element);
258
+ if (value === undefined || value.trim().length === 0 || value.includes('\0')) {
259
+ diagnostics.push(makeDiagnostic(
260
+ sourceFile,
261
+ element,
262
+ options.code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
263
+ `${options.label || 'JWT policy'} must contain only non-empty string literals.`,
264
+ 'Replace the dynamic or empty entry with a static value.'
265
+ ));
266
+ continue;
267
+ }
268
+ values.push(value);
269
+ }
270
+ if (options.nonEmpty !== false && values.length === 0) {
271
+ diagnostics.push(makeDiagnostic(
272
+ sourceFile,
273
+ current,
274
+ options.emptyCode || options.code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
275
+ `${options.label || 'JWT policy'} cannot be empty.`,
276
+ 'Declare at least one static value.'
277
+ ));
278
+ }
279
+ if (options.maximum !== undefined && values.length > options.maximum) {
280
+ diagnostics.push(makeDiagnostic(
281
+ sourceFile,
282
+ current,
283
+ options.limitCode || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LIMIT_EXCEEDED,
284
+ `${options.label || 'JWT policy'} exceeds the Native resource limit.`,
285
+ `Use no more than ${options.maximum} entries.`,
286
+ { category: options.category || 'policy', limit: options.maximum, actual: values.length }
287
+ ));
288
+ }
289
+ if (new Set(values).size !== values.length) {
290
+ diagnostics.push(makeDiagnostic(
291
+ sourceFile,
292
+ current,
293
+ options.duplicateCode || options.code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
294
+ `${options.label || 'JWT policy'} contains duplicate values.`,
295
+ 'Keep each static value once.'
296
+ ));
297
+ }
298
+ return values;
299
+ }
300
+
301
+ function parseAlgorithms(ts, sourceFile, node, diagnostics) {
302
+ const values = literalStringArray(ts, sourceFile, node, diagnostics, {
303
+ label: 'JWT algorithms',
304
+ category: 'algorithms',
305
+ code: jwtContracts.JWT_DIAGNOSTIC_CODES.ALGORITHMS_LITERAL_REQUIRED,
306
+ emptyCode: jwtContracts.JWT_DIAGNOSTIC_CODES.ALGORITHMS_EMPTY,
307
+ duplicateCode: jwtContracts.JWT_DIAGNOSTIC_CODES.ALGORITHM_UNSUPPORTED,
308
+ limitCode: jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LIMIT_EXCEEDED,
309
+ maximum: jwtContracts.JWT_RESOURCE_LIMITS.algorithms
310
+ });
311
+ for (const value of values) {
312
+ if (!jwtContracts.JWT_IMPLEMENTED_ALGORITHMS.includes(value)) {
313
+ diagnostics.push(makeDiagnostic(
314
+ sourceFile,
315
+ node,
316
+ jwtContracts.JWT_DIAGNOSTIC_CODES.ALGORITHM_UNSUPPORTED,
317
+ 'JWT algorithms contains a value outside the package verification vocabulary.',
318
+ `Use one or more of: ${jwtContracts.JWT_IMPLEMENTED_ALGORITHMS.join(', ')}.`,
319
+ {
320
+ category: 'algorithms',
321
+ supported: jwtContracts.JWT_IMPLEMENTED_ALGORITHMS,
322
+ unavailable: jwtContracts.JWT_ALGORITHMS.filter(
323
+ (algorithm) => !jwtContracts.JWT_IMPLEMENTED_ALGORITHMS.includes(algorithm)
324
+ ),
325
+ automaticFallback: false
326
+ }
327
+ ));
328
+ }
329
+ }
330
+ return [...new Set(values.filter(
331
+ (value) => jwtContracts.JWT_IMPLEMENTED_ALGORITHMS.includes(value)
332
+ ))];
333
+ }
334
+
335
+ function parseStringOrStringArray(ts, sourceFile, node, diagnostics, label) {
336
+ const direct = literalString(ts, node);
337
+ if (direct !== undefined) {
338
+ if (direct.trim().length > 0 && !direct.includes('\0')) return direct;
339
+ }
340
+ const current = unwrapExpression(ts, node);
341
+ if (current && ts.isArrayLiteralExpression(current)) {
342
+ return literalStringArray(ts, sourceFile, current, diagnostics, { label, category: label.toLowerCase() });
343
+ }
344
+ diagnostics.push(makeDiagnostic(
345
+ sourceFile,
346
+ current || node,
347
+ jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
348
+ `${label} must be a non-empty string literal or static string-literal array.`,
349
+ 'Inline the accepted policy value.'
350
+ ));
351
+ return undefined;
352
+ }
353
+
354
+ function parseOptionalString(ts, sourceFile, node, diagnostics, label, code) {
355
+ const value = literalString(ts, node);
356
+ if (value === undefined || value.trim().length === 0 || value.includes('\0')) {
357
+ diagnostics.push(makeDiagnostic(
358
+ sourceFile,
359
+ node,
360
+ code || jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LITERAL_REQUIRED,
361
+ `${label} must be a non-empty string literal.`,
362
+ 'Use a static non-empty string.'
363
+ ));
364
+ return undefined;
365
+ }
366
+ return value;
367
+ }
368
+
369
+ function canonicalCoordinate(value) {
370
+ if (
371
+ typeof value !== 'string'
372
+ || value.length === 0
373
+ || value.includes('=')
374
+ || !/^[A-Za-z0-9_-]+$/.test(value)
375
+ || value.length % 4 === 1
376
+ ) return false;
377
+ let bytes;
378
+ try {
379
+ bytes = Buffer.from(value, 'base64url');
380
+ } catch {
381
+ return false;
382
+ }
383
+ return bytes.length === 32 && bytes.toString('base64url') === value;
384
+ }
385
+
386
+ function parseEs256Jwk(ts, sourceFile, node, diagnostics) {
387
+ const members = objectMembers(
388
+ ts,
389
+ sourceFile,
390
+ node,
391
+ new Set(['kty', 'crv', 'x', 'y', 'alg', 'use', 'key_ops', 'kid']),
392
+ diagnostics,
393
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
394
+ 'ES256 public JWK'
395
+ );
396
+ const required = {};
397
+ for (const name of ['kty', 'crv', 'x', 'y']) {
398
+ const value = members.has(name) ? literalString(ts, members.get(name)) : undefined;
399
+ if (value === undefined || value.length === 0) {
400
+ diagnostics.push(makeDiagnostic(
401
+ sourceFile,
402
+ members.get(name) || node,
403
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
404
+ `ES256 public JWK requires static ${name}.`,
405
+ 'Use a bounded inline public P-256 JWK.'
406
+ ));
407
+ } else required[name] = value;
408
+ }
409
+ if (required.kty !== undefined && required.kty !== 'EC') diagnostics.push(makeDiagnostic(
410
+ sourceFile,
411
+ members.get('kty') || node,
412
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
413
+ 'ES256 public JWK kty must be EC.',
414
+ 'Use kty: "EC".'
415
+ ));
416
+ if (required.crv !== undefined && required.crv !== 'P-256') diagnostics.push(makeDiagnostic(
417
+ sourceFile,
418
+ members.get('crv') || node,
419
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
420
+ 'ES256 public JWK crv must be P-256.',
421
+ 'Use crv: "P-256".'
422
+ ));
423
+ for (const name of ['x', 'y']) {
424
+ if (required[name] !== undefined && !canonicalCoordinate(required[name])) diagnostics.push(makeDiagnostic(
425
+ sourceFile,
426
+ members.get(name) || node,
427
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
428
+ `ES256 public JWK ${name} must be canonical unpadded base64url for exactly 32 bytes.`,
429
+ 'Use one exact P-256 coordinate.'
430
+ ));
431
+ }
432
+
433
+ const output = { ...required };
434
+ for (const name of ['alg', 'use', 'kid']) {
435
+ if (!members.has(name)) continue;
436
+ const value = parseOptionalString(
437
+ ts,
438
+ sourceFile,
439
+ members.get(name),
440
+ diagnostics,
441
+ `ES256 public JWK ${name}`,
442
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID
443
+ );
444
+ if (value !== undefined) output[name] = value;
445
+ }
446
+ if (output.alg !== undefined && output.alg !== 'ES256') diagnostics.push(makeDiagnostic(
447
+ sourceFile,
448
+ members.get('alg'),
449
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_ALGORITHM_MISMATCH,
450
+ 'ES256 public JWK alg must be absent or ES256.',
451
+ 'Remove alg or use alg: "ES256".'
452
+ ));
453
+ if (output.use !== undefined && output.use !== 'sig') diagnostics.push(makeDiagnostic(
454
+ sourceFile,
455
+ members.get('use'),
456
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
457
+ 'ES256 public JWK use must be absent or sig.',
458
+ 'Remove use or use use: "sig".'
459
+ ));
460
+ if (members.has('key_ops')) {
461
+ const operations = literalStringArray(
462
+ ts,
463
+ sourceFile,
464
+ members.get('key_ops'),
465
+ diagnostics,
466
+ {
467
+ label: 'ES256 public JWK key_ops',
468
+ category: 'jwk-key-ops',
469
+ maximum: 1,
470
+ code: jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
471
+ limitCode: jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID
472
+ }
473
+ );
474
+ if (operations.length !== 1 || operations[0] !== 'verify') diagnostics.push(makeDiagnostic(
475
+ sourceFile,
476
+ members.get('key_ops'),
477
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWK_INVALID,
478
+ 'ES256 public JWK key_ops must be absent or exactly ["verify"].',
479
+ 'Remove key_ops or declare only verify.'
480
+ ));
481
+ output.key_ops = operations;
482
+ }
483
+ return deepFreeze(output);
484
+ }
485
+
486
+ function parseEs256Jwks(ts, sourceFile, node, diagnostics) {
487
+ const current = unwrapExpression(ts, node);
488
+ if (!current || !ts.isArrayLiteralExpression(current)) {
489
+ diagnostics.push(makeDiagnostic(
490
+ sourceFile,
491
+ current || node,
492
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED,
493
+ 'ES256 JWKS keys must be a bounded static array.',
494
+ 'Inline no more than 16 public P-256 JWK objects.'
495
+ ));
496
+ return [];
497
+ }
498
+ if (current.elements.length === 0) diagnostics.push(makeDiagnostic(
499
+ sourceFile,
500
+ current,
501
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWKS_AMBIGUOUS,
502
+ 'ES256 JWKS cannot be empty.',
503
+ 'Declare at least one public P-256 JWK.'
504
+ ));
505
+ if (current.elements.length > jwtContracts.JWT_RESOURCE_LIMITS.jwksEntries) diagnostics.push(makeDiagnostic(
506
+ sourceFile,
507
+ current,
508
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWKS_LIMIT_EXCEEDED,
509
+ 'ES256 JWKS exceeds the static key limit.',
510
+ `Use no more than ${jwtContracts.JWT_RESOURCE_LIMITS.jwksEntries} keys.`,
511
+ {
512
+ limit: jwtContracts.JWT_RESOURCE_LIMITS.jwksEntries,
513
+ actual: current.elements.length
514
+ }
515
+ ));
516
+ const keys = current.elements.slice(0, jwtContracts.JWT_RESOURCE_LIMITS.jwksEntries)
517
+ .map((entry) => parseEs256Jwk(ts, sourceFile, entry, diagnostics));
518
+ const kids = keys.map((entry) => entry.kid).filter(Boolean);
519
+ if (new Set(kids).size !== kids.length) diagnostics.push(makeDiagnostic(
520
+ sourceFile,
521
+ current,
522
+ jwtContracts.JWT_DIAGNOSTIC_CODES.JWKS_AMBIGUOUS,
523
+ 'ES256 JWKS contains duplicate kid values.',
524
+ 'Keep each configured kid unique.'
525
+ ));
526
+ return keys;
527
+ }
528
+
529
+ function keyArtifact(type, keys) {
530
+ const material = type === 'jwk'
531
+ ? { type, key: keys[0] }
532
+ : { type, keys };
533
+ const materialHash = crypto.createHash('sha256')
534
+ .update(stableStringify(material))
535
+ .digest('hex');
536
+ const id = `pulse.jwt.es256-key.${materialHash.slice(0, 24)}`;
537
+ return Object.freeze({
538
+ descriptor: Object.freeze({
539
+ type,
540
+ count: keys.length,
541
+ keyArtifactId: id,
542
+ materialHash
543
+ }),
544
+ resource: Object.freeze({
545
+ keyType: type,
546
+ keyArtifactId: id,
547
+ secretBinding: null
548
+ }),
549
+ realizationArtifact: deepFreeze({
550
+ version: 'pulse.jwt-es256-key-artifact.v1',
551
+ id,
552
+ contractId: jwtContracts.JWT_CONTRACT_ID,
553
+ package: jwtContracts.JWT_PACKAGE_NAME,
554
+ kind: 'jwt-es256-static-public-key',
555
+ mediaType: 'application/vnd.pulse.jwt-es256-key+json',
556
+ materialHash,
557
+ redaction: ['x', 'y'],
558
+ data: material
559
+ })
560
+ });
561
+ }
562
+
563
+ function parseKey(ts, sourceFile, node, algorithms, diagnostics) {
564
+ const members = objectMembers(
565
+ ts,
566
+ sourceFile,
567
+ node,
568
+ new Set(['type', 'binding', 'key', 'keys']),
569
+ diagnostics,
570
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED,
571
+ 'JWT key descriptor'
572
+ );
573
+ const type = members.has('type') ? literalString(ts, members.get('type')) : undefined;
574
+ if (!jwtContracts.JWT_KEY_TYPES.includes(type)) {
575
+ diagnostics.push(makeDiagnostic(
576
+ sourceFile,
577
+ members.get('type') || node,
578
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_TYPE_UNSUPPORTED,
579
+ 'JWT key descriptor requires static type secret, jwk, or jwks.',
580
+ 'Choose one documented verification key form.'
581
+ ));
582
+ return Object.freeze({
583
+ descriptor: Object.freeze({ type: 'unknown', count: 0 }),
584
+ resource: Object.freeze({ keyType: 'unknown', keyArtifactId: null, secretBinding: null }),
585
+ realizationArtifact: null
586
+ });
587
+ }
588
+ if (type === 'secret') {
589
+ if (members.size !== 2 || !members.has('binding')) diagnostics.push(makeDiagnostic(
590
+ sourceFile,
591
+ node,
592
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED,
593
+ 'Named-secret JWT keys require exactly type and binding.',
594
+ 'Use { type: "secret", binding: "NAME" }.'
595
+ ));
596
+ const binding = parseOptionalString(
597
+ ts,
598
+ sourceFile,
599
+ members.get('binding'),
600
+ diagnostics,
601
+ 'JWT secret binding',
602
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED
603
+ );
604
+ if (algorithms.some((algorithm) => algorithm !== 'HS256')) diagnostics.push(makeDiagnostic(
605
+ sourceFile,
606
+ node,
607
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_ALGORITHM_MISMATCH,
608
+ 'Named-secret JWT verification is compatible only with HS256.',
609
+ 'Use HS256 alone.',
610
+ { keyType: 'secret', algorithms }
611
+ ));
612
+ return Object.freeze({
613
+ descriptor: Object.freeze({ type, count: 1 }),
614
+ resource: Object.freeze({
615
+ keyType: type,
616
+ keyArtifactId: null,
617
+ secretBinding: binding || null
618
+ }),
619
+ realizationArtifact: null
620
+ });
621
+ }
622
+
623
+ if (algorithms.length !== 1 || algorithms[0] !== 'ES256') {
624
+ diagnostics.push(makeDiagnostic(
625
+ sourceFile,
626
+ node,
627
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_ALGORITHM_MISMATCH,
628
+ 'Public P-256 JWT keys are compatible only with ES256.',
629
+ 'Use ES256 alone.',
630
+ { keyType: type, algorithms }
631
+ ));
632
+ return Object.freeze({
633
+ descriptor: Object.freeze({ type, count: 0 }),
634
+ resource: Object.freeze({
635
+ keyType: type,
636
+ keyArtifactId: null,
637
+ secretBinding: null
638
+ }),
639
+ realizationArtifact: null
640
+ });
641
+ }
642
+ if (type === 'jwk') {
643
+ if (members.size !== 2 || !members.has('key')) diagnostics.push(makeDiagnostic(
644
+ sourceFile,
645
+ node,
646
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED,
647
+ 'Inline JWK descriptors require exactly type and key.',
648
+ 'Use { type: "jwk", key: { ...publicP256Jwk } }.'
649
+ ));
650
+ return keyArtifact(
651
+ type,
652
+ [parseEs256Jwk(ts, sourceFile, members.get('key'), diagnostics)]
653
+ );
654
+ }
655
+ if (members.size !== 2 || !members.has('keys')) diagnostics.push(makeDiagnostic(
656
+ sourceFile,
657
+ node,
658
+ jwtContracts.JWT_DIAGNOSTIC_CODES.KEY_LITERAL_REQUIRED,
659
+ 'Static JWKS descriptors require exactly type and keys.',
660
+ 'Use { type: "jwks", keys: [ ...publicP256Jwks ] }.'
661
+ ));
662
+ return keyArtifact(
663
+ type,
664
+ parseEs256Jwks(ts, sourceFile, members.get('keys'), diagnostics)
665
+ );
666
+ }
667
+
668
+ function schemaSets(schemaBundle) {
669
+ const registrySchemas = schemaBundle && schemaBundle.registry && Array.isArray(schemaBundle.registry.schemas)
670
+ ? schemaBundle.registry.schemas
671
+ : [];
672
+ const realized = new Set(registrySchemas.map((entry) => String(entry.id)));
673
+ const declared = new Set(
674
+ schemaBundle && Array.isArray(schemaBundle.declaredSchemaIds)
675
+ ? schemaBundle.declaredSchemaIds.map(String)
676
+ : (
677
+ schemaBundle && Array.isArray(schemaBundle.schemaIds)
678
+ ? schemaBundle.schemaIds.map(String)
679
+ : [...realized]
680
+ )
681
+ );
682
+ return { declared, realized };
683
+ }
684
+
685
+ function parseOptions(ts, sourceFile, node, diagnostics, inputs) {
686
+ const members = objectMembers(
687
+ ts,
688
+ sourceFile,
689
+ node,
690
+ OPTION_MEMBERS,
691
+ diagnostics,
692
+ jwtContracts.JWT_DIAGNOSTIC_CODES.OPTIONS_LITERAL_REQUIRED,
693
+ 'JWT verification options'
694
+ );
695
+ if (!members.has('algorithms') || !members.has('key')) diagnostics.push(makeDiagnostic(
696
+ sourceFile,
697
+ node,
698
+ jwtContracts.JWT_DIAGNOSTIC_CODES.ARGUMENT_SHAPE_UNSUPPORTED,
699
+ 'JWT verification options require static algorithms and key properties.',
700
+ 'Declare both algorithms and key in the inline options object.'
701
+ ));
702
+
703
+ const algorithms = parseAlgorithms(ts, sourceFile, members.get('algorithms'), diagnostics);
704
+ const key = parseKey(ts, sourceFile, members.get('key'), algorithms, diagnostics);
705
+ const policy = { algorithms };
706
+ if (members.has('issuer')) policy.issuer = parseStringOrStringArray(ts, sourceFile, members.get('issuer'), diagnostics, 'JWT issuer');
707
+ if (members.has('audience')) policy.audience = parseStringOrStringArray(ts, sourceFile, members.get('audience'), diagnostics, 'JWT audience');
708
+ if (members.has('subject')) policy.subject = parseOptionalString(ts, sourceFile, members.get('subject'), diagnostics, 'JWT subject');
709
+ if (members.has('typ')) policy.typ = parseOptionalString(ts, sourceFile, members.get('typ'), diagnostics, 'JWT typ');
710
+ if (members.has('clockToleranceSeconds')) {
711
+ const value = literalNumber(ts, members.get('clockToleranceSeconds'));
712
+ if (!Number.isSafeInteger(value) || value < 0 || value > jwtContracts.JWT_RESOURCE_LIMITS.clockToleranceSeconds) diagnostics.push(makeDiagnostic(
713
+ sourceFile,
714
+ members.get('clockToleranceSeconds'),
715
+ jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LIMIT_EXCEEDED,
716
+ 'JWT clockToleranceSeconds must be a static integer within the package limit.',
717
+ `Use an integer from 0 through ${jwtContracts.JWT_RESOURCE_LIMITS.clockToleranceSeconds}.`,
718
+ { category: 'clock-tolerance', limit: jwtContracts.JWT_RESOURCE_LIMITS.clockToleranceSeconds }
719
+ ));
720
+ else policy.clockToleranceSeconds = value;
721
+ }
722
+ if (members.has('maxTokenAgeSeconds')) {
723
+ const value = literalNumber(ts, members.get('maxTokenAgeSeconds'));
724
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_TOKEN_AGE_SECONDS) diagnostics.push(makeDiagnostic(
725
+ sourceFile,
726
+ members.get('maxTokenAgeSeconds'),
727
+ jwtContracts.JWT_DIAGNOSTIC_CODES.POLICY_LIMIT_EXCEEDED,
728
+ 'JWT maxTokenAgeSeconds must be a bounded static integer.',
729
+ `Use an integer from 1 through ${MAX_TOKEN_AGE_SECONDS}.`,
730
+ { category: 'max-token-age', limit: MAX_TOKEN_AGE_SECONDS }
731
+ ));
732
+ else policy.maxTokenAgeSeconds = value;
733
+ }
734
+ if (members.has('requiredClaims')) policy.requiredClaims = literalStringArray(
735
+ ts,
736
+ sourceFile,
737
+ members.get('requiredClaims'),
738
+ diagnostics,
739
+ {
740
+ label: 'JWT requiredClaims',
741
+ category: 'required-claims',
742
+ maximum: jwtContracts.JWT_RESOURCE_LIMITS.requiredClaimNames
743
+ }
744
+ );
745
+
746
+ let schemaReference;
747
+ if (members.has('claimsSchema')) {
748
+ const schemaId = literalString(ts, members.get('claimsSchema'));
749
+ if (schemaId === undefined || schemaId.trim().length === 0 || schemaId.includes('\0')) diagnostics.push(makeDiagnostic(
750
+ sourceFile,
751
+ members.get('claimsSchema'),
752
+ jwtContracts.JWT_DIAGNOSTIC_CODES.SCHEMA_LITERAL_REQUIRED,
753
+ 'JWT claimsSchema must be a non-empty schema ID literal.',
754
+ 'Reference one statically declared project schema.'
755
+ ));
756
+ else {
757
+ policy.claimsSchema = schemaId;
758
+ const sets = schemaSets(inputs.schemaBundle);
759
+ if (!sets.declared.has(schemaId)) diagnostics.push(makeDiagnostic(
760
+ sourceFile,
761
+ members.get('claimsSchema'),
762
+ jwtContracts.JWT_DIAGNOSTIC_CODES.SCHEMA_UNKNOWN,
763
+ `JWT claims schema ${JSON.stringify(schemaId)} is not declared by pulse.schema.`,
764
+ 'Declare the schema in the selected project registry or correct the literal ID.',
765
+ { usage: 'jwt-claims', schemaId, declared: [...sets.declared].sort() }
766
+ ));
767
+ else if (!sets.realized.has(schemaId)) diagnostics.push(makeDiagnostic(
768
+ sourceFile,
769
+ members.get('claimsSchema'),
770
+ jwtContracts.JWT_DIAGNOSTIC_CODES.SCHEMA_UNREALIZED,
771
+ `JWT claims schema ${JSON.stringify(schemaId)} has no cross-target codec realization.`,
772
+ 'Use a schema whose codec shape is realized on both targets.',
773
+ { usage: 'jwt-claims', schemaId, fullCodecRealization: false, automaticFallback: false }
774
+ ));
775
+ schemaReference = Object.freeze({
776
+ id: schemaId,
777
+ usage: 'jwt-claims',
778
+ capability: 'schema.decode',
779
+ file: sourceFile.fileName,
780
+ position: positionFor(sourceFile, members.get('claimsSchema'))
781
+ });
782
+ }
783
+ }
784
+ return Object.freeze({
785
+ policy: deepFreeze(policy),
786
+ key,
787
+ schemaReference,
788
+ providerRequirements: jwtContracts.jwtVerifyNativeRequirements({
789
+ algorithms,
790
+ key: { type: key.descriptor.type },
791
+ ...(policy.claimsSchema ? { claimsSchema: policy.claimsSchema } : {})
792
+ })
793
+ });
794
+ }
795
+
796
+ function collectFacadeBindings(ts, sourceFile, manifest) {
797
+ const bindings = {
798
+ namespaces: new Set(),
799
+ verify: new Set(),
800
+ bearer: new Set(),
801
+ imports: []
802
+ };
803
+ for (const statement of sourceFile.statements) {
804
+ if (!ts.isImportDeclaration(statement) || !statement.importClause || statement.importClause.isTypeOnly) continue;
805
+ const moduleName = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : '';
806
+ if (moduleName !== manifest.lowerableSubpath) continue;
807
+ const clause = statement.importClause;
808
+ if (clause.name) {
809
+ bindings.namespaces.add(clause.name.text);
810
+ bindings.imports.push({ imported: 'default', local: clause.name.text, loc: locFor(sourceFile, clause.name) });
811
+ }
812
+ const named = clause.namedBindings;
813
+ if (named && ts.isNamespaceImport(named)) {
814
+ bindings.namespaces.add(named.name.text);
815
+ bindings.imports.push({ imported: '*', local: named.name.text, loc: locFor(sourceFile, named.name) });
816
+ }
817
+ if (named && ts.isNamedImports(named)) {
818
+ for (const specifier of named.elements) {
819
+ if (specifier.isTypeOnly) continue;
820
+ const imported = specifier.propertyName ? specifier.propertyName.text : specifier.name.text;
821
+ const local = specifier.name.text;
822
+ if (imported === 'jwt' || imported === 'default') bindings.namespaces.add(local);
823
+ if (imported === 'verify') bindings.verify.add(local);
824
+ if (imported === 'bearer') bindings.bearer.add(local);
825
+ bindings.imports.push({ imported, local, loc: locFor(sourceFile, specifier.name) });
826
+ }
827
+ }
828
+ }
829
+ return bindings;
830
+ }
831
+
832
+ function resolveFacadeCall(ts, call, bindings) {
833
+ const expression = unwrapExpression(ts, call.expression);
834
+ if (expression && ts.isPropertyAccessExpression(expression)) {
835
+ const object = unwrapExpression(ts, expression.expression);
836
+ if (object && ts.isIdentifier(object) && bindings.namespaces.has(object.text)) {
837
+ if (expression.name.text === 'verify' || expression.name.text === 'bearer') {
838
+ return { method: expression.name.text, local: object.text };
839
+ }
840
+ }
841
+ }
842
+ if (expression && ts.isIdentifier(expression)) {
843
+ if (bindings.verify.has(expression.text)) return { method: 'verify', local: expression.text };
844
+ if (bindings.bearer.has(expression.text)) return { method: 'bearer', local: expression.text };
845
+ }
846
+ return undefined;
847
+ }
848
+
849
+ function enclosingContextName(ts, node) {
850
+ let current = node;
851
+ while (current) {
852
+ if (ts.isFunctionLike(current)) {
853
+ const parameter = current.parameters && current.parameters[0];
854
+ return parameter && ts.isIdentifier(parameter.name) ? parameter.name.text : undefined;
855
+ }
856
+ current = current.parent;
857
+ }
858
+ return undefined;
859
+ }
860
+
861
+ function currentContextArgument(ts, node, contextName) {
862
+ const current = unwrapExpression(ts, node);
863
+ return Boolean(current && ts.isIdentifier(current) && current.text === contextName);
864
+ }
865
+
866
+ function currentRequestArgument(ts, node, contextName) {
867
+ const current = unwrapExpression(ts, node);
868
+ if (!current || !ts.isPropertyAccessExpression(current) || current.name.text !== 'req') return false;
869
+ const object = unwrapExpression(ts, current.expression);
870
+ return Boolean(object && ts.isIdentifier(object) && object.text === contextName);
871
+ }
872
+
873
+ function placementForCall(ts, call, contextName) {
874
+ let current = call;
875
+ let parent = current.parent;
876
+ let awaited = false;
877
+ if (parent && ts.isAwaitExpression(parent)) {
878
+ awaited = true;
879
+ current = parent;
880
+ parent = current.parent;
881
+ }
882
+ if (parent && ts.isVariableDeclaration(parent) && parent.initializer === current) {
883
+ return { kind: 'variable', awaited, declaration: parent };
884
+ }
885
+ if (parent && ts.isReturnStatement(parent)) return { kind: 'return', awaited };
886
+ if (
887
+ parent
888
+ && ts.isPropertyAssignment(parent)
889
+ && parent.initializer === current
890
+ && parent.parent
891
+ && ts.isObjectLiteralExpression(parent.parent)
892
+ ) {
893
+ let invocation = parent.parent.parent;
894
+ if (invocation && ts.isCallExpression(invocation)) {
895
+ const target = unwrapExpression(ts, invocation.expression);
896
+ if (
897
+ target
898
+ && ts.isPropertyAccessExpression(target)
899
+ && target.name.text === 'parallel'
900
+ ) {
901
+ const receiver = unwrapExpression(ts, target.expression);
902
+ if (receiver && ts.isIdentifier(receiver) && receiver.text === contextName) {
903
+ return { kind: 'statement', awaited, parallel: invocation };
904
+ }
905
+ }
906
+ }
907
+ }
908
+ return { kind: 'unsupported', awaited };
909
+ }
910
+
911
+ function bearerIntrinsic(sourceFile, call) {
912
+ const descriptor = jwtContracts.JWT_PACKAGE_INTRINSICS.bearer;
913
+ return Object.freeze({
914
+ version: packageContracts.PACKAGE_INTRINSIC_VERSION,
915
+ contractId: jwtContracts.JWT_CONTRACT_ID,
916
+ package: jwtContracts.JWT_PACKAGE_NAME,
917
+ import: jwtContracts.JWT_PACKAGE_NAME,
918
+ kind: 'jwt.bearer',
919
+ operation: 'bearer',
920
+ intrinsic: descriptor.nativeIntrinsic || descriptor.name,
921
+ packageIntrinsic: descriptor.name,
922
+ compilerName: descriptor.compilerName,
923
+ valueKind: descriptor.valueKind,
924
+ argumentIndexes: descriptor.argumentIndexes,
925
+ staticArguments: descriptor.staticArguments,
926
+ range: rangeFor(sourceFile, call),
927
+ loc: locFor(sourceFile, call)
928
+ });
929
+ }
930
+
931
+ function validateBearerTokenSource(ts, sourceFile, node, contextName, bindings, diagnostics, consumedBearers) {
932
+ const current = unwrapExpression(ts, node);
933
+ if (!current || !ts.isCallExpression(current)) {
934
+ diagnostics.push(makeDiagnostic(
935
+ sourceFile,
936
+ current || node,
937
+ jwtContracts.JWT_DIAGNOSTIC_CODES.TOKEN_SOURCE_UNSUPPORTED,
938
+ 'Native JWT verification accepts only jwt.bearer(ctx.req) as its token source.',
939
+ 'Read the current request Bearer token through the package helper.'
940
+ ));
941
+ return undefined;
942
+ }
943
+ const resolved = resolveFacadeCall(ts, current, bindings);
944
+ if (!resolved || resolved.method !== 'bearer') {
945
+ diagnostics.push(makeDiagnostic(
946
+ sourceFile,
947
+ current,
948
+ jwtContracts.JWT_DIAGNOSTIC_CODES.TOKEN_SOURCE_UNSUPPORTED,
949
+ 'Native JWT verification accepts only the package Bearer intrinsic as its token source.',
950
+ 'Pass jwt.bearer(ctx.req) directly.'
951
+ ));
952
+ return undefined;
953
+ }
954
+ consumedBearers.add(current);
955
+ if (current.arguments.length !== 1 || !currentRequestArgument(ts, current.arguments[0], contextName)) {
956
+ diagnostics.push(makeDiagnostic(
957
+ sourceFile,
958
+ current.arguments[0] || current,
959
+ jwtContracts.JWT_DIAGNOSTIC_CODES.BEARER_REQUEST_UNSUPPORTED,
960
+ 'jwt.bearer(...) must read the current canonical handler request.',
961
+ `Pass jwt.bearer(${contextName || 'ctx'}.req) directly.`
962
+ ));
963
+ }
964
+ return bearerIntrinsic(sourceFile, current);
965
+ }
966
+
967
+ function canonicalEffect(sourceFile, call, placement, parsed) {
968
+ return Object.freeze({
969
+ ...jwtContracts.JWT_VERIFY_OPERATION,
970
+ version: canonicalRuntimeContracts.CANONICAL_PACKAGE_EFFECT_VERSION,
971
+ placement: placement.kind,
972
+ range: rangeFor(sourceFile, call),
973
+ resource: parsed.key.resource,
974
+ payload: Object.freeze({
975
+ token: Object.freeze({ kind: 'bearer-request-header' }),
976
+ ...clone(parsed.policy)
977
+ }),
978
+ runtimeInputs: Object.freeze([
979
+ Object.freeze({
980
+ name: 'token',
981
+ argumentIndex: 1,
982
+ source: 'package-call-argument'
983
+ })
984
+ ]),
985
+ providerRequirements: parsed.providerRequirements,
986
+ schemaReferences: parsed.schemaReference ? Object.freeze([parsed.schemaReference]) : Object.freeze([]),
987
+ redaction: jwtContracts.JWT_EXECUTION_REQUIREMENTS.redaction,
988
+ loc: locFor(sourceFile, call)
989
+ });
990
+ }
991
+
992
+ function declarationResultName(ts, placement) {
993
+ return placement && placement.declaration && ts.isIdentifier(placement.declaration.name)
994
+ ? placement.declaration.name.text
995
+ : undefined;
996
+ }
997
+
998
+ function isDeclarationIdentifier(ts, node) {
999
+ const parent = node.parent;
1000
+ return Boolean(
1001
+ parent
1002
+ && (
1003
+ (ts.isVariableDeclaration(parent) && parent.name === node)
1004
+ || (ts.isParameter(parent) && parent.name === node)
1005
+ || (ts.isImportSpecifier(parent) && parent.name === node)
1006
+ || (ts.isImportClause(parent) && parent.name === node)
1007
+ || (ts.isNamespaceImport(parent) && parent.name === node)
1008
+ )
1009
+ );
1010
+ }
1011
+
1012
+ function validateResultAccess(ts, sourceFile, entry, diagnostics) {
1013
+ const name = entry.resultName;
1014
+ if (!name) return;
1015
+ let reported = false;
1016
+ function visit(node) {
1017
+ if (reported) return;
1018
+ if (ts.isIdentifier(node) && node.text === name && !isDeclarationIdentifier(ts, node)) {
1019
+ const parent = node.parent;
1020
+ const allowed = parent
1021
+ && ts.isPropertyAccessExpression(parent)
1022
+ && parent.expression === node
1023
+ && ['claims', 'protectedHeader'].includes(parent.name.text);
1024
+ if (!allowed) {
1025
+ diagnostics.push(makeDiagnostic(
1026
+ sourceFile,
1027
+ node,
1028
+ jwtContracts.JWT_DIAGNOSTIC_CODES.RESULT_ACCESS_UNSUPPORTED,
1029
+ 'Native JWT verification results may be read only through claims or protectedHeader.',
1030
+ `Use ${name}.claims or ${name}.protectedHeader without passing the complete verification object through user state.`,
1031
+ { result: 'jwt-verification' }
1032
+ ));
1033
+ reported = true;
1034
+ return;
1035
+ }
1036
+ }
1037
+ ts.forEachChild(node, visit);
1038
+ }
1039
+ visit(sourceFile);
1040
+ }
1041
+
1042
+ function sourceFileFromInputs(ts, inputs) {
1043
+ if (inputs.sourceFile) return inputs.sourceFile;
1044
+ const sourceText = inputs.sourceText === undefined ? '' : String(inputs.sourceText);
1045
+ const sourcePath = inputs.sourcePath || '<jwt-lowering-plan>';
1046
+ return ts.createSourceFile(
1047
+ sourcePath,
1048
+ sourceText,
1049
+ ts.ScriptTarget.ES2022,
1050
+ true,
1051
+ /\.[cm]?tsx?$/.test(sourcePath) ? ts.ScriptKind.TS : ts.ScriptKind.JS
1052
+ );
1053
+ }
1054
+
1055
+ function buildJwtLoweringPlan(inputs = {}) {
1056
+ const ts = loadTypeScript(inputs);
1057
+ const sourceFile = sourceFileFromInputs(ts, inputs);
1058
+ const manifest = inputs.jwtManifest || inputs.manifest || require('./pulsewasm.manifest.cjs');
1059
+ const diagnostics = [];
1060
+ const bindings = collectFacadeBindings(ts, sourceFile, manifest);
1061
+ const entries = [];
1062
+ const privateRealizationArtifacts = [];
1063
+ const bearerCalls = [];
1064
+ const consumedBearers = new Set();
1065
+
1066
+ function visit(node) {
1067
+ if (ts.isCallExpression(node)) {
1068
+ const resolved = resolveFacadeCall(ts, node, bindings);
1069
+ if (resolved && resolved.method === 'bearer') bearerCalls.push(node);
1070
+ if (resolved && resolved.method === 'verify') {
1071
+ const contextName = enclosingContextName(ts, node);
1072
+ if (node.arguments.length !== 3) diagnostics.push(makeDiagnostic(
1073
+ sourceFile,
1074
+ node,
1075
+ jwtContracts.JWT_DIAGNOSTIC_CODES.ARGUMENT_SHAPE_UNSUPPORTED,
1076
+ 'jwt.verify(...) requires the current context, Bearer token source, and static options.',
1077
+ 'Call jwt.verify(ctx, jwt.bearer(ctx.req), { ...staticPolicy }).'
1078
+ ));
1079
+ if (!currentContextArgument(ts, node.arguments[0], contextName)) diagnostics.push(makeDiagnostic(
1080
+ sourceFile,
1081
+ node.arguments[0] || node,
1082
+ jwtContracts.JWT_DIAGNOSTIC_CODES.CONTEXT_UNSUPPORTED,
1083
+ 'jwt.verify(...) must receive the current canonical handler context.',
1084
+ `Pass ${contextName || 'ctx'} directly as the first argument.`
1085
+ ));
1086
+ const intrinsic = validateBearerTokenSource(
1087
+ ts,
1088
+ sourceFile,
1089
+ node.arguments[1],
1090
+ contextName,
1091
+ bindings,
1092
+ diagnostics,
1093
+ consumedBearers
1094
+ );
1095
+ const placement = placementForCall(ts, node, contextName);
1096
+ if (
1097
+ placement.kind === 'unsupported'
1098
+ || (placement.kind === 'variable' && !placement.awaited)
1099
+ || (placement.kind === 'statement' && placement.awaited)
1100
+ ) diagnostics.push(makeDiagnostic(
1101
+ sourceFile,
1102
+ node,
1103
+ jwtContracts.JWT_DIAGNOSTIC_CODES.PLACEMENT_UNSUPPORTED,
1104
+ 'jwt.verify(...) is not in a supported Native effect position.',
1105
+ 'Directly await it into a variable, return it, or place it directly inside awaited ctx.parallel({ ... }).',
1106
+ { placement: placement.kind, awaited: placement.awaited }
1107
+ ));
1108
+ const parsed = parseOptions(ts, sourceFile, node.arguments[2], diagnostics, inputs);
1109
+ if (parsed.key.realizationArtifact) {
1110
+ privateRealizationArtifacts.push(parsed.key.realizationArtifact);
1111
+ }
1112
+ const effect = canonicalEffect(sourceFile, node, placement, parsed);
1113
+ entries.push(Object.freeze({
1114
+ kind: 'jwt-verify',
1115
+ symbol: 'jwt.verify',
1116
+ placement: placement.kind,
1117
+ awaited: placement.awaited,
1118
+ resultName: declarationResultName(ts, placement),
1119
+ algorithms: Object.freeze([...parsed.policy.algorithms]),
1120
+ key: parsed.key.descriptor,
1121
+ providerRequirements: parsed.providerRequirements,
1122
+ schemaReference: parsed.schemaReference,
1123
+ bearerIntrinsic: intrinsic,
1124
+ range: rangeFor(sourceFile, node),
1125
+ loc: locFor(sourceFile, node),
1126
+ canonicalEffect: effect
1127
+ }));
1128
+ }
1129
+ }
1130
+ ts.forEachChild(node, visit);
1131
+ }
1132
+ visit(sourceFile);
1133
+
1134
+ for (const call of bearerCalls) {
1135
+ if (consumedBearers.has(call)) continue;
1136
+ diagnostics.push(makeDiagnostic(
1137
+ sourceFile,
1138
+ call,
1139
+ jwtContracts.JWT_DIAGNOSTIC_CODES.BEARER_PLACEMENT_UNSUPPORTED,
1140
+ 'jwt.bearer(...) is a Native compiler intrinsic only when used directly as jwt.verify token input.',
1141
+ 'Pass jwt.bearer(ctx.req) directly as the second jwt.verify argument.'
1142
+ ));
1143
+ }
1144
+ for (const entry of entries) validateResultAccess(ts, sourceFile, entry, diagnostics);
1145
+
1146
+ const canonicalEffects = entries.map((entry) => entry.canonicalEffect);
1147
+ const canonicalIntrinsics = entries.map((entry) => entry.bearerIntrinsic).filter(Boolean);
1148
+ const schemaReferences = entries.map((entry) => entry.schemaReference).filter(Boolean);
1149
+ const cryptoRequirements = jwtContracts.jwtCryptoRequirements(
1150
+ entries.flatMap((entry) => entry.algorithms)
1151
+ );
1152
+ const keyArtifacts = entries
1153
+ .filter((entry) => entry.key.keyArtifactId)
1154
+ .map((entry) => Object.freeze({
1155
+ id: entry.key.keyArtifactId,
1156
+ keyType: entry.key.type,
1157
+ keyCount: entry.key.count,
1158
+ materialHash: entry.key.materialHash,
1159
+ materialIncludedInPlan: false
1160
+ }));
1161
+ const uniqueKeyArtifacts = [...new Map(keyArtifacts.map((entry) => [entry.id, entry])).values()];
1162
+ const realizationArtifacts = [...new Map(privateRealizationArtifacts
1163
+ .map((entry) => [entry.id, entry])).values()];
1164
+ const guestUnits = entries.some((entry) => entry.algorithms.includes('ES256'))
1165
+ ? Object.freeze([loadCryptoGuestContribution()])
1166
+ : Object.freeze([]);
1167
+ const artifact = deepFreeze(normalizeArtifact({
1168
+ version: jwtContracts.JWT_LOWERING_PLAN_VERSION,
1169
+ generatedBy: inputs.generatedBy || 'pulse.jwt-package-lowerer.v1',
1170
+ phase: phaseName,
1171
+ contractId: jwtContracts.JWT_CONTRACT_ID,
1172
+ package: jwtContracts.JWT_PACKAGE_NAME,
1173
+ lowerableSubpath: manifest.lowerableSubpath,
1174
+ status: hasErrors(diagnostics) ? 'error' : 'ok',
1175
+ source: stableFileName(sourceFile.fileName, inputs.cwd || process.cwd()),
1176
+ manifest: {
1177
+ version: manifest.version,
1178
+ contractId: manifest.contractId,
1179
+ package: manifest.npmPackage,
1180
+ builderOwner: manifest.compiler && manifest.compiler.builderOwner,
1181
+ builderTrust: manifest.compiler && manifest.compiler.trust,
1182
+ sidecar: manifest.modes && manifest.modes.wasm && manifest.modes.wasm.sidecar
1183
+ },
1184
+ entries: entries.map((entry) => ({
1185
+ kind: entry.kind,
1186
+ placement: entry.placement,
1187
+ awaited: entry.awaited,
1188
+ algorithms: entry.algorithms,
1189
+ key: entry.key,
1190
+ providerRequirements: entry.providerRequirements,
1191
+ schemaReference: entry.schemaReference,
1192
+ range: entry.range,
1193
+ loc: entry.loc
1194
+ })),
1195
+ canonicalEffects,
1196
+ canonicalIntrinsics,
1197
+ schemaReferences,
1198
+ cryptoRequirements,
1199
+ keyArtifacts: uniqueKeyArtifacts,
1200
+ diagnostics,
1201
+ policy: {
1202
+ packageOwnsRecognition: true,
1203
+ compilerOwnsPackageMapping: false,
1204
+ canonicalOperationOnly: true,
1205
+ bearerIntrinsicOnly: true,
1206
+ staticPolicyOnly: true,
1207
+ keyMaterialExcludedFromPlan: true,
1208
+ rawTokenExcludedFromPlan: true,
1209
+ providerNeutral: true,
1210
+ targetBindingSwappable: true,
1211
+ targetEligibilityDeferredToProviderBoundary: true,
1212
+ cryptoSemanticOwner: '@pulse-compute/crypto',
1213
+ cryptoRequirementComposition: true,
1214
+ jwtOwnsCryptoRealizationSelection: false,
1215
+ automaticFallback: false
1216
+ },
1217
+ summary: {
1218
+ entries: entries.length,
1219
+ effects: canonicalEffects.length,
1220
+ intrinsics: canonicalIntrinsics.length,
1221
+ schemaReferences: schemaReferences.length,
1222
+ cryptoRequirements: cryptoRequirements.length,
1223
+ keyArtifacts: uniqueKeyArtifacts.length,
1224
+ guestUnits: guestUnits.length,
1225
+ errors: diagnostics.filter((entry) => String(entry.severity || 'error') === 'error').length
1226
+ }
1227
+ }, inputs.cwd || process.cwd()));
1228
+
1229
+ return Object.freeze({
1230
+ artifact,
1231
+ entries: Object.freeze(entries),
1232
+ canonicalEffects: Object.freeze(canonicalEffects),
1233
+ canonicalIntrinsics: Object.freeze(canonicalIntrinsics),
1234
+ schemaReferences: Object.freeze(schemaReferences),
1235
+ cryptoRequirements,
1236
+ keyArtifacts: Object.freeze(uniqueKeyArtifacts),
1237
+ realizationArtifacts: Object.freeze(realizationArtifacts),
1238
+ guestUnits,
1239
+ diagnostics: Object.freeze(diagnostics),
1240
+ manifest,
1241
+ dependencies: Object.freeze([]),
1242
+ hasErrors: hasErrors(diagnostics)
1243
+ });
1244
+ }
1245
+
1246
+ const jwtLoweringBuilder = Object.freeze({
1247
+ name: 'jwt-lowering-builder',
1248
+ owner: jwtContracts.JWT_PACKAGE_NAME,
1249
+ trust: 'first-party',
1250
+ version: jwtContracts.JWT_LOWERING_PLAN_VERSION,
1251
+ build: buildJwtLoweringPlan
1252
+ });
1253
+
1254
+ module.exports = Object.freeze({
1255
+ buildJwtLoweringPlan,
1256
+ jwtLoweringBuilder,
1257
+ JWT_DIAGNOSTIC_CODES: jwtContracts.JWT_DIAGNOSTIC_CODES
1258
+ });