@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.
@@ -1,8 +1,8 @@
1
1
  // src/schemas.ts
2
- import { z as z12 } from "zod";
2
+ import { z as z13 } from "zod";
3
3
 
4
4
  // src/semantics/registry.ts
5
- import { z as z11 } from "zod";
5
+ import { z as z12 } from "zod";
6
6
 
7
7
  // src/semantics/functionCall.ts
8
8
  import { z } from "zod";
@@ -23,6 +23,9 @@ function unitIdentityKey(deploymentTarget, instanceName) {
23
23
  function metricIdentityKey(metricSystem, metricType) {
24
24
  return `metric:${metricSystem} ${metricType}`;
25
25
  }
26
+ function ioIdentityKey(target) {
27
+ return `io:${target}`;
28
+ }
26
29
 
27
30
  // src/semantics/definition.ts
28
31
  function defineBoundarySemantics(definition) {
@@ -33,10 +36,11 @@ function defineBoundarySemantics(definition) {
33
36
  var FunctionCallSemanticsSchema = z.object({
34
37
  name: z.literal("function-call"),
35
38
  /**
36
- * Optional module identifier for cross-unit references
37
- * (e.g. `"./components/Button"` for a React component, or the TS
38
- * module path for a bare function export). Packs that don't do
39
- * cross-module pairing can leave it unset.
39
+ * Where the function lives inside this repository. It is either the
40
+ * name of a module the project lists in `suss.json`, such as
41
+ * `"billing"`, or a module path, such as the file a server action is
42
+ * written in. A name has no separator in it and a path always does.
43
+ * Packs that don't do cross-module pairing can leave it unset.
40
44
  */
41
45
  module: z.string().optional(),
42
46
  /** Named export within the module, when applicable. */
@@ -61,35 +65,38 @@ var FunctionCallSemanticsSchema = z.object({
61
65
  */
62
66
  exportPath: z.array(z.string()).optional()
63
67
  });
68
+ function isModuleName(module) {
69
+ return !module.includes("/") && !module.includes("\\");
70
+ }
64
71
  var functionCallSemantics = defineBoundarySemantics({
65
72
  name: "function-call",
66
73
  schema: FunctionCallSemanticsSchema,
67
- // A call that never leaves the process does not produce a span, and
68
- // the conventions do not name one. All of it is ours.
74
+ // A call inside the process does not produce a span, and the
75
+ // conventions have no attributes for it.
69
76
  semconv: {},
70
77
  behavior: {
71
- /** A call returns a value, which is not a status and a body. */
78
+ /** A call returns a value, with no status. */
72
79
  exchangesHttpResponses: false,
73
80
  leavesTheProcess: false,
74
81
  reportsUnpairedItself: false,
75
82
  /**
76
- * In-repo units (components, bare handlers) are keyless by design
77
- * and pair through call edges rather than the keyed pass, so a
78
- * missing key is not a boundary nothing can reach.
83
+ * In-repo units such as components have no key on purpose. They
84
+ * pair through call edges instead of the keyed pass, so a missing
85
+ * key does not make them unreachable.
79
86
  */
80
87
  canPair: () => true,
81
- /**
82
- * `"fn:<package>::<exportPath>"` when both are set, and
83
- * `"fn:<module>::<exportName>"` for an in-repo unit that states its
84
- * module, the way a server action does. Components and bare
85
- * handlers state neither and have no key at all.
86
- */
87
88
  rewritePaths(semantics, rewrite) {
88
- if (semantics.module === void 0) {
89
+ if (semantics.module === void 0 || isModuleName(semantics.module)) {
89
90
  return semantics;
90
91
  }
91
92
  return { ...semantics, module: rewrite(semantics.module) };
92
93
  },
94
+ /**
95
+ * `"fn:<package>::<exportPath>"` when both are set, and
96
+ * `"fn:<module>::<exportName>"` for an in-repo unit that gives its
97
+ * module, as a server action does. Components and plain handlers
98
+ * give neither and have no key.
99
+ */
93
100
  identityKey(semantics) {
94
101
  if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
95
102
  return fnIdentityKey(semantics.package, semantics.exportPath);
@@ -120,15 +127,15 @@ var graphqlOperationSemantics = defineBoundarySemantics({
120
127
  behavior: {
121
128
  /**
122
129
  * An operation gets a data-and-errors document back, which the
123
- * GraphQL contract checker reads. Status codes say nothing here.
130
+ * GraphQL contract checker reads. Status codes do not apply.
124
131
  */
125
132
  exchangesHttpResponses: false,
126
133
  leavesTheProcess: true,
127
134
  reportsUnpairedItself: false,
128
135
  identityKey: () => null,
129
- /** Keyless by design; the dedicated pass pairs it by its document. */
136
+ /** No key on purpose. The dedicated pass pairs an operation by its document. */
130
137
  canPair: () => true,
131
- /** "query GetUser". The contract checker pairs it, the key does not. */
138
+ /** `query GetUser`. Only for display, since the contract checker does the pairing. */
132
139
  displayLabel(semantics) {
133
140
  return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
134
141
  }
@@ -151,14 +158,14 @@ var GraphqlResolverSemanticsSchema = z3.object({
151
158
  var graphqlResolverSemantics = defineBoundarySemantics({
152
159
  name: "graphql-resolver",
153
160
  schema: GraphqlResolverSemanticsSchema,
154
- // The conventions name the operation a client sent, not the
155
- // resolver the server ran for one field of it.
161
+ // The conventions describe the operation a client sent. They have no
162
+ // attribute for the resolver the server ran for one field of it.
156
163
  semconv: {},
157
164
  behavior: {
158
- /** A resolver returns a field value, not a status and a body. */
165
+ /** A resolver returns a field value, with no status. */
159
166
  exchangesHttpResponses: false,
160
- // A query somebody sent from outside runs it, even though the
161
- // server calls the function in its own process.
167
+ // A resolver runs because a client outside the process sent a query,
168
+ // even though the server calls it in its own process.
162
169
  leavesTheProcess: true,
163
170
  reportsUnpairedItself: false,
164
171
  /**
@@ -175,8 +182,32 @@ var graphqlResolverSemantics = defineBoundarySemantics({
175
182
  }
176
183
  });
177
184
 
178
- // src/semantics/messageBus.ts
185
+ // src/semantics/io.ts
179
186
  import { z as z4 } from "zod";
187
+ var IoSemanticsSchema = z4.object({
188
+ name: z4.literal("io"),
189
+ /** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
190
+ target: z4.string().nullable()
191
+ });
192
+ var ioSemantics = defineBoundarySemantics({
193
+ name: "io",
194
+ schema: IoSemanticsSchema,
195
+ // The semantic conventions have no attribute for a process's own streams.
196
+ semconv: {},
197
+ behavior: {
198
+ exchangesHttpResponses: false,
199
+ leavesTheProcess: true,
200
+ reportsUnpairedItself: false,
201
+ identityKey(semantics) {
202
+ return semantics.target === null ? null : ioIdentityKey(semantics.target);
203
+ },
204
+ // Nothing declares the reading side, so a write has nothing to meet.
205
+ canPair: () => false
206
+ }
207
+ });
208
+
209
+ // src/semantics/messageBus.ts
210
+ import { z as z5 } from "zod";
180
211
 
181
212
  // src/channel.ts
182
213
  function parseChannel(channel) {
@@ -202,12 +233,11 @@ function channelsPair(a, b) {
202
233
  }
203
234
 
204
235
  // src/semantics/messageBus.ts
205
- var MessageBusSemanticsSchema = z4.object({
206
- name: z4.literal("message-bus"),
207
- messageBus: z4.enum([
208
- // The conventions spell the two AWS buses this way, one with an
209
- // underscore and one with a dot, so these are their strings and
210
- // not a typo.
236
+ var MessageBusSemanticsSchema = z5.object({
237
+ name: z5.literal("message-bus"),
238
+ messageBus: z5.enum([
239
+ // The conventions write these two AWS buses differently, one with an
240
+ // underscore and one with a dot. Both are as the conventions give them.
211
241
  "aws_sqs",
212
242
  "aws.sns",
213
243
  "s3",
@@ -226,12 +256,13 @@ var MessageBusSemanticsSchema = z4.object({
226
256
  "cloudflare-tail"
227
257
  ]),
228
258
  /**
229
- * Stable channel identifier: a CFN logical id, a queue or topic name,
230
- * a subject pattern, or `bus#detailType`. Null when this source does
231
- * not say which channel, as with a send whose queue the code picks at
232
- * runtime, or a receive whose queue the event-source mapping states.
259
+ * A stable channel identifier: a CloudFormation logical id, a queue or
260
+ * topic name, a subject pattern, or `bus#detailType`. Null when this
261
+ * source does not say which channel, as with a send whose queue the
262
+ * code picks at run time, or a receive whose queue is set in the
263
+ * event-source mapping.
233
264
  */
234
- channel: z4.string().min(1).nullable()
265
+ channel: z5.string().min(1).nullable()
235
266
  });
236
267
  var messageBusSemantics = defineBoundarySemantics({
237
268
  name: "message-bus",
@@ -245,25 +276,26 @@ var messageBusSemantics = defineBoundarySemantics({
245
276
  exchangesHttpResponses: false,
246
277
  leavesTheProcess: true,
247
278
  /**
248
- * `checkMessageBus` looks at every channel, including the unused
249
- * ones, so the generic unmatched lists leave them alone.
279
+ * `checkMessageBus` reports every channel, unused ones included, so
280
+ * the generic unmatched list leaves them out.
250
281
  */
251
282
  reportsUnpairedItself: true,
252
283
  /**
253
- * A channel unnamed here can still pair: the dedicated pass
254
- * collapses env chains before comparing, so a consumer whose
255
- * channel arrives at runtime meets the template that declares it.
284
+ * A channel that is null here can still pair. The dedicated pass
285
+ * follows environment variable chains before comparing, so a
286
+ * consumer whose channel is set at run time meets the template that
287
+ * declares it.
256
288
  */
257
289
  canPair: () => true,
258
290
  /**
259
291
  * `"bus:<messageBus> <subject>"`, or null when the channel is null
260
292
  * or its subject is empty.
261
293
  *
262
- * The key has the subject in it and leaves out the bus, so a
263
- * template writing `default#order.placed` and a handler writing
294
+ * The key has the subject and leaves out the bus, so a template
295
+ * writing `default#order.placed` and a handler writing
264
296
  * `order.placed` land in one bucket, where `sidesAgree` compares the
265
- * buses. A side that cannot know its bus would otherwise be keyed
266
- * away from the side that can.
297
+ * buses. Keying on the bus would separate a side that cannot know
298
+ * its bus from the side that can.
267
299
  *
268
300
  * The bus technology stays in, because a queue and an event router
269
301
  * are different destinations even with the same subject. Case stays
@@ -286,9 +318,9 @@ var messageBusSemantics = defineBoundarySemantics({
286
318
  return channelsPair(a.channel, b.channel);
287
319
  },
288
320
  /**
289
- * The whole channel, bus included, unlike the identity key, which
290
- * has the subject alone. Someone reading a list of unmatched
291
- * channels wants to see which bus each one was on.
321
+ * The whole channel, bus included, where the identity key has only
322
+ * the subject. A reader looking at unmatched channels needs to see
323
+ * which bus each one was on.
292
324
  */
293
325
  displayLabel(semantics) {
294
326
  if (semantics.channel === null) {
@@ -300,25 +332,25 @@ var messageBusSemantics = defineBoundarySemantics({
300
332
  });
301
333
 
302
334
  // src/semantics/metric.ts
303
- import { z as z5 } from "zod";
304
- var MetricSemanticsSchema = z5.object({
305
- name: z5.literal("metric"),
306
- /** Which system the series lives in: cloud-monitoring, cloudwatch. */
307
- metricSystem: z5.string(),
335
+ import { z as z6 } from "zod";
336
+ var MetricSemanticsSchema = z6.object({
337
+ name: z6.literal("metric"),
338
+ /** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
339
+ metricSystem: z6.string(),
308
340
  /**
309
- * The type string the system knows the metric by, spelled the way
310
- * both sides spell it. Null when the source states one this reader
311
- * could not settle, as with a query it could not read: a metric
312
- * nobody could name pairs with nothing rather than with whatever
313
- * happens to share its source text.
341
+ * The type string the system uses for the metric, written the same
342
+ * way on both sides. Null when the reader could not work the string
343
+ * out, as with a query it could not parse. A null type pairs with
344
+ * nothing, so it cannot pair with an unrelated metric that happens to
345
+ * share its source text.
314
346
  */
315
- metricType: z5.string().nullable()
347
+ metricType: z6.string().nullable()
316
348
  });
317
349
  var metricSemantics = defineBoundarySemantics({
318
350
  name: "metric",
319
351
  schema: MetricSemanticsSchema,
320
- // OpenTelemetry knows a metric by its instrument name, and has no
321
- // attribute for the system it lives in or the type string it goes by.
352
+ // OpenTelemetry identifies a metric by its instrument name. It has no
353
+ // attribute for the metric's system or its type string.
322
354
  semconv: {},
323
355
  behavior: {
324
356
  exchangesHttpResponses: false,
@@ -334,7 +366,7 @@ var metricSemantics = defineBoundarySemantics({
334
366
  });
335
367
 
336
368
  // src/semantics/rest.ts
337
- import { z as z6 } from "zod";
369
+ import { z as z7 } from "zod";
338
370
 
339
371
  // src/dispatch.ts
340
372
  function dispatchByType(table, value) {
@@ -482,6 +514,38 @@ function pathAfterOrigin(text) {
482
514
  function statesAnOrigin(text) {
483
515
  return SCHEME_ORIGIN.test(text) || PROTOCOL_RELATIVE_ORIGIN.test(text);
484
516
  }
517
+ var PRIVATE_SUFFIXES = [
518
+ "local",
519
+ "internal",
520
+ "localhost",
521
+ "lan",
522
+ "home.arpa",
523
+ "svc",
524
+ "test",
525
+ "example",
526
+ "invalid",
527
+ "example.com",
528
+ "example.net",
529
+ "example.org"
530
+ ];
531
+ var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
532
+ function hostnameOf(host) {
533
+ const lower = host.toLowerCase();
534
+ const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
535
+ return name.replace(/\.$/, "");
536
+ }
537
+ function hostCanBeOwn(host) {
538
+ const name = hostnameOf(host);
539
+ if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
540
+ return true;
541
+ }
542
+ if (!name.includes(".")) {
543
+ return true;
544
+ }
545
+ return PRIVATE_SUFFIXES.some(
546
+ (suffix) => name === suffix || name.endsWith(`.${suffix}`)
547
+ );
548
+ }
485
549
 
486
550
  // src/semantics/pathPattern.ts
487
551
  var HOLE_RANGE_SUFFIX = {
@@ -548,14 +612,18 @@ function shapedItem(segment) {
548
612
  return ".*";
549
613
  }
550
614
  if (part.startsWith("{")) {
551
- return "[^/]+";
615
+ return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
552
616
  }
553
617
  return escapedForRegex(part);
554
618
  }).join("");
619
+ const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
620
+ const text = segment.replace(/\{[^}]*\}|\*/g, "");
555
621
  return {
556
622
  kind: "shaped",
557
623
  shape: segment.replace(/\{[^}]*\}/g, "{}"),
558
- test: new RegExp(`^${source}$`)
624
+ test: new RegExp(`^${source}$`),
625
+ bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
626
+ textLength: text.length
559
627
  };
560
628
  }
561
629
  function itemsOf(segment) {
@@ -575,10 +643,35 @@ function segmentsOf(alternative) {
575
643
  const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
576
644
  return trimmed === "" ? [] : trimmed.split("/");
577
645
  }
646
+ function patternSegmentsOf(alternative) {
647
+ const segments = segmentsOf(alternative);
648
+ const first = segments[0];
649
+ const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
650
+ return opensWithBaseUrl ? segments.slice(1) : segments;
651
+ }
652
+ function pathAfterBaseUrl(path) {
653
+ const segments = segmentsOf(path);
654
+ const first = segments[0];
655
+ if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
656
+ return path;
657
+ }
658
+ return `/${segments.slice(1).join("/")}`;
659
+ }
660
+ var PATTERNS = /* @__PURE__ */ new Map();
661
+ var PATTERN_CACHE_LIMIT = 5e4;
578
662
  function patternOf(path) {
579
- return alternativesOf(path).map(
580
- (alternative) => segmentsOf(alternative).flatMap(itemsOf)
663
+ const known = PATTERNS.get(path);
664
+ if (known !== void 0) {
665
+ return known;
666
+ }
667
+ const pattern = alternativesOf(path).map(
668
+ (alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
581
669
  );
670
+ if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
671
+ PATTERNS.clear();
672
+ }
673
+ PATTERNS.set(path, pattern);
674
+ return pattern;
582
675
  }
583
676
  function literalItemsOf(path) {
584
677
  return segmentsOf(path).map((text) => ({ kind: "text", text }));
@@ -657,17 +750,104 @@ function pathSpansShapes(path) {
657
750
  function countOf(items, kinds) {
658
751
  return items.filter((item) => kinds.includes(item.kind)).length;
659
752
  }
660
- function pathSpecificity(path) {
661
- const alternatives = patternOf(path);
662
- const ranks = alternatives.map((items) => [
753
+ function readingSpecificity(items) {
754
+ return [
663
755
  countOf(items, ["text"]),
664
756
  countOf(items, ["shaped"]),
665
757
  -countOf(items, ["optional", "star"])
666
- ]);
667
- const loosest = ranks.reduce(
668
- (low, rank) => compareRanks(rank, low) < 0 ? rank : low
758
+ ];
759
+ }
760
+ function segmentFit(route, request) {
761
+ if (!meetOnOneSegment(route, request)) {
762
+ return "apart";
763
+ }
764
+ if (request.kind === "text") {
765
+ if (route.kind === "text") {
766
+ return "stated";
767
+ }
768
+ return route.kind === "shaped" ? "shaped" : "free";
769
+ }
770
+ if (absorbsAnything(route)) {
771
+ return "free";
772
+ }
773
+ if (request.kind !== "shaped") {
774
+ return "guess";
775
+ }
776
+ if (route.kind === "shaped") {
777
+ return "free";
778
+ }
779
+ return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
780
+ }
781
+ function shapedText(item) {
782
+ return item.kind === "shaped" ? item.textLength : 0;
783
+ }
784
+ function betterFit(a, b) {
785
+ return a === void 0 || compareRanks(b, a) > 0;
786
+ }
787
+ function bestFit(route, request) {
788
+ const width = request.length + 1;
789
+ const best = new Array((route.length + 1) * width);
790
+ best[0] = [0, 0];
791
+ const offer = (i, j, fit) => {
792
+ const at = i * width + j;
793
+ if (betterFit(best[at], fit)) {
794
+ best[at] = fit;
795
+ }
796
+ };
797
+ for (let reached = 0; reached <= route.length + request.length; reached += 1) {
798
+ for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
799
+ const j = reached - i;
800
+ const fit = best[i * width + j];
801
+ if (fit === void 0) {
802
+ continue;
803
+ }
804
+ const left = route[i];
805
+ const right = request[j];
806
+ if (left !== void 0 && skippable(left)) {
807
+ offer(i + 1, j, fit);
808
+ }
809
+ if (right !== void 0 && skippable(right)) {
810
+ offer(i, j + 1, fit);
811
+ }
812
+ if (left === void 0 || right === void 0) {
813
+ continue;
814
+ }
815
+ if (left.kind === "star" && right.kind === "star") {
816
+ continue;
817
+ }
818
+ const kind = segmentFit(left, right);
819
+ if (kind === "apart" || kind === "guess") {
820
+ continue;
821
+ }
822
+ offer(
823
+ left.kind === "star" ? i : i + 1,
824
+ right.kind === "star" ? j : j + 1,
825
+ [
826
+ fit[0] + (kind === "stated" ? 1 : 0),
827
+ fit[1] + (kind === "shaped" ? shapedText(left) : 0)
828
+ ]
829
+ );
830
+ }
831
+ }
832
+ return best[route.length * width + request.length] ?? null;
833
+ }
834
+ function requestRank(route, request) {
835
+ let found = null;
836
+ for (const routeItems of patternOf(route)) {
837
+ for (const requestItems of patternOf(request)) {
838
+ const fit = bestFit(routeItems, requestItems);
839
+ const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
840
+ if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
841
+ found = rank;
842
+ }
843
+ }
844
+ }
845
+ return found;
846
+ }
847
+ function isCatchAll(route) {
848
+ return patternOf(route).some(
849
+ (items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
669
850
  );
670
- return [...loosest, 1 - alternatives.length];
671
851
  }
672
852
  function compareRanks(a, b) {
673
853
  const length = Math.max(a.length, b.length);
@@ -681,23 +861,34 @@ function compareRanks(a, b) {
681
861
  }
682
862
 
683
863
  // src/semantics/rest.ts
684
- var RestSemanticsSchema = z6.object({
685
- name: z6.literal("rest"),
864
+ var RestSemanticsSchema = z7.object({
865
+ name: z7.literal("rest"),
686
866
  /**
687
867
  * Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
688
868
  * responds to every method, or null when this source does not say
689
869
  * which method.
690
870
  */
691
- method: z6.string().min(1).nullable(),
871
+ method: z7.string().min(1).nullable(),
692
872
  /** Normalized route path ("/users/{id}"), or null when this source does not give one. */
693
- path: z6.string().min(1).nullable(),
873
+ path: z7.string().min(1).nullable(),
874
+ /**
875
+ * Status codes the producing source declared explicitly, such as
876
+ * OpenAPI responses or CloudFormation MethodResponses. They are kept
877
+ * here so the pairing layer can see them without unwrapping any
878
+ * metadata. Absent or empty for an inferred source.
879
+ */
880
+ declaredResponses: z7.array(z7.number()).optional(),
694
881
  /**
695
- * Status codes the producing source explicitly declared (OpenAPI
696
- * responses, CFN MethodResponses, ts-rest router statuses). They are
697
- * kept here so the pairing layer can see them without unwrapping any
698
- * metadata. Absent, or empty, for a source that was inferred.
882
+ * The host a client wrote in an absolute URL, with its port
883
+ * (`api.example.com:8443`). A piece suss could not read is written
884
+ * `{name}`. Absent for a relative URL and for a route.
699
885
  */
700
- declaredResponses: z6.array(z6.number()).optional()
886
+ host: z7.string().min(1).optional(),
887
+ /**
888
+ * True for a route the framework also serves with `.{format}` after
889
+ * its path, the way Rails serves `/search.json` from `get "search"`.
890
+ */
891
+ optionalFormat: z7.boolean().optional()
701
892
  });
702
893
  var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
703
894
  function baseUrlReference(semantics) {
@@ -705,7 +896,10 @@ function baseUrlReference(semantics) {
705
896
  return label === void 0 ? null : referenceFromName(patternHole(label));
706
897
  }
707
898
  function normalizePath(path) {
708
- let normalized = path.replace(/:([a-zA-Z_]\w*)([?+*]?)/g, "{$1$2}");
899
+ let normalized = withoutParameterConstraints(path).replace(
900
+ /:([a-zA-Z_]\w*)([?+*]?)/g,
901
+ "{$1$2}"
902
+ );
709
903
  if (normalized.length > 1 && normalized.endsWith("/")) {
710
904
  normalized = normalized.slice(0, -1);
711
905
  }
@@ -715,6 +909,46 @@ function normalizePath(path) {
715
909
  );
716
910
  return normalized;
717
911
  }
912
+ function withoutParameterConstraints(path) {
913
+ let out = "";
914
+ let i = 0;
915
+ while (i < path.length) {
916
+ const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
917
+ if (name === null) {
918
+ out += path[i];
919
+ i += 1;
920
+ continue;
921
+ }
922
+ const open = i + name[0].length - 1;
923
+ const close = closingParenthesis(path, open);
924
+ if (close === -1) {
925
+ return out + path.slice(i);
926
+ }
927
+ out += path.slice(i, open);
928
+ i = close + 1;
929
+ }
930
+ return out;
931
+ }
932
+ function closingParenthesis(text, open) {
933
+ let depth = 0;
934
+ for (let i = open; i < text.length; i += 1) {
935
+ const char = text[i];
936
+ if (char === "\\") {
937
+ i += 1;
938
+ continue;
939
+ }
940
+ if (char === "(") {
941
+ depth += 1;
942
+ }
943
+ if (char === ")") {
944
+ depth -= 1;
945
+ if (depth === 0) {
946
+ return i;
947
+ }
948
+ }
949
+ }
950
+ return -1;
951
+ }
718
952
  function pathShape(path) {
719
953
  return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
720
954
  }
@@ -730,6 +964,40 @@ function routePathAdmits(declaredPath, requestPath) {
730
964
  function routePathsMeet(a, b) {
731
965
  return pathsMeet(normalizePath(a), normalizePath(b));
732
966
  }
967
+ function readHost(semantics) {
968
+ const host = semantics.host;
969
+ return host === void 0 || /[{(]/.test(host) ? void 0 : host;
970
+ }
971
+ function hostsAgree(a, b) {
972
+ return readHost(a) === readHost(b);
973
+ }
974
+ function hostUnread(semantics) {
975
+ if (semantics.host !== void 0) {
976
+ return readHost(semantics) === void 0;
977
+ }
978
+ return baseUrlReference(semantics) !== null;
979
+ }
980
+ function servedPath(semantics, path) {
981
+ const normalized = normalizePath(path);
982
+ return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
983
+ }
984
+ function withoutHost(semantics) {
985
+ const { host: _host, ...rest } = semantics;
986
+ return rest;
987
+ }
988
+ function groundedBaseUrl(semantics, deployment) {
989
+ const reference = baseUrlReference(semantics);
990
+ if (reference === null) {
991
+ return null;
992
+ }
993
+ const base = deployment.setTo(reference);
994
+ if (base === null) {
995
+ return null;
996
+ }
997
+ const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
998
+ const grounded = pathAfterOrigin(`${base}${rest}`);
999
+ return { ...semantics, path: grounded === "" ? "/" : grounded };
1000
+ }
733
1001
  var restSemantics = defineBoundarySemantics({
734
1002
  name: "rest",
735
1003
  schema: RestSemanticsSchema,
@@ -754,39 +1022,65 @@ var restSemantics = defineBoundarySemantics({
754
1022
  return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
755
1023
  },
756
1024
  /**
757
- * The bucket has only the path in it, so a `"*"` route lands with
758
- * the consumers that each specify a method, and `methodsAgree`
759
- * settles it inside the bucket. The identity key stays what a reader
760
- * sees and a suppression targets ("GET /users", "* /users"), and
761
- * this key groups what a reader still tells apart.
1025
+ * The bucket has only the path, so a `"*"` route lands with the
1026
+ * consumers that each give a method, and `methodsAgree` compares the
1027
+ * methods inside the bucket. The identity key is still what a reader
1028
+ * sees and what a suppression targets ("GET /users", "* /users").
762
1029
  *
763
- * A parameter's name goes too. A spec written by hand says
764
- * `{userId}` where the Express route that serves it says `:id`, and
765
- * both match the same requests, so pairing on the name gave two
766
- * boundaries that never met and a run with nothing to report.
1030
+ * Parameter names are dropped too. A hand-written spec writes
1031
+ * `{userId}` where the Express route that serves it writes `:id`,
1032
+ * and both match the same requests. Pairing on the name would leave
1033
+ * two boundaries that never meet.
767
1034
  */
768
1035
  pairingKey(semantics) {
769
1036
  if (semantics.method === null || semantics.path === null) {
770
1037
  return null;
771
1038
  }
772
- return `rest ${pathShape(semantics.path)}`;
1039
+ const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
1040
+ const host = readHost(semantics);
1041
+ return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
1042
+ },
1043
+ /**
1044
+ * A spec writes `/teams/{team_id}` where the route that serves it
1045
+ * writes `/teams/:id`. The method stays in, so a `"*"` handler is
1046
+ * never compared with the document for one method.
1047
+ */
1048
+ operationKey(semantics) {
1049
+ if (semantics.method === null || semantics.path === null) {
1050
+ return null;
1051
+ }
1052
+ return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
773
1053
  },
774
1054
  sidesAgree(a, b) {
775
- return methodsAgree(a.method, b.method);
1055
+ return methodsAgree(a.method, b.method) && hostsAgree(a, b);
776
1056
  },
777
1057
  spansBuckets(semantics) {
778
- return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
1058
+ return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
779
1059
  },
780
1060
  bucketsMeet(a, b) {
781
- return a.path !== null && b.path !== null && routePathsMeet(a.path, b.path);
1061
+ return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
782
1062
  },
783
- bucketRank(semantics) {
784
- return semantics.path === null ? [] : pathSpecificity(normalizePath(semantics.path));
1063
+ /**
1064
+ * A catch-all route never takes a call whose host suss could not
1065
+ * read, since nothing else ties that call to this app.
1066
+ */
1067
+ pairRank(provider, consumer) {
1068
+ if (provider.path === null || consumer.path === null) {
1069
+ return [];
1070
+ }
1071
+ const route = normalizePath(provider.path);
1072
+ if (hostUnread(consumer) && isCatchAll(route)) {
1073
+ return null;
1074
+ }
1075
+ return requestRank(
1076
+ servedPath(provider, provider.path),
1077
+ normalizePath(consumer.path)
1078
+ );
785
1079
  },
786
1080
  /**
787
- * What the identity key says, with a missing half still readable:
788
- * ANY when no method was stated, and ? when no path was. Null when
789
- * neither was stated, because then there is nothing to show.
1081
+ * The identity key, with a missing half still readable: `ANY` when
1082
+ * the source gave no method, and `?` when it gave no path. Null when
1083
+ * both are missing, since then there is nothing to show.
790
1084
  */
791
1085
  displayLabel(semantics) {
792
1086
  if (semantics.method === null && semantics.path === null) {
@@ -797,11 +1091,11 @@ var restSemantics = defineBoundarySemantics({
797
1091
  return `${method} ${path}`;
798
1092
  },
799
1093
  /**
800
- * A route with both halves stated decides for itself: the method
801
- * through `methodsAgree` (so a `"*"` route matches every method),
802
- * and the path through `routePathAdmits`. A route missing either
803
- * half might still be the one that handles the request, and nothing
804
- * here can settle that, so it abstains instead of refusing.
1094
+ * With both halves present, the method goes through `methodsAgree`
1095
+ * (so a `"*"` route matches every method) and the path through
1096
+ * `routePathAdmits`. A route missing either half might still handle
1097
+ * the request, and this declaration cannot settle that, so the
1098
+ * result is `"unknown"`.
805
1099
  */
806
1100
  servesRequest(semantics, method, path) {
807
1101
  if (semantics.method === null || semantics.path === null) {
@@ -814,43 +1108,41 @@ var restSemantics = defineBoundarySemantics({
814
1108
  },
815
1109
  /**
816
1110
  * A route the registration pattern admits is inside it. A route
817
- * whose path this source never gave is left outside, because
818
- * nothing here can show it is one the pattern covers.
1111
+ * without a path counts as outside, because nothing shows that the
1112
+ * pattern covers it.
819
1113
  */
820
1114
  withinScope(semantics, scope) {
821
1115
  return semantics.path !== null && routePathAdmits(scope, semantics.path);
822
1116
  },
823
1117
  /**
824
1118
  * A call whose base URL the deployment fills in, resolved to the
825
- * path it reaches.
826
- *
827
- * The source cannot settle this on its own, which is why the
828
- * adapter leaves the hole in. `API_BASE` could be
829
- * `http://backend.internal`, and then the path is `/orders`. It
830
- * could equally be `/api/v2`, and then the path is
831
- * `/api/v2/orders`. Putting the deployed value in and reading the
832
- * path back out gets the right one either way.
1119
+ * path it reaches, and a call to a host that can be the project's
1120
+ * own, with the host left out so it meets the route.
833
1121
  *
834
- * Only a hole at the front is a base URL. A hole further along is a
835
- * route parameter, and `/orders/{id}` means every id rather than a
836
- * variable somebody set.
1122
+ * The source cannot settle a base URL on its own, so the adapter
1123
+ * leaves the hole in. `API_BASE` could be `http://backend.internal`,
1124
+ * and then the path is `/orders`. It could equally be `/api/v2`, and
1125
+ * then the path is `/api/v2/orders`. Filling in the deployed value
1126
+ * and reading the path back out gets the right one either way, and
1127
+ * a base the deployment sets counts as the app's own.
837
1128
  */
838
- groundName(semantics, deployment) {
839
- const reference = baseUrlReference(semantics);
840
- if (reference === null) {
841
- return null;
842
- }
843
- const base = deployment.setTo(reference);
844
- if (base === null) {
1129
+ /** A listed host matches with or without the port the call gives. */
1130
+ withoutOwnHost(semantics, ownHosts) {
1131
+ const host = semantics.host?.toLowerCase();
1132
+ if (host === void 0) {
845
1133
  return null;
846
1134
  }
847
- const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
848
- const grounded = pathAfterOrigin(`${base}${rest}`);
849
- return { ...semantics, path: grounded === "" ? "/" : grounded };
1135
+ return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
1136
+ },
1137
+ groundName(semantics, deployment) {
1138
+ const based = groundedBaseUrl(semantics, deployment);
1139
+ const grounded = based ?? semantics;
1140
+ const host = readHost(grounded);
1141
+ return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
850
1142
  },
851
1143
  nameReference: baseUrlReference,
852
1144
  ruleBoundary: {
853
- // "METHOD /path": one leading token, then an absolute path.
1145
+ // "METHOD /path": one token, a space, then an absolute path.
854
1146
  claims(raw) {
855
1147
  return /^\S+ +\//.test(raw.trim());
856
1148
  },
@@ -865,12 +1157,12 @@ var restSemantics = defineBoundarySemantics({
865
1157
  });
866
1158
 
867
1159
  // src/semantics/runtimeConfig.ts
868
- import { z as z8 } from "zod";
1160
+ import { z as z9 } from "zod";
869
1161
 
870
1162
  // src/deployableUnit.ts
871
- import { z as z7 } from "zod";
872
- var DeployableUnitSchema = z7.object({
873
- deploymentTarget: z7.enum([
1163
+ import { z as z8 } from "zod";
1164
+ var DeployableUnitSchema = z8.object({
1165
+ deploymentTarget: z8.enum([
874
1166
  "lambda",
875
1167
  "ecs-task",
876
1168
  "container",
@@ -879,7 +1171,7 @@ var DeployableUnitSchema = z7.object({
879
1171
  ]),
880
1172
  // An empty name would agree with every other empty name, so a unit
881
1173
  // with no name has to leave the field off instead.
882
- instanceName: z7.string().min(1)
1174
+ instanceName: z8.string().min(1)
883
1175
  });
884
1176
  function ecsContainerInstanceName(taskDefinitionLogicalId, containerName) {
885
1177
  return `${taskDefinitionLogicalId}/${containerName}`;
@@ -890,14 +1182,14 @@ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
890
1182
  deploymentTarget: true,
891
1183
  instanceName: true
892
1184
  }).extend({
893
- name: z8.literal("runtime-config")
1185
+ name: z9.literal("runtime-config")
894
1186
  });
895
1187
  var runtimeConfigSemantics = defineBoundarySemantics({
896
1188
  name: "runtime-config",
897
1189
  schema: RuntimeConfigSemanticsSchema,
898
- // service.name and cloud.resource_id name the same deployable, but
899
- // instanceName is the deployment template's logical id, which is
900
- // neither of those strings.
1190
+ // service.name and cloud.resource_id identify the same deployable, but
1191
+ // instanceName is the template's logical id, a different string from
1192
+ // both.
901
1193
  semconv: {},
902
1194
  behavior: {
903
1195
  /** A process reads its config at startup, and nothing replies. */
@@ -906,46 +1198,53 @@ var runtimeConfigSemantics = defineBoundarySemantics({
906
1198
  // comes from outside the code.
907
1199
  leavesTheProcess: true,
908
1200
  reportsUnpairedItself: false,
909
- identityKey: () => null
1201
+ identityKey: () => null,
1202
+ // A reader sees which deployable's environment this is. Code that
1203
+ // reads a variable does not say which deployable runs it, so a read
1204
+ // has no name.
1205
+ displayLabel(semantics) {
1206
+ const instance = semantics.instanceName;
1207
+ return instance === void 0 ? null : `runtime-config:${instance}`;
1208
+ }
910
1209
  }
911
1210
  });
912
1211
 
913
1212
  // src/semantics/storage.ts
914
- import { z as z9 } from "zod";
915
- var StorageSemanticsSchema = z9.object({
916
- name: z9.literal("storage"),
1213
+ import { z as z10 } from "zod";
1214
+ var StorageSemanticsSchema = z10.object({
1215
+ name: z10.literal("storage"),
917
1216
  /**
918
- * Which store this is: postgresql, mysql, aws.dynamodb, or our own
919
- * word for one OpenTelemetry never named, such as s3, or null when
920
- * the source states one this reader could not settle. Two products'
921
- * containers can share a name, so this keeps them apart. A null
922
- * keeps none of them apart, and the pairing pass says what it does
923
- * with one.
1217
+ * The storage system: an OpenTelemetry value such as `postgresql` or
1218
+ * `aws.dynamodb`, or suss's own word for one the conventions do not
1219
+ * cover, such as `s3`. Null when the reader could not work out which
1220
+ * system the source uses. Two products' containers can share a name,
1221
+ * and this field keeps them apart. The pairing pass decides how to
1222
+ * treat a null.
924
1223
  */
925
- storageSystem: z9.string().nullable(),
1224
+ storageSystem: z10.string().nullable(),
926
1225
  /**
927
1226
  * The ORM, schema, or deployment scope. A single-database setup uses
928
1227
  * `"default"`, and a monorepo with several schemas gives each one its
929
1228
  * own value.
930
1229
  */
931
- scope: z9.string(),
1230
+ scope: z10.string(),
932
1231
  /**
933
- * The table, bucket, collection, or index, as the source declares
934
- * it, or null when the source states one this reader could not
935
- * settle. A null container pairs with nothing, rather than with
936
- * whatever its source text happens to spell. The string is in the
937
- * boundary-name syntax: a literal, a pattern with `{}` holes, or a
938
- * reference, and `parseBoundaryName` is the one reader of it.
1232
+ * The table, bucket, collection or index, as the source declares it.
1233
+ * Null when the reader could not work out the container, and a null
1234
+ * container pairs with nothing, so it cannot match by accident of
1235
+ * shared source text. The string uses the boundary-name syntax (a
1236
+ * literal, a pattern with `{}` holes, or a reference), and only
1237
+ * `parseBoundaryName` should read it.
939
1238
  */
940
- container: z9.string().nullable(),
1239
+ container: z10.string().nullable(),
941
1240
  /**
942
- * A secondary way into the container, a DynamoDB global secondary
943
- * index or an Elasticsearch alias, each with its own key fields.
944
- * Null means the container's own primary way in. A query through an
1241
+ * A secondary way into the container, such as a DynamoDB global
1242
+ * secondary index or an Elasticsearch alias, each with its own key
1243
+ * fields. Null means the container's primary key. A query through an
945
1244
  * index and a query through the table are different accesses, so
946
1245
  * they pair separately.
947
1246
  */
948
- accessPath: z9.string().nullable()
1247
+ accessPath: z10.string().nullable()
949
1248
  });
950
1249
  function containerReference(semantics) {
951
1250
  return semantics.container === null ? null : referenceFromName(semantics.container);
@@ -955,21 +1254,21 @@ var storageSemantics = defineBoundarySemantics({
955
1254
  schema: StorageSemanticsSchema,
956
1255
  semconv: {
957
1256
  storageSystem: { name: "db.system.name" },
958
- // "default" is our word for a source that named no database.
1257
+ // suss writes "default" when the source did not say which database.
959
1258
  scope: { name: "db.namespace", placeholderValues: ["default"] },
960
1259
  container: { name: "db.collection.name" }
961
- // A secondary index has no attribute of its own, so accessPath
962
- // stays our word.
1260
+ // The conventions have no attribute for a secondary index, so
1261
+ // accessPath is left out.
963
1262
  },
964
1263
  behavior: {
965
- /** A query returns rows or items, not a status and a body. */
1264
+ /** A query returns rows or items, with no status. */
966
1265
  exchangesHttpResponses: false,
967
1266
  leavesTheProcess: true,
968
1267
  reportsUnpairedItself: false,
969
1268
  identityKey: () => null,
970
1269
  displayLabel: storageLabel,
971
1270
  /**
972
- * A container the source states as a variable is whatever the
1271
+ * A container the source gives as a variable is whatever the
973
1272
  * deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
974
1273
  * code and `prod-subscribers-v1` in the manifest are one table.
975
1274
  */
@@ -999,9 +1298,9 @@ function storageContainerLabel(semantics) {
999
1298
  }
1000
1299
 
1001
1300
  // src/semantics/unitInvocation.ts
1002
- import { z as z10 } from "zod";
1301
+ import { z as z11 } from "zod";
1003
1302
  var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
1004
- name: z10.literal("unit-invocation"),
1303
+ name: z11.literal("unit-invocation"),
1005
1304
  instanceName: DeployableUnitSchema.shape.instanceName.nullable()
1006
1305
  });
1007
1306
  function calleeReference(semantics) {
@@ -1031,9 +1330,9 @@ var unitInvocationSemantics = defineBoundarySemantics({
1031
1330
  */
1032
1331
  reportsUnpairedItself: false,
1033
1332
  /**
1034
- * A callee the source states only as a variable is not a name for
1035
- * anything until the deployment fills it in, so it keys as nothing
1036
- * rather than as a bucket that would agree with itself alone.
1333
+ * A callee the source gives only as a variable has no name until the
1334
+ * deployment fills it in. It gets no key, since a key built from the
1335
+ * variable would pair only with itself.
1037
1336
  */
1038
1337
  identityKey(semantics) {
1039
1338
  if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
@@ -1054,11 +1353,10 @@ var unitInvocationSemantics = defineBoundarySemantics({
1054
1353
  );
1055
1354
  },
1056
1355
  /**
1057
- * A call that reads its callee's name out of the environment
1058
- * reaches whichever resource the template wires that variable to.
1059
- * The logical id is the answer rather than the deployed name,
1060
- * because the invoked unit's own summary is keyed by the logical
1061
- * id and neither side ever writes the other's string.
1356
+ * A call that reads its callee's name from the environment reaches
1357
+ * whichever resource the template wires that variable to. The name
1358
+ * is grounded to the logical id and not the deployed name, because
1359
+ * the invoked unit's own summary is keyed by the logical id.
1062
1360
  */
1063
1361
  groundName(semantics, deployment) {
1064
1362
  const reference = calleeReference(semantics);
@@ -1073,7 +1371,7 @@ var unitInvocationSemantics = defineBoundarySemantics({
1073
1371
  });
1074
1372
 
1075
1373
  // src/semantics/registry.ts
1076
- var SemanticsSchema = z11.discriminatedUnion("name", [
1374
+ var SemanticsSchema = z12.discriminatedUnion("name", [
1077
1375
  restSemantics.schema,
1078
1376
  functionCallSemantics.schema,
1079
1377
  graphqlResolverSemantics.schema,
@@ -1082,7 +1380,8 @@ var SemanticsSchema = z11.discriminatedUnion("name", [
1082
1380
  storageSemantics.schema,
1083
1381
  messageBusSemantics.schema,
1084
1382
  metricSemantics.schema,
1085
- unitInvocationSemantics.schema
1383
+ unitInvocationSemantics.schema,
1384
+ ioSemantics.schema
1086
1385
  ]);
1087
1386
  var DEFINITIONS = [
1088
1387
  restSemantics,
@@ -1093,7 +1392,8 @@ var DEFINITIONS = [
1093
1392
  storageSemantics,
1094
1393
  messageBusSemantics,
1095
1394
  metricSemantics,
1096
- unitInvocationSemantics
1395
+ unitInvocationSemantics,
1396
+ ioSemantics
1097
1397
  ];
1098
1398
  var BY_NAME = new Map(
1099
1399
  DEFINITIONS.map((d) => [d.name, d])
@@ -1116,90 +1416,95 @@ function allBehaviors() {
1116
1416
  }
1117
1417
 
1118
1418
  // src/schemas.ts
1119
- var ConfidenceSourceSchema = z12.enum([
1419
+ var ConfidenceSourceSchema = z13.enum([
1120
1420
  "inferred_static",
1121
1421
  "inferred_ai",
1122
1422
  "declared",
1123
1423
  "derived"
1124
1424
  ]);
1125
- var ConfidenceLevelSchema = z12.enum(["high", "medium", "low"]);
1126
- var CorroborationSchema = z12.object({
1127
- outcome: z12.enum(["observed", "refuted", "untested"]),
1425
+ var ConfidenceLevelSchema = z13.enum(["high", "medium", "low"]);
1426
+ var CorroborationSchema = z13.object({
1427
+ outcome: z13.enum(["observed", "refuted", "untested"]),
1128
1428
  /** Executions that produced a verdict for this claim. */
1129
- runs: z12.number(),
1429
+ runs: z13.number(),
1130
1430
  /** Present when refuted: the input and observation that disagreed. */
1131
- counterexample: z12.unknown().optional(),
1431
+ counterexample: z13.unknown().optional(),
1132
1432
  /** Present when untested: why no verdict was reachable. */
1133
- reason: z12.string().optional()
1433
+ reason: z13.string().optional()
1134
1434
  });
1135
- var ConfidenceInfoSchema = z12.object({
1435
+ var ConfidenceInfoSchema = z13.object({
1136
1436
  source: ConfidenceSourceSchema,
1137
1437
  level: ConfidenceLevelSchema,
1138
1438
  corroboration: CorroborationSchema.optional()
1139
1439
  });
1140
- var SourceLocationSchema = z12.object({
1141
- file: z12.string(),
1440
+ var SourceLocationSchema = z13.object({
1441
+ file: z13.string(),
1142
1442
  /** Line numbers, for a person reading the summary or an editor link. */
1143
- range: z12.object({ start: z12.number(), end: z12.number() }),
1443
+ range: z13.object({ start: z13.number(), end: z13.number() }),
1144
1444
  /**
1145
- * Character offsets of the unit in its file. Identity and joins
1146
- * measure with these, since two functions can share a line and never
1147
- * an offset range; the line `range` above is the presentation of the
1148
- * same place. Absent on summaries no source position backs, a
1149
- * contract artifact's say.
1445
+ * Character offsets of the unit in its file. Identity and joins use
1446
+ * these, because two functions can share a line but never an offset
1447
+ * range. `range` gives the same place in lines. Absent on a summary
1448
+ * with no source position, such as one read from a contract.
1150
1449
  */
1151
- span: z12.object({ start: z12.number(), end: z12.number() }).optional(),
1152
- exportName: z12.string().nullable(),
1450
+ span: z13.object({ start: z13.number(), end: z13.number() }).optional(),
1451
+ exportName: z13.string().nullable(),
1153
1452
  /**
1154
- * The project the extract was pointed at, by the name that project
1155
- * calls itself.
1453
+ * The project the extract ran on, by the name the project gives
1454
+ * itself.
1156
1455
  *
1157
1456
  * Paths are relative to wherever the extract ran, so two services in
1158
- * one repository both report `src/handlers.ts`, and merging their
1159
- * summaries puts them on top of each other. This field is what tells
1160
- * them apart, and what a reader groups by.
1457
+ * one repository can both report `src/handlers.ts`, and merging their
1458
+ * summaries would mix them up. This field tells them apart, and a
1459
+ * reader groups by it.
1460
+ */
1461
+ workspace: z13.string().optional(),
1462
+ /**
1463
+ * The module of the application the unit belongs to, by the name the
1464
+ * project's `suss.json` gives it. Set only when the project lists its
1465
+ * modules, and then on every unit whose file is under a module's root.
1161
1466
  */
1162
- workspace: z12.string().optional()
1467
+ module: z13.string().optional()
1163
1468
  });
1164
- var BoundaryBindingSchema = z12.object({
1165
- transport: z12.string(),
1469
+ var BoundaryBindingSchema = z13.object({
1470
+ transport: z13.string(),
1166
1471
  semantics: SemanticsSchema,
1167
- recognition: z12.string()
1472
+ recognition: z13.string()
1168
1473
  });
1169
1474
  function typeDefinitionKey(ref) {
1170
1475
  return ref.def ?? null;
1171
1476
  }
1172
- var TypeShapeSchema = z12.lazy(
1173
- () => z12.discriminatedUnion("type", [
1174
- z12.object({
1175
- type: z12.literal("record"),
1176
- properties: z12.record(z12.string(), TypeShapeSchema),
1177
- spreads: z12.array(z12.object({ sourceText: z12.string() })).optional()
1477
+ var TypeShapeSchema = z13.lazy(
1478
+ () => z13.discriminatedUnion("type", [
1479
+ z13.object({
1480
+ type: z13.literal("record"),
1481
+ properties: z13.record(z13.string(), TypeShapeSchema),
1482
+ spreads: z13.array(z13.object({ sourceText: z13.string() })).optional()
1178
1483
  }),
1179
- z12.object({ type: z12.literal("dictionary"), values: TypeShapeSchema }),
1180
- z12.object({ type: z12.literal("array"), items: TypeShapeSchema }),
1181
- z12.object({
1182
- type: z12.literal("literal"),
1183
- value: z12.union([z12.string(), z12.number(), z12.boolean()]),
1184
- raw: z12.string().optional()
1484
+ z13.object({ type: z13.literal("dictionary"), values: TypeShapeSchema }),
1485
+ z13.object({ type: z13.literal("array"), items: TypeShapeSchema }),
1486
+ z13.object({
1487
+ type: z13.literal("literal"),
1488
+ value: z13.union([z13.string(), z13.number(), z13.boolean()]),
1489
+ raw: z13.string().optional()
1185
1490
  }),
1186
- z12.object({ type: z12.literal("text") }),
1187
- z12.object({ type: z12.literal("integer") }),
1188
- z12.object({ type: z12.literal("number") }),
1189
- z12.object({ type: z12.literal("boolean") }),
1190
- z12.object({ type: z12.literal("null") }),
1191
- z12.object({ type: z12.literal("undefined") }),
1192
- z12.object({
1193
- type: z12.literal("union"),
1194
- variants: z12.array(TypeShapeSchema)
1491
+ z13.object({ type: z13.literal("text") }),
1492
+ z13.object({ type: z13.literal("integer") }),
1493
+ z13.object({ type: z13.literal("number") }),
1494
+ z13.object({ type: z13.literal("boolean") }),
1495
+ z13.object({ type: z13.literal("null") }),
1496
+ z13.object({ type: z13.literal("undefined") }),
1497
+ z13.object({
1498
+ type: z13.literal("union"),
1499
+ variants: z13.array(TypeShapeSchema)
1195
1500
  }),
1196
- z12.object({
1197
- type: z12.literal("ref"),
1198
- name: z12.string(),
1199
- from: z12.string().optional(),
1200
- def: z12.string().optional()
1501
+ z13.object({
1502
+ type: z13.literal("ref"),
1503
+ name: z13.string(),
1504
+ from: z13.string().optional(),
1505
+ def: z13.string().optional()
1201
1506
  }),
1202
- z12.object({ type: z12.literal("unknown") })
1507
+ z13.object({ type: z13.literal("unknown") })
1203
1508
  ])
1204
1509
  );
1205
1510
  var MAX_DEFINITION_DEPTH = 6;
@@ -1250,8 +1555,11 @@ export {
1250
1555
  busIdentityKey,
1251
1556
  unitIdentityKey,
1252
1557
  metricIdentityKey,
1558
+ ioIdentityKey,
1253
1559
  FunctionCallSemanticsSchema,
1560
+ isModuleName,
1254
1561
  GraphqlResolverSemanticsSchema,
1562
+ IoSemanticsSchema,
1255
1563
  parseChannel,
1256
1564
  formatChannel,
1257
1565
  busesAgree,
@@ -1273,6 +1581,7 @@ export {
1273
1581
  referenceFromName,
1274
1582
  pathAfterOrigin,
1275
1583
  statesAnOrigin,
1584
+ hostCanBeOwn,
1276
1585
  rangedHole,
1277
1586
  setPiece,
1278
1587
  compareRanks,