@suss/ir-core 0.33.1 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/dist/{chunk-N564FH5U.js → chunk-A46EUIKC.js} +546 -237
- package/dist/index.cjs +659 -284
- package/dist/index.d.cts +452 -360
- package/dist/index.d.ts +452 -360
- package/dist/index.js +94 -31
- package/dist/{schemas-CIns0fqF.d.cts → schemas-lfg8kvak.d.cts} +94 -81
- package/dist/{schemas-CIns0fqF.d.ts → schemas-lfg8kvak.d.ts} +94 -81
- package/dist/schemas.cjs +541 -239
- package/dist/schemas.d.cts +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
FunctionCallSemanticsSchema: () => FunctionCallSemanticsSchema,
|
|
32
32
|
GraphqlOperationSemanticsSchema: () => GraphqlOperationSemanticsSchema,
|
|
33
33
|
GraphqlResolverSemanticsSchema: () => GraphqlResolverSemanticsSchema,
|
|
34
|
+
IoSemanticsSchema: () => IoSemanticsSchema,
|
|
34
35
|
MessageBusSemanticsSchema: () => MessageBusSemanticsSchema,
|
|
35
36
|
MetricSemanticsSchema: () => MetricSemanticsSchema,
|
|
36
37
|
NOTHING_DEPLOYED: () => NOTHING_DEPLOYED,
|
|
@@ -43,6 +44,7 @@ __export(index_exports, {
|
|
|
43
44
|
StorageSemanticsSchema: () => StorageSemanticsSchema,
|
|
44
45
|
SuppressionFileSchema: () => SuppressionFileSchema,
|
|
45
46
|
SuppressionRuleSchema: () => SuppressionRuleSchema,
|
|
47
|
+
TEST_TITLE_SEPARATOR: () => TEST_TITLE_SEPARATOR,
|
|
46
48
|
TypeShapeSchema: () => TypeShapeSchema,
|
|
47
49
|
UnitInvocationSemanticsSchema: () => UnitInvocationSemanticsSchema,
|
|
48
50
|
applySuppressionsToFindings: () => applySuppressionsToFindings,
|
|
@@ -52,7 +54,6 @@ __export(index_exports, {
|
|
|
52
54
|
boundaryKey: () => boundaryKey,
|
|
53
55
|
boundaryLabel: () => boundaryLabel,
|
|
54
56
|
boundaryNameString: () => boundaryNameString,
|
|
55
|
-
bucketRank: () => bucketRank,
|
|
56
57
|
bucketsMeet: () => bucketsMeet,
|
|
57
58
|
busIdentityKey: () => busIdentityKey,
|
|
58
59
|
busesAgree: () => busesAgree,
|
|
@@ -76,12 +77,18 @@ __export(index_exports, {
|
|
|
76
77
|
groundBinding: () => groundBinding,
|
|
77
78
|
groundedPairingKey: () => groundedPairingKey,
|
|
78
79
|
hasNameHole: () => hasNameHole,
|
|
80
|
+
hostCanBeOwn: () => hostCanBeOwn,
|
|
81
|
+
ioBinding: () => ioBinding,
|
|
82
|
+
ioIdentityKey: () => ioIdentityKey,
|
|
79
83
|
isGraphqlOperationBinding: () => isGraphqlOperationBinding,
|
|
84
|
+
isModuleName: () => isModuleName,
|
|
85
|
+
labelWithDetail: () => labelWithDetail,
|
|
80
86
|
leavesTheProcess: () => leavesTheProcess,
|
|
81
87
|
messageBusBinding: () => messageBusBinding,
|
|
82
88
|
methodsAgree: () => methodsAgree,
|
|
83
89
|
metricBinding: () => metricBinding,
|
|
84
90
|
metricIdentityKey: () => metricIdentityKey,
|
|
91
|
+
moduleExportBinding: () => moduleExportBinding,
|
|
85
92
|
namePatternFromSub: () => namePatternFromSub,
|
|
86
93
|
namePatternKey: () => namePatternKey,
|
|
87
94
|
nameReference: () => nameReference,
|
|
@@ -91,7 +98,9 @@ __export(index_exports, {
|
|
|
91
98
|
namesNothing: () => namesNothing,
|
|
92
99
|
normalizePath: () => normalizePath,
|
|
93
100
|
normalizeRuleBoundary: () => normalizeRuleBoundary,
|
|
101
|
+
operationKey: () => operationKey,
|
|
94
102
|
packageExportBinding: () => packageExportBinding,
|
|
103
|
+
pairRank: () => pairRank,
|
|
95
104
|
pairingKey: () => pairingKey,
|
|
96
105
|
parseBoundaryName: () => parseBoundaryName,
|
|
97
106
|
parseChannel: () => parseChannel,
|
|
@@ -121,13 +130,15 @@ __export(index_exports, {
|
|
|
121
130
|
storageContainerLabel: () => storageContainerLabel,
|
|
122
131
|
storageLabel: () => storageLabel,
|
|
123
132
|
storageSystemLabel: () => storageSystemLabel,
|
|
133
|
+
testUnitName: () => testUnitName,
|
|
124
134
|
typeDefinitionKey: () => typeDefinitionKey,
|
|
125
135
|
unitIdentityKey: () => unitIdentityKey,
|
|
126
136
|
unitInvocationBinding: () => unitInvocationBinding,
|
|
127
137
|
validateRule: () => validateRule,
|
|
128
138
|
withDefinitionsInlined: () => withDefinitionsInlined,
|
|
129
139
|
withRewrittenPaths: () => withRewrittenPaths,
|
|
130
|
-
withinScope: () => withinScope
|
|
140
|
+
withinScope: () => withinScope,
|
|
141
|
+
withoutOwnHost: () => withoutOwnHost
|
|
131
142
|
});
|
|
132
143
|
module.exports = __toCommonJS(index_exports);
|
|
133
144
|
|
|
@@ -156,15 +167,15 @@ var graphqlOperationSemantics = defineBoundarySemantics({
|
|
|
156
167
|
behavior: {
|
|
157
168
|
/**
|
|
158
169
|
* An operation gets a data-and-errors document back, which the
|
|
159
|
-
* GraphQL contract checker reads. Status codes
|
|
170
|
+
* GraphQL contract checker reads. Status codes do not apply.
|
|
160
171
|
*/
|
|
161
172
|
exchangesHttpResponses: false,
|
|
162
173
|
leavesTheProcess: true,
|
|
163
174
|
reportsUnpairedItself: false,
|
|
164
175
|
identityKey: () => null,
|
|
165
|
-
/**
|
|
176
|
+
/** No key on purpose. The dedicated pass pairs an operation by its document. */
|
|
166
177
|
canPair: () => true,
|
|
167
|
-
/**
|
|
178
|
+
/** `query GetUser`. Only for display, since the contract checker does the pairing. */
|
|
168
179
|
displayLabel(semantics) {
|
|
169
180
|
return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
|
|
170
181
|
}
|
|
@@ -187,22 +198,26 @@ function unitIdentityKey(deploymentTarget, instanceName) {
|
|
|
187
198
|
function metricIdentityKey(metricSystem, metricType) {
|
|
188
199
|
return `metric:${metricSystem} ${metricType}`;
|
|
189
200
|
}
|
|
201
|
+
function ioIdentityKey(target) {
|
|
202
|
+
return `io:${target}`;
|
|
203
|
+
}
|
|
190
204
|
|
|
191
205
|
// src/schemas.ts
|
|
192
|
-
var
|
|
206
|
+
var import_zod13 = require("zod");
|
|
193
207
|
|
|
194
208
|
// src/semantics/registry.ts
|
|
195
|
-
var
|
|
209
|
+
var import_zod12 = require("zod");
|
|
196
210
|
|
|
197
211
|
// src/semantics/functionCall.ts
|
|
198
212
|
var import_zod2 = require("zod");
|
|
199
213
|
var FunctionCallSemanticsSchema = import_zod2.z.object({
|
|
200
214
|
name: import_zod2.z.literal("function-call"),
|
|
201
215
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* module path
|
|
205
|
-
*
|
|
216
|
+
* Where the function lives inside this repository. It is either the
|
|
217
|
+
* name of a module the project lists in `suss.json`, such as
|
|
218
|
+
* `"billing"`, or a module path, such as the file a server action is
|
|
219
|
+
* written in. A name has no separator in it and a path always does.
|
|
220
|
+
* Packs that don't do cross-module pairing can leave it unset.
|
|
206
221
|
*/
|
|
207
222
|
module: import_zod2.z.string().optional(),
|
|
208
223
|
/** Named export within the module, when applicable. */
|
|
@@ -227,35 +242,38 @@ var FunctionCallSemanticsSchema = import_zod2.z.object({
|
|
|
227
242
|
*/
|
|
228
243
|
exportPath: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
229
244
|
});
|
|
245
|
+
function isModuleName(module2) {
|
|
246
|
+
return !module2.includes("/") && !module2.includes("\\");
|
|
247
|
+
}
|
|
230
248
|
var functionCallSemantics = defineBoundarySemantics({
|
|
231
249
|
name: "function-call",
|
|
232
250
|
schema: FunctionCallSemanticsSchema,
|
|
233
|
-
// A call
|
|
234
|
-
//
|
|
251
|
+
// A call inside the process does not produce a span, and the
|
|
252
|
+
// conventions have no attributes for it.
|
|
235
253
|
semconv: {},
|
|
236
254
|
behavior: {
|
|
237
|
-
/** A call returns a value,
|
|
255
|
+
/** A call returns a value, with no status. */
|
|
238
256
|
exchangesHttpResponses: false,
|
|
239
257
|
leavesTheProcess: false,
|
|
240
258
|
reportsUnpairedItself: false,
|
|
241
259
|
/**
|
|
242
|
-
* In-repo units
|
|
243
|
-
*
|
|
244
|
-
*
|
|
260
|
+
* In-repo units such as components have no key on purpose. They
|
|
261
|
+
* pair through call edges instead of the keyed pass, so a missing
|
|
262
|
+
* key does not make them unreachable.
|
|
245
263
|
*/
|
|
246
264
|
canPair: () => true,
|
|
247
|
-
/**
|
|
248
|
-
* `"fn:<package>::<exportPath>"` when both are set, and
|
|
249
|
-
* `"fn:<module>::<exportName>"` for an in-repo unit that states its
|
|
250
|
-
* module, the way a server action does. Components and bare
|
|
251
|
-
* handlers state neither and have no key at all.
|
|
252
|
-
*/
|
|
253
265
|
rewritePaths(semantics, rewrite) {
|
|
254
|
-
if (semantics.module === void 0) {
|
|
266
|
+
if (semantics.module === void 0 || isModuleName(semantics.module)) {
|
|
255
267
|
return semantics;
|
|
256
268
|
}
|
|
257
269
|
return { ...semantics, module: rewrite(semantics.module) };
|
|
258
270
|
},
|
|
271
|
+
/**
|
|
272
|
+
* `"fn:<package>::<exportPath>"` when both are set, and
|
|
273
|
+
* `"fn:<module>::<exportName>"` for an in-repo unit that gives its
|
|
274
|
+
* module, as a server action does. Components and plain handlers
|
|
275
|
+
* give neither and have no key.
|
|
276
|
+
*/
|
|
259
277
|
identityKey(semantics) {
|
|
260
278
|
if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
|
|
261
279
|
return fnIdentityKey(semantics.package, semantics.exportPath);
|
|
@@ -284,14 +302,14 @@ var GraphqlResolverSemanticsSchema = import_zod3.z.object({
|
|
|
284
302
|
var graphqlResolverSemantics = defineBoundarySemantics({
|
|
285
303
|
name: "graphql-resolver",
|
|
286
304
|
schema: GraphqlResolverSemanticsSchema,
|
|
287
|
-
// The conventions
|
|
288
|
-
// resolver the server ran for one field of it.
|
|
305
|
+
// The conventions describe the operation a client sent. They have no
|
|
306
|
+
// attribute for the resolver the server ran for one field of it.
|
|
289
307
|
semconv: {},
|
|
290
308
|
behavior: {
|
|
291
|
-
/** A resolver returns a field value,
|
|
309
|
+
/** A resolver returns a field value, with no status. */
|
|
292
310
|
exchangesHttpResponses: false,
|
|
293
|
-
// A
|
|
294
|
-
// server calls
|
|
311
|
+
// A resolver runs because a client outside the process sent a query,
|
|
312
|
+
// even though the server calls it in its own process.
|
|
295
313
|
leavesTheProcess: true,
|
|
296
314
|
reportsUnpairedItself: false,
|
|
297
315
|
/**
|
|
@@ -308,8 +326,32 @@ var graphqlResolverSemantics = defineBoundarySemantics({
|
|
|
308
326
|
}
|
|
309
327
|
});
|
|
310
328
|
|
|
311
|
-
// src/semantics/
|
|
329
|
+
// src/semantics/io.ts
|
|
312
330
|
var import_zod4 = require("zod");
|
|
331
|
+
var IoSemanticsSchema = import_zod4.z.object({
|
|
332
|
+
name: import_zod4.z.literal("io"),
|
|
333
|
+
/** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
|
|
334
|
+
target: import_zod4.z.string().nullable()
|
|
335
|
+
});
|
|
336
|
+
var ioSemantics = defineBoundarySemantics({
|
|
337
|
+
name: "io",
|
|
338
|
+
schema: IoSemanticsSchema,
|
|
339
|
+
// The semantic conventions have no attribute for a process's own streams.
|
|
340
|
+
semconv: {},
|
|
341
|
+
behavior: {
|
|
342
|
+
exchangesHttpResponses: false,
|
|
343
|
+
leavesTheProcess: true,
|
|
344
|
+
reportsUnpairedItself: false,
|
|
345
|
+
identityKey(semantics) {
|
|
346
|
+
return semantics.target === null ? null : ioIdentityKey(semantics.target);
|
|
347
|
+
},
|
|
348
|
+
// Nothing declares the reading side, so a write has nothing to meet.
|
|
349
|
+
canPair: () => false
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// src/semantics/messageBus.ts
|
|
354
|
+
var import_zod5 = require("zod");
|
|
313
355
|
|
|
314
356
|
// src/channel.ts
|
|
315
357
|
function parseChannel(channel) {
|
|
@@ -335,12 +377,11 @@ function channelsPair(a, b) {
|
|
|
335
377
|
}
|
|
336
378
|
|
|
337
379
|
// src/semantics/messageBus.ts
|
|
338
|
-
var MessageBusSemanticsSchema =
|
|
339
|
-
name:
|
|
340
|
-
messageBus:
|
|
341
|
-
// The conventions
|
|
342
|
-
// underscore and one with a dot
|
|
343
|
-
// not a typo.
|
|
380
|
+
var MessageBusSemanticsSchema = import_zod5.z.object({
|
|
381
|
+
name: import_zod5.z.literal("message-bus"),
|
|
382
|
+
messageBus: import_zod5.z.enum([
|
|
383
|
+
// The conventions write these two AWS buses differently, one with an
|
|
384
|
+
// underscore and one with a dot. Both are as the conventions give them.
|
|
344
385
|
"aws_sqs",
|
|
345
386
|
"aws.sns",
|
|
346
387
|
"s3",
|
|
@@ -359,12 +400,13 @@ var MessageBusSemanticsSchema = import_zod4.z.object({
|
|
|
359
400
|
"cloudflare-tail"
|
|
360
401
|
]),
|
|
361
402
|
/**
|
|
362
|
-
*
|
|
363
|
-
* a subject pattern, or `bus#detailType`. Null when this
|
|
364
|
-
* not say which channel, as with a send whose queue the
|
|
365
|
-
*
|
|
403
|
+
* A stable channel identifier: a CloudFormation logical id, a queue or
|
|
404
|
+
* topic name, a subject pattern, or `bus#detailType`. Null when this
|
|
405
|
+
* source does not say which channel, as with a send whose queue the
|
|
406
|
+
* code picks at run time, or a receive whose queue is set in the
|
|
407
|
+
* event-source mapping.
|
|
366
408
|
*/
|
|
367
|
-
channel:
|
|
409
|
+
channel: import_zod5.z.string().min(1).nullable()
|
|
368
410
|
});
|
|
369
411
|
var messageBusSemantics = defineBoundarySemantics({
|
|
370
412
|
name: "message-bus",
|
|
@@ -378,25 +420,26 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
378
420
|
exchangesHttpResponses: false,
|
|
379
421
|
leavesTheProcess: true,
|
|
380
422
|
/**
|
|
381
|
-
* `checkMessageBus`
|
|
382
|
-
*
|
|
423
|
+
* `checkMessageBus` reports every channel, unused ones included, so
|
|
424
|
+
* the generic unmatched list leaves them out.
|
|
383
425
|
*/
|
|
384
426
|
reportsUnpairedItself: true,
|
|
385
427
|
/**
|
|
386
|
-
* A channel
|
|
387
|
-
*
|
|
388
|
-
* channel
|
|
428
|
+
* A channel that is null here can still pair. The dedicated pass
|
|
429
|
+
* follows environment variable chains before comparing, so a
|
|
430
|
+
* consumer whose channel is set at run time meets the template that
|
|
431
|
+
* declares it.
|
|
389
432
|
*/
|
|
390
433
|
canPair: () => true,
|
|
391
434
|
/**
|
|
392
435
|
* `"bus:<messageBus> <subject>"`, or null when the channel is null
|
|
393
436
|
* or its subject is empty.
|
|
394
437
|
*
|
|
395
|
-
* The key has the subject
|
|
396
|
-
*
|
|
438
|
+
* The key has the subject and leaves out the bus, so a template
|
|
439
|
+
* writing `default#order.placed` and a handler writing
|
|
397
440
|
* `order.placed` land in one bucket, where `sidesAgree` compares the
|
|
398
|
-
* buses.
|
|
399
|
-
*
|
|
441
|
+
* buses. Keying on the bus would separate a side that cannot know
|
|
442
|
+
* its bus from the side that can.
|
|
400
443
|
*
|
|
401
444
|
* The bus technology stays in, because a queue and an event router
|
|
402
445
|
* are different destinations even with the same subject. Case stays
|
|
@@ -419,9 +462,9 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
419
462
|
return channelsPair(a.channel, b.channel);
|
|
420
463
|
},
|
|
421
464
|
/**
|
|
422
|
-
* The whole channel, bus included,
|
|
423
|
-
*
|
|
424
|
-
*
|
|
465
|
+
* The whole channel, bus included, where the identity key has only
|
|
466
|
+
* the subject. A reader looking at unmatched channels needs to see
|
|
467
|
+
* which bus each one was on.
|
|
425
468
|
*/
|
|
426
469
|
displayLabel(semantics) {
|
|
427
470
|
if (semantics.channel === null) {
|
|
@@ -433,25 +476,25 @@ var messageBusSemantics = defineBoundarySemantics({
|
|
|
433
476
|
});
|
|
434
477
|
|
|
435
478
|
// src/semantics/metric.ts
|
|
436
|
-
var
|
|
437
|
-
var MetricSemanticsSchema =
|
|
438
|
-
name:
|
|
439
|
-
/**
|
|
440
|
-
metricSystem:
|
|
479
|
+
var import_zod6 = require("zod");
|
|
480
|
+
var MetricSemanticsSchema = import_zod6.z.object({
|
|
481
|
+
name: import_zod6.z.literal("metric"),
|
|
482
|
+
/** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
|
|
483
|
+
metricSystem: import_zod6.z.string(),
|
|
441
484
|
/**
|
|
442
|
-
* The type string the system
|
|
443
|
-
* both sides
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
485
|
+
* The type string the system uses for the metric, written the same
|
|
486
|
+
* way on both sides. Null when the reader could not work the string
|
|
487
|
+
* out, as with a query it could not parse. A null type pairs with
|
|
488
|
+
* nothing, so it cannot pair with an unrelated metric that happens to
|
|
489
|
+
* share its source text.
|
|
447
490
|
*/
|
|
448
|
-
metricType:
|
|
491
|
+
metricType: import_zod6.z.string().nullable()
|
|
449
492
|
});
|
|
450
493
|
var metricSemantics = defineBoundarySemantics({
|
|
451
494
|
name: "metric",
|
|
452
495
|
schema: MetricSemanticsSchema,
|
|
453
|
-
// OpenTelemetry
|
|
454
|
-
// attribute for the system
|
|
496
|
+
// OpenTelemetry identifies a metric by its instrument name. It has no
|
|
497
|
+
// attribute for the metric's system or its type string.
|
|
455
498
|
semconv: {},
|
|
456
499
|
behavior: {
|
|
457
500
|
exchangesHttpResponses: false,
|
|
@@ -467,7 +510,7 @@ var metricSemantics = defineBoundarySemantics({
|
|
|
467
510
|
});
|
|
468
511
|
|
|
469
512
|
// src/semantics/rest.ts
|
|
470
|
-
var
|
|
513
|
+
var import_zod7 = require("zod");
|
|
471
514
|
|
|
472
515
|
// src/dispatch.ts
|
|
473
516
|
function dispatchByType(table, value) {
|
|
@@ -615,6 +658,38 @@ function pathAfterOrigin(text) {
|
|
|
615
658
|
function statesAnOrigin(text) {
|
|
616
659
|
return SCHEME_ORIGIN.test(text) || PROTOCOL_RELATIVE_ORIGIN.test(text);
|
|
617
660
|
}
|
|
661
|
+
var PRIVATE_SUFFIXES = [
|
|
662
|
+
"local",
|
|
663
|
+
"internal",
|
|
664
|
+
"localhost",
|
|
665
|
+
"lan",
|
|
666
|
+
"home.arpa",
|
|
667
|
+
"svc",
|
|
668
|
+
"test",
|
|
669
|
+
"example",
|
|
670
|
+
"invalid",
|
|
671
|
+
"example.com",
|
|
672
|
+
"example.net",
|
|
673
|
+
"example.org"
|
|
674
|
+
];
|
|
675
|
+
var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
|
|
676
|
+
function hostnameOf(host) {
|
|
677
|
+
const lower = host.toLowerCase();
|
|
678
|
+
const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
|
|
679
|
+
return name.replace(/\.$/, "");
|
|
680
|
+
}
|
|
681
|
+
function hostCanBeOwn(host) {
|
|
682
|
+
const name = hostnameOf(host);
|
|
683
|
+
if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
if (!name.includes(".")) {
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
return PRIVATE_SUFFIXES.some(
|
|
690
|
+
(suffix) => name === suffix || name.endsWith(`.${suffix}`)
|
|
691
|
+
);
|
|
692
|
+
}
|
|
618
693
|
|
|
619
694
|
// src/semantics/pathPattern.ts
|
|
620
695
|
var HOLE_RANGE_SUFFIX = {
|
|
@@ -681,14 +756,18 @@ function shapedItem(segment) {
|
|
|
681
756
|
return ".*";
|
|
682
757
|
}
|
|
683
758
|
if (part.startsWith("{")) {
|
|
684
|
-
return "[^/]+";
|
|
759
|
+
return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
|
|
685
760
|
}
|
|
686
761
|
return escapedForRegex(part);
|
|
687
762
|
}).join("");
|
|
763
|
+
const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
|
|
764
|
+
const text = segment.replace(/\{[^}]*\}|\*/g, "");
|
|
688
765
|
return {
|
|
689
766
|
kind: "shaped",
|
|
690
767
|
shape: segment.replace(/\{[^}]*\}/g, "{}"),
|
|
691
|
-
test: new RegExp(`^${source}$`)
|
|
768
|
+
test: new RegExp(`^${source}$`),
|
|
769
|
+
bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
|
|
770
|
+
textLength: text.length
|
|
692
771
|
};
|
|
693
772
|
}
|
|
694
773
|
function itemsOf(segment) {
|
|
@@ -708,10 +787,35 @@ function segmentsOf(alternative) {
|
|
|
708
787
|
const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
|
|
709
788
|
return trimmed === "" ? [] : trimmed.split("/");
|
|
710
789
|
}
|
|
790
|
+
function patternSegmentsOf(alternative) {
|
|
791
|
+
const segments = segmentsOf(alternative);
|
|
792
|
+
const first = segments[0];
|
|
793
|
+
const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
|
|
794
|
+
return opensWithBaseUrl ? segments.slice(1) : segments;
|
|
795
|
+
}
|
|
796
|
+
function pathAfterBaseUrl(path) {
|
|
797
|
+
const segments = segmentsOf(path);
|
|
798
|
+
const first = segments[0];
|
|
799
|
+
if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
|
|
800
|
+
return path;
|
|
801
|
+
}
|
|
802
|
+
return `/${segments.slice(1).join("/")}`;
|
|
803
|
+
}
|
|
804
|
+
var PATTERNS = /* @__PURE__ */ new Map();
|
|
805
|
+
var PATTERN_CACHE_LIMIT = 5e4;
|
|
711
806
|
function patternOf(path) {
|
|
712
|
-
|
|
713
|
-
|
|
807
|
+
const known = PATTERNS.get(path);
|
|
808
|
+
if (known !== void 0) {
|
|
809
|
+
return known;
|
|
810
|
+
}
|
|
811
|
+
const pattern = alternativesOf(path).map(
|
|
812
|
+
(alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
|
|
714
813
|
);
|
|
814
|
+
if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
|
|
815
|
+
PATTERNS.clear();
|
|
816
|
+
}
|
|
817
|
+
PATTERNS.set(path, pattern);
|
|
818
|
+
return pattern;
|
|
715
819
|
}
|
|
716
820
|
function literalItemsOf(path) {
|
|
717
821
|
return segmentsOf(path).map((text) => ({ kind: "text", text }));
|
|
@@ -790,17 +894,104 @@ function pathSpansShapes(path) {
|
|
|
790
894
|
function countOf(items, kinds) {
|
|
791
895
|
return items.filter((item) => kinds.includes(item.kind)).length;
|
|
792
896
|
}
|
|
793
|
-
function
|
|
794
|
-
|
|
795
|
-
const ranks = alternatives.map((items) => [
|
|
897
|
+
function readingSpecificity(items) {
|
|
898
|
+
return [
|
|
796
899
|
countOf(items, ["text"]),
|
|
797
900
|
countOf(items, ["shaped"]),
|
|
798
901
|
-countOf(items, ["optional", "star"])
|
|
799
|
-
]
|
|
800
|
-
|
|
801
|
-
|
|
902
|
+
];
|
|
903
|
+
}
|
|
904
|
+
function segmentFit(route, request) {
|
|
905
|
+
if (!meetOnOneSegment(route, request)) {
|
|
906
|
+
return "apart";
|
|
907
|
+
}
|
|
908
|
+
if (request.kind === "text") {
|
|
909
|
+
if (route.kind === "text") {
|
|
910
|
+
return "stated";
|
|
911
|
+
}
|
|
912
|
+
return route.kind === "shaped" ? "shaped" : "free";
|
|
913
|
+
}
|
|
914
|
+
if (absorbsAnything(route)) {
|
|
915
|
+
return "free";
|
|
916
|
+
}
|
|
917
|
+
if (request.kind !== "shaped") {
|
|
918
|
+
return "guess";
|
|
919
|
+
}
|
|
920
|
+
if (route.kind === "shaped") {
|
|
921
|
+
return "free";
|
|
922
|
+
}
|
|
923
|
+
return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
|
|
924
|
+
}
|
|
925
|
+
function shapedText(item) {
|
|
926
|
+
return item.kind === "shaped" ? item.textLength : 0;
|
|
927
|
+
}
|
|
928
|
+
function betterFit(a, b) {
|
|
929
|
+
return a === void 0 || compareRanks(b, a) > 0;
|
|
930
|
+
}
|
|
931
|
+
function bestFit(route, request) {
|
|
932
|
+
const width = request.length + 1;
|
|
933
|
+
const best = new Array((route.length + 1) * width);
|
|
934
|
+
best[0] = [0, 0];
|
|
935
|
+
const offer = (i, j, fit) => {
|
|
936
|
+
const at = i * width + j;
|
|
937
|
+
if (betterFit(best[at], fit)) {
|
|
938
|
+
best[at] = fit;
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
for (let reached = 0; reached <= route.length + request.length; reached += 1) {
|
|
942
|
+
for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
|
|
943
|
+
const j = reached - i;
|
|
944
|
+
const fit = best[i * width + j];
|
|
945
|
+
if (fit === void 0) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const left = route[i];
|
|
949
|
+
const right = request[j];
|
|
950
|
+
if (left !== void 0 && skippable(left)) {
|
|
951
|
+
offer(i + 1, j, fit);
|
|
952
|
+
}
|
|
953
|
+
if (right !== void 0 && skippable(right)) {
|
|
954
|
+
offer(i, j + 1, fit);
|
|
955
|
+
}
|
|
956
|
+
if (left === void 0 || right === void 0) {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (left.kind === "star" && right.kind === "star") {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
const kind = segmentFit(left, right);
|
|
963
|
+
if (kind === "apart" || kind === "guess") {
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
offer(
|
|
967
|
+
left.kind === "star" ? i : i + 1,
|
|
968
|
+
right.kind === "star" ? j : j + 1,
|
|
969
|
+
[
|
|
970
|
+
fit[0] + (kind === "stated" ? 1 : 0),
|
|
971
|
+
fit[1] + (kind === "shaped" ? shapedText(left) : 0)
|
|
972
|
+
]
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return best[route.length * width + request.length] ?? null;
|
|
977
|
+
}
|
|
978
|
+
function requestRank(route, request) {
|
|
979
|
+
let found = null;
|
|
980
|
+
for (const routeItems of patternOf(route)) {
|
|
981
|
+
for (const requestItems of patternOf(request)) {
|
|
982
|
+
const fit = bestFit(routeItems, requestItems);
|
|
983
|
+
const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
|
|
984
|
+
if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
|
|
985
|
+
found = rank;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return found;
|
|
990
|
+
}
|
|
991
|
+
function isCatchAll(route) {
|
|
992
|
+
return patternOf(route).some(
|
|
993
|
+
(items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
|
|
802
994
|
);
|
|
803
|
-
return [...loosest, 1 - alternatives.length];
|
|
804
995
|
}
|
|
805
996
|
function compareRanks(a, b) {
|
|
806
997
|
const length = Math.max(a.length, b.length);
|
|
@@ -814,23 +1005,34 @@ function compareRanks(a, b) {
|
|
|
814
1005
|
}
|
|
815
1006
|
|
|
816
1007
|
// src/semantics/rest.ts
|
|
817
|
-
var RestSemanticsSchema =
|
|
818
|
-
name:
|
|
1008
|
+
var RestSemanticsSchema = import_zod7.z.object({
|
|
1009
|
+
name: import_zod7.z.literal("rest"),
|
|
819
1010
|
/**
|
|
820
1011
|
* Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
|
|
821
1012
|
* responds to every method, or null when this source does not say
|
|
822
1013
|
* which method.
|
|
823
1014
|
*/
|
|
824
|
-
method:
|
|
1015
|
+
method: import_zod7.z.string().min(1).nullable(),
|
|
825
1016
|
/** Normalized route path ("/users/{id}"), or null when this source does not give one. */
|
|
826
|
-
path:
|
|
1017
|
+
path: import_zod7.z.string().min(1).nullable(),
|
|
827
1018
|
/**
|
|
828
|
-
* Status codes the producing source explicitly
|
|
829
|
-
* responses
|
|
830
|
-
*
|
|
831
|
-
* metadata. Absent
|
|
1019
|
+
* Status codes the producing source declared explicitly, such as
|
|
1020
|
+
* OpenAPI responses or CloudFormation MethodResponses. They are kept
|
|
1021
|
+
* here so the pairing layer can see them without unwrapping any
|
|
1022
|
+
* metadata. Absent or empty for an inferred source.
|
|
832
1023
|
*/
|
|
833
|
-
declaredResponses:
|
|
1024
|
+
declaredResponses: import_zod7.z.array(import_zod7.z.number()).optional(),
|
|
1025
|
+
/**
|
|
1026
|
+
* The host a client wrote in an absolute URL, with its port
|
|
1027
|
+
* (`api.example.com:8443`). A piece suss could not read is written
|
|
1028
|
+
* `{name}`. Absent for a relative URL and for a route.
|
|
1029
|
+
*/
|
|
1030
|
+
host: import_zod7.z.string().min(1).optional(),
|
|
1031
|
+
/**
|
|
1032
|
+
* True for a route the framework also serves with `.{format}` after
|
|
1033
|
+
* its path, the way Rails serves `/search.json` from `get "search"`.
|
|
1034
|
+
*/
|
|
1035
|
+
optionalFormat: import_zod7.z.boolean().optional()
|
|
834
1036
|
});
|
|
835
1037
|
var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
|
|
836
1038
|
function baseUrlReference(semantics) {
|
|
@@ -838,7 +1040,10 @@ function baseUrlReference(semantics) {
|
|
|
838
1040
|
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
839
1041
|
}
|
|
840
1042
|
function normalizePath(path) {
|
|
841
|
-
let normalized = path.replace(
|
|
1043
|
+
let normalized = withoutParameterConstraints(path).replace(
|
|
1044
|
+
/:([a-zA-Z_]\w*)([?+*]?)/g,
|
|
1045
|
+
"{$1$2}"
|
|
1046
|
+
);
|
|
842
1047
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
843
1048
|
normalized = normalized.slice(0, -1);
|
|
844
1049
|
}
|
|
@@ -848,6 +1053,46 @@ function normalizePath(path) {
|
|
|
848
1053
|
);
|
|
849
1054
|
return normalized;
|
|
850
1055
|
}
|
|
1056
|
+
function withoutParameterConstraints(path) {
|
|
1057
|
+
let out = "";
|
|
1058
|
+
let i = 0;
|
|
1059
|
+
while (i < path.length) {
|
|
1060
|
+
const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
|
|
1061
|
+
if (name === null) {
|
|
1062
|
+
out += path[i];
|
|
1063
|
+
i += 1;
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
const open = i + name[0].length - 1;
|
|
1067
|
+
const close = closingParenthesis(path, open);
|
|
1068
|
+
if (close === -1) {
|
|
1069
|
+
return out + path.slice(i);
|
|
1070
|
+
}
|
|
1071
|
+
out += path.slice(i, open);
|
|
1072
|
+
i = close + 1;
|
|
1073
|
+
}
|
|
1074
|
+
return out;
|
|
1075
|
+
}
|
|
1076
|
+
function closingParenthesis(text, open) {
|
|
1077
|
+
let depth = 0;
|
|
1078
|
+
for (let i = open; i < text.length; i += 1) {
|
|
1079
|
+
const char = text[i];
|
|
1080
|
+
if (char === "\\") {
|
|
1081
|
+
i += 1;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (char === "(") {
|
|
1085
|
+
depth += 1;
|
|
1086
|
+
}
|
|
1087
|
+
if (char === ")") {
|
|
1088
|
+
depth -= 1;
|
|
1089
|
+
if (depth === 0) {
|
|
1090
|
+
return i;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return -1;
|
|
1095
|
+
}
|
|
851
1096
|
function pathShape(path) {
|
|
852
1097
|
return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
|
|
853
1098
|
}
|
|
@@ -863,6 +1108,40 @@ function routePathAdmits(declaredPath, requestPath) {
|
|
|
863
1108
|
function routePathsMeet(a, b) {
|
|
864
1109
|
return pathsMeet(normalizePath(a), normalizePath(b));
|
|
865
1110
|
}
|
|
1111
|
+
function readHost(semantics) {
|
|
1112
|
+
const host = semantics.host;
|
|
1113
|
+
return host === void 0 || /[{(]/.test(host) ? void 0 : host;
|
|
1114
|
+
}
|
|
1115
|
+
function hostsAgree(a, b) {
|
|
1116
|
+
return readHost(a) === readHost(b);
|
|
1117
|
+
}
|
|
1118
|
+
function hostUnread(semantics) {
|
|
1119
|
+
if (semantics.host !== void 0) {
|
|
1120
|
+
return readHost(semantics) === void 0;
|
|
1121
|
+
}
|
|
1122
|
+
return baseUrlReference(semantics) !== null;
|
|
1123
|
+
}
|
|
1124
|
+
function servedPath(semantics, path) {
|
|
1125
|
+
const normalized = normalizePath(path);
|
|
1126
|
+
return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
|
|
1127
|
+
}
|
|
1128
|
+
function withoutHost(semantics) {
|
|
1129
|
+
const { host: _host, ...rest } = semantics;
|
|
1130
|
+
return rest;
|
|
1131
|
+
}
|
|
1132
|
+
function groundedBaseUrl(semantics, deployment) {
|
|
1133
|
+
const reference = baseUrlReference(semantics);
|
|
1134
|
+
if (reference === null) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
const base = deployment.setTo(reference);
|
|
1138
|
+
if (base === null) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
|
|
1142
|
+
const grounded = pathAfterOrigin(`${base}${rest}`);
|
|
1143
|
+
return { ...semantics, path: grounded === "" ? "/" : grounded };
|
|
1144
|
+
}
|
|
866
1145
|
var restSemantics = defineBoundarySemantics({
|
|
867
1146
|
name: "rest",
|
|
868
1147
|
schema: RestSemanticsSchema,
|
|
@@ -887,39 +1166,65 @@ var restSemantics = defineBoundarySemantics({
|
|
|
887
1166
|
return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
|
|
888
1167
|
},
|
|
889
1168
|
/**
|
|
890
|
-
* The bucket has only the path
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
* sees and a suppression targets ("GET /users", "* /users")
|
|
894
|
-
* this key groups what a reader still tells apart.
|
|
1169
|
+
* The bucket has only the path, so a `"*"` route lands with the
|
|
1170
|
+
* consumers that each give a method, and `methodsAgree` compares the
|
|
1171
|
+
* methods inside the bucket. The identity key is still what a reader
|
|
1172
|
+
* sees and what a suppression targets ("GET /users", "* /users").
|
|
895
1173
|
*
|
|
896
|
-
*
|
|
897
|
-
* `{userId}` where the Express route that serves it
|
|
898
|
-
* both match the same requests
|
|
899
|
-
* boundaries that never
|
|
1174
|
+
* Parameter names are dropped too. A hand-written spec writes
|
|
1175
|
+
* `{userId}` where the Express route that serves it writes `:id`,
|
|
1176
|
+
* and both match the same requests. Pairing on the name would leave
|
|
1177
|
+
* two boundaries that never meet.
|
|
900
1178
|
*/
|
|
901
1179
|
pairingKey(semantics) {
|
|
902
1180
|
if (semantics.method === null || semantics.path === null) {
|
|
903
1181
|
return null;
|
|
904
1182
|
}
|
|
905
|
-
|
|
1183
|
+
const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
|
|
1184
|
+
const host = readHost(semantics);
|
|
1185
|
+
return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
|
|
1186
|
+
},
|
|
1187
|
+
/**
|
|
1188
|
+
* A spec writes `/teams/{team_id}` where the route that serves it
|
|
1189
|
+
* writes `/teams/:id`. The method stays in, so a `"*"` handler is
|
|
1190
|
+
* never compared with the document for one method.
|
|
1191
|
+
*/
|
|
1192
|
+
operationKey(semantics) {
|
|
1193
|
+
if (semantics.method === null || semantics.path === null) {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
|
|
906
1197
|
},
|
|
907
1198
|
sidesAgree(a, b) {
|
|
908
|
-
return methodsAgree(a.method, b.method);
|
|
1199
|
+
return methodsAgree(a.method, b.method) && hostsAgree(a, b);
|
|
909
1200
|
},
|
|
910
1201
|
spansBuckets(semantics) {
|
|
911
|
-
return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
|
|
1202
|
+
return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
|
|
912
1203
|
},
|
|
913
1204
|
bucketsMeet(a, b) {
|
|
914
|
-
return a.path !== null && b.path !== null &&
|
|
1205
|
+
return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
|
|
915
1206
|
},
|
|
916
|
-
|
|
917
|
-
|
|
1207
|
+
/**
|
|
1208
|
+
* A catch-all route never takes a call whose host suss could not
|
|
1209
|
+
* read, since nothing else ties that call to this app.
|
|
1210
|
+
*/
|
|
1211
|
+
pairRank(provider, consumer) {
|
|
1212
|
+
if (provider.path === null || consumer.path === null) {
|
|
1213
|
+
return [];
|
|
1214
|
+
}
|
|
1215
|
+
const route = normalizePath(provider.path);
|
|
1216
|
+
if (hostUnread(consumer) && isCatchAll(route)) {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
return requestRank(
|
|
1220
|
+
servedPath(provider, provider.path),
|
|
1221
|
+
normalizePath(consumer.path)
|
|
1222
|
+
);
|
|
918
1223
|
},
|
|
919
1224
|
/**
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
*
|
|
1225
|
+
* The identity key, with a missing half still readable: `ANY` when
|
|
1226
|
+
* the source gave no method, and `?` when it gave no path. Null when
|
|
1227
|
+
* both are missing, since then there is nothing to show.
|
|
923
1228
|
*/
|
|
924
1229
|
displayLabel(semantics) {
|
|
925
1230
|
if (semantics.method === null && semantics.path === null) {
|
|
@@ -930,11 +1235,11 @@ var restSemantics = defineBoundarySemantics({
|
|
|
930
1235
|
return `${method} ${path}`;
|
|
931
1236
|
},
|
|
932
1237
|
/**
|
|
933
|
-
*
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
1238
|
+
* With both halves present, the method goes through `methodsAgree`
|
|
1239
|
+
* (so a `"*"` route matches every method) and the path through
|
|
1240
|
+
* `routePathAdmits`. A route missing either half might still handle
|
|
1241
|
+
* the request, and this declaration cannot settle that, so the
|
|
1242
|
+
* result is `"unknown"`.
|
|
938
1243
|
*/
|
|
939
1244
|
servesRequest(semantics, method, path) {
|
|
940
1245
|
if (semantics.method === null || semantics.path === null) {
|
|
@@ -947,43 +1252,41 @@ var restSemantics = defineBoundarySemantics({
|
|
|
947
1252
|
},
|
|
948
1253
|
/**
|
|
949
1254
|
* A route the registration pattern admits is inside it. A route
|
|
950
|
-
*
|
|
951
|
-
*
|
|
1255
|
+
* without a path counts as outside, because nothing shows that the
|
|
1256
|
+
* pattern covers it.
|
|
952
1257
|
*/
|
|
953
1258
|
withinScope(semantics, scope) {
|
|
954
1259
|
return semantics.path !== null && routePathAdmits(scope, semantics.path);
|
|
955
1260
|
},
|
|
956
1261
|
/**
|
|
957
1262
|
* A call whose base URL the deployment fills in, resolved to the
|
|
958
|
-
* path it reaches
|
|
959
|
-
*
|
|
960
|
-
* The source cannot settle this on its own, which is why the
|
|
961
|
-
* adapter leaves the hole in. `API_BASE` could be
|
|
962
|
-
* `http://backend.internal`, and then the path is `/orders`. It
|
|
963
|
-
* could equally be `/api/v2`, and then the path is
|
|
964
|
-
* `/api/v2/orders`. Putting the deployed value in and reading the
|
|
965
|
-
* path back out gets the right one either way.
|
|
1263
|
+
* path it reaches, and a call to a host that can be the project's
|
|
1264
|
+
* own, with the host left out so it meets the route.
|
|
966
1265
|
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
1266
|
+
* The source cannot settle a base URL on its own, so the adapter
|
|
1267
|
+
* leaves the hole in. `API_BASE` could be `http://backend.internal`,
|
|
1268
|
+
* and then the path is `/orders`. It could equally be `/api/v2`, and
|
|
1269
|
+
* then the path is `/api/v2/orders`. Filling in the deployed value
|
|
1270
|
+
* and reading the path back out gets the right one either way, and
|
|
1271
|
+
* a base the deployment sets counts as the app's own.
|
|
970
1272
|
*/
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1273
|
+
/** A listed host matches with or without the port the call gives. */
|
|
1274
|
+
withoutOwnHost(semantics, ownHosts) {
|
|
1275
|
+
const host = semantics.host?.toLowerCase();
|
|
1276
|
+
if (host === void 0) {
|
|
974
1277
|
return null;
|
|
975
1278
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
return
|
|
1279
|
+
return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
|
|
1280
|
+
},
|
|
1281
|
+
groundName(semantics, deployment) {
|
|
1282
|
+
const based = groundedBaseUrl(semantics, deployment);
|
|
1283
|
+
const grounded = based ?? semantics;
|
|
1284
|
+
const host = readHost(grounded);
|
|
1285
|
+
return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
|
|
983
1286
|
},
|
|
984
1287
|
nameReference: baseUrlReference,
|
|
985
1288
|
ruleBoundary: {
|
|
986
|
-
// "METHOD /path": one
|
|
1289
|
+
// "METHOD /path": one token, a space, then an absolute path.
|
|
987
1290
|
claims(raw) {
|
|
988
1291
|
return /^\S+ +\//.test(raw.trim());
|
|
989
1292
|
},
|
|
@@ -998,12 +1301,12 @@ var restSemantics = defineBoundarySemantics({
|
|
|
998
1301
|
});
|
|
999
1302
|
|
|
1000
1303
|
// src/semantics/runtimeConfig.ts
|
|
1001
|
-
var
|
|
1304
|
+
var import_zod9 = require("zod");
|
|
1002
1305
|
|
|
1003
1306
|
// src/deployableUnit.ts
|
|
1004
|
-
var
|
|
1005
|
-
var DeployableUnitSchema =
|
|
1006
|
-
deploymentTarget:
|
|
1307
|
+
var import_zod8 = require("zod");
|
|
1308
|
+
var DeployableUnitSchema = import_zod8.z.object({
|
|
1309
|
+
deploymentTarget: import_zod8.z.enum([
|
|
1007
1310
|
"lambda",
|
|
1008
1311
|
"ecs-task",
|
|
1009
1312
|
"container",
|
|
@@ -1012,7 +1315,7 @@ var DeployableUnitSchema = import_zod7.z.object({
|
|
|
1012
1315
|
]),
|
|
1013
1316
|
// An empty name would agree with every other empty name, so a unit
|
|
1014
1317
|
// with no name has to leave the field off instead.
|
|
1015
|
-
instanceName:
|
|
1318
|
+
instanceName: import_zod8.z.string().min(1)
|
|
1016
1319
|
});
|
|
1017
1320
|
function ecsContainerInstanceName(taskDefinitionLogicalId, containerName) {
|
|
1018
1321
|
return `${taskDefinitionLogicalId}/${containerName}`;
|
|
@@ -1023,14 +1326,14 @@ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
|
|
|
1023
1326
|
deploymentTarget: true,
|
|
1024
1327
|
instanceName: true
|
|
1025
1328
|
}).extend({
|
|
1026
|
-
name:
|
|
1329
|
+
name: import_zod9.z.literal("runtime-config")
|
|
1027
1330
|
});
|
|
1028
1331
|
var runtimeConfigSemantics = defineBoundarySemantics({
|
|
1029
1332
|
name: "runtime-config",
|
|
1030
1333
|
schema: RuntimeConfigSemanticsSchema,
|
|
1031
|
-
// service.name and cloud.resource_id
|
|
1032
|
-
// instanceName is the
|
|
1033
|
-
//
|
|
1334
|
+
// service.name and cloud.resource_id identify the same deployable, but
|
|
1335
|
+
// instanceName is the template's logical id, a different string from
|
|
1336
|
+
// both.
|
|
1034
1337
|
semconv: {},
|
|
1035
1338
|
behavior: {
|
|
1036
1339
|
/** A process reads its config at startup, and nothing replies. */
|
|
@@ -1039,46 +1342,53 @@ var runtimeConfigSemantics = defineBoundarySemantics({
|
|
|
1039
1342
|
// comes from outside the code.
|
|
1040
1343
|
leavesTheProcess: true,
|
|
1041
1344
|
reportsUnpairedItself: false,
|
|
1042
|
-
identityKey: () => null
|
|
1345
|
+
identityKey: () => null,
|
|
1346
|
+
// A reader sees which deployable's environment this is. Code that
|
|
1347
|
+
// reads a variable does not say which deployable runs it, so a read
|
|
1348
|
+
// has no name.
|
|
1349
|
+
displayLabel(semantics) {
|
|
1350
|
+
const instance = semantics.instanceName;
|
|
1351
|
+
return instance === void 0 ? null : `runtime-config:${instance}`;
|
|
1352
|
+
}
|
|
1043
1353
|
}
|
|
1044
1354
|
});
|
|
1045
1355
|
|
|
1046
1356
|
// src/semantics/storage.ts
|
|
1047
|
-
var
|
|
1048
|
-
var StorageSemanticsSchema =
|
|
1049
|
-
name:
|
|
1357
|
+
var import_zod10 = require("zod");
|
|
1358
|
+
var StorageSemanticsSchema = import_zod10.z.object({
|
|
1359
|
+
name: import_zod10.z.literal("storage"),
|
|
1050
1360
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
* containers can share a name,
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1361
|
+
* The storage system: an OpenTelemetry value such as `postgresql` or
|
|
1362
|
+
* `aws.dynamodb`, or suss's own word for one the conventions do not
|
|
1363
|
+
* cover, such as `s3`. Null when the reader could not work out which
|
|
1364
|
+
* system the source uses. Two products' containers can share a name,
|
|
1365
|
+
* and this field keeps them apart. The pairing pass decides how to
|
|
1366
|
+
* treat a null.
|
|
1057
1367
|
*/
|
|
1058
|
-
storageSystem:
|
|
1368
|
+
storageSystem: import_zod10.z.string().nullable(),
|
|
1059
1369
|
/**
|
|
1060
1370
|
* The ORM, schema, or deployment scope. A single-database setup uses
|
|
1061
1371
|
* `"default"`, and a monorepo with several schemas gives each one its
|
|
1062
1372
|
* own value.
|
|
1063
1373
|
*/
|
|
1064
|
-
scope:
|
|
1374
|
+
scope: import_zod10.z.string(),
|
|
1065
1375
|
/**
|
|
1066
|
-
* The table, bucket, collection
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1376
|
+
* The table, bucket, collection or index, as the source declares it.
|
|
1377
|
+
* Null when the reader could not work out the container, and a null
|
|
1378
|
+
* container pairs with nothing, so it cannot match by accident of
|
|
1379
|
+
* shared source text. The string uses the boundary-name syntax (a
|
|
1380
|
+
* literal, a pattern with `{}` holes, or a reference), and only
|
|
1381
|
+
* `parseBoundaryName` should read it.
|
|
1072
1382
|
*/
|
|
1073
|
-
container:
|
|
1383
|
+
container: import_zod10.z.string().nullable(),
|
|
1074
1384
|
/**
|
|
1075
|
-
* A secondary way into the container, a DynamoDB global
|
|
1076
|
-
* index or an Elasticsearch alias, each with its own key
|
|
1077
|
-
* Null means the container's
|
|
1385
|
+
* A secondary way into the container, such as a DynamoDB global
|
|
1386
|
+
* secondary index or an Elasticsearch alias, each with its own key
|
|
1387
|
+
* fields. Null means the container's primary key. A query through an
|
|
1078
1388
|
* index and a query through the table are different accesses, so
|
|
1079
1389
|
* they pair separately.
|
|
1080
1390
|
*/
|
|
1081
|
-
accessPath:
|
|
1391
|
+
accessPath: import_zod10.z.string().nullable()
|
|
1082
1392
|
});
|
|
1083
1393
|
function containerReference(semantics) {
|
|
1084
1394
|
return semantics.container === null ? null : referenceFromName(semantics.container);
|
|
@@ -1088,21 +1398,21 @@ var storageSemantics = defineBoundarySemantics({
|
|
|
1088
1398
|
schema: StorageSemanticsSchema,
|
|
1089
1399
|
semconv: {
|
|
1090
1400
|
storageSystem: { name: "db.system.name" },
|
|
1091
|
-
// "default"
|
|
1401
|
+
// suss writes "default" when the source did not say which database.
|
|
1092
1402
|
scope: { name: "db.namespace", placeholderValues: ["default"] },
|
|
1093
1403
|
container: { name: "db.collection.name" }
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1404
|
+
// The conventions have no attribute for a secondary index, so
|
|
1405
|
+
// accessPath is left out.
|
|
1096
1406
|
},
|
|
1097
1407
|
behavior: {
|
|
1098
|
-
/** A query returns rows or items,
|
|
1408
|
+
/** A query returns rows or items, with no status. */
|
|
1099
1409
|
exchangesHttpResponses: false,
|
|
1100
1410
|
leavesTheProcess: true,
|
|
1101
1411
|
reportsUnpairedItself: false,
|
|
1102
1412
|
identityKey: () => null,
|
|
1103
1413
|
displayLabel: storageLabel,
|
|
1104
1414
|
/**
|
|
1105
|
-
* A container the source
|
|
1415
|
+
* A container the source gives as a variable is whatever the
|
|
1106
1416
|
* deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
|
|
1107
1417
|
* code and `prod-subscribers-v1` in the manifest are one table.
|
|
1108
1418
|
*/
|
|
@@ -1132,9 +1442,9 @@ function storageContainerLabel(semantics) {
|
|
|
1132
1442
|
}
|
|
1133
1443
|
|
|
1134
1444
|
// src/semantics/unitInvocation.ts
|
|
1135
|
-
var
|
|
1445
|
+
var import_zod11 = require("zod");
|
|
1136
1446
|
var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
|
|
1137
|
-
name:
|
|
1447
|
+
name: import_zod11.z.literal("unit-invocation"),
|
|
1138
1448
|
instanceName: DeployableUnitSchema.shape.instanceName.nullable()
|
|
1139
1449
|
});
|
|
1140
1450
|
function calleeReference(semantics) {
|
|
@@ -1164,9 +1474,9 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1164
1474
|
*/
|
|
1165
1475
|
reportsUnpairedItself: false,
|
|
1166
1476
|
/**
|
|
1167
|
-
* A callee the source
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1477
|
+
* A callee the source gives only as a variable has no name until the
|
|
1478
|
+
* deployment fills it in. It gets no key, since a key built from the
|
|
1479
|
+
* variable would pair only with itself.
|
|
1170
1480
|
*/
|
|
1171
1481
|
identityKey(semantics) {
|
|
1172
1482
|
if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
|
|
@@ -1187,11 +1497,10 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1187
1497
|
);
|
|
1188
1498
|
},
|
|
1189
1499
|
/**
|
|
1190
|
-
* A call that reads its callee's name
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1194
|
-
* id and neither side ever writes the other's string.
|
|
1500
|
+
* A call that reads its callee's name from the environment reaches
|
|
1501
|
+
* whichever resource the template wires that variable to. The name
|
|
1502
|
+
* is grounded to the logical id and not the deployed name, because
|
|
1503
|
+
* the invoked unit's own summary is keyed by the logical id.
|
|
1195
1504
|
*/
|
|
1196
1505
|
groundName(semantics, deployment) {
|
|
1197
1506
|
const reference = calleeReference(semantics);
|
|
@@ -1206,7 +1515,7 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
1206
1515
|
});
|
|
1207
1516
|
|
|
1208
1517
|
// src/semantics/registry.ts
|
|
1209
|
-
var SemanticsSchema =
|
|
1518
|
+
var SemanticsSchema = import_zod12.z.discriminatedUnion("name", [
|
|
1210
1519
|
restSemantics.schema,
|
|
1211
1520
|
functionCallSemantics.schema,
|
|
1212
1521
|
graphqlResolverSemantics.schema,
|
|
@@ -1215,7 +1524,8 @@ var SemanticsSchema = import_zod11.z.discriminatedUnion("name", [
|
|
|
1215
1524
|
storageSemantics.schema,
|
|
1216
1525
|
messageBusSemantics.schema,
|
|
1217
1526
|
metricSemantics.schema,
|
|
1218
|
-
unitInvocationSemantics.schema
|
|
1527
|
+
unitInvocationSemantics.schema,
|
|
1528
|
+
ioSemantics.schema
|
|
1219
1529
|
]);
|
|
1220
1530
|
var DEFINITIONS = [
|
|
1221
1531
|
restSemantics,
|
|
@@ -1226,7 +1536,8 @@ var DEFINITIONS = [
|
|
|
1226
1536
|
storageSemantics,
|
|
1227
1537
|
messageBusSemantics,
|
|
1228
1538
|
metricSemantics,
|
|
1229
|
-
unitInvocationSemantics
|
|
1539
|
+
unitInvocationSemantics,
|
|
1540
|
+
ioSemantics
|
|
1230
1541
|
];
|
|
1231
1542
|
var BY_NAME = new Map(
|
|
1232
1543
|
DEFINITIONS.map((d) => [d.name, d])
|
|
@@ -1249,90 +1560,95 @@ function allBehaviors() {
|
|
|
1249
1560
|
}
|
|
1250
1561
|
|
|
1251
1562
|
// src/schemas.ts
|
|
1252
|
-
var ConfidenceSourceSchema =
|
|
1563
|
+
var ConfidenceSourceSchema = import_zod13.z.enum([
|
|
1253
1564
|
"inferred_static",
|
|
1254
1565
|
"inferred_ai",
|
|
1255
1566
|
"declared",
|
|
1256
1567
|
"derived"
|
|
1257
1568
|
]);
|
|
1258
|
-
var ConfidenceLevelSchema =
|
|
1259
|
-
var CorroborationSchema =
|
|
1260
|
-
outcome:
|
|
1569
|
+
var ConfidenceLevelSchema = import_zod13.z.enum(["high", "medium", "low"]);
|
|
1570
|
+
var CorroborationSchema = import_zod13.z.object({
|
|
1571
|
+
outcome: import_zod13.z.enum(["observed", "refuted", "untested"]),
|
|
1261
1572
|
/** Executions that produced a verdict for this claim. */
|
|
1262
|
-
runs:
|
|
1573
|
+
runs: import_zod13.z.number(),
|
|
1263
1574
|
/** Present when refuted: the input and observation that disagreed. */
|
|
1264
|
-
counterexample:
|
|
1575
|
+
counterexample: import_zod13.z.unknown().optional(),
|
|
1265
1576
|
/** Present when untested: why no verdict was reachable. */
|
|
1266
|
-
reason:
|
|
1577
|
+
reason: import_zod13.z.string().optional()
|
|
1267
1578
|
});
|
|
1268
|
-
var ConfidenceInfoSchema =
|
|
1579
|
+
var ConfidenceInfoSchema = import_zod13.z.object({
|
|
1269
1580
|
source: ConfidenceSourceSchema,
|
|
1270
1581
|
level: ConfidenceLevelSchema,
|
|
1271
1582
|
corroboration: CorroborationSchema.optional()
|
|
1272
1583
|
});
|
|
1273
|
-
var SourceLocationSchema =
|
|
1274
|
-
file:
|
|
1584
|
+
var SourceLocationSchema = import_zod13.z.object({
|
|
1585
|
+
file: import_zod13.z.string(),
|
|
1275
1586
|
/** Line numbers, for a person reading the summary or an editor link. */
|
|
1276
|
-
range:
|
|
1587
|
+
range: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }),
|
|
1277
1588
|
/**
|
|
1278
|
-
* Character offsets of the unit in its file. Identity and joins
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
* contract artifact's say.
|
|
1589
|
+
* Character offsets of the unit in its file. Identity and joins use
|
|
1590
|
+
* these, because two functions can share a line but never an offset
|
|
1591
|
+
* range. `range` gives the same place in lines. Absent on a summary
|
|
1592
|
+
* with no source position, such as one read from a contract.
|
|
1283
1593
|
*/
|
|
1284
|
-
span:
|
|
1285
|
-
exportName:
|
|
1594
|
+
span: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }).optional(),
|
|
1595
|
+
exportName: import_zod13.z.string().nullable(),
|
|
1286
1596
|
/**
|
|
1287
|
-
* The project the extract
|
|
1288
|
-
*
|
|
1597
|
+
* The project the extract ran on, by the name the project gives
|
|
1598
|
+
* itself.
|
|
1289
1599
|
*
|
|
1290
1600
|
* Paths are relative to wherever the extract ran, so two services in
|
|
1291
|
-
* one repository both report `src/handlers.ts`, and merging their
|
|
1292
|
-
* summaries
|
|
1293
|
-
*
|
|
1601
|
+
* one repository can both report `src/handlers.ts`, and merging their
|
|
1602
|
+
* summaries would mix them up. This field tells them apart, and a
|
|
1603
|
+
* reader groups by it.
|
|
1294
1604
|
*/
|
|
1295
|
-
workspace:
|
|
1605
|
+
workspace: import_zod13.z.string().optional(),
|
|
1606
|
+
/**
|
|
1607
|
+
* The module of the application the unit belongs to, by the name the
|
|
1608
|
+
* project's `suss.json` gives it. Set only when the project lists its
|
|
1609
|
+
* modules, and then on every unit whose file is under a module's root.
|
|
1610
|
+
*/
|
|
1611
|
+
module: import_zod13.z.string().optional()
|
|
1296
1612
|
});
|
|
1297
|
-
var BoundaryBindingSchema =
|
|
1298
|
-
transport:
|
|
1613
|
+
var BoundaryBindingSchema = import_zod13.z.object({
|
|
1614
|
+
transport: import_zod13.z.string(),
|
|
1299
1615
|
semantics: SemanticsSchema,
|
|
1300
|
-
recognition:
|
|
1616
|
+
recognition: import_zod13.z.string()
|
|
1301
1617
|
});
|
|
1302
1618
|
function typeDefinitionKey(ref) {
|
|
1303
1619
|
return ref.def ?? null;
|
|
1304
1620
|
}
|
|
1305
|
-
var TypeShapeSchema =
|
|
1306
|
-
() =>
|
|
1307
|
-
|
|
1308
|
-
type:
|
|
1309
|
-
properties:
|
|
1310
|
-
spreads:
|
|
1621
|
+
var TypeShapeSchema = import_zod13.z.lazy(
|
|
1622
|
+
() => import_zod13.z.discriminatedUnion("type", [
|
|
1623
|
+
import_zod13.z.object({
|
|
1624
|
+
type: import_zod13.z.literal("record"),
|
|
1625
|
+
properties: import_zod13.z.record(import_zod13.z.string(), TypeShapeSchema),
|
|
1626
|
+
spreads: import_zod13.z.array(import_zod13.z.object({ sourceText: import_zod13.z.string() })).optional()
|
|
1311
1627
|
}),
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
type:
|
|
1316
|
-
value:
|
|
1317
|
-
raw:
|
|
1628
|
+
import_zod13.z.object({ type: import_zod13.z.literal("dictionary"), values: TypeShapeSchema }),
|
|
1629
|
+
import_zod13.z.object({ type: import_zod13.z.literal("array"), items: TypeShapeSchema }),
|
|
1630
|
+
import_zod13.z.object({
|
|
1631
|
+
type: import_zod13.z.literal("literal"),
|
|
1632
|
+
value: import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()]),
|
|
1633
|
+
raw: import_zod13.z.string().optional()
|
|
1318
1634
|
}),
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
type:
|
|
1327
|
-
variants:
|
|
1635
|
+
import_zod13.z.object({ type: import_zod13.z.literal("text") }),
|
|
1636
|
+
import_zod13.z.object({ type: import_zod13.z.literal("integer") }),
|
|
1637
|
+
import_zod13.z.object({ type: import_zod13.z.literal("number") }),
|
|
1638
|
+
import_zod13.z.object({ type: import_zod13.z.literal("boolean") }),
|
|
1639
|
+
import_zod13.z.object({ type: import_zod13.z.literal("null") }),
|
|
1640
|
+
import_zod13.z.object({ type: import_zod13.z.literal("undefined") }),
|
|
1641
|
+
import_zod13.z.object({
|
|
1642
|
+
type: import_zod13.z.literal("union"),
|
|
1643
|
+
variants: import_zod13.z.array(TypeShapeSchema)
|
|
1328
1644
|
}),
|
|
1329
|
-
|
|
1330
|
-
type:
|
|
1331
|
-
name:
|
|
1332
|
-
from:
|
|
1333
|
-
def:
|
|
1645
|
+
import_zod13.z.object({
|
|
1646
|
+
type: import_zod13.z.literal("ref"),
|
|
1647
|
+
name: import_zod13.z.string(),
|
|
1648
|
+
from: import_zod13.z.string().optional(),
|
|
1649
|
+
def: import_zod13.z.string().optional()
|
|
1334
1650
|
}),
|
|
1335
|
-
|
|
1651
|
+
import_zod13.z.object({ type: import_zod13.z.literal("unknown") })
|
|
1336
1652
|
])
|
|
1337
1653
|
);
|
|
1338
1654
|
var MAX_DEFINITION_DEPTH = 6;
|
|
@@ -1412,6 +1728,13 @@ function withRewrittenPaths(binding, rewrite) {
|
|
|
1412
1728
|
semantics: behavior.rewritePaths(binding.semantics, rewrite)
|
|
1413
1729
|
};
|
|
1414
1730
|
}
|
|
1731
|
+
function withoutOwnHost(binding, ownHosts) {
|
|
1732
|
+
const semantics = behaviorOf(binding.semantics).withoutOwnHost?.(
|
|
1733
|
+
binding.semantics,
|
|
1734
|
+
ownHosts
|
|
1735
|
+
);
|
|
1736
|
+
return semantics === void 0 || semantics === null ? binding : { ...binding, semantics };
|
|
1737
|
+
}
|
|
1415
1738
|
function groundedPairingKey(binding, deployment) {
|
|
1416
1739
|
return pairingKey(groundBinding(binding, deployment));
|
|
1417
1740
|
}
|
|
@@ -1427,6 +1750,10 @@ function pairingKey(binding) {
|
|
|
1427
1750
|
const behavior = behaviorOf(binding.semantics);
|
|
1428
1751
|
return (behavior.pairingKey ?? behavior.identityKey)(binding.semantics);
|
|
1429
1752
|
}
|
|
1753
|
+
function operationKey(binding) {
|
|
1754
|
+
const behavior = behaviorOf(binding.semantics);
|
|
1755
|
+
return (behavior.operationKey ?? behavior.identityKey)(binding.semantics);
|
|
1756
|
+
}
|
|
1430
1757
|
function semanticsAgree(a, b) {
|
|
1431
1758
|
if (a.name !== b.name) {
|
|
1432
1759
|
return false;
|
|
@@ -1444,8 +1771,12 @@ function bucketsMeet(a, b) {
|
|
|
1444
1771
|
const meet = behaviorOf(a.semantics).bucketsMeet;
|
|
1445
1772
|
return meet !== void 0 && meet(a.semantics, b.semantics);
|
|
1446
1773
|
}
|
|
1447
|
-
function
|
|
1448
|
-
|
|
1774
|
+
function pairRank(provider, consumer) {
|
|
1775
|
+
if (provider.semantics.name !== consumer.semantics.name) {
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1778
|
+
const rank = behaviorOf(provider.semantics).pairRank;
|
|
1779
|
+
return rank === void 0 ? [] : rank(provider.semantics, consumer.semantics);
|
|
1449
1780
|
}
|
|
1450
1781
|
function boundaryLabel(binding) {
|
|
1451
1782
|
const behavior = behaviorOf(binding.semantics);
|
|
@@ -1454,6 +1785,9 @@ function boundaryLabel(binding) {
|
|
|
1454
1785
|
function displayLabel(binding) {
|
|
1455
1786
|
return boundaryLabel(binding) ?? `${binding.semantics.name}:${binding.recognition}`;
|
|
1456
1787
|
}
|
|
1788
|
+
function labelWithDetail(binding, detail) {
|
|
1789
|
+
return `${boundaryLabel(binding) ?? binding.semantics.name} ${detail}`;
|
|
1790
|
+
}
|
|
1457
1791
|
function normalizeRuleBoundary(raw) {
|
|
1458
1792
|
const trimmed = raw.trim();
|
|
1459
1793
|
for (const behavior of allBehaviors()) {
|
|
@@ -1617,8 +1951,8 @@ var PLATFORM_INJECTED_ENV_VARS = {
|
|
|
1617
1951
|
};
|
|
1618
1952
|
|
|
1619
1953
|
// src/relation.ts
|
|
1620
|
-
var
|
|
1621
|
-
var RelationSchema =
|
|
1954
|
+
var import_zod14 = require("zod");
|
|
1955
|
+
var RelationSchema = import_zod14.z.enum([
|
|
1622
1956
|
"provides",
|
|
1623
1957
|
"reads",
|
|
1624
1958
|
"writes",
|
|
@@ -1627,71 +1961,78 @@ var RelationSchema = import_zod13.z.enum([
|
|
|
1627
1961
|
var EffectRelationSchema = RelationSchema.exclude(["provides"]);
|
|
1628
1962
|
|
|
1629
1963
|
// src/suppressions.ts
|
|
1630
|
-
var
|
|
1631
|
-
var SuppressionSideSchema =
|
|
1632
|
-
summary:
|
|
1633
|
-
transitionId:
|
|
1964
|
+
var import_zod15 = require("zod");
|
|
1965
|
+
var SuppressionSideSchema = import_zod15.z.object({
|
|
1966
|
+
summary: import_zod15.z.string().optional(),
|
|
1967
|
+
transitionId: import_zod15.z.string().optional()
|
|
1634
1968
|
}).optional();
|
|
1635
|
-
var SuppressionRuleSchema =
|
|
1969
|
+
var SuppressionRuleSchema = import_zod15.z.object({
|
|
1636
1970
|
/**
|
|
1637
|
-
* The finding kind to match,
|
|
1638
|
-
* string
|
|
1639
|
-
*
|
|
1971
|
+
* The finding kind to match, behavioral or intent. The schema accepts
|
|
1972
|
+
* any string because ir-core cannot import either IR's kind enum. The
|
|
1973
|
+
* CLI's loader checks it against both.
|
|
1640
1974
|
*/
|
|
1641
|
-
kind:
|
|
1975
|
+
kind: import_zod15.z.string().optional(),
|
|
1642
1976
|
/**
|
|
1643
|
-
*
|
|
1644
|
-
* "fn:@acme/api::getUser". REST
|
|
1645
|
-
*
|
|
1646
|
-
* equal.
|
|
1977
|
+
* The boundary's human-readable key, such as "GET /pet/{petId}" or
|
|
1978
|
+
* "fn:@acme/api::getUser". A REST key goes through the same path
|
|
1979
|
+
* normalizer the checkers use, so `:id` and `{id}` compare equal.
|
|
1647
1980
|
*/
|
|
1648
|
-
boundary:
|
|
1981
|
+
boundary: import_zod15.z.string().optional(),
|
|
1649
1982
|
/**
|
|
1650
|
-
* Consumer-side discriminators, the narrowest useful match.
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1983
|
+
* Consumer-side discriminators, the narrowest useful match. They
|
|
1984
|
+
* apply only to behavioral findings. A rule that sets `consumer`
|
|
1985
|
+
* never matches an intent finding, because an intent finding has no
|
|
1986
|
+
* consumer side.
|
|
1654
1987
|
*/
|
|
1655
1988
|
consumer: SuppressionSideSchema,
|
|
1656
1989
|
/**
|
|
1657
|
-
* Provider-side discriminators, the
|
|
1658
|
-
* about a status the provider produces has its
|
|
1659
|
-
* side, and that id is the only
|
|
1660
|
-
* that one finding. As with `consumer`, a rule
|
|
1661
|
-
* `provider` never matches an intent finding.
|
|
1990
|
+
* Provider-side discriminators, matching the same fields as
|
|
1991
|
+
* `consumer`. A finding about a status the provider produces has its
|
|
1992
|
+
* transition id on this side, and that id is the only value narrow
|
|
1993
|
+
* enough to pick out that one finding. As with `consumer`, a rule
|
|
1994
|
+
* that sets `provider` never matches an intent finding.
|
|
1662
1995
|
*/
|
|
1663
1996
|
provider: SuppressionSideSchema,
|
|
1664
1997
|
/**
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
* regressions in that category.
|
|
1998
|
+
* The title of one PRD scenario. A PRD finding's boundary is the
|
|
1999
|
+
* whole PRD, so without this a rule accepts every scenario in it,
|
|
2000
|
+
* including one added later. A rule that sets `scenario` matches
|
|
2001
|
+
* only intent findings about a scenario with that title.
|
|
1670
2002
|
*/
|
|
1671
|
-
|
|
2003
|
+
scenario: import_zod15.z.string().optional(),
|
|
2004
|
+
/**
|
|
2005
|
+
* "narrow", the default, requires `kind` plus one of `boundary`,
|
|
2006
|
+
* `scenario`, `consumer.transitionId` or `provider.transitionId`, so
|
|
2007
|
+
* the rule targets a specific class of finding. "broad" allows a
|
|
2008
|
+
* match on kind alone or boundary alone, and such a rule also
|
|
2009
|
+
* silences future regressions in that category.
|
|
2010
|
+
*/
|
|
2011
|
+
scope: import_zod15.z.enum(["narrow", "broad"]).default("narrow"),
|
|
1672
2012
|
/** Required human-written justification. */
|
|
1673
|
-
reason:
|
|
2013
|
+
reason: import_zod15.z.string().min(1),
|
|
1674
2014
|
/** What to do when a finding matches. */
|
|
1675
|
-
effect:
|
|
2015
|
+
effect: import_zod15.z.enum(["mark", "downgrade", "hide"]).default("mark")
|
|
1676
2016
|
}).strict();
|
|
1677
|
-
var SuppressionFileSchema =
|
|
1678
|
-
version:
|
|
1679
|
-
rules:
|
|
2017
|
+
var SuppressionFileSchema = import_zod15.z.object({
|
|
2018
|
+
version: import_zod15.z.literal(1),
|
|
2019
|
+
rules: import_zod15.z.array(SuppressionRuleSchema)
|
|
1680
2020
|
}).strict();
|
|
1681
2021
|
function validateRule(rule) {
|
|
1682
2022
|
if (rule.scope === "broad") {
|
|
1683
|
-
if (rule.kind === void 0 && rule.boundary === void 0 && rule.consumer === void 0 && rule.provider === void 0) {
|
|
1684
|
-
return "broad-scope rule must constrain at least one field (kind, boundary, consumer, or provider)";
|
|
2023
|
+
if (rule.kind === void 0 && rule.boundary === void 0 && rule.scenario === void 0 && rule.consumer === void 0 && rule.provider === void 0) {
|
|
2024
|
+
return "broad-scope rule must constrain at least one field (kind, boundary, scenario, consumer, or provider)";
|
|
1685
2025
|
}
|
|
1686
2026
|
return null;
|
|
1687
2027
|
}
|
|
1688
2028
|
const hasKind = rule.kind !== void 0;
|
|
1689
2029
|
const hasBoundary = rule.boundary !== void 0;
|
|
2030
|
+
const hasScenario = rule.scenario !== void 0;
|
|
1690
2031
|
const hasTransition = rule.consumer?.transitionId !== void 0 || rule.provider?.transitionId !== void 0;
|
|
1691
|
-
if (hasKind && (hasBoundary || hasTransition)) {
|
|
2032
|
+
if (hasKind && (hasBoundary || hasScenario || hasTransition)) {
|
|
1692
2033
|
return null;
|
|
1693
2034
|
}
|
|
1694
|
-
return "narrow-scope rule must specify kind AND (boundary OR consumer.transitionId OR provider.transitionId); set scope: 'broad' to silence wider categories";
|
|
2035
|
+
return "narrow-scope rule must specify kind AND (boundary OR scenario OR consumer.transitionId OR provider.transitionId); set scope: 'broad' to silence wider categories";
|
|
1695
2036
|
}
|
|
1696
2037
|
function ruleBoundaryMatchesKey(ruleBoundary, key) {
|
|
1697
2038
|
if (key === null) {
|
|
@@ -1743,6 +2084,12 @@ function countsForThreshold(finding) {
|
|
|
1743
2084
|
return finding.suppressed.effect === "downgrade";
|
|
1744
2085
|
}
|
|
1745
2086
|
|
|
2087
|
+
// src/testName.ts
|
|
2088
|
+
var TEST_TITLE_SEPARATOR = " > ";
|
|
2089
|
+
function testUnitName(titles) {
|
|
2090
|
+
return titles.join(TEST_TITLE_SEPARATOR);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
1746
2093
|
// src/typeShapeMatch.ts
|
|
1747
2094
|
function bodyShapesMatch(actual, declared) {
|
|
1748
2095
|
if (actual.type === "unknown" || declared.type === "unknown") {
|
|
@@ -1904,7 +2251,9 @@ function restBinding(opts) {
|
|
|
1904
2251
|
name: "rest",
|
|
1905
2252
|
method: method === null ? null : method.toUpperCase(),
|
|
1906
2253
|
path: namedOrNull(opts.path, "rest path"),
|
|
1907
|
-
...opts.declaredResponses !== void 0 ? { declaredResponses: opts.declaredResponses } : {}
|
|
2254
|
+
...opts.declaredResponses !== void 0 ? { declaredResponses: opts.declaredResponses } : {},
|
|
2255
|
+
...opts.host !== void 0 && opts.host !== "" ? { host: opts.host } : {},
|
|
2256
|
+
...opts.optionalFormat === true ? { optionalFormat: true } : {}
|
|
1908
2257
|
},
|
|
1909
2258
|
recognition: opts.recognition
|
|
1910
2259
|
};
|
|
@@ -1930,6 +2279,14 @@ function packageExportBinding(opts) {
|
|
|
1930
2279
|
exportPath: opts.exportPath
|
|
1931
2280
|
});
|
|
1932
2281
|
}
|
|
2282
|
+
function moduleExportBinding(opts) {
|
|
2283
|
+
return functionCallBinding({
|
|
2284
|
+
transport: "in-process",
|
|
2285
|
+
recognition: opts.recognition,
|
|
2286
|
+
module: opts.module,
|
|
2287
|
+
exportName: opts.exportName
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
1933
2290
|
function graphqlResolverBinding(opts) {
|
|
1934
2291
|
return {
|
|
1935
2292
|
transport: opts.transport,
|
|
@@ -2012,6 +2369,13 @@ function metricBinding(opts) {
|
|
|
2012
2369
|
recognition: opts.recognition
|
|
2013
2370
|
};
|
|
2014
2371
|
}
|
|
2372
|
+
function ioBinding(opts) {
|
|
2373
|
+
return {
|
|
2374
|
+
transport: "in-process",
|
|
2375
|
+
semantics: { name: "io", target: namedOrNull(opts.target, "io target") },
|
|
2376
|
+
recognition: opts.recognition
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2015
2379
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2016
2380
|
0 && (module.exports = {
|
|
2017
2381
|
BoundaryBindingSchema,
|
|
@@ -2025,6 +2389,7 @@ function metricBinding(opts) {
|
|
|
2025
2389
|
FunctionCallSemanticsSchema,
|
|
2026
2390
|
GraphqlOperationSemanticsSchema,
|
|
2027
2391
|
GraphqlResolverSemanticsSchema,
|
|
2392
|
+
IoSemanticsSchema,
|
|
2028
2393
|
MessageBusSemanticsSchema,
|
|
2029
2394
|
MetricSemanticsSchema,
|
|
2030
2395
|
NOTHING_DEPLOYED,
|
|
@@ -2037,6 +2402,7 @@ function metricBinding(opts) {
|
|
|
2037
2402
|
StorageSemanticsSchema,
|
|
2038
2403
|
SuppressionFileSchema,
|
|
2039
2404
|
SuppressionRuleSchema,
|
|
2405
|
+
TEST_TITLE_SEPARATOR,
|
|
2040
2406
|
TypeShapeSchema,
|
|
2041
2407
|
UnitInvocationSemanticsSchema,
|
|
2042
2408
|
applySuppressionsToFindings,
|
|
@@ -2046,7 +2412,6 @@ function metricBinding(opts) {
|
|
|
2046
2412
|
boundaryKey,
|
|
2047
2413
|
boundaryLabel,
|
|
2048
2414
|
boundaryNameString,
|
|
2049
|
-
bucketRank,
|
|
2050
2415
|
bucketsMeet,
|
|
2051
2416
|
busIdentityKey,
|
|
2052
2417
|
busesAgree,
|
|
@@ -2070,12 +2435,18 @@ function metricBinding(opts) {
|
|
|
2070
2435
|
groundBinding,
|
|
2071
2436
|
groundedPairingKey,
|
|
2072
2437
|
hasNameHole,
|
|
2438
|
+
hostCanBeOwn,
|
|
2439
|
+
ioBinding,
|
|
2440
|
+
ioIdentityKey,
|
|
2073
2441
|
isGraphqlOperationBinding,
|
|
2442
|
+
isModuleName,
|
|
2443
|
+
labelWithDetail,
|
|
2074
2444
|
leavesTheProcess,
|
|
2075
2445
|
messageBusBinding,
|
|
2076
2446
|
methodsAgree,
|
|
2077
2447
|
metricBinding,
|
|
2078
2448
|
metricIdentityKey,
|
|
2449
|
+
moduleExportBinding,
|
|
2079
2450
|
namePatternFromSub,
|
|
2080
2451
|
namePatternKey,
|
|
2081
2452
|
nameReference,
|
|
@@ -2085,7 +2456,9 @@ function metricBinding(opts) {
|
|
|
2085
2456
|
namesNothing,
|
|
2086
2457
|
normalizePath,
|
|
2087
2458
|
normalizeRuleBoundary,
|
|
2459
|
+
operationKey,
|
|
2088
2460
|
packageExportBinding,
|
|
2461
|
+
pairRank,
|
|
2089
2462
|
pairingKey,
|
|
2090
2463
|
parseBoundaryName,
|
|
2091
2464
|
parseChannel,
|
|
@@ -2115,11 +2488,13 @@ function metricBinding(opts) {
|
|
|
2115
2488
|
storageContainerLabel,
|
|
2116
2489
|
storageLabel,
|
|
2117
2490
|
storageSystemLabel,
|
|
2491
|
+
testUnitName,
|
|
2118
2492
|
typeDefinitionKey,
|
|
2119
2493
|
unitIdentityKey,
|
|
2120
2494
|
unitInvocationBinding,
|
|
2121
2495
|
validateRule,
|
|
2122
2496
|
withDefinitionsInlined,
|
|
2123
2497
|
withRewrittenPaths,
|
|
2124
|
-
withinScope
|
|
2498
|
+
withinScope,
|
|
2499
|
+
withoutOwnHost
|
|
2125
2500
|
});
|