@suss/ir-core 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/chunk-5KE7OWN3.js +597 -0
- package/dist/index.cjs +575 -220
- package/dist/index.d.cts +251 -123
- package/dist/index.d.ts +251 -123
- package/dist/index.js +85 -81
- package/dist/schemas-BWvfkxSH.d.cts +432 -0
- package/dist/schemas-BWvfkxSH.d.ts +432 -0
- package/dist/schemas.cjs +458 -127
- package/dist/schemas.d.cts +2 -374
- package/dist/schemas.d.ts +2 -374
- package/dist/schemas.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-L4XGOS3I.js +0 -237
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @suss/ir-core
|
|
2
2
|
|
|
3
|
-
Primitives shared across suss's intermediate representations: the
|
|
3
|
+
Primitives shared across suss's intermediate representations: the pieces every IR references, in one place that each of them can reach without depending on the others.
|
|
4
4
|
|
|
5
5
|
## What this package is
|
|
6
6
|
|
|
7
7
|
The types that any suss IR is built from:
|
|
8
8
|
|
|
9
|
-
- `TypeShape`: the
|
|
9
|
+
- `TypeShape`: the structure of a value, for body / payload / field comparison.
|
|
10
10
|
- `BoundaryBinding` + the `Semantics` variants (rest, function-call, graphql-resolver, graphql-operation, runtime-config, storage-relational, message-bus), plus the eight blessed binding constructors (`restBinding`, `functionCallBinding`, …).
|
|
11
11
|
- `SourceLocation` and `Confidence` (`source` + `level`).
|
|
12
12
|
|
|
@@ -21,11 +21,19 @@ const binding = restBinding({
|
|
|
21
21
|
});
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## Message-bus channels
|
|
25
|
+
|
|
26
|
+
A channel is a subject, optionally qualified by the bus that carries it, written `${bus}#${subject}`. Two channels pair when their subjects are equal and their buses agree, and buses agree when they are the same or when either side does not know its bus.
|
|
27
|
+
|
|
28
|
+
The two sides rarely know the same amount. A CloudFormation template gives both the bus and the detail-type, and it goes out of its way to distinguish one bus from another, so when a side does know its bus we keep that precision. Code usually knows only the subject: the code pack reads `subject: 'order.placed'` from a handler's config, but which bus actually reaches that handler is deployment configuration the code never mentions. So `default#order.placed` pairs with `order.placed`, while `staging#order.placed` does not pair with `default#order.placed`.
|
|
29
|
+
|
|
30
|
+
The boundary key uses only the subject, so both forms end up in the same bucket and `channelsPair` compares the buses within it.
|
|
31
|
+
|
|
24
32
|
Schemas are the single source of truth (`@suss/ir-core/schemas`); the types are derived from them. The recursive `TypeShape` is a hand-written named export so consuming packages reference it by name across the package boundary rather than inlining the recursion.
|
|
25
33
|
|
|
26
|
-
## Where it
|
|
34
|
+
## Where it fits in suss
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
Both `@suss/behavioral-ir` (what code does) and `@suss/intent-ir` (what the team meant) build on this package, so neither IR depends on the other. They describe boundaries in the same vocabulary, and suss compares them rather than merging them. `@suss/behavioral-ir` re-exports these primitives, so existing consumers keep importing them from there unchanged.
|
|
29
37
|
|
|
30
38
|
## Status
|
|
31
39
|
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
// src/schemas.ts
|
|
2
|
+
import { z as z10 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/semantics/registry.ts
|
|
5
|
+
import { z as z9 } from "zod";
|
|
6
|
+
|
|
7
|
+
// src/semantics/functionCall.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/semantics/definition.ts
|
|
11
|
+
function defineBoundarySemantics(definition) {
|
|
12
|
+
return definition;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/semantics/functionCall.ts
|
|
16
|
+
var FunctionCallSemanticsSchema = z.object({
|
|
17
|
+
name: z.literal("function-call"),
|
|
18
|
+
/**
|
|
19
|
+
* Optional module identifier for cross-unit references
|
|
20
|
+
* (e.g. `"./components/Button"` for a React component, or the TS
|
|
21
|
+
* module path for a bare function export). Packs that don't do
|
|
22
|
+
* cross-module pairing can leave it unset.
|
|
23
|
+
*/
|
|
24
|
+
module: z.string().optional(),
|
|
25
|
+
/** Named export within the module, when applicable. */
|
|
26
|
+
exportName: z.string().optional(),
|
|
27
|
+
/**
|
|
28
|
+
* Package name, as written in `package.json`, when this identity
|
|
29
|
+
* refers to a public package export, for example
|
|
30
|
+
* `"@suss/behavioral-ir"`. Packs that resolve a package's public
|
|
31
|
+
* surface (the `packageExports` discovery variant) set it alongside
|
|
32
|
+
* `exportPath`. It is different from `module`, which is a
|
|
33
|
+
* repo-relative module path used for pairing inside one repo.
|
|
34
|
+
*/
|
|
35
|
+
package: z.string().optional(),
|
|
36
|
+
/**
|
|
37
|
+
* Path to the exported binding within the package, starting with the
|
|
38
|
+
* sub-path key when one is used. Examples:
|
|
39
|
+
* `["parseSummary"]`: a root export
|
|
40
|
+
* `["schemas", "BoundaryBindingSchema"]`: the sub-path `./schemas`
|
|
41
|
+
*
|
|
42
|
+
* The first segment is the sub-path without the leading `./`, and
|
|
43
|
+
* `"."` is left out. The last segment is the exported name.
|
|
44
|
+
*/
|
|
45
|
+
exportPath: z.array(z.string()).optional()
|
|
46
|
+
});
|
|
47
|
+
var functionCallSemantics = defineBoundarySemantics({
|
|
48
|
+
name: "function-call",
|
|
49
|
+
schema: FunctionCallSemanticsSchema,
|
|
50
|
+
behavior: {
|
|
51
|
+
/** A call returns a value, which is not a status and a body. */
|
|
52
|
+
exchangesHttpResponses: false,
|
|
53
|
+
reportsUnpairedItself: false,
|
|
54
|
+
/**
|
|
55
|
+
* `"fn:<package>::<exportPath>"` when both are set. Other in-process
|
|
56
|
+
* function-call units, such as components and bare handlers inside
|
|
57
|
+
* one repo, have no key at all.
|
|
58
|
+
*/
|
|
59
|
+
identityKey(semantics) {
|
|
60
|
+
if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
|
|
61
|
+
return `fn:${semantics.package}::${semantics.exportPath.join(".")}`;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// src/semantics/graphqlOperation.ts
|
|
69
|
+
import { z as z2 } from "zod";
|
|
70
|
+
var GraphqlOperationSemanticsSchema = z2.object({
|
|
71
|
+
name: z2.literal("graphql-operation"),
|
|
72
|
+
/** Optional operation name. An anonymous query or mutation leaves it unset. */
|
|
73
|
+
operationName: z2.string().optional(),
|
|
74
|
+
operationType: z2.enum(["query", "mutation", "subscription"])
|
|
75
|
+
});
|
|
76
|
+
var graphqlOperationSemantics = defineBoundarySemantics({
|
|
77
|
+
name: "graphql-operation",
|
|
78
|
+
schema: GraphqlOperationSemanticsSchema,
|
|
79
|
+
behavior: {
|
|
80
|
+
/**
|
|
81
|
+
* An operation gets a data-and-errors document back, which the
|
|
82
|
+
* GraphQL contract checker reads. Status codes say nothing here.
|
|
83
|
+
*/
|
|
84
|
+
exchangesHttpResponses: false,
|
|
85
|
+
reportsUnpairedItself: false,
|
|
86
|
+
identityKey: () => null,
|
|
87
|
+
/** "query GetUser". The contract checker pairs it, the key does not. */
|
|
88
|
+
displayLabel(semantics) {
|
|
89
|
+
return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/semantics/graphqlResolver.ts
|
|
95
|
+
import { z as z3 } from "zod";
|
|
96
|
+
var GraphqlResolverSemanticsSchema = z3.object({
|
|
97
|
+
name: z3.literal("graphql-resolver"),
|
|
98
|
+
/**
|
|
99
|
+
* GraphQL type the resolver attaches to: "Query", "Mutation",
|
|
100
|
+
* "Subscription", or an object-type name. Null when the source never
|
|
101
|
+
* says which one, as with a `@Resolver()` class that has no argument.
|
|
102
|
+
*/
|
|
103
|
+
typeName: z3.string().min(1).nullable(),
|
|
104
|
+
/** Field name on that type. */
|
|
105
|
+
fieldName: z3.string()
|
|
106
|
+
});
|
|
107
|
+
var graphqlResolverSemantics = defineBoundarySemantics({
|
|
108
|
+
name: "graphql-resolver",
|
|
109
|
+
schema: GraphqlResolverSemanticsSchema,
|
|
110
|
+
behavior: {
|
|
111
|
+
/** A resolver returns a field value, not a status and a body. */
|
|
112
|
+
exchangesHttpResponses: false,
|
|
113
|
+
reportsUnpairedItself: false,
|
|
114
|
+
/**
|
|
115
|
+
* `"gql:<TypeName>.<fieldName>"`, or null when the type is null or
|
|
116
|
+
* the field is empty, which is how a resolver reports that the
|
|
117
|
+
* source never gave them.
|
|
118
|
+
*/
|
|
119
|
+
identityKey(semantics) {
|
|
120
|
+
if (semantics.typeName === null || semantics.fieldName === "") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return `gql:${semantics.typeName}.${semantics.fieldName}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// src/semantics/messageBus.ts
|
|
129
|
+
import { z as z4 } from "zod";
|
|
130
|
+
|
|
131
|
+
// src/channel.ts
|
|
132
|
+
function parseChannel(channel) {
|
|
133
|
+
const hash = channel.indexOf("#");
|
|
134
|
+
if (hash === -1) {
|
|
135
|
+
return { bus: null, subject: channel };
|
|
136
|
+
}
|
|
137
|
+
return { bus: channel.slice(0, hash), subject: channel.slice(hash + 1) };
|
|
138
|
+
}
|
|
139
|
+
function formatChannel(bus, subject) {
|
|
140
|
+
if (bus === null) {
|
|
141
|
+
return subject;
|
|
142
|
+
}
|
|
143
|
+
return `${bus}#${subject}`;
|
|
144
|
+
}
|
|
145
|
+
function busesAgree(a, b) {
|
|
146
|
+
return a === null || b === null || a === b;
|
|
147
|
+
}
|
|
148
|
+
function channelsPair(a, b) {
|
|
149
|
+
const left = parseChannel(a);
|
|
150
|
+
const right = parseChannel(b);
|
|
151
|
+
return left.subject === right.subject && busesAgree(left.bus, right.bus);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/semantics/messageBus.ts
|
|
155
|
+
var MessageBusSemanticsSchema = z4.object({
|
|
156
|
+
name: z4.literal("message-bus"),
|
|
157
|
+
messageBus: z4.enum([
|
|
158
|
+
"sqs",
|
|
159
|
+
"sns",
|
|
160
|
+
"s3",
|
|
161
|
+
"eventbridge",
|
|
162
|
+
"bullmq",
|
|
163
|
+
"kafka",
|
|
164
|
+
"nats"
|
|
165
|
+
]),
|
|
166
|
+
/**
|
|
167
|
+
* Stable channel identifier: a CFN logical id, a queue or topic name,
|
|
168
|
+
* a subject pattern, or `bus#detailType`. Null when this source does
|
|
169
|
+
* not say which channel, as with a send whose queue the code picks at
|
|
170
|
+
* runtime, or a receive whose queue the event-source mapping states.
|
|
171
|
+
*/
|
|
172
|
+
channel: z4.string().min(1).nullable()
|
|
173
|
+
});
|
|
174
|
+
var messageBusSemantics = defineBoundarySemantics({
|
|
175
|
+
name: "message-bus",
|
|
176
|
+
schema: MessageBusSemanticsSchema,
|
|
177
|
+
behavior: {
|
|
178
|
+
/** A message goes onto the channel and nothing comes back. */
|
|
179
|
+
exchangesHttpResponses: false,
|
|
180
|
+
/**
|
|
181
|
+
* `checkMessageBus` looks at every channel, including the unused
|
|
182
|
+
* ones, so the generic unmatched lists leave them alone.
|
|
183
|
+
*/
|
|
184
|
+
reportsUnpairedItself: true,
|
|
185
|
+
/**
|
|
186
|
+
* `"bus:<messageBus> <subject>"`, or null when the channel is null
|
|
187
|
+
* or its subject is empty.
|
|
188
|
+
*
|
|
189
|
+
* The key has the subject in it and leaves out the bus, so a
|
|
190
|
+
* template writing `default#order.placed` and a handler writing
|
|
191
|
+
* `order.placed` land in one bucket, where `sidesAgree` compares the
|
|
192
|
+
* buses. A side that cannot know its bus would otherwise be keyed
|
|
193
|
+
* away from the side that can.
|
|
194
|
+
*
|
|
195
|
+
* The bus technology stays in, because a queue and an event router
|
|
196
|
+
* are different destinations even with the same subject. Case stays
|
|
197
|
+
* too, because AWS compares detail-types and queue ids byte for byte.
|
|
198
|
+
*/
|
|
199
|
+
identityKey(semantics) {
|
|
200
|
+
if (semantics.channel === null) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const { subject } = parseChannel(semantics.channel);
|
|
204
|
+
if (subject === "") {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return `bus:${semantics.messageBus} ${subject}`;
|
|
208
|
+
},
|
|
209
|
+
sidesAgree(a, b) {
|
|
210
|
+
if (a.channel === null || b.channel === null) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return channelsPair(a.channel, b.channel);
|
|
214
|
+
},
|
|
215
|
+
/**
|
|
216
|
+
* The whole channel, bus included, unlike the identity key, which
|
|
217
|
+
* has the subject alone. Someone reading a list of unmatched
|
|
218
|
+
* channels wants to see which bus each one was on.
|
|
219
|
+
*/
|
|
220
|
+
displayLabel(semantics) {
|
|
221
|
+
if (semantics.channel === null) {
|
|
222
|
+
return `bus:${semantics.messageBus} (channel named at runtime)`;
|
|
223
|
+
}
|
|
224
|
+
return `bus:${semantics.messageBus} ${semantics.channel}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// src/semantics/rest.ts
|
|
230
|
+
import { z as z5 } from "zod";
|
|
231
|
+
var RestSemanticsSchema = z5.object({
|
|
232
|
+
name: z5.literal("rest"),
|
|
233
|
+
/**
|
|
234
|
+
* Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
|
|
235
|
+
* responds to every method, or null when this source does not say
|
|
236
|
+
* which method.
|
|
237
|
+
*/
|
|
238
|
+
method: z5.string().min(1).nullable(),
|
|
239
|
+
/** Normalized route path ("/users/{id}"), or null when this source does not give one. */
|
|
240
|
+
path: z5.string().min(1).nullable(),
|
|
241
|
+
/**
|
|
242
|
+
* Status codes the producing source explicitly declared (OpenAPI
|
|
243
|
+
* responses, CFN MethodResponses, ts-rest router statuses). They are
|
|
244
|
+
* kept here so the pairing layer can see them without unwrapping any
|
|
245
|
+
* metadata. Absent, or empty, for a source that was inferred.
|
|
246
|
+
*/
|
|
247
|
+
declaredResponses: z5.array(z5.number()).optional()
|
|
248
|
+
});
|
|
249
|
+
function normalizePath(path) {
|
|
250
|
+
let normalized = path.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
251
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
252
|
+
normalized = normalized.slice(0, -1);
|
|
253
|
+
}
|
|
254
|
+
normalized = normalized.replace(
|
|
255
|
+
/\{[^}]+\}|[^{]+/g,
|
|
256
|
+
(segment) => segment.startsWith("{") ? segment : segment.toLowerCase()
|
|
257
|
+
);
|
|
258
|
+
return normalized;
|
|
259
|
+
}
|
|
260
|
+
function methodsAgree(a, b) {
|
|
261
|
+
if (a === null || b === null) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return a === b || a === "*" || b === "*";
|
|
265
|
+
}
|
|
266
|
+
function routePathRegex(pattern) {
|
|
267
|
+
const source = pattern.split(/(\{[^}]+\}|\*)/g).map((part) => {
|
|
268
|
+
if (part === "*") {
|
|
269
|
+
return ".*";
|
|
270
|
+
}
|
|
271
|
+
if (part.startsWith("{") && part.endsWith("}")) {
|
|
272
|
+
return "[^/]+";
|
|
273
|
+
}
|
|
274
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
275
|
+
}).join("");
|
|
276
|
+
return new RegExp(`^${source}$`);
|
|
277
|
+
}
|
|
278
|
+
function routePathAdmits(declaredPath, requestPath) {
|
|
279
|
+
return routePathRegex(normalizePath(declaredPath)).test(
|
|
280
|
+
normalizePath(requestPath)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
var restSemantics = defineBoundarySemantics({
|
|
284
|
+
name: "rest",
|
|
285
|
+
schema: RestSemanticsSchema,
|
|
286
|
+
behavior: {
|
|
287
|
+
/** A request goes out, a status and a body come back. */
|
|
288
|
+
exchangesHttpResponses: true,
|
|
289
|
+
reportsUnpairedItself: false,
|
|
290
|
+
/** `"METHOD /normalized/path"`, or null when either half is missing. */
|
|
291
|
+
identityKey(semantics) {
|
|
292
|
+
if (semantics.method === null || semantics.path === null) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
|
|
296
|
+
},
|
|
297
|
+
/**
|
|
298
|
+
* The bucket has only the path in it, so a `"*"` route lands with
|
|
299
|
+
* the consumers that each specify a method, and `methodsAgree`
|
|
300
|
+
* settles it inside the bucket. The identity key stays what a reader
|
|
301
|
+
* sees and a suppression targets ("GET /users", "* /users"), and
|
|
302
|
+
* this key groups what a reader still tells apart.
|
|
303
|
+
*/
|
|
304
|
+
pairingKey(semantics) {
|
|
305
|
+
if (semantics.method === null || semantics.path === null) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return `rest ${normalizePath(semantics.path)}`;
|
|
309
|
+
},
|
|
310
|
+
sidesAgree(a, b) {
|
|
311
|
+
return methodsAgree(a.method, b.method);
|
|
312
|
+
},
|
|
313
|
+
/**
|
|
314
|
+
* What the identity key says, with a missing half still readable:
|
|
315
|
+
* ANY when no method was stated, and ? when no path was. Null when
|
|
316
|
+
* neither was stated, because then there is nothing to show.
|
|
317
|
+
*/
|
|
318
|
+
displayLabel(semantics) {
|
|
319
|
+
if (semantics.method === null && semantics.path === null) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
const method = semantics.method === null ? "ANY" : semantics.method.toUpperCase();
|
|
323
|
+
const path = semantics.path === null ? "?" : normalizePath(semantics.path);
|
|
324
|
+
return `${method} ${path}`;
|
|
325
|
+
},
|
|
326
|
+
/**
|
|
327
|
+
* A route with both halves stated decides for itself: the method
|
|
328
|
+
* through `methodsAgree` (so a `"*"` route matches every method),
|
|
329
|
+
* and the path through `routePathAdmits`. A route missing either
|
|
330
|
+
* half might still be the one that handles the request, and nothing
|
|
331
|
+
* here can settle that, so it abstains instead of refusing.
|
|
332
|
+
*/
|
|
333
|
+
servesRequest(semantics, method, path) {
|
|
334
|
+
if (semantics.method === null || semantics.path === null) {
|
|
335
|
+
return "unknown";
|
|
336
|
+
}
|
|
337
|
+
if (!methodsAgree(semantics.method, method.toUpperCase())) {
|
|
338
|
+
return "nomatch";
|
|
339
|
+
}
|
|
340
|
+
return routePathAdmits(semantics.path, path) ? "match" : "nomatch";
|
|
341
|
+
},
|
|
342
|
+
ruleBoundary: {
|
|
343
|
+
// "METHOD /path": one leading token, then an absolute path.
|
|
344
|
+
claims(raw) {
|
|
345
|
+
return /^\S+ +\//.test(raw.trim());
|
|
346
|
+
},
|
|
347
|
+
normalize(raw) {
|
|
348
|
+
const trimmed = raw.trim();
|
|
349
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
350
|
+
const method = trimmed.slice(0, spaceIdx).toUpperCase();
|
|
351
|
+
return `${method} ${normalizePath(trimmed.slice(spaceIdx + 1).trim())}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// src/semantics/runtimeConfig.ts
|
|
358
|
+
import { z as z7 } from "zod";
|
|
359
|
+
|
|
360
|
+
// src/deployableUnit.ts
|
|
361
|
+
import { z as z6 } from "zod";
|
|
362
|
+
var DeployableUnitSchema = z6.object({
|
|
363
|
+
deploymentTarget: z6.enum([
|
|
364
|
+
"lambda",
|
|
365
|
+
"ecs-task",
|
|
366
|
+
"container",
|
|
367
|
+
"k8s-deployment"
|
|
368
|
+
]),
|
|
369
|
+
// An empty name would agree with every other empty name, so a unit
|
|
370
|
+
// with no name has to leave the field off instead.
|
|
371
|
+
instanceName: z6.string().min(1)
|
|
372
|
+
});
|
|
373
|
+
function ecsContainerInstanceName(taskDefinitionLogicalId, containerName) {
|
|
374
|
+
return `${taskDefinitionLogicalId}/${containerName}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/semantics/runtimeConfig.ts
|
|
378
|
+
var RuntimeConfigSemanticsSchema = DeployableUnitSchema.extend({
|
|
379
|
+
name: z7.literal("runtime-config")
|
|
380
|
+
});
|
|
381
|
+
var runtimeConfigSemantics = defineBoundarySemantics({
|
|
382
|
+
name: "runtime-config",
|
|
383
|
+
schema: RuntimeConfigSemanticsSchema,
|
|
384
|
+
behavior: {
|
|
385
|
+
/** A process reads its config at startup, and nothing replies. */
|
|
386
|
+
exchangesHttpResponses: false,
|
|
387
|
+
reportsUnpairedItself: false,
|
|
388
|
+
identityKey: () => null
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// src/semantics/storageRelational.ts
|
|
393
|
+
import { z as z8 } from "zod";
|
|
394
|
+
var StorageRelationalSemanticsSchema = z8.object({
|
|
395
|
+
name: z8.literal("storage-relational"),
|
|
396
|
+
storageSystem: z8.enum(["postgres", "mysql", "sqlite"]),
|
|
397
|
+
/**
|
|
398
|
+
* The ORM or driver scope. A single-database setup uses `"default"`,
|
|
399
|
+
* and a monorepo with several schemas gives each one its own value.
|
|
400
|
+
*/
|
|
401
|
+
scope: z8.string(),
|
|
402
|
+
/** The table or model name, as declared in the schema. */
|
|
403
|
+
table: z8.string()
|
|
404
|
+
});
|
|
405
|
+
var storageRelationalSemantics = defineBoundarySemantics({
|
|
406
|
+
name: "storage-relational",
|
|
407
|
+
schema: StorageRelationalSemanticsSchema,
|
|
408
|
+
behavior: {
|
|
409
|
+
/** A query returns rows, not a status and a body. */
|
|
410
|
+
exchangesHttpResponses: false,
|
|
411
|
+
reportsUnpairedItself: false,
|
|
412
|
+
identityKey: () => null
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// src/semantics/registry.ts
|
|
417
|
+
var SemanticsSchema = z9.discriminatedUnion("name", [
|
|
418
|
+
restSemantics.schema,
|
|
419
|
+
functionCallSemantics.schema,
|
|
420
|
+
graphqlResolverSemantics.schema,
|
|
421
|
+
graphqlOperationSemantics.schema,
|
|
422
|
+
runtimeConfigSemantics.schema,
|
|
423
|
+
storageRelationalSemantics.schema,
|
|
424
|
+
messageBusSemantics.schema
|
|
425
|
+
]);
|
|
426
|
+
var DEFINITIONS = [
|
|
427
|
+
restSemantics,
|
|
428
|
+
functionCallSemantics,
|
|
429
|
+
graphqlResolverSemantics,
|
|
430
|
+
graphqlOperationSemantics,
|
|
431
|
+
runtimeConfigSemantics,
|
|
432
|
+
storageRelationalSemantics,
|
|
433
|
+
messageBusSemantics
|
|
434
|
+
];
|
|
435
|
+
var BY_NAME = new Map(
|
|
436
|
+
DEFINITIONS.map((d) => [d.name, d])
|
|
437
|
+
);
|
|
438
|
+
function behaviorOf(semantics) {
|
|
439
|
+
const definition = BY_NAME.get(semantics.name);
|
|
440
|
+
if (definition === void 0) {
|
|
441
|
+
throw new Error(`no boundary behavior for semantics "${semantics.name}"`);
|
|
442
|
+
}
|
|
443
|
+
return definition.behavior;
|
|
444
|
+
}
|
|
445
|
+
function allBehaviors() {
|
|
446
|
+
return DEFINITIONS.map((d) => d.behavior);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/schemas.ts
|
|
450
|
+
var ConfidenceSourceSchema = z10.enum([
|
|
451
|
+
"inferred_static",
|
|
452
|
+
"inferred_ai",
|
|
453
|
+
"declared",
|
|
454
|
+
"derived"
|
|
455
|
+
]);
|
|
456
|
+
var ConfidenceLevelSchema = z10.enum(["high", "medium", "low"]);
|
|
457
|
+
var CorroborationSchema = z10.object({
|
|
458
|
+
outcome: z10.enum(["observed", "refuted", "untested"]),
|
|
459
|
+
/** Executions that produced a verdict for this claim. */
|
|
460
|
+
runs: z10.number(),
|
|
461
|
+
/** Present when refuted: the input and observation that disagreed. */
|
|
462
|
+
counterexample: z10.unknown().optional(),
|
|
463
|
+
/** Present when untested: why no verdict was reachable. */
|
|
464
|
+
reason: z10.string().optional()
|
|
465
|
+
});
|
|
466
|
+
var ConfidenceInfoSchema = z10.object({
|
|
467
|
+
source: ConfidenceSourceSchema,
|
|
468
|
+
level: ConfidenceLevelSchema,
|
|
469
|
+
corroboration: CorroborationSchema.optional()
|
|
470
|
+
});
|
|
471
|
+
var SourceLocationSchema = z10.object({
|
|
472
|
+
file: z10.string(),
|
|
473
|
+
range: z10.object({ start: z10.number(), end: z10.number() }),
|
|
474
|
+
exportName: z10.string().nullable(),
|
|
475
|
+
/**
|
|
476
|
+
* The project the extract was pointed at, by the name that project
|
|
477
|
+
* calls itself.
|
|
478
|
+
*
|
|
479
|
+
* Paths are relative to wherever the extract ran, so two services in
|
|
480
|
+
* one repository both report `src/handlers.ts`, and merging their
|
|
481
|
+
* summaries puts them on top of each other. This field is what tells
|
|
482
|
+
* them apart, and what a reader groups by.
|
|
483
|
+
*/
|
|
484
|
+
workspace: z10.string().optional()
|
|
485
|
+
});
|
|
486
|
+
var BoundaryBindingSchema = z10.object({
|
|
487
|
+
transport: z10.string(),
|
|
488
|
+
semantics: SemanticsSchema,
|
|
489
|
+
recognition: z10.string()
|
|
490
|
+
});
|
|
491
|
+
function typeDefinitionKey(ref) {
|
|
492
|
+
return ref.def ?? null;
|
|
493
|
+
}
|
|
494
|
+
var TypeShapeSchema = z10.lazy(
|
|
495
|
+
() => z10.discriminatedUnion("type", [
|
|
496
|
+
z10.object({
|
|
497
|
+
type: z10.literal("record"),
|
|
498
|
+
properties: z10.record(z10.string(), TypeShapeSchema),
|
|
499
|
+
spreads: z10.array(z10.object({ sourceText: z10.string() })).optional()
|
|
500
|
+
}),
|
|
501
|
+
z10.object({ type: z10.literal("dictionary"), values: TypeShapeSchema }),
|
|
502
|
+
z10.object({ type: z10.literal("array"), items: TypeShapeSchema }),
|
|
503
|
+
z10.object({
|
|
504
|
+
type: z10.literal("literal"),
|
|
505
|
+
value: z10.union([z10.string(), z10.number(), z10.boolean()]),
|
|
506
|
+
raw: z10.string().optional()
|
|
507
|
+
}),
|
|
508
|
+
z10.object({ type: z10.literal("text") }),
|
|
509
|
+
z10.object({ type: z10.literal("integer") }),
|
|
510
|
+
z10.object({ type: z10.literal("number") }),
|
|
511
|
+
z10.object({ type: z10.literal("boolean") }),
|
|
512
|
+
z10.object({ type: z10.literal("null") }),
|
|
513
|
+
z10.object({ type: z10.literal("undefined") }),
|
|
514
|
+
z10.object({
|
|
515
|
+
type: z10.literal("union"),
|
|
516
|
+
variants: z10.array(TypeShapeSchema)
|
|
517
|
+
}),
|
|
518
|
+
z10.object({
|
|
519
|
+
type: z10.literal("ref"),
|
|
520
|
+
name: z10.string(),
|
|
521
|
+
from: z10.string().optional(),
|
|
522
|
+
def: z10.string().optional()
|
|
523
|
+
}),
|
|
524
|
+
z10.object({ type: z10.literal("unknown") })
|
|
525
|
+
])
|
|
526
|
+
);
|
|
527
|
+
var MAX_DEFINITION_DEPTH = 6;
|
|
528
|
+
function withDefinitionsInlined(shape, definitions, depth = 0, inProgress = /* @__PURE__ */ new Set()) {
|
|
529
|
+
if (definitions === void 0 || depth >= MAX_DEFINITION_DEPTH) {
|
|
530
|
+
return shape;
|
|
531
|
+
}
|
|
532
|
+
const deeper = (inner) => withDefinitionsInlined(inner, definitions, depth + 1, inProgress);
|
|
533
|
+
if (shape.type === "ref") {
|
|
534
|
+
const key = typeDefinitionKey(shape);
|
|
535
|
+
if (key === null) {
|
|
536
|
+
return shape;
|
|
537
|
+
}
|
|
538
|
+
const defined = definitions[key];
|
|
539
|
+
if (defined === void 0 || inProgress.has(key)) {
|
|
540
|
+
return shape;
|
|
541
|
+
}
|
|
542
|
+
return withDefinitionsInlined(
|
|
543
|
+
defined,
|
|
544
|
+
definitions,
|
|
545
|
+
depth,
|
|
546
|
+
/* @__PURE__ */ new Set([...inProgress, key])
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
if (shape.type === "record") {
|
|
550
|
+
const properties = {};
|
|
551
|
+
for (const [name, value] of Object.entries(shape.properties)) {
|
|
552
|
+
properties[name] = deeper(value);
|
|
553
|
+
}
|
|
554
|
+
return { ...shape, properties };
|
|
555
|
+
}
|
|
556
|
+
if (shape.type === "array") {
|
|
557
|
+
return { ...shape, items: deeper(shape.items) };
|
|
558
|
+
}
|
|
559
|
+
if (shape.type === "dictionary") {
|
|
560
|
+
return { ...shape, values: deeper(shape.values) };
|
|
561
|
+
}
|
|
562
|
+
if (shape.type === "union") {
|
|
563
|
+
return { ...shape, variants: shape.variants.map(deeper) };
|
|
564
|
+
}
|
|
565
|
+
return shape;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export {
|
|
569
|
+
FunctionCallSemanticsSchema,
|
|
570
|
+
GraphqlOperationSemanticsSchema,
|
|
571
|
+
GraphqlResolverSemanticsSchema,
|
|
572
|
+
parseChannel,
|
|
573
|
+
formatChannel,
|
|
574
|
+
busesAgree,
|
|
575
|
+
channelsPair,
|
|
576
|
+
MessageBusSemanticsSchema,
|
|
577
|
+
RestSemanticsSchema,
|
|
578
|
+
normalizePath,
|
|
579
|
+
methodsAgree,
|
|
580
|
+
routePathAdmits,
|
|
581
|
+
DeployableUnitSchema,
|
|
582
|
+
ecsContainerInstanceName,
|
|
583
|
+
RuntimeConfigSemanticsSchema,
|
|
584
|
+
StorageRelationalSemanticsSchema,
|
|
585
|
+
SemanticsSchema,
|
|
586
|
+
behaviorOf,
|
|
587
|
+
allBehaviors,
|
|
588
|
+
ConfidenceSourceSchema,
|
|
589
|
+
ConfidenceLevelSchema,
|
|
590
|
+
CorroborationSchema,
|
|
591
|
+
ConfidenceInfoSchema,
|
|
592
|
+
SourceLocationSchema,
|
|
593
|
+
BoundaryBindingSchema,
|
|
594
|
+
typeDefinitionKey,
|
|
595
|
+
TypeShapeSchema,
|
|
596
|
+
withDefinitionsInlined
|
|
597
|
+
};
|