@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/dist/schemas.cjs CHANGED
@@ -40,10 +40,10 @@ __export(schemas_exports, {
40
40
  withDefinitionsInlined: () => withDefinitionsInlined
41
41
  });
42
42
  module.exports = __toCommonJS(schemas_exports);
43
- var import_zod12 = require("zod");
43
+ var import_zod13 = require("zod");
44
44
 
45
45
  // src/semantics/registry.ts
46
- var import_zod11 = require("zod");
46
+ var import_zod12 = require("zod");
47
47
 
48
48
  // src/semantics/functionCall.ts
49
49
  var import_zod = require("zod");
@@ -64,6 +64,9 @@ function unitIdentityKey(deploymentTarget, instanceName) {
64
64
  function metricIdentityKey(metricSystem, metricType) {
65
65
  return `metric:${metricSystem} ${metricType}`;
66
66
  }
67
+ function ioIdentityKey(target) {
68
+ return `io:${target}`;
69
+ }
67
70
 
68
71
  // src/semantics/definition.ts
69
72
  function defineBoundarySemantics(definition) {
@@ -74,10 +77,11 @@ function defineBoundarySemantics(definition) {
74
77
  var FunctionCallSemanticsSchema = import_zod.z.object({
75
78
  name: import_zod.z.literal("function-call"),
76
79
  /**
77
- * Optional module identifier for cross-unit references
78
- * (e.g. `"./components/Button"` for a React component, or the TS
79
- * module path for a bare function export). Packs that don't do
80
- * cross-module pairing can leave it unset.
80
+ * Where the function lives inside this repository. It is either the
81
+ * name of a module the project lists in `suss.json`, such as
82
+ * `"billing"`, or a module path, such as the file a server action is
83
+ * written in. A name has no separator in it and a path always does.
84
+ * Packs that don't do cross-module pairing can leave it unset.
81
85
  */
82
86
  module: import_zod.z.string().optional(),
83
87
  /** Named export within the module, when applicable. */
@@ -102,35 +106,38 @@ var FunctionCallSemanticsSchema = import_zod.z.object({
102
106
  */
103
107
  exportPath: import_zod.z.array(import_zod.z.string()).optional()
104
108
  });
109
+ function isModuleName(module2) {
110
+ return !module2.includes("/") && !module2.includes("\\");
111
+ }
105
112
  var functionCallSemantics = defineBoundarySemantics({
106
113
  name: "function-call",
107
114
  schema: FunctionCallSemanticsSchema,
108
- // A call that never leaves the process does not produce a span, and
109
- // the conventions do not name one. All of it is ours.
115
+ // A call inside the process does not produce a span, and the
116
+ // conventions have no attributes for it.
110
117
  semconv: {},
111
118
  behavior: {
112
- /** A call returns a value, which is not a status and a body. */
119
+ /** A call returns a value, with no status. */
113
120
  exchangesHttpResponses: false,
114
121
  leavesTheProcess: false,
115
122
  reportsUnpairedItself: false,
116
123
  /**
117
- * In-repo units (components, bare handlers) are keyless by design
118
- * and pair through call edges rather than the keyed pass, so a
119
- * missing key is not a boundary nothing can reach.
124
+ * In-repo units such as components have no key on purpose. They
125
+ * pair through call edges instead of the keyed pass, so a missing
126
+ * key does not make them unreachable.
120
127
  */
121
128
  canPair: () => true,
122
- /**
123
- * `"fn:<package>::<exportPath>"` when both are set, and
124
- * `"fn:<module>::<exportName>"` for an in-repo unit that states its
125
- * module, the way a server action does. Components and bare
126
- * handlers state neither and have no key at all.
127
- */
128
129
  rewritePaths(semantics, rewrite) {
129
- if (semantics.module === void 0) {
130
+ if (semantics.module === void 0 || isModuleName(semantics.module)) {
130
131
  return semantics;
131
132
  }
132
133
  return { ...semantics, module: rewrite(semantics.module) };
133
134
  },
135
+ /**
136
+ * `"fn:<package>::<exportPath>"` when both are set, and
137
+ * `"fn:<module>::<exportName>"` for an in-repo unit that gives its
138
+ * module, as a server action does. Components and plain handlers
139
+ * give neither and have no key.
140
+ */
134
141
  identityKey(semantics) {
135
142
  if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
136
143
  return fnIdentityKey(semantics.package, semantics.exportPath);
@@ -161,15 +168,15 @@ var graphqlOperationSemantics = defineBoundarySemantics({
161
168
  behavior: {
162
169
  /**
163
170
  * An operation gets a data-and-errors document back, which the
164
- * GraphQL contract checker reads. Status codes say nothing here.
171
+ * GraphQL contract checker reads. Status codes do not apply.
165
172
  */
166
173
  exchangesHttpResponses: false,
167
174
  leavesTheProcess: true,
168
175
  reportsUnpairedItself: false,
169
176
  identityKey: () => null,
170
- /** Keyless by design; the dedicated pass pairs it by its document. */
177
+ /** No key on purpose. The dedicated pass pairs an operation by its document. */
171
178
  canPair: () => true,
172
- /** "query GetUser". The contract checker pairs it, the key does not. */
179
+ /** `query GetUser`. Only for display, since the contract checker does the pairing. */
173
180
  displayLabel(semantics) {
174
181
  return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
175
182
  }
@@ -192,14 +199,14 @@ var GraphqlResolverSemanticsSchema = import_zod3.z.object({
192
199
  var graphqlResolverSemantics = defineBoundarySemantics({
193
200
  name: "graphql-resolver",
194
201
  schema: GraphqlResolverSemanticsSchema,
195
- // The conventions name the operation a client sent, not the
196
- // resolver the server ran for one field of it.
202
+ // The conventions describe the operation a client sent. They have no
203
+ // attribute for the resolver the server ran for one field of it.
197
204
  semconv: {},
198
205
  behavior: {
199
- /** A resolver returns a field value, not a status and a body. */
206
+ /** A resolver returns a field value, with no status. */
200
207
  exchangesHttpResponses: false,
201
- // A query somebody sent from outside runs it, even though the
202
- // server calls the function in its own process.
208
+ // A resolver runs because a client outside the process sent a query,
209
+ // even though the server calls it in its own process.
203
210
  leavesTheProcess: true,
204
211
  reportsUnpairedItself: false,
205
212
  /**
@@ -216,8 +223,32 @@ var graphqlResolverSemantics = defineBoundarySemantics({
216
223
  }
217
224
  });
218
225
 
219
- // src/semantics/messageBus.ts
226
+ // src/semantics/io.ts
220
227
  var import_zod4 = require("zod");
228
+ var IoSemanticsSchema = import_zod4.z.object({
229
+ name: import_zod4.z.literal("io"),
230
+ /** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
231
+ target: import_zod4.z.string().nullable()
232
+ });
233
+ var ioSemantics = defineBoundarySemantics({
234
+ name: "io",
235
+ schema: IoSemanticsSchema,
236
+ // The semantic conventions have no attribute for a process's own streams.
237
+ semconv: {},
238
+ behavior: {
239
+ exchangesHttpResponses: false,
240
+ leavesTheProcess: true,
241
+ reportsUnpairedItself: false,
242
+ identityKey(semantics) {
243
+ return semantics.target === null ? null : ioIdentityKey(semantics.target);
244
+ },
245
+ // Nothing declares the reading side, so a write has nothing to meet.
246
+ canPair: () => false
247
+ }
248
+ });
249
+
250
+ // src/semantics/messageBus.ts
251
+ var import_zod5 = require("zod");
221
252
 
222
253
  // src/channel.ts
223
254
  function parseChannel(channel) {
@@ -237,12 +268,11 @@ function channelsPair(a, b) {
237
268
  }
238
269
 
239
270
  // src/semantics/messageBus.ts
240
- var MessageBusSemanticsSchema = import_zod4.z.object({
241
- name: import_zod4.z.literal("message-bus"),
242
- messageBus: import_zod4.z.enum([
243
- // The conventions spell the two AWS buses this way, one with an
244
- // underscore and one with a dot, so these are their strings and
245
- // not a typo.
271
+ var MessageBusSemanticsSchema = import_zod5.z.object({
272
+ name: import_zod5.z.literal("message-bus"),
273
+ messageBus: import_zod5.z.enum([
274
+ // The conventions write these two AWS buses differently, one with an
275
+ // underscore and one with a dot. Both are as the conventions give them.
246
276
  "aws_sqs",
247
277
  "aws.sns",
248
278
  "s3",
@@ -261,12 +291,13 @@ var MessageBusSemanticsSchema = import_zod4.z.object({
261
291
  "cloudflare-tail"
262
292
  ]),
263
293
  /**
264
- * Stable channel identifier: a CFN logical id, a queue or topic name,
265
- * a subject pattern, or `bus#detailType`. Null when this source does
266
- * not say which channel, as with a send whose queue the code picks at
267
- * runtime, or a receive whose queue the event-source mapping states.
294
+ * A stable channel identifier: a CloudFormation logical id, a queue or
295
+ * topic name, a subject pattern, or `bus#detailType`. Null when this
296
+ * source does not say which channel, as with a send whose queue the
297
+ * code picks at run time, or a receive whose queue is set in the
298
+ * event-source mapping.
268
299
  */
269
- channel: import_zod4.z.string().min(1).nullable()
300
+ channel: import_zod5.z.string().min(1).nullable()
270
301
  });
271
302
  var messageBusSemantics = defineBoundarySemantics({
272
303
  name: "message-bus",
@@ -280,25 +311,26 @@ var messageBusSemantics = defineBoundarySemantics({
280
311
  exchangesHttpResponses: false,
281
312
  leavesTheProcess: true,
282
313
  /**
283
- * `checkMessageBus` looks at every channel, including the unused
284
- * ones, so the generic unmatched lists leave them alone.
314
+ * `checkMessageBus` reports every channel, unused ones included, so
315
+ * the generic unmatched list leaves them out.
285
316
  */
286
317
  reportsUnpairedItself: true,
287
318
  /**
288
- * A channel unnamed here can still pair: the dedicated pass
289
- * collapses env chains before comparing, so a consumer whose
290
- * channel arrives at runtime meets the template that declares it.
319
+ * A channel that is null here can still pair. The dedicated pass
320
+ * follows environment variable chains before comparing, so a
321
+ * consumer whose channel is set at run time meets the template that
322
+ * declares it.
291
323
  */
292
324
  canPair: () => true,
293
325
  /**
294
326
  * `"bus:<messageBus> <subject>"`, or null when the channel is null
295
327
  * or its subject is empty.
296
328
  *
297
- * The key has the subject in it and leaves out the bus, so a
298
- * template writing `default#order.placed` and a handler writing
329
+ * The key has the subject and leaves out the bus, so a template
330
+ * writing `default#order.placed` and a handler writing
299
331
  * `order.placed` land in one bucket, where `sidesAgree` compares the
300
- * buses. A side that cannot know its bus would otherwise be keyed
301
- * away from the side that can.
332
+ * buses. Keying on the bus would separate a side that cannot know
333
+ * its bus from the side that can.
302
334
  *
303
335
  * The bus technology stays in, because a queue and an event router
304
336
  * are different destinations even with the same subject. Case stays
@@ -321,9 +353,9 @@ var messageBusSemantics = defineBoundarySemantics({
321
353
  return channelsPair(a.channel, b.channel);
322
354
  },
323
355
  /**
324
- * The whole channel, bus included, unlike the identity key, which
325
- * has the subject alone. Someone reading a list of unmatched
326
- * channels wants to see which bus each one was on.
356
+ * The whole channel, bus included, where the identity key has only
357
+ * the subject. A reader looking at unmatched channels needs to see
358
+ * which bus each one was on.
327
359
  */
328
360
  displayLabel(semantics) {
329
361
  if (semantics.channel === null) {
@@ -335,25 +367,25 @@ var messageBusSemantics = defineBoundarySemantics({
335
367
  });
336
368
 
337
369
  // src/semantics/metric.ts
338
- var import_zod5 = require("zod");
339
- var MetricSemanticsSchema = import_zod5.z.object({
340
- name: import_zod5.z.literal("metric"),
341
- /** Which system the series lives in: cloud-monitoring, cloudwatch. */
342
- metricSystem: import_zod5.z.string(),
370
+ var import_zod6 = require("zod");
371
+ var MetricSemanticsSchema = import_zod6.z.object({
372
+ name: import_zod6.z.literal("metric"),
373
+ /** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
374
+ metricSystem: import_zod6.z.string(),
343
375
  /**
344
- * The type string the system knows the metric by, spelled the way
345
- * both sides spell it. Null when the source states one this reader
346
- * could not settle, as with a query it could not read: a metric
347
- * nobody could name pairs with nothing rather than with whatever
348
- * happens to share its source text.
376
+ * The type string the system uses for the metric, written the same
377
+ * way on both sides. Null when the reader could not work the string
378
+ * out, as with a query it could not parse. A null type pairs with
379
+ * nothing, so it cannot pair with an unrelated metric that happens to
380
+ * share its source text.
349
381
  */
350
- metricType: import_zod5.z.string().nullable()
382
+ metricType: import_zod6.z.string().nullable()
351
383
  });
352
384
  var metricSemantics = defineBoundarySemantics({
353
385
  name: "metric",
354
386
  schema: MetricSemanticsSchema,
355
- // OpenTelemetry knows a metric by its instrument name, and has no
356
- // attribute for the system it lives in or the type string it goes by.
387
+ // OpenTelemetry identifies a metric by its instrument name. It has no
388
+ // attribute for the metric's system or its type string.
357
389
  semconv: {},
358
390
  behavior: {
359
391
  exchangesHttpResponses: false,
@@ -369,7 +401,7 @@ var metricSemantics = defineBoundarySemantics({
369
401
  });
370
402
 
371
403
  // src/semantics/rest.ts
372
- var import_zod6 = require("zod");
404
+ var import_zod7 = require("zod");
373
405
 
374
406
  // src/boundaryName.ts
375
407
  var HOLE_SPLIT = /(\{[^}]*\})/;
@@ -417,6 +449,38 @@ function pathAfterOrigin(text) {
417
449
  const cut = withoutOrigin.search(/[?#]/);
418
450
  return cut === -1 ? withoutOrigin : withoutOrigin.slice(0, cut);
419
451
  }
452
+ var PRIVATE_SUFFIXES = [
453
+ "local",
454
+ "internal",
455
+ "localhost",
456
+ "lan",
457
+ "home.arpa",
458
+ "svc",
459
+ "test",
460
+ "example",
461
+ "invalid",
462
+ "example.com",
463
+ "example.net",
464
+ "example.org"
465
+ ];
466
+ var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
467
+ function hostnameOf(host) {
468
+ const lower = host.toLowerCase();
469
+ const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
470
+ return name.replace(/\.$/, "");
471
+ }
472
+ function hostCanBeOwn(host) {
473
+ const name = hostnameOf(host);
474
+ if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
475
+ return true;
476
+ }
477
+ if (!name.includes(".")) {
478
+ return true;
479
+ }
480
+ return PRIVATE_SUFFIXES.some(
481
+ (suffix) => name === suffix || name.endsWith(`.${suffix}`)
482
+ );
483
+ }
420
484
 
421
485
  // src/semantics/pathPattern.ts
422
486
  var ONE = { kind: "one" };
@@ -467,14 +531,18 @@ function shapedItem(segment) {
467
531
  return ".*";
468
532
  }
469
533
  if (part.startsWith("{")) {
470
- return "[^/]+";
534
+ return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
471
535
  }
472
536
  return escapedForRegex(part);
473
537
  }).join("");
538
+ const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
539
+ const text = segment.replace(/\{[^}]*\}|\*/g, "");
474
540
  return {
475
541
  kind: "shaped",
476
542
  shape: segment.replace(/\{[^}]*\}/g, "{}"),
477
- test: new RegExp(`^${source}$`)
543
+ test: new RegExp(`^${source}$`),
544
+ bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
545
+ textLength: text.length
478
546
  };
479
547
  }
480
548
  function itemsOf(segment) {
@@ -494,10 +562,35 @@ function segmentsOf(alternative) {
494
562
  const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
495
563
  return trimmed === "" ? [] : trimmed.split("/");
496
564
  }
565
+ function patternSegmentsOf(alternative) {
566
+ const segments = segmentsOf(alternative);
567
+ const first = segments[0];
568
+ const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
569
+ return opensWithBaseUrl ? segments.slice(1) : segments;
570
+ }
571
+ function pathAfterBaseUrl(path) {
572
+ const segments = segmentsOf(path);
573
+ const first = segments[0];
574
+ if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
575
+ return path;
576
+ }
577
+ return `/${segments.slice(1).join("/")}`;
578
+ }
579
+ var PATTERNS = /* @__PURE__ */ new Map();
580
+ var PATTERN_CACHE_LIMIT = 5e4;
497
581
  function patternOf(path) {
498
- return alternativesOf(path).map(
499
- (alternative) => segmentsOf(alternative).flatMap(itemsOf)
582
+ const known = PATTERNS.get(path);
583
+ if (known !== void 0) {
584
+ return known;
585
+ }
586
+ const pattern = alternativesOf(path).map(
587
+ (alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
500
588
  );
589
+ if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
590
+ PATTERNS.clear();
591
+ }
592
+ PATTERNS.set(path, pattern);
593
+ return pattern;
501
594
  }
502
595
  function literalItemsOf(path) {
503
596
  return segmentsOf(path).map((text) => ({ kind: "text", text }));
@@ -576,17 +669,104 @@ function pathSpansShapes(path) {
576
669
  function countOf(items, kinds) {
577
670
  return items.filter((item) => kinds.includes(item.kind)).length;
578
671
  }
579
- function pathSpecificity(path) {
580
- const alternatives = patternOf(path);
581
- const ranks = alternatives.map((items) => [
672
+ function readingSpecificity(items) {
673
+ return [
582
674
  countOf(items, ["text"]),
583
675
  countOf(items, ["shaped"]),
584
676
  -countOf(items, ["optional", "star"])
585
- ]);
586
- const loosest = ranks.reduce(
587
- (low, rank) => compareRanks(rank, low) < 0 ? rank : low
677
+ ];
678
+ }
679
+ function segmentFit(route, request) {
680
+ if (!meetOnOneSegment(route, request)) {
681
+ return "apart";
682
+ }
683
+ if (request.kind === "text") {
684
+ if (route.kind === "text") {
685
+ return "stated";
686
+ }
687
+ return route.kind === "shaped" ? "shaped" : "free";
688
+ }
689
+ if (absorbsAnything(route)) {
690
+ return "free";
691
+ }
692
+ if (request.kind !== "shaped") {
693
+ return "guess";
694
+ }
695
+ if (route.kind === "shaped") {
696
+ return "free";
697
+ }
698
+ return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
699
+ }
700
+ function shapedText(item) {
701
+ return item.kind === "shaped" ? item.textLength : 0;
702
+ }
703
+ function betterFit(a, b) {
704
+ return a === void 0 || compareRanks(b, a) > 0;
705
+ }
706
+ function bestFit(route, request) {
707
+ const width = request.length + 1;
708
+ const best = new Array((route.length + 1) * width);
709
+ best[0] = [0, 0];
710
+ const offer = (i, j, fit) => {
711
+ const at = i * width + j;
712
+ if (betterFit(best[at], fit)) {
713
+ best[at] = fit;
714
+ }
715
+ };
716
+ for (let reached = 0; reached <= route.length + request.length; reached += 1) {
717
+ for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
718
+ const j = reached - i;
719
+ const fit = best[i * width + j];
720
+ if (fit === void 0) {
721
+ continue;
722
+ }
723
+ const left = route[i];
724
+ const right = request[j];
725
+ if (left !== void 0 && skippable(left)) {
726
+ offer(i + 1, j, fit);
727
+ }
728
+ if (right !== void 0 && skippable(right)) {
729
+ offer(i, j + 1, fit);
730
+ }
731
+ if (left === void 0 || right === void 0) {
732
+ continue;
733
+ }
734
+ if (left.kind === "star" && right.kind === "star") {
735
+ continue;
736
+ }
737
+ const kind = segmentFit(left, right);
738
+ if (kind === "apart" || kind === "guess") {
739
+ continue;
740
+ }
741
+ offer(
742
+ left.kind === "star" ? i : i + 1,
743
+ right.kind === "star" ? j : j + 1,
744
+ [
745
+ fit[0] + (kind === "stated" ? 1 : 0),
746
+ fit[1] + (kind === "shaped" ? shapedText(left) : 0)
747
+ ]
748
+ );
749
+ }
750
+ }
751
+ return best[route.length * width + request.length] ?? null;
752
+ }
753
+ function requestRank(route, request) {
754
+ let found = null;
755
+ for (const routeItems of patternOf(route)) {
756
+ for (const requestItems of patternOf(request)) {
757
+ const fit = bestFit(routeItems, requestItems);
758
+ const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
759
+ if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
760
+ found = rank;
761
+ }
762
+ }
763
+ }
764
+ return found;
765
+ }
766
+ function isCatchAll(route) {
767
+ return patternOf(route).some(
768
+ (items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
588
769
  );
589
- return [...loosest, 1 - alternatives.length];
590
770
  }
591
771
  function compareRanks(a, b) {
592
772
  const length = Math.max(a.length, b.length);
@@ -600,23 +780,34 @@ function compareRanks(a, b) {
600
780
  }
601
781
 
602
782
  // src/semantics/rest.ts
603
- var RestSemanticsSchema = import_zod6.z.object({
604
- name: import_zod6.z.literal("rest"),
783
+ var RestSemanticsSchema = import_zod7.z.object({
784
+ name: import_zod7.z.literal("rest"),
605
785
  /**
606
786
  * Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
607
787
  * responds to every method, or null when this source does not say
608
788
  * which method.
609
789
  */
610
- method: import_zod6.z.string().min(1).nullable(),
790
+ method: import_zod7.z.string().min(1).nullable(),
611
791
  /** Normalized route path ("/users/{id}"), or null when this source does not give one. */
612
- path: import_zod6.z.string().min(1).nullable(),
792
+ path: import_zod7.z.string().min(1).nullable(),
613
793
  /**
614
- * Status codes the producing source explicitly declared (OpenAPI
615
- * responses, CFN MethodResponses, ts-rest router statuses). They are
616
- * kept here so the pairing layer can see them without unwrapping any
617
- * metadata. Absent, or empty, for a source that was inferred.
794
+ * Status codes the producing source declared explicitly, such as
795
+ * OpenAPI responses or CloudFormation MethodResponses. They are kept
796
+ * here so the pairing layer can see them without unwrapping any
797
+ * metadata. Absent or empty for an inferred source.
618
798
  */
619
- declaredResponses: import_zod6.z.array(import_zod6.z.number()).optional()
799
+ declaredResponses: import_zod7.z.array(import_zod7.z.number()).optional(),
800
+ /**
801
+ * The host a client wrote in an absolute URL, with its port
802
+ * (`api.example.com:8443`). A piece suss could not read is written
803
+ * `{name}`. Absent for a relative URL and for a route.
804
+ */
805
+ host: import_zod7.z.string().min(1).optional(),
806
+ /**
807
+ * True for a route the framework also serves with `.{format}` after
808
+ * its path, the way Rails serves `/search.json` from `get "search"`.
809
+ */
810
+ optionalFormat: import_zod7.z.boolean().optional()
620
811
  });
621
812
  var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
622
813
  function baseUrlReference(semantics) {
@@ -624,7 +815,10 @@ function baseUrlReference(semantics) {
624
815
  return label === void 0 ? null : referenceFromName(patternHole(label));
625
816
  }
626
817
  function normalizePath(path) {
627
- let normalized = path.replace(/:([a-zA-Z_]\w*)([?+*]?)/g, "{$1$2}");
818
+ let normalized = withoutParameterConstraints(path).replace(
819
+ /:([a-zA-Z_]\w*)([?+*]?)/g,
820
+ "{$1$2}"
821
+ );
628
822
  if (normalized.length > 1 && normalized.endsWith("/")) {
629
823
  normalized = normalized.slice(0, -1);
630
824
  }
@@ -634,6 +828,46 @@ function normalizePath(path) {
634
828
  );
635
829
  return normalized;
636
830
  }
831
+ function withoutParameterConstraints(path) {
832
+ let out = "";
833
+ let i = 0;
834
+ while (i < path.length) {
835
+ const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
836
+ if (name === null) {
837
+ out += path[i];
838
+ i += 1;
839
+ continue;
840
+ }
841
+ const open = i + name[0].length - 1;
842
+ const close = closingParenthesis(path, open);
843
+ if (close === -1) {
844
+ return out + path.slice(i);
845
+ }
846
+ out += path.slice(i, open);
847
+ i = close + 1;
848
+ }
849
+ return out;
850
+ }
851
+ function closingParenthesis(text, open) {
852
+ let depth = 0;
853
+ for (let i = open; i < text.length; i += 1) {
854
+ const char = text[i];
855
+ if (char === "\\") {
856
+ i += 1;
857
+ continue;
858
+ }
859
+ if (char === "(") {
860
+ depth += 1;
861
+ }
862
+ if (char === ")") {
863
+ depth -= 1;
864
+ if (depth === 0) {
865
+ return i;
866
+ }
867
+ }
868
+ }
869
+ return -1;
870
+ }
637
871
  function pathShape(path) {
638
872
  return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
639
873
  }
@@ -646,8 +880,39 @@ function methodsAgree(a, b) {
646
880
  function routePathAdmits(declaredPath, requestPath) {
647
881
  return patternAdmits(normalizePath(declaredPath), normalizePath(requestPath));
648
882
  }
649
- function routePathsMeet(a, b) {
650
- return pathsMeet(normalizePath(a), normalizePath(b));
883
+ function readHost(semantics) {
884
+ const host = semantics.host;
885
+ return host === void 0 || /[{(]/.test(host) ? void 0 : host;
886
+ }
887
+ function hostsAgree(a, b) {
888
+ return readHost(a) === readHost(b);
889
+ }
890
+ function hostUnread(semantics) {
891
+ if (semantics.host !== void 0) {
892
+ return readHost(semantics) === void 0;
893
+ }
894
+ return baseUrlReference(semantics) !== null;
895
+ }
896
+ function servedPath(semantics, path) {
897
+ const normalized = normalizePath(path);
898
+ return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
899
+ }
900
+ function withoutHost(semantics) {
901
+ const { host: _host, ...rest } = semantics;
902
+ return rest;
903
+ }
904
+ function groundedBaseUrl(semantics, deployment) {
905
+ const reference = baseUrlReference(semantics);
906
+ if (reference === null) {
907
+ return null;
908
+ }
909
+ const base = deployment.setTo(reference);
910
+ if (base === null) {
911
+ return null;
912
+ }
913
+ const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
914
+ const grounded = pathAfterOrigin(`${base}${rest}`);
915
+ return { ...semantics, path: grounded === "" ? "/" : grounded };
651
916
  }
652
917
  var restSemantics = defineBoundarySemantics({
653
918
  name: "rest",
@@ -673,39 +938,65 @@ var restSemantics = defineBoundarySemantics({
673
938
  return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
674
939
  },
675
940
  /**
676
- * The bucket has only the path in it, so a `"*"` route lands with
677
- * the consumers that each specify a method, and `methodsAgree`
678
- * settles it inside the bucket. The identity key stays what a reader
679
- * sees and a suppression targets ("GET /users", "* /users"), and
680
- * this key groups what a reader still tells apart.
941
+ * The bucket has only the path, so a `"*"` route lands with the
942
+ * consumers that each give a method, and `methodsAgree` compares the
943
+ * methods inside the bucket. The identity key is still what a reader
944
+ * sees and what a suppression targets ("GET /users", "* /users").
681
945
  *
682
- * A parameter's name goes too. A spec written by hand says
683
- * `{userId}` where the Express route that serves it says `:id`, and
684
- * both match the same requests, so pairing on the name gave two
685
- * boundaries that never met and a run with nothing to report.
946
+ * Parameter names are dropped too. A hand-written spec writes
947
+ * `{userId}` where the Express route that serves it writes `:id`,
948
+ * and both match the same requests. Pairing on the name would leave
949
+ * two boundaries that never meet.
686
950
  */
687
951
  pairingKey(semantics) {
688
952
  if (semantics.method === null || semantics.path === null) {
689
953
  return null;
690
954
  }
691
- return `rest ${pathShape(semantics.path)}`;
955
+ const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
956
+ const host = readHost(semantics);
957
+ return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
958
+ },
959
+ /**
960
+ * A spec writes `/teams/{team_id}` where the route that serves it
961
+ * writes `/teams/:id`. The method stays in, so a `"*"` handler is
962
+ * never compared with the document for one method.
963
+ */
964
+ operationKey(semantics) {
965
+ if (semantics.method === null || semantics.path === null) {
966
+ return null;
967
+ }
968
+ return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
692
969
  },
693
970
  sidesAgree(a, b) {
694
- return methodsAgree(a.method, b.method);
971
+ return methodsAgree(a.method, b.method) && hostsAgree(a, b);
695
972
  },
696
973
  spansBuckets(semantics) {
697
- return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
974
+ return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
698
975
  },
699
976
  bucketsMeet(a, b) {
700
- return a.path !== null && b.path !== null && routePathsMeet(a.path, b.path);
977
+ return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
701
978
  },
702
- bucketRank(semantics) {
703
- return semantics.path === null ? [] : pathSpecificity(normalizePath(semantics.path));
979
+ /**
980
+ * A catch-all route never takes a call whose host suss could not
981
+ * read, since nothing else ties that call to this app.
982
+ */
983
+ pairRank(provider, consumer) {
984
+ if (provider.path === null || consumer.path === null) {
985
+ return [];
986
+ }
987
+ const route = normalizePath(provider.path);
988
+ if (hostUnread(consumer) && isCatchAll(route)) {
989
+ return null;
990
+ }
991
+ return requestRank(
992
+ servedPath(provider, provider.path),
993
+ normalizePath(consumer.path)
994
+ );
704
995
  },
705
996
  /**
706
- * What the identity key says, with a missing half still readable:
707
- * ANY when no method was stated, and ? when no path was. Null when
708
- * neither was stated, because then there is nothing to show.
997
+ * The identity key, with a missing half still readable: `ANY` when
998
+ * the source gave no method, and `?` when it gave no path. Null when
999
+ * both are missing, since then there is nothing to show.
709
1000
  */
710
1001
  displayLabel(semantics) {
711
1002
  if (semantics.method === null && semantics.path === null) {
@@ -716,11 +1007,11 @@ var restSemantics = defineBoundarySemantics({
716
1007
  return `${method} ${path}`;
717
1008
  },
718
1009
  /**
719
- * A route with both halves stated decides for itself: the method
720
- * through `methodsAgree` (so a `"*"` route matches every method),
721
- * and the path through `routePathAdmits`. A route missing either
722
- * half might still be the one that handles the request, and nothing
723
- * here can settle that, so it abstains instead of refusing.
1010
+ * With both halves present, the method goes through `methodsAgree`
1011
+ * (so a `"*"` route matches every method) and the path through
1012
+ * `routePathAdmits`. A route missing either half might still handle
1013
+ * the request, and this declaration cannot settle that, so the
1014
+ * result is `"unknown"`.
724
1015
  */
725
1016
  servesRequest(semantics, method, path) {
726
1017
  if (semantics.method === null || semantics.path === null) {
@@ -733,43 +1024,41 @@ var restSemantics = defineBoundarySemantics({
733
1024
  },
734
1025
  /**
735
1026
  * A route the registration pattern admits is inside it. A route
736
- * whose path this source never gave is left outside, because
737
- * nothing here can show it is one the pattern covers.
1027
+ * without a path counts as outside, because nothing shows that the
1028
+ * pattern covers it.
738
1029
  */
739
1030
  withinScope(semantics, scope) {
740
1031
  return semantics.path !== null && routePathAdmits(scope, semantics.path);
741
1032
  },
742
1033
  /**
743
1034
  * A call whose base URL the deployment fills in, resolved to the
744
- * path it reaches.
1035
+ * path it reaches, and a call to a host that can be the project's
1036
+ * own, with the host left out so it meets the route.
745
1037
  *
746
- * The source cannot settle this on its own, which is why the
747
- * adapter leaves the hole in. `API_BASE` could be
748
- * `http://backend.internal`, and then the path is `/orders`. It
749
- * could equally be `/api/v2`, and then the path is
750
- * `/api/v2/orders`. Putting the deployed value in and reading the
751
- * path back out gets the right one either way.
752
- *
753
- * Only a hole at the front is a base URL. A hole further along is a
754
- * route parameter, and `/orders/{id}` means every id rather than a
755
- * variable somebody set.
1038
+ * The source cannot settle a base URL on its own, so the adapter
1039
+ * leaves the hole in. `API_BASE` could be `http://backend.internal`,
1040
+ * and then the path is `/orders`. It could equally be `/api/v2`, and
1041
+ * then the path is `/api/v2/orders`. Filling in the deployed value
1042
+ * and reading the path back out gets the right one either way, and
1043
+ * a base the deployment sets counts as the app's own.
756
1044
  */
757
- groundName(semantics, deployment) {
758
- const reference = baseUrlReference(semantics);
759
- if (reference === null) {
760
- return null;
761
- }
762
- const base = deployment.setTo(reference);
763
- if (base === null) {
1045
+ /** A listed host matches with or without the port the call gives. */
1046
+ withoutOwnHost(semantics, ownHosts) {
1047
+ const host = semantics.host?.toLowerCase();
1048
+ if (host === void 0) {
764
1049
  return null;
765
1050
  }
766
- const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
767
- const grounded = pathAfterOrigin(`${base}${rest}`);
768
- return { ...semantics, path: grounded === "" ? "/" : grounded };
1051
+ return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
1052
+ },
1053
+ groundName(semantics, deployment) {
1054
+ const based = groundedBaseUrl(semantics, deployment);
1055
+ const grounded = based ?? semantics;
1056
+ const host = readHost(grounded);
1057
+ return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
769
1058
  },
770
1059
  nameReference: baseUrlReference,
771
1060
  ruleBoundary: {
772
- // "METHOD /path": one leading token, then an absolute path.
1061
+ // "METHOD /path": one token, a space, then an absolute path.
773
1062
  claims(raw) {
774
1063
  return /^\S+ +\//.test(raw.trim());
775
1064
  },
@@ -784,12 +1073,12 @@ var restSemantics = defineBoundarySemantics({
784
1073
  });
785
1074
 
786
1075
  // src/semantics/runtimeConfig.ts
787
- var import_zod8 = require("zod");
1076
+ var import_zod9 = require("zod");
788
1077
 
789
1078
  // src/deployableUnit.ts
790
- var import_zod7 = require("zod");
791
- var DeployableUnitSchema = import_zod7.z.object({
792
- deploymentTarget: import_zod7.z.enum([
1079
+ var import_zod8 = require("zod");
1080
+ var DeployableUnitSchema = import_zod8.z.object({
1081
+ deploymentTarget: import_zod8.z.enum([
793
1082
  "lambda",
794
1083
  "ecs-task",
795
1084
  "container",
@@ -798,7 +1087,7 @@ var DeployableUnitSchema = import_zod7.z.object({
798
1087
  ]),
799
1088
  // An empty name would agree with every other empty name, so a unit
800
1089
  // with no name has to leave the field off instead.
801
- instanceName: import_zod7.z.string().min(1)
1090
+ instanceName: import_zod8.z.string().min(1)
802
1091
  });
803
1092
 
804
1093
  // src/semantics/runtimeConfig.ts
@@ -806,14 +1095,14 @@ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
806
1095
  deploymentTarget: true,
807
1096
  instanceName: true
808
1097
  }).extend({
809
- name: import_zod8.z.literal("runtime-config")
1098
+ name: import_zod9.z.literal("runtime-config")
810
1099
  });
811
1100
  var runtimeConfigSemantics = defineBoundarySemantics({
812
1101
  name: "runtime-config",
813
1102
  schema: RuntimeConfigSemanticsSchema,
814
- // service.name and cloud.resource_id name the same deployable, but
815
- // instanceName is the deployment template's logical id, which is
816
- // neither of those strings.
1103
+ // service.name and cloud.resource_id identify the same deployable, but
1104
+ // instanceName is the template's logical id, a different string from
1105
+ // both.
817
1106
  semconv: {},
818
1107
  behavior: {
819
1108
  /** A process reads its config at startup, and nothing replies. */
@@ -822,46 +1111,53 @@ var runtimeConfigSemantics = defineBoundarySemantics({
822
1111
  // comes from outside the code.
823
1112
  leavesTheProcess: true,
824
1113
  reportsUnpairedItself: false,
825
- identityKey: () => null
1114
+ identityKey: () => null,
1115
+ // A reader sees which deployable's environment this is. Code that
1116
+ // reads a variable does not say which deployable runs it, so a read
1117
+ // has no name.
1118
+ displayLabel(semantics) {
1119
+ const instance = semantics.instanceName;
1120
+ return instance === void 0 ? null : `runtime-config:${instance}`;
1121
+ }
826
1122
  }
827
1123
  });
828
1124
 
829
1125
  // src/semantics/storage.ts
830
- var import_zod9 = require("zod");
831
- var StorageSemanticsSchema = import_zod9.z.object({
832
- name: import_zod9.z.literal("storage"),
1126
+ var import_zod10 = require("zod");
1127
+ var StorageSemanticsSchema = import_zod10.z.object({
1128
+ name: import_zod10.z.literal("storage"),
833
1129
  /**
834
- * Which store this is: postgresql, mysql, aws.dynamodb, or our own
835
- * word for one OpenTelemetry never named, such as s3, or null when
836
- * the source states one this reader could not settle. Two products'
837
- * containers can share a name, so this keeps them apart. A null
838
- * keeps none of them apart, and the pairing pass says what it does
839
- * with one.
1130
+ * The storage system: an OpenTelemetry value such as `postgresql` or
1131
+ * `aws.dynamodb`, or suss's own word for one the conventions do not
1132
+ * cover, such as `s3`. Null when the reader could not work out which
1133
+ * system the source uses. Two products' containers can share a name,
1134
+ * and this field keeps them apart. The pairing pass decides how to
1135
+ * treat a null.
840
1136
  */
841
- storageSystem: import_zod9.z.string().nullable(),
1137
+ storageSystem: import_zod10.z.string().nullable(),
842
1138
  /**
843
1139
  * The ORM, schema, or deployment scope. A single-database setup uses
844
1140
  * `"default"`, and a monorepo with several schemas gives each one its
845
1141
  * own value.
846
1142
  */
847
- scope: import_zod9.z.string(),
1143
+ scope: import_zod10.z.string(),
848
1144
  /**
849
- * The table, bucket, collection, or index, as the source declares
850
- * it, or null when the source states one this reader could not
851
- * settle. A null container pairs with nothing, rather than with
852
- * whatever its source text happens to spell. The string is in the
853
- * boundary-name syntax: a literal, a pattern with `{}` holes, or a
854
- * reference, and `parseBoundaryName` is the one reader of it.
1145
+ * The table, bucket, collection or index, as the source declares it.
1146
+ * Null when the reader could not work out the container, and a null
1147
+ * container pairs with nothing, so it cannot match by accident of
1148
+ * shared source text. The string uses the boundary-name syntax (a
1149
+ * literal, a pattern with `{}` holes, or a reference), and only
1150
+ * `parseBoundaryName` should read it.
855
1151
  */
856
- container: import_zod9.z.string().nullable(),
1152
+ container: import_zod10.z.string().nullable(),
857
1153
  /**
858
- * A secondary way into the container, a DynamoDB global secondary
859
- * index or an Elasticsearch alias, each with its own key fields.
860
- * Null means the container's own primary way in. A query through an
1154
+ * A secondary way into the container, such as a DynamoDB global
1155
+ * secondary index or an Elasticsearch alias, each with its own key
1156
+ * fields. Null means the container's primary key. A query through an
861
1157
  * index and a query through the table are different accesses, so
862
1158
  * they pair separately.
863
1159
  */
864
- accessPath: import_zod9.z.string().nullable()
1160
+ accessPath: import_zod10.z.string().nullable()
865
1161
  });
866
1162
  function containerReference(semantics) {
867
1163
  return semantics.container === null ? null : referenceFromName(semantics.container);
@@ -871,21 +1167,21 @@ var storageSemantics = defineBoundarySemantics({
871
1167
  schema: StorageSemanticsSchema,
872
1168
  semconv: {
873
1169
  storageSystem: { name: "db.system.name" },
874
- // "default" is our word for a source that named no database.
1170
+ // suss writes "default" when the source did not say which database.
875
1171
  scope: { name: "db.namespace", placeholderValues: ["default"] },
876
1172
  container: { name: "db.collection.name" }
877
- // A secondary index has no attribute of its own, so accessPath
878
- // stays our word.
1173
+ // The conventions have no attribute for a secondary index, so
1174
+ // accessPath is left out.
879
1175
  },
880
1176
  behavior: {
881
- /** A query returns rows or items, not a status and a body. */
1177
+ /** A query returns rows or items, with no status. */
882
1178
  exchangesHttpResponses: false,
883
1179
  leavesTheProcess: true,
884
1180
  reportsUnpairedItself: false,
885
1181
  identityKey: () => null,
886
1182
  displayLabel: storageLabel,
887
1183
  /**
888
- * A container the source states as a variable is whatever the
1184
+ * A container the source gives as a variable is whatever the
889
1185
  * deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
890
1186
  * code and `prod-subscribers-v1` in the manifest are one table.
891
1187
  */
@@ -914,9 +1210,9 @@ function storageContainerLabel(semantics) {
914
1210
  }
915
1211
 
916
1212
  // src/semantics/unitInvocation.ts
917
- var import_zod10 = require("zod");
1213
+ var import_zod11 = require("zod");
918
1214
  var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
919
- name: import_zod10.z.literal("unit-invocation"),
1215
+ name: import_zod11.z.literal("unit-invocation"),
920
1216
  instanceName: DeployableUnitSchema.shape.instanceName.nullable()
921
1217
  });
922
1218
  function calleeReference(semantics) {
@@ -946,9 +1242,9 @@ var unitInvocationSemantics = defineBoundarySemantics({
946
1242
  */
947
1243
  reportsUnpairedItself: false,
948
1244
  /**
949
- * A callee the source states only as a variable is not a name for
950
- * anything until the deployment fills it in, so it keys as nothing
951
- * rather than as a bucket that would agree with itself alone.
1245
+ * A callee the source gives only as a variable has no name until the
1246
+ * deployment fills it in. It gets no key, since a key built from the
1247
+ * variable would pair only with itself.
952
1248
  */
953
1249
  identityKey(semantics) {
954
1250
  if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
@@ -969,11 +1265,10 @@ var unitInvocationSemantics = defineBoundarySemantics({
969
1265
  );
970
1266
  },
971
1267
  /**
972
- * A call that reads its callee's name out of the environment
973
- * reaches whichever resource the template wires that variable to.
974
- * The logical id is the answer rather than the deployed name,
975
- * because the invoked unit's own summary is keyed by the logical
976
- * id and neither side ever writes the other's string.
1268
+ * A call that reads its callee's name from the environment reaches
1269
+ * whichever resource the template wires that variable to. The name
1270
+ * is grounded to the logical id and not the deployed name, because
1271
+ * the invoked unit's own summary is keyed by the logical id.
977
1272
  */
978
1273
  groundName(semantics, deployment) {
979
1274
  const reference = calleeReference(semantics);
@@ -988,7 +1283,7 @@ var unitInvocationSemantics = defineBoundarySemantics({
988
1283
  });
989
1284
 
990
1285
  // src/semantics/registry.ts
991
- var SemanticsSchema = import_zod11.z.discriminatedUnion("name", [
1286
+ var SemanticsSchema = import_zod12.z.discriminatedUnion("name", [
992
1287
  restSemantics.schema,
993
1288
  functionCallSemantics.schema,
994
1289
  graphqlResolverSemantics.schema,
@@ -997,7 +1292,8 @@ var SemanticsSchema = import_zod11.z.discriminatedUnion("name", [
997
1292
  storageSemantics.schema,
998
1293
  messageBusSemantics.schema,
999
1294
  metricSemantics.schema,
1000
- unitInvocationSemantics.schema
1295
+ unitInvocationSemantics.schema,
1296
+ ioSemantics.schema
1001
1297
  ]);
1002
1298
  var DEFINITIONS = [
1003
1299
  restSemantics,
@@ -1008,97 +1304,103 @@ var DEFINITIONS = [
1008
1304
  storageSemantics,
1009
1305
  messageBusSemantics,
1010
1306
  metricSemantics,
1011
- unitInvocationSemantics
1307
+ unitInvocationSemantics,
1308
+ ioSemantics
1012
1309
  ];
1013
1310
  var BY_NAME = new Map(
1014
1311
  DEFINITIONS.map((d) => [d.name, d])
1015
1312
  );
1016
1313
 
1017
1314
  // src/schemas.ts
1018
- var ConfidenceSourceSchema = import_zod12.z.enum([
1315
+ var ConfidenceSourceSchema = import_zod13.z.enum([
1019
1316
  "inferred_static",
1020
1317
  "inferred_ai",
1021
1318
  "declared",
1022
1319
  "derived"
1023
1320
  ]);
1024
- var ConfidenceLevelSchema = import_zod12.z.enum(["high", "medium", "low"]);
1025
- var CorroborationSchema = import_zod12.z.object({
1026
- outcome: import_zod12.z.enum(["observed", "refuted", "untested"]),
1321
+ var ConfidenceLevelSchema = import_zod13.z.enum(["high", "medium", "low"]);
1322
+ var CorroborationSchema = import_zod13.z.object({
1323
+ outcome: import_zod13.z.enum(["observed", "refuted", "untested"]),
1027
1324
  /** Executions that produced a verdict for this claim. */
1028
- runs: import_zod12.z.number(),
1325
+ runs: import_zod13.z.number(),
1029
1326
  /** Present when refuted: the input and observation that disagreed. */
1030
- counterexample: import_zod12.z.unknown().optional(),
1327
+ counterexample: import_zod13.z.unknown().optional(),
1031
1328
  /** Present when untested: why no verdict was reachable. */
1032
- reason: import_zod12.z.string().optional()
1329
+ reason: import_zod13.z.string().optional()
1033
1330
  });
1034
- var ConfidenceInfoSchema = import_zod12.z.object({
1331
+ var ConfidenceInfoSchema = import_zod13.z.object({
1035
1332
  source: ConfidenceSourceSchema,
1036
1333
  level: ConfidenceLevelSchema,
1037
1334
  corroboration: CorroborationSchema.optional()
1038
1335
  });
1039
- var SourceLocationSchema = import_zod12.z.object({
1040
- file: import_zod12.z.string(),
1336
+ var SourceLocationSchema = import_zod13.z.object({
1337
+ file: import_zod13.z.string(),
1041
1338
  /** Line numbers, for a person reading the summary or an editor link. */
1042
- range: import_zod12.z.object({ start: import_zod12.z.number(), end: import_zod12.z.number() }),
1339
+ range: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }),
1043
1340
  /**
1044
- * Character offsets of the unit in its file. Identity and joins
1045
- * measure with these, since two functions can share a line and never
1046
- * an offset range; the line `range` above is the presentation of the
1047
- * same place. Absent on summaries no source position backs, a
1048
- * contract artifact's say.
1341
+ * Character offsets of the unit in its file. Identity and joins use
1342
+ * these, because two functions can share a line but never an offset
1343
+ * range. `range` gives the same place in lines. Absent on a summary
1344
+ * with no source position, such as one read from a contract.
1049
1345
  */
1050
- span: import_zod12.z.object({ start: import_zod12.z.number(), end: import_zod12.z.number() }).optional(),
1051
- exportName: import_zod12.z.string().nullable(),
1346
+ span: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }).optional(),
1347
+ exportName: import_zod13.z.string().nullable(),
1052
1348
  /**
1053
- * The project the extract was pointed at, by the name that project
1054
- * calls itself.
1349
+ * The project the extract ran on, by the name the project gives
1350
+ * itself.
1055
1351
  *
1056
1352
  * Paths are relative to wherever the extract ran, so two services in
1057
- * one repository both report `src/handlers.ts`, and merging their
1058
- * summaries puts them on top of each other. This field is what tells
1059
- * them apart, and what a reader groups by.
1353
+ * one repository can both report `src/handlers.ts`, and merging their
1354
+ * summaries would mix them up. This field tells them apart, and a
1355
+ * reader groups by it.
1356
+ */
1357
+ workspace: import_zod13.z.string().optional(),
1358
+ /**
1359
+ * The module of the application the unit belongs to, by the name the
1360
+ * project's `suss.json` gives it. Set only when the project lists its
1361
+ * modules, and then on every unit whose file is under a module's root.
1060
1362
  */
1061
- workspace: import_zod12.z.string().optional()
1363
+ module: import_zod13.z.string().optional()
1062
1364
  });
1063
- var BoundaryBindingSchema = import_zod12.z.object({
1064
- transport: import_zod12.z.string(),
1365
+ var BoundaryBindingSchema = import_zod13.z.object({
1366
+ transport: import_zod13.z.string(),
1065
1367
  semantics: SemanticsSchema,
1066
- recognition: import_zod12.z.string()
1368
+ recognition: import_zod13.z.string()
1067
1369
  });
1068
1370
  function typeDefinitionKey(ref) {
1069
1371
  return ref.def ?? null;
1070
1372
  }
1071
- var TypeShapeSchema = import_zod12.z.lazy(
1072
- () => import_zod12.z.discriminatedUnion("type", [
1073
- import_zod12.z.object({
1074
- type: import_zod12.z.literal("record"),
1075
- properties: import_zod12.z.record(import_zod12.z.string(), TypeShapeSchema),
1076
- spreads: import_zod12.z.array(import_zod12.z.object({ sourceText: import_zod12.z.string() })).optional()
1373
+ var TypeShapeSchema = import_zod13.z.lazy(
1374
+ () => import_zod13.z.discriminatedUnion("type", [
1375
+ import_zod13.z.object({
1376
+ type: import_zod13.z.literal("record"),
1377
+ properties: import_zod13.z.record(import_zod13.z.string(), TypeShapeSchema),
1378
+ spreads: import_zod13.z.array(import_zod13.z.object({ sourceText: import_zod13.z.string() })).optional()
1077
1379
  }),
1078
- import_zod12.z.object({ type: import_zod12.z.literal("dictionary"), values: TypeShapeSchema }),
1079
- import_zod12.z.object({ type: import_zod12.z.literal("array"), items: TypeShapeSchema }),
1080
- import_zod12.z.object({
1081
- type: import_zod12.z.literal("literal"),
1082
- value: import_zod12.z.union([import_zod12.z.string(), import_zod12.z.number(), import_zod12.z.boolean()]),
1083
- raw: import_zod12.z.string().optional()
1380
+ import_zod13.z.object({ type: import_zod13.z.literal("dictionary"), values: TypeShapeSchema }),
1381
+ import_zod13.z.object({ type: import_zod13.z.literal("array"), items: TypeShapeSchema }),
1382
+ import_zod13.z.object({
1383
+ type: import_zod13.z.literal("literal"),
1384
+ value: import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()]),
1385
+ raw: import_zod13.z.string().optional()
1084
1386
  }),
1085
- import_zod12.z.object({ type: import_zod12.z.literal("text") }),
1086
- import_zod12.z.object({ type: import_zod12.z.literal("integer") }),
1087
- import_zod12.z.object({ type: import_zod12.z.literal("number") }),
1088
- import_zod12.z.object({ type: import_zod12.z.literal("boolean") }),
1089
- import_zod12.z.object({ type: import_zod12.z.literal("null") }),
1090
- import_zod12.z.object({ type: import_zod12.z.literal("undefined") }),
1091
- import_zod12.z.object({
1092
- type: import_zod12.z.literal("union"),
1093
- variants: import_zod12.z.array(TypeShapeSchema)
1387
+ import_zod13.z.object({ type: import_zod13.z.literal("text") }),
1388
+ import_zod13.z.object({ type: import_zod13.z.literal("integer") }),
1389
+ import_zod13.z.object({ type: import_zod13.z.literal("number") }),
1390
+ import_zod13.z.object({ type: import_zod13.z.literal("boolean") }),
1391
+ import_zod13.z.object({ type: import_zod13.z.literal("null") }),
1392
+ import_zod13.z.object({ type: import_zod13.z.literal("undefined") }),
1393
+ import_zod13.z.object({
1394
+ type: import_zod13.z.literal("union"),
1395
+ variants: import_zod13.z.array(TypeShapeSchema)
1094
1396
  }),
1095
- import_zod12.z.object({
1096
- type: import_zod12.z.literal("ref"),
1097
- name: import_zod12.z.string(),
1098
- from: import_zod12.z.string().optional(),
1099
- def: import_zod12.z.string().optional()
1397
+ import_zod13.z.object({
1398
+ type: import_zod13.z.literal("ref"),
1399
+ name: import_zod13.z.string(),
1400
+ from: import_zod13.z.string().optional(),
1401
+ def: import_zod13.z.string().optional()
1100
1402
  }),
1101
- import_zod12.z.object({ type: import_zod12.z.literal("unknown") })
1403
+ import_zod13.z.object({ type: import_zod13.z.literal("unknown") })
1102
1404
  ])
1103
1405
  );
1104
1406
  var MAX_DEFINITION_DEPTH = 6;