import { z } from 'zod'; import { T as TypeShape, M as MessageBusSemantics, D as DeployableUnit, S as Semantics, B as BoundaryBindingSchema, C as ConfidenceInfoSchema, a as ConfidenceLevelSchema, b as ConfidenceSourceSchema, c as CorroborationSchema, d as SourceLocationSchema, e as MessageBusTechnology } from './schemas-CghZNwSE.cjs'; export { f as DeployableUnitSchema, E as EVERY_FIELD, F as FunctionCallSemantics, g as FunctionCallSemanticsSchema, G as GraphqlOperationSemantics, h as GraphqlOperationSemanticsSchema, i as GraphqlResolverSemantics, j as GraphqlResolverSemanticsSchema, k as MessageBusSemanticsSchema, R as RestSemantics, l as RestSemanticsSchema, m as RuntimeConfigSemantics, n as RuntimeConfigSemanticsSchema, o as SemanticsSchema, p as StorageSemantics, q as StorageSemanticsSchema, r as TypeShapeSchema, s as ecsContainerInstanceName, t as isModuleName, u as methodsAgree, v as normalizePath, w as pathShape, x as routePathAdmits, y as routePathsMeet, z as storageContainerLabel, A as storageLabel, H as storageSystemLabel, I as typeDefinitionKey, J as withDefinitionsInlined } from './schemas-CghZNwSE.cjs'; /** * The boundary-name syntax, parsed and printed in one place. * * A name in a summary is one of three things, and the braces alone tell * them apart: a literal (`orders-v1`), a pattern with holes a deployment * fills (`{stage}-orders-v1`), or a reference that says where to look * the name up (`{location.bucket}`, `{ORDER_TABLE}`). A second parser * could classify a string differently, and a second printer could write * a string this parser cannot read back. So all code that reads or * writes the braces goes through `parseBoundaryName` and * `boundaryNameString`, and the other helpers here are built on those * two. The package README has the longer explanation, including why * REST route paths and message-bus channels are handled separately. */ /** One piece of a pattern: fixed text, or a hole. */ type NamePart = { type: "text"; text: string; } | { type: "hole"; label: string; }; /** * A parsed boundary name. A `reference` keeps its raw dot-separated * path, and `referenceOf` checks that every part is present. A * malformed reference still classifies as a reference, so it still * pairs with nothing. */ type BoundaryName = { type: "literal"; value: string; } | { type: "pattern"; parts: NamePart[]; } | { type: "reference"; path: string[]; }; /** Parse a name string. The exact inverse of `boundaryNameString`. */ declare function parseBoundaryName(name: string): BoundaryName; /** The name as a summary writes it. The exact inverse of `parseBoundaryName`. */ declare function boundaryNameString(name: BoundaryName): string; /** * The string for one hole. Code that builds a pattern one part at a * time calls this for each hole, so the result always parses back. */ declare function patternHole(label: string): string; /** * The name pattern a CloudFormation `Fn::Sub` value produces, with each * `${X}` turned into a hole. In the array form the template is the * first element. The variable map only gives each substitution's * source, which does not change the pattern. */ declare function namePatternFromSub(value: unknown): string | null; /** * Whether a name is a reference, which gives where to look the name up * and not the name itself. A wrapper that takes its bucket as an * argument writes `{location.bucket}`. Such a name agrees with nothing * until it is grounded, since otherwise it would agree with every name. */ declare function namesNothing(name: string): boolean; /** Whether a name has any hole left for the deployment to fill. */ declare function hasNameHole(name: string): boolean; /** * The name with every hole label blanked, for comparing two patterns. * Each side chooses its own label for a parameter, so labels never * decide a match. */ declare function namePatternKey(name: string): string; /** How many characters of fixed text a name has. A reference has none. */ declare function fixedTextLength(name: string): number; /** * Whether two names are the same name. Two patterns agree when their * fixed parts line up, since a hole on one side meets a hole on the * other. A pattern and a concrete name agree when the fixed text is * where the pattern puts it. That happens when one side hardcoded what * the other parameterized. A reference agrees with nothing. */ declare function namesAgree(a: string, b: string): boolean; /** A parsed reference: the value the code starts from, and the fields it reads inside it. */ interface Reference { /** * A parameter of the unit the reference was written in, or a variable * the deployment sets. The string does not record which. The grounding * pass works that out from the unit's inputs. */ root: string; /** The fields read inside the root, outermost first. */ fields: string[]; } /** * The string for a reference, such as `{location.bucket}`. Null when * any part is empty, since such a reference could never be settled. */ declare function referenceName(reference: Reference): string | null; /** * The `Reference` in a parsed name. Null when the name is not a * reference, or when a part of it is empty and nothing could settle it. */ declare function referenceOf(name: BoundaryName): Reference | null; /** The `Reference` in a name string, or null when the name is a literal or a pattern. */ declare function referenceFromName(name: string): Reference | null; /** * The values a deployment sets for the variables its code reads. * * A queue URL, a function name and a table name only exist once a stack * is deployed, so the source reaches them through a variable and the * template sets that variable. A protocol whose boundary name has a * hole in it looks the hole up through this interface. * * The interface is declared in ir-core so a protocol can use it without * depending on the summary types. `@suss/behavioral-ir` implements it * from a set of summaries. */ interface Deployment { /** * The variable a reference points at. Null when a caller's argument * settles the reference and the deployment has no say in it. */ variableFor(reference: Reference): string | null; /** * The string this deployment sets that variable to. Null when it sets * nothing, and null when two deployments of this code set it to * different strings, since picking one of two would be a guess. */ setTo(reference: Reference): string | null; /** * The logical id of the declared resource this deployment wires the * variable to. A template that writes `!Ref ArchiveWorker` never gives * the deployed function's name, but the unit's own summary is keyed by * that logical id, so an invoke can still be matched to its unit. */ pointsAt(reference: Reference): string | null; } /** A deployment that settles nothing, for a run with no template in it. */ declare const NOTHING_DEPLOYED: Deployment; /** * Structural comparison of two TypeShapes. * * The behavioral checker compares a provider body against a declared * contract, and the intent checker compares a code body against declared * intent. The two have to reach the same verdict for the same pair, so * the comparison is defined next to TypeShape and both checkers import * it. * * The comparison is asymmetric and has three results. `unknown` keeps a * shape that cannot be inspected from being reported as a match. */ /** * The result of comparing two shapes: * - "match": `actual` satisfies `declared` * - "nomatch": the shapes have a specific, checkable incompatibility * - "unknown": part of a shape cannot be inspected (a spread, a ref, an * unknown shape), and calling it a match could hide a mismatch */ type MatchResult = "match" | "nomatch" | "unknown"; /** * Whether an actual body satisfies a declared body. * * The arguments are not interchangeable. `actual` is the value that is * produced (a response body, a return value), and `declared` is the * contract it has to conform to. The caller decides whether to report * an `"unknown"` result. */ declare function bodyShapesMatch(actual: TypeShape, declared: TypeShape): MatchResult; /** * The interface a boundary protocol implements. * * Each protocol (REST, message-bus, one side of GraphQL) is one module * in this directory. Its schema lists the identity fields, and its * behavior defines how those fields key and agree. The two are defined * together, so neither can be added without the other. * * The definitions ship with ir-core and not with the packs, because a * published summary has to mean the same thing to a reader who never * installed the pack that wrote it. A pack that needs a new protocol * adds a module here. Each protocol also maps its fields to * OpenTelemetry attributes in `semconv`, as the package README explains. */ /** One identity field written as an OpenTelemetry attribute. */ interface SemconvAttribute { /** The attribute name, as the semantic conventions spell it. */ name: string; /** * Values suss writes when the source gave none. A span never has * them, so the projection leaves the attribute out instead of writing * a value that could never match. */ placeholderValues?: readonly string[]; } /** * Which identity fields the semantic conventions have an attribute * for. A field is absent when the conventions have no attribute for it, * or when suss writes its own value there instead of the one a span gets. */ type SemconvMapping = { readonly [K in Exclude]?: SemconvAttribute; }; /** * How one protocol keys its boundaries and pairs them up. Each protocol * implements this in its own module, and every rule for how its * boundaries key or agree is defined there. */ interface BoundaryBehavior { /** Null when the source never gave the boundary a name. */ identityKey(semantics: S): string | null; /** * The bucket the pairing pass groups by. Defaults to `identityKey`. * Define it where one side can know more than its counterpart, and put * only the shared part in the bucket: a `"*"` REST route has to meet * consumers that each specify one method, so REST buckets by path * alone. Whatever the bucket leaves out, `sidesAgree` has to compare. */ pairingKey?(semantics: S): string | null; /** * Compares the fields `pairingKey` left out of the bucket. When a * protocol does not define it, every pair in a bucket agrees. */ sidesAgree?(a: S, b: S): boolean; /** * Whether this boundary's bucket can meet buckets with other keys. A * REST path with a hole that spans some number of segments has a key * of its own and serves what several other keys serve, so the pairing * pass compares it against every bucket on the other side with * `bucketsMeet`. A variant that does not define it meets only its own * key. */ spansBuckets?(semantics: S): boolean; /** Whether two buckets describe at least one boundary in common. */ bucketsMeet?(a: S, b: S): boolean; /** * How narrowly this bucket states what it serves, compared * lexicographically. When a consumer meets more than one bucket, it * reaches the one ranked highest, and a tie is reported instead of * paired. */ bucketRank?(semantics: S): readonly number[]; /** The label a reader sees for this boundary. Defaults to `identityKey`. */ displayLabel?(semantics: S): string | null; /** * The semantics with every filesystem path in it rewritten, for the * pass that makes a summary's paths project-relative. A protocol * without paths leaves this undefined. */ rewritePaths?(semantics: S, rewrite: (path: string) => string): S; /** * How a hand-written suppression boundary is normalized. The first * protocol whose `claims` returns true normalizes the string, and a * string no protocol claims is compared byte for byte. A protocol * whose keys compare exactly leaves this undefined. */ ruleBoundary?: { claims(raw: string): boolean; normalize(raw: string): string; }; /** * Whether this boundary serves a request with the given method and * path. A protocol whose boundaries have no URL leaves this undefined. * Undefined means the question does not apply, and an `"unknown"` * result means this declaration cannot settle it. */ servesRequest?(semantics: S, method: string, path: string): MatchResult; /** * Whether every request this boundary takes falls inside a pattern * something else was registered for. Middleware registered for * `/v1/*` runs for `/v1/tenants/{id}` and not for `/health`, so * composing middleware into a unit checks this first. * * A protocol whose boundaries no pattern addresses leaves this * undefined, and nothing registered with a pattern reaches them. */ withinScope?(semantics: S, scope: string): boolean; /** * The same boundary with the deployment's values filled into its name. * * A call written as `fetch(`${process.env.API_BASE}/orders`)` gets * part of its boundary from the source and part from whatever runs * the code. Both parts are needed before the two sides can be matched * as one boundary. * * `deployment` is already scoped to the unit this boundary belongs * to. The protocol looks up the reference in its name through it. * Return null to leave the semantics unchanged, when nothing needs * filling in or nothing can fill it. A protocol whose names are fixed * in the source leaves this undefined. */ groundName?(semantics: S, deployment: Deployment): S | null; /** * The reference in this boundary's name, when the name is a * reference. A caller explaining why two sides did not pair uses it * to say which input would settle the name. * * A protocol that defines `groundName` defines this too, and * `groundName` looks up the same reference. */ nameReference?(semantics: S): Reference | null; /** * Whether a provider produces a status and a body that a consumer * reads back. It is required, so a new protocol cannot end up in the * HTTP-style checks because nobody remembered to exclude it. */ exchangesHttpResponses: boolean; /** * Whether crossing this boundary leaves the process. A report on what * a change means for callers outside the code covers only these. A * call between two functions in one project is a boundary too, and a * reader has the source diff for it. */ leavesTheProcess: boolean; /** * Whether this protocol's own pass already reports a boundary that * paired with nothing, so the generic unmatched list can leave it out. */ reportsUnpairedItself: boolean; /** * Whether a boundary with these semantics can pair with anything. * Defaults to a non-null pairing key. A protocol whose dedicated * pass pairs keyless boundaries overrides it, the way GraphQL * operations pair by document rather than by key. */ canPair?(semantics: S): boolean; } /** * One protocol in full: the schema for its identity fields, and the * behavior that keys them. The registry composes these into the * `Semantics` union and the runtime lookup. */ interface BoundarySemanticsDefinition> { name: z.infer["name"]; schema: Z; behavior: BoundaryBehavior>; /** * Required even when empty, so adding a protocol means deciding which * of its fields are OpenTelemetry attributes. */ semconv: SemconvMapping>; } declare const IdentityKeyBrand: unique symbol; /** `gql:Type.field`, the boundary key both GraphQL sides pair on. */ type GqlIdentityKey = `gql:${string}.${string}` & { readonly [IdentityKeyBrand]: "gql"; }; declare function gqlIdentityKey(typeName: string, fieldName: string): GqlIdentityKey; /** `fn:package::export.path`, the key a package-export boundary pairs on. */ type FnIdentityKey = `fn:${string}::${string}` & { readonly [IdentityKeyBrand]: "fn"; }; declare function fnIdentityKey(packageName: string, exportPath: readonly string[]): FnIdentityKey; /** `bus:technology subject`, where the technology is one the message-bus schema allows. */ type BusIdentityKey = `bus:${MessageBusSemantics["messageBus"]} ${string}` & { readonly [IdentityKeyBrand]: "bus"; }; declare function busIdentityKey(messageBus: MessageBusSemantics["messageBus"], subject: string): BusIdentityKey; /** * `unit:platform name`, the key an invoker and the unit it invokes pair * on. The platform stays in because two clouds can each deploy * something called `Worker` and they are not the same thing. */ type UnitIdentityKey = `unit:${DeployableUnit["deploymentTarget"]} ${string}` & { readonly [IdentityKeyBrand]: "unit"; }; declare function unitIdentityKey(deploymentTarget: DeployableUnit["deploymentTarget"], instanceName: string): UnitIdentityKey; /** `metric:system type`, the key both sides of a metric pair on. */ type MetricIdentityKey = `metric:${string} ${string}` & { readonly [IdentityKeyBrand]: "metric"; }; declare function metricIdentityKey(metricSystem: string, metricType: string): MetricIdentityKey; /** `io:stream`, the key a write to one of the process's streams has. */ type IoIdentityKey = `io:${string}` & { readonly [IdentityKeyBrand]: "io"; }; declare function ioIdentityKey(target: string): IoIdentityKey; declare const IoSemanticsSchema: z.ZodObject<{ name: z.ZodLiteral<"io">; target: z.ZodNullable; }, z.core.$strip>; type IoSemantics = z.infer; declare const MetricSemanticsSchema: z.ZodObject<{ name: z.ZodLiteral<"metric">; metricSystem: z.ZodString; metricType: z.ZodNullable; }, z.core.$strip>; type MetricSemantics = z.infer; /** * `instanceName` is nullable here and required on the unit itself, * because only the provider always knows the name. A call that picks * its callee at run time still happened. Recording it with a null name * keeps the call without pairing it against every unit. */ declare const UnitInvocationSemanticsSchema: z.ZodObject<{ deploymentTarget: z.ZodEnum<{ lambda: "lambda"; "ecs-task": "ecs-task"; container: "container"; "k8s-deployment": "k8s-deployment"; worker: "worker"; }>; name: z.ZodLiteral<"unit-invocation">; instanceName: z.ZodNullable; }, z.core.$strip>; type UnitInvocationSemantics = z.infer; /** * A boundary binding read as OpenTelemetry attributes. * * A summary records what a unit can reach and a span records what it * did reach. Joining the two means both spell the boundary the same * way. suss writes the value a span gets wherever the semantic * conventions have a word for it, and this projection puts those values * under the attribute names a trace store already indexes. * * Each protocol declares its own field-to-attribute mapping next to its * definition, so this module has no protocol-specific code. */ /** * The binding's OpenTelemetry attributes, keyed by attribute name. * * A field the source never gave is left out, and so is a placeholder * value or a field the conventions have no attribute for. Every value * returned compares byte for byte against the same attribute on a span. */ declare function semconvAttributes(binding: BoundaryBinding): Record; /** * What a pack declares about itself, for tools that list or suggest * packs without loading their patterns. * * `suss init` suggests a pack when a project's manifest lists the * library, and the packages page lists every pack with a line about * what it reads. Both read these declarations, so there is no separate * table to keep in step with the packs. */ /** The manifest a dependency is declared in. */ type Ecosystem = "npm" | "pypi" | "rubygems"; /** * A value a pack cannot work out from the code, which the project has * to write down before the pack reads anything: which directory the * classes are under, which file the routes are in. */ interface PackConfiguration { /** Where to write it, relative to the project. */ file: string; /** A starting point, with this project's own values to fill in. */ example: Record; /** Whether the pack refuses to run without it. */ required: boolean; /** What the value is, in a sentence. */ why: string; /** * What `suss init` writes to `file` when it sets a project up: the * values a project the library's own generator made has. Absent when no * value suits every project. */ defaults?: Record; /** * Reads the values from a file the project already has, so init can * write them when there are no defaults. Null when the project does * not say. */ readFromProject?: (projectRoot: string) => Record | null; } interface PackDeclaration { /** * What the pack contributes. A `framework` pack discovers units, a * `client` pack binds a call to the route it reaches, and an * `effects` pack recognises calls inside units another pack found, so * on its own it comes back empty. */ kind: "framework" | "client" | "effects"; /** The npm package that ships the pack, which a project installs. */ package: string; /** * What a project has to depend on for this pack to read anything. * Several entries mean any one of them, the way react-router ships * under two names. */ dependencies: Array<{ ecosystem: Ecosystem; name: string; }>; /** * Set for a library the language itself ships, which no manifest * lists: Ruby's Net::HTTP, the browser's fetch, Node's own surface. * Every project in that language is a candidate. */ shippedWith?: "typescript" | "python" | "ruby"; /** The line the packages page shows, one sentence, what it reads. */ reads: string; /** Set when the pack needs a value from the project to run at all. */ configuration?: PackConfiguration; } /** * Keys for a boundary binding, and whether two bindings agree. Each * function looks up the binding's protocol and delegates to it, so the * rules are defined with each protocol's semantics. * * The behavioral checker pairs code summaries by these keys, and the * intent checker pairs intent against code by the same keys. If the two * keyed differently, intent and code would stop lining up, so the * keying is defined next to the binding and neither checker has its * own. */ /** * Whether a binding is of one protocol, narrowing its semantics to that * protocol's type. `name` only accepts a protocol that exists. Comparing * `binding?.semantics.name` by hand does not narrow, and a misspelled * protocol name there compiles and never matches. */ declare function bindingIs(binding: BoundaryBinding | null | undefined, name: N): binding is BoundaryBinding & { semantics: Extract; }; declare function boundaryKey(binding: BoundaryBinding): string | null; /** * The binding with every filesystem path in its semantics rewritten. * The CLI uses it to make a summary's paths project-relative. A binding * whose protocol has no paths comes back unchanged. */ declare function withRewrittenPaths(binding: BoundaryBinding, rewrite: (path: string) => string): BoundaryBinding; /** * The pairing key after the deployment's values are filled into the * boundary's name. * * A consumer that gets its base URL at run time writes a different * string from the provider it reaches, though the two are one boundary. * A protocol whose names are fixed in the source gets the same key as * `pairingKey` would give. */ declare function groundedPairingKey(binding: BoundaryBinding, deployment: Deployment): string | null; /** * The same boundary, with the deployment's values filled into its name. * * The pairing pass, the drafter that writes an intent document and the * intent checker that reads one back all ground a binding here before * reading its name. If only one of them grounded it, the drafter could * write a name the checker then disputes. */ declare function groundBinding(binding: BoundaryBinding, deployment: Deployment): BoundaryBinding; /** * The reference in this boundary's name, or null when the source gave * the name outright. A report that explains why two sides did not pair * uses it to say which input would settle the name. */ declare function nameReference(binding: BoundaryBinding): Reference | null; declare function pairingKey(binding: BoundaryBinding): string | null; declare function semanticsAgree(a: Semantics, b: Semantics): boolean; /** Whether the binding's bucket can meet buckets with other keys. */ declare function spansBuckets(binding: BoundaryBinding): boolean; /** Whether two bindings' buckets describe a boundary in common. */ declare function bucketsMeet(a: BoundaryBinding, b: BoundaryBinding): boolean; /** How narrowly the binding's bucket states what it serves; see `compareRanks`. */ declare function bucketRank(binding: BoundaryBinding): readonly number[]; declare function boundaryLabel(binding: BoundaryBinding): string | null; declare function displayLabel(binding: BoundaryBinding): string; /** * The boundary and the name a read took across it, as a report prints * them: `runtime-config ACCOUNTS_REGION`. A boundary with no label of its * own prints as its protocol, because the name already says what was * read, and the recognizer the fallback label adds is a package name * nobody reading the report can act on. */ declare function labelWithDetail(binding: BoundaryBinding, detail: string): string; /** * A suppression rule's boundary string, normalized by the protocol that * recognizes it. A string no protocol recognizes keeps its case, * because message-bus keys are case-sensitive and uppercasing one would * make the rule stop matching without any error. */ declare function normalizeRuleBoundary(raw: string): string; declare function exchangesHttpResponses(binding: BoundaryBinding): boolean; /** Whether crossing this boundary leaves the process. */ declare function leavesTheProcess(binding: BoundaryBinding): boolean; /** * Whether anything can pair with this binding: it has a pairing key, * or its protocol pairs keyless boundaries in a dedicated pass. */ declare function canPair(binding: BoundaryBinding): boolean; declare function reportsUnpairedItself(binding: BoundaryBinding): boolean; /** * Whether this binding serves a request with the given method and path. * * Null means the protocol does not address its boundaries by method and * path at all. A caller has to tell that apart from `"unknown"`, which * means the protocol does, but this declaration cannot settle it. */ declare function servesRequest(binding: BoundaryBinding, method: string, path: string): MatchResult | null; /** * Whether a wrapper registered for `scope` runs for this boundary. * False for a protocol whose boundaries no pattern addresses, so a * scoped registration reaches nothing it cannot be shown to cover. */ declare function withinScope(binding: BoundaryBinding, scope: string): boolean; /** * How a person writes a boundary they want to ask about, and whether * what they wrote picks out a given boundary. * * The text and the boundary are both split into tokens, and a boundary * matches when it has every token in the text. So * `aws.dynamodb:editions` matches the table and every index on it, and * adding `#by-publication` narrows it to the one index. * * `suss ask`, `suss check --at` and an intent document that gives the * store a write reaches all match through these functions, so a * question and an assertion resolve the same way. */ /** * The name inside a fully qualified cloud resource id. Any other string * comes back unchanged. * * `arn:aws:lambda:us-east-1:1234:function:prod-worker` and * `projects/p/locations/l/functions/prod-worker` are `prod-worker` plus * an account and a region. Two deployments of one resource have * different full ids, so readers reduce an id to its name here and the * two sides compare the part they both know. A Lambda ARN's trailing * alias or version is dropped too, since the function is the boundary * whichever published version a call reaches. */ declare function resourceNameIn(spelling: string): string; /** * The lowercase words in a boundary spelling. The text is split at the * separators between parts of a name, and each part is kept whole, so * `by-publication` stays one word and `{id}` and `:id` both become `id`. */ declare function spellingTokens(text: string): string[]; /** Every word a person can use to ask about this boundary. */ declare function bindingTokens(binding: BoundaryBinding): Set; /** * Whether the typed text has every word of this boundary's label and * nothing more. `POST /articles` matches the collection route exactly, * and only partly matches `POST /articles/{slug}/comments`. */ declare function namesBoundaryExactly(subject: string, binding: BoundaryBinding): boolean; /** Whether the typed text picks out this boundary: every word in it is one of the boundary's words. */ declare function namesBoundary(subject: string, binding: BoundaryBinding): boolean; /** * When two message-bus channel strings mean the same channel. * * A channel is a subject, optionally qualified by the bus it travels * on, written `${bus}#${subject}`. The two sides of a pairing rarely * know the same amount, so they pair on the subject, and the buses have * to match only when both sides know one. The package README explains * why, with examples. * * All code that writes or reads the string goes through `formatChannel` * and `parseChannel`. A template reader that wrote the `#` by hand would * drift from them the first time either one changed. */ /** A channel string, written `${bus}#${subject}`, split into its two parts. */ interface ParsedChannel { bus: string | null; subject: string; } declare function parseChannel(channel: string): ParsedChannel; declare const ChannelBrand: unique symbol; /** * `bus#subject`, or the bare subject when no source gave the bus. * * Fields that store a channel stay typed as `string`, since a bare * subject is legal there. The brand exists so that a `Channel` value * can only come from `formatChannel` and never from a string built by * hand (#167). */ type Channel = string & { readonly [ChannelBrand]: "channel"; }; declare function formatChannel(bus: string | null, subject: string): Channel; /** A side that does not know its bus agrees with any bus. */ declare function busesAgree(a: string | null, b: string | null): boolean; declare function channelsPair(a: string, b: string): boolean; /** * The source directory a deployable unit is built from, and the handler * string that says which code the platform calls. * * A deploy template gives each unit a directory, and a summary stores it * as `metadata.codeScope.path`. Producers write that path and the * checker reads it back as a prefix test, so both sides normalize it * through `codeScopePath` and agree on the trailing slash. The test * stops at a segment boundary, or `src/foo` would cover `src/foobar` and * a handler would pair with the wrong function. CloudFormation and * Terraform write a handler the same way, so both read it through * `parseHandler`. */ /** * The canonical form of a code-scope directory: no leading `./`, no * trailing slash, no surrounding whitespace. An empty result means the * scope is the project root. */ declare function codeScopePath(raw: string): string; /** * Whether a project-relative file is inside a code scope. Matching stops * at a segment boundary, so `src/foo` covers `src/foo/a.ts` and never * `src/foobar/a.ts`. A scope that is the project root covers every file. * * Both arguments are normalized with `codeScopePath`, so a trailing * slash on either one makes no difference. */ declare function fileInCodeScope(file: string, scope: string): boolean; interface ParsedHandler { modulePath: string; exportName: string; } /** * Split a handler string into its module path and exported symbol. * The final dot separates them: `"src/handlers/confirmToken.handler"` → * `{ modulePath: "src/handlers/confirmToken", exportName: "handler" }`. * Returns null when there's no dot, since there's no export to bind to. */ declare function parseHandler(handler: string): ParsedHandler | null; /** * Dispatch on a discriminated union through a mapped-type Record, so a * table that misses a variant fails to compile. This is decision #8 in * the internal style guide. * * Every package that walks an IR union needs it, so it lives in ir-core * and packages do not keep their own copies. */ type DispatchTable = { [K in T["type"]]: (variant: Extract) => R; }; declare function dispatchByType(table: DispatchTable, value: T): R; /** * The variables a deployment platform puts in a process's environment * whatever its configuration says. * * Code reads them and no template declares them, so without this list * every deployment would be reported for failing to supply * `AWS_REGION`. A CloudFormation template and a Terraform configuration * can describe the same Lambda, and both use this one list so they * agree on which variables are present. A platform whose vendor * documents a different set per product, such as Cloud Run with its * `K_SERVICE`, lists its variables in the pack that describes it. */ /** * Sourced from each platform's own documentation: * * Lambda: https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html * "Reserved environment variables" * ECS: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_metadata.html * plus the AWS_DEFAULT_REGION the Fargate runtime sets. */ declare const PLATFORM_INJECTED_ENV_VARS: Record>; /** * What a unit does at a boundary, in the words `suss ask` uses. * * A user asks `what writes postgresql:invoices`, and an intent doc * asserts "this outcome writes postgresql:invoices". The report, the * question parser and the intent reader all have to spell the verbs the * same way, so the verbs are defined here once. * * `provides` is a boundary the unit serves. `reads`, `writes` and * `invokes` are what a call site does at a boundary somebody else * serves. Calling a deployed unit by name is its own act, so it gets * its own verb. */ declare const RelationSchema: z.ZodEnum<{ provides: "provides"; reads: "reads"; writes: "writes"; invokes: "invokes"; }>; type Relation = z.infer; /** * The verbs an effect can have. A boundary a unit serves is not an * effect of the unit, so `provides` is not one of them. */ declare const EffectRelationSchema: z.ZodEnum<{ reads: "reads"; writes: "writes"; invokes: "invokes"; }>; type EffectRelation = z.infer; /** * Whether two route paths describe a request in common. * * A normalized path is text with holes in it. `{id}` is exactly one * segment, `{tenant?}` is zero or one, `{rest+}` is one or more and * `{rest*}` is zero or more, the modifiers Express puts on `:name`. A * bare `*` segment is zero or more segments, Express 4's reading of a * star. A set piece is written `(v1|v2)` and matches any one of its * options, and an option can contain a slash. * * `pathsMeet` expands the sets into alternatives, splits each one into * segments, and walks the two segment lists with a reachability table * in which a hole absorbs as many segments as its range allows. */ /** How many segments a hole takes: exactly one, zero or one, one or more, or any number. */ type HoleRange = "one" | "optional" | "many" | "any"; /** The path text for a hole that takes `range` segments, such as `{rest+}`. */ declare function rangedHole(name: string, range: HoleRange): string; /** * The path text for a piece that matches any one of `options`, such as * `(v1|v2)`. Null when an option would parse as something else, such as * a set boundary or the start of a query. */ declare function setPiece(options: readonly string[]): string | null; /** Negative when `a` ranks below `b`, positive above, zero when equal. */ declare function compareRanks(a: readonly number[], b: readonly number[]): number; /** * Suppression rules and how they apply to findings from either checker. * * Behavioral `Finding` and intent `IntentFinding` both have `kind`, * `severity` and an optional `suppressed`, and the pipeline needs * nothing else. The rule schema, first-match-wins matching, effect * application and threshold counting are here so the two checkers share * one implementation without depending on each other. Each checker * passes in its own matcher for a rule's discriminators past `kind`. * * The CLI reads the `.sussignore` file, checks each kind against the * published enums, and passes the parsed rules in. */ declare const SuppressionRuleSchema: z.ZodObject<{ kind: z.ZodOptional; boundary: z.ZodOptional; consumer: z.ZodOptional; transitionId: z.ZodOptional; }, z.core.$strip>>; provider: z.ZodOptional; transitionId: z.ZodOptional; }, z.core.$strip>>; scenario: z.ZodOptional; scope: z.ZodDefault>; reason: z.ZodString; effect: z.ZodDefault>; }, z.core.$strict>; type SuppressionRule = z.infer; declare const SuppressionFileSchema: z.ZodObject<{ version: z.ZodLiteral<1>; rules: z.ZodArray; boundary: z.ZodOptional; consumer: z.ZodOptional; transitionId: z.ZodOptional; }, z.core.$strip>>; provider: z.ZodOptional; transitionId: z.ZodOptional; }, z.core.$strip>>; scenario: z.ZodOptional; scope: z.ZodDefault>; reason: z.ZodString; effect: z.ZodDefault>; }, z.core.$strict>>; }, z.core.$strict>; type SuppressionFile = z.infer; type SuppressableSeverity = "error" | "warning" | "info"; /** The `suppressed` annotation stamped onto a matched finding. */ interface FindingSuppression { reason: string; effect: "mark" | "downgrade" | "hide"; /** Original severity, present only when effect is "downgrade". */ originalSeverity?: SuppressableSeverity | undefined; } /** * The fields both finding types share. Behavioral `Finding` and intent * `IntentFinding` each declare these fields in their own schemas, and * the two declarations have to stay structurally identical. The * `| undefined` unions match what zod infers for `.optional()` fields * under exactOptionalPropertyTypes. */ interface SuppressibleFinding { kind: string; severity: SuppressableSeverity; suppressed?: FindingSuppression | undefined; } /** * An error message when a rule does not constrain enough, or null when * it is valid. A rule with only a `reason` would suppress every finding * in the codebase, which is almost always a mistake. A broad rule needs * one constraint of any kind, and a narrow rule needs `kind` plus a * boundary or transition id. */ declare function validateRule(rule: SuppressionRule): string | null; /** * Whether a rule's `boundary` discriminator matches a finding's boundary * key. It tries an exact match first, which covers "fn:..." and * "gql:...", and then the REST-normalized form. */ declare function ruleBoundaryMatchesKey(ruleBoundary: string, key: string | null): boolean; /** * Apply suppression rules to a list of findings of either type. This * function checks `kind`, and `matches` checks the rest of a rule's * discriminators (boundary, consumer, and so on) against a finding. * * Returns a new array. The first matching rule applies. A finding a * `hide` rule matches is left out unless `keepHidden` is set. A finding * a `mark` or `downgrade` rule matches is kept with a `suppressed` field * added. */ declare function applySuppressionsToFindings(findings: T[], rules: SuppressionRule[], matches: (rule: SuppressionRule, finding: T) => boolean, opts?: { keepHidden?: boolean; }): T[]; /** * Whether a finding counts toward the exit-code threshold. A `hide` or * `mark` finding does not count, and a `downgrade` finding counts at the * severity it was downgraded to. */ declare function countsForThreshold(finding: SuppressibleFinding): boolean; /** * How a `test` unit is named. Every test pack names a test by its title * path, the suite titles outermost first and then the test's own, and a * PRD scenario's `coveredBy` entry is matched against that name, so the * adapters that write it and the check that reads it share this one * spelling. */ /** What joins the titles in a test unit's name, and the file and titles in a `coveredBy` entry. */ declare const TEST_TITLE_SEPARATOR = " > "; declare function testUnitName(titles: readonly string[]): string; /** * The path part of a URL, however the URL was written. * * The two sides of one boundary spell a URL differently. A provider * writes `/orders`, because a route is mounted at a path. A consumer * writes `http://backend.internal/orders`, because a call has to give a * host. Pairing them means reading the path out of both. * * The query and the fragment are removed along with the origin, because * neither one picks a route: `/orders?page=2` reaches the same handler * as `/orders`. */ /** * The path a URL states, with any origin, query, and fragment removed. * * Text without an origin comes back with only the query and the * fragment removed, since a relative URL is already a path. A URL that * is only an origin comes back as an empty string, and a caller should * treat that as the root. */ declare function pathAfterOrigin(text: string): string; /** Whether the text starts with an origin (a scheme and host, or `//host`). */ declare function statesAnOrigin(text: string): boolean; /** * @suss/ir-core: primitives shared across suss IRs. * * The zod schemas are the source of truth, and the types are derived * from them. Each boundary protocol defines its schema and its pairing * behavior together in one module. The boundary-binding constructors * are here too, so any package that produces an IR object, such as a * pattern pack or a contract reader, can build a binding without * depending on a specific IR. */ type ConfidenceSource = z.infer; type ConfidenceLevel = z.infer; type ConfidenceInfo = z.infer; type Corroboration = z.infer; type SourceLocation = z.infer; type BoundaryBinding = z.infer; /** * Build a REST-semantics binding. `method` and `path` are null when the * source does not specify them. `"*"` is the method wildcard, for a * handler that responds to every method. When pairing, null means * unspecified and matches nothing, and `"*"` matches every concrete method. */ declare function restBinding(opts: { transport: string; method: string | null; path: string | null; recognition: string; declaredResponses?: number[]; }): BoundaryBinding; /** * Build a function-call-semantics binding, for an in-process boundary * such as a React component or an exported TypeScript function. */ declare function functionCallBinding(opts: { transport: string; recognition: string; module?: string; exportName?: string; package?: string; exportPath?: string[]; }): BoundaryBinding; /** * Build a function-call binding that identifies a public package export, * which is the provider side of a library boundary. Transport defaults * to `"in-process"`, which is how a TypeScript library is usually used. */ declare function packageExportBinding(opts: { transport?: string; recognition: string; packageName: string; exportPath: string[]; }): BoundaryBinding; /** * Build a function-call binding for a public export of one of the * modules a project lists in `suss.json`. Its key is * `fn:::`, so an intent document or a PRD scenario * can point at it by the module's name. */ declare function moduleExportBinding(opts: { recognition: string; module: string; exportName: string; }): BoundaryBinding; /** * Build a graphql-resolver-semantics binding. Transport varies by * deployment: `"http"` for Apollo Server, `"aws-https"` for AppSync. */ declare function graphqlResolverBinding(opts: { transport: string; recognition: string; /** Null when the source never says which type the resolver attaches to. */ typeName: string | null; fieldName: string; }): BoundaryBinding; /** * Build a graphql-operation-semantics binding, which is the consumer side * of a GraphQL boundary. Anonymous operations leave `operationName` unset. */ declare function graphqlOperationBinding(opts: { transport: string; recognition: string; operationType: "query" | "mutation" | "subscription"; operationName?: string; }): BoundaryBinding; /** * Whether a binding is the consumer side of a GraphQL boundary, checked * against the protocol's own schema so callers do not compare the * semantics tag by hand. */ declare function isGraphqlOperationBinding(binding: BoundaryBinding | null | undefined): boolean; /** * Build a runtime-config binding, for the environment variables a * deployed unit starts with. Transport is `"os"` because the operating * system passes environment variables to the process at startup, * whatever platform deployed it. * * A provider passes its deployment target and instance name. A * recognizer at a read in the code leaves both out, since the code does * not say which deployment runs it. */ declare function runtimeConfigBinding(opts: { recognition: string; deploymentTarget?: DeployableUnit["deploymentTarget"]; instanceName?: string; }): BoundaryBinding; /** * Build a unit-invocation binding, the boundary between a deployed unit * and whatever calls it by name. Transport is `"invoke"` because the * call goes through the platform's own invoke API whichever cloud runs * the unit. */ declare function unitInvocationBinding(opts: { recognition: string; deploymentTarget: DeployableUnit["deploymentTarget"]; /** Null when this source does not say which unit it invokes. */ instanceName: string | null; }): BoundaryBinding; /** * Build a storage binding, which describes a store in terms both a * schema reader and a call site can write. `transport` defaults to the * storage system's name, which suits a database whose product and wire * protocol share a name. A store reached through an SDK passes its wire * protocol. With no storage system either, the transport is `"storage"`. */ declare function storageBinding(opts: { recognition: string; /** Null when the source states an engine this reader could not settle. */ storageSystem: string | null; transport?: string; scope: string; /** Null when the source gives a container this reader could not settle. */ container: string | null; /** A secondary index or alias, or null for the container's own primary key. */ accessPath?: string | null; }): BoundaryBinding; /** * Build a message-bus binding, the boundary between a producer that * sends discrete messages and the consumer(s) that receive them. */ declare function messageBusBinding(opts: { recognition: string; messageBus: MessageBusTechnology; /** Null when this source does not say which channel. */ channel: string | null; }): BoundaryBinding; /** * Build a metric binding, the boundary between whatever declares a * named series of measurements and whatever reads it back by that name. */ declare function metricBinding(opts: { recognition: string; metricSystem: string; /** Null when this source states a metric it could not name. */ metricType: string | null; }): BoundaryBinding; /** * Build an io binding, for a write to one of the process's own streams. * The transport is `"in-process"` because the write is a call the * program makes, and the runtime moves the bytes on from there. */ declare function ioBinding(opts: { recognition: string; /** Null when this source does not say which stream. */ target: string | null; }): BoundaryBinding; export { type BoundaryBehavior, type BoundaryBinding, BoundaryBindingSchema, type BoundaryName, type BoundarySemanticsDefinition, type BusIdentityKey, type Channel, type ConfidenceInfo, ConfidenceInfoSchema, type ConfidenceLevel, ConfidenceLevelSchema, type ConfidenceSource, ConfidenceSourceSchema, type Corroboration, CorroborationSchema, DeployableUnit, type Deployment, type DispatchTable, type Ecosystem, type EffectRelation, EffectRelationSchema, type FindingSuppression, type FnIdentityKey, type GqlIdentityKey, type HoleRange, type IoIdentityKey, type IoSemantics, IoSemanticsSchema, type MatchResult, MessageBusSemantics, MessageBusTechnology, type MetricIdentityKey, type MetricSemantics, MetricSemanticsSchema, NOTHING_DEPLOYED, type NamePart, PLATFORM_INJECTED_ENV_VARS, type PackConfiguration, type PackDeclaration, type ParsedChannel, type ParsedHandler, type Reference, type Relation, RelationSchema, Semantics, type SemconvAttribute, type SemconvMapping, type SourceLocation, SourceLocationSchema, type SuppressableSeverity, type SuppressibleFinding, type SuppressionFile, SuppressionFileSchema, type SuppressionRule, SuppressionRuleSchema, TEST_TITLE_SEPARATOR, TypeShape, type UnitIdentityKey, type UnitInvocationSemantics, UnitInvocationSemanticsSchema, applySuppressionsToFindings, bindingIs, bindingTokens, bodyShapesMatch, boundaryKey, boundaryLabel, boundaryNameString, bucketRank, bucketsMeet, busIdentityKey, busesAgree, canPair, channelsPair, codeScopePath, compareRanks, countsForThreshold, dispatchByType, displayLabel, exchangesHttpResponses, fileInCodeScope, fixedTextLength, fnIdentityKey, formatChannel, functionCallBinding, gqlIdentityKey, graphqlOperationBinding, graphqlResolverBinding, groundBinding, groundedPairingKey, hasNameHole, ioBinding, ioIdentityKey, isGraphqlOperationBinding, labelWithDetail, leavesTheProcess, messageBusBinding, metricBinding, metricIdentityKey, moduleExportBinding, namePatternFromSub, namePatternKey, nameReference, namesAgree, namesBoundary, namesBoundaryExactly, namesNothing, normalizeRuleBoundary, packageExportBinding, pairingKey, parseBoundaryName, parseChannel, parseHandler, pathAfterOrigin, patternHole, rangedHole, referenceFromName, referenceName, referenceOf, reportsUnpairedItself, resourceNameIn, restBinding, ruleBoundaryMatchesKey, runtimeConfigBinding, semanticsAgree, semconvAttributes, servesRequest, setPiece, spansBuckets, spellingTokens, statesAnOrigin, storageBinding, testUnitName, unitIdentityKey, unitInvocationBinding, validateRule, withRewrittenPaths, withinScope };