@intentius/chant 0.66.1 → 0.67.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.
Files changed (36) hide show
  1. package/dist/behaviour-engine.d.ts +212 -0
  2. package/dist/behaviour-engine.d.ts.map +1 -0
  3. package/dist/behaviour-http.d.ts +1 -1
  4. package/dist/behaviour-http.d.ts.map +1 -1
  5. package/dist/behaviour-kinds.d.ts +220 -0
  6. package/dist/behaviour-kinds.d.ts.map +1 -0
  7. package/dist/behaviour-predict.d.ts +83 -0
  8. package/dist/behaviour-predict.d.ts.map +1 -0
  9. package/dist/behaviour-request.d.ts +141 -0
  10. package/dist/behaviour-request.d.ts.map +1 -0
  11. package/dist/behaviour.d.ts +7 -7
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/lexicon.d.ts +19 -0
  15. package/dist/lexicon.d.ts.map +1 -1
  16. package/dist/op/activities/index.d.ts +1 -1
  17. package/dist/op/activities/index.d.ts.map +1 -1
  18. package/dist/op/activities/predict-behaviour.d.ts +12 -5
  19. package/dist/op/activities/predict-behaviour.d.ts.map +1 -1
  20. package/package.json +1 -1
  21. package/src/behaviour-delta.test.ts +8 -8
  22. package/src/behaviour-engine.ts +478 -0
  23. package/src/behaviour-http.ts +1 -1
  24. package/src/behaviour-kinds.test.ts +235 -0
  25. package/src/behaviour-kinds.ts +324 -0
  26. package/src/behaviour-predict.ts +253 -0
  27. package/src/behaviour-request.ts +294 -0
  28. package/src/behaviour.ts +7 -7
  29. package/src/cli/handlers/scenario.test.ts +1 -1
  30. package/src/cli/handlers/scenario.ts +1 -1
  31. package/src/index.ts +1 -0
  32. package/src/lexicon.ts +20 -0
  33. package/src/lifecycle/scenario-cost.test.ts +2 -2
  34. package/src/op/activities/index.ts +1 -2
  35. package/src/op/activities/predict-behaviour.test.ts +20 -7
  36. package/src/op/activities/predict-behaviour.ts +32 -30
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The seam between core and whatever answers its request (#2357),
3
+ * on the contract's transport (#2373, decided in #2359).
4
+ *
5
+ * `packages/core/src/behaviour.ts` says what a prediction may mean, how an
6
+ * absent engine must refuse, and — since #2359 — how a request reaches an
7
+ * engine: a `BehaviourTransport` carries the rendered request and brings back
8
+ * the engine's text or a finished refusal. This file adds what is core's on
9
+ * top of that and nothing more: the parse of a `behaviour/v1` answer, a command
10
+ * transport for an address that is a program on `PATH`, and the chooser that
11
+ * turns an address into one transport or the other.
12
+ *
13
+ * {@link BehaviourEngine} is what `./predict-behaviour.ts` talks to. It is
14
+ * one level above the transport — request in, parsed answer or refusal out —
15
+ * so the fixture engine in `__fixtures__` can be one without pretending to be
16
+ * a wire, and so `predict-behaviour.ts` never sees a byte. {@link
17
+ * transportEngine} is the only bridge between the two levels.
18
+ *
19
+ * ## The two transports
20
+ *
21
+ * A **URL** is dialled by core's `httpBehaviourTransport`
22
+ * (`packages/core/src/behaviour-http.ts`): `POST`, a bearer token from
23
+ * `CHANT_BEHAVIOUR_TOKEN` → `BEHAVIOUR_TOKEN`,
24
+ * and the status mapping the contract fixes. Nothing about that is core's,
25
+ * which is why it does not live here.
26
+ *
27
+ * A **command on PATH** is {@link commandTransport}, below: request on stdin,
28
+ * answer on stdout, no shell — a shell would make the address a
29
+ * code-execution surface for whatever set the variable. The child gets
30
+ * `behaviourEngineChildEnvironment()` and nothing else, for the reason given
31
+ * on that function: the request-side screen cannot see an inherited
32
+ * `process.env`. An engine that answered and still refused says so on stderr,
33
+ * and the words it uses pick the cause.
34
+ *
35
+ * ## A malformed answer is unreachable, not a report with holes
36
+ *
37
+ * Taking the fields that parsed and reporting the rest unpredicted would turn
38
+ * an engine emitting garbage into an estate that looks partly free — the
39
+ * failure the refusal arm exists to prevent, one level down. So the parse
40
+ * refuses whole, with a detail naming the entity and the field.
41
+ *
42
+ * With one exception, and it is the same rule the command transport applies
43
+ * to stderr: an engine that took the request, answered `200`, and wrote
44
+ * `{"error": "out of credit"}` has refused for a reason a status never
45
+ * carried. {@link transportEngine} reads that body's words after the parse
46
+ * has failed and never before, so an answer that priced the estate and
47
+ * declined one node "rate limit reached for this region" stays the report it
48
+ * is. Both transports reach the same three causes; only the evidence differs.
49
+ */
50
+ import type { BehaviourBasis, BehaviourEngineEndpoint, BehaviourRefusalReport, BehaviourTransport, ResilienceVerdict } from "./behaviour.js";
51
+ import type { HttpBehaviourTransportDeps } from "./behaviour-http.js";
52
+ import type { EngineRequest } from "./behaviour-request.js";
53
+ /**
54
+ * The name a refusal is built under.
55
+ *
56
+ * It was the predicting lexicon's name while a lexicon predicted. Core
57
+ * predicts for the whole project since #2382, so there is no lexicon to scope
58
+ * the variable chain by: the chain is the chant-wide one, and this is the word
59
+ * a message uses for the thing that refused.
60
+ */
61
+ export declare const BEHAVIOUR_SCOPE = "chant";
62
+ /** One entity's figures, as the engine states them. */
63
+ export interface EngineFigure {
64
+ perHour: number;
65
+ currency: string;
66
+ headroom: {
67
+ cpu?: number;
68
+ latency?: number;
69
+ };
70
+ errorRate: number;
71
+ resilience: {
72
+ failure: string;
73
+ verdict: ResilienceVerdict;
74
+ note?: string;
75
+ };
76
+ rightSize?: {
77
+ suggestion: string;
78
+ reason?: string;
79
+ };
80
+ }
81
+ /** A run the engine answered. */
82
+ export interface EngineAnswer {
83
+ /** How the engine names itself, its version, and the tolerance it states. */
84
+ engine: string;
85
+ version: string;
86
+ tolerance: string;
87
+ basis: BehaviourBasis;
88
+ /** An estate total, when the engine states one of its own. Never chant's sum. */
89
+ total?: {
90
+ perHour: number;
91
+ currency: string;
92
+ };
93
+ /** Figures, keyed by the node name the request used. */
94
+ figures: Record<string, EngineFigure>;
95
+ /**
96
+ * Nodes the engine was sent and declined, keyed by name, with its reason.
97
+ * Separate from an absent key: a node in neither map is a defect, and
98
+ * `./predict-behaviour.ts` reports it rather than dropping it.
99
+ */
100
+ declined?: Record<string, string>;
101
+ }
102
+ /**
103
+ * What an engine call comes back with: a parsed answer, or a refusal built
104
+ * by whoever saw the failure — the transport for a wire condition, this file
105
+ * for an answer that does not parse. `./predict-behaviour.ts` returns the
106
+ * refusal as it stands and never rebuilds one, which is how the contract's
107
+ * four causes stay four remedies rather than one lexicon's guess.
108
+ */
109
+ export type EngineOutcome = {
110
+ ok: true;
111
+ answer: EngineAnswer;
112
+ } | {
113
+ ok: false;
114
+ refusal: BehaviourRefusalReport;
115
+ };
116
+ /** Whatever answers a request, one level above the wire. The fixture engine is one of these. */
117
+ export interface BehaviourEngine {
118
+ predict(request: EngineRequest): Promise<EngineOutcome>;
119
+ }
120
+ /**
121
+ * Resolve an address to an engine, or to `undefined` when nothing here
122
+ * speaks it. `env` is where a transport that authenticates reads its token
123
+ * from, and a chooser that has no use for it may ignore it. A caller that
124
+ * gets `undefined` refuses as `engine-unreachable` naming the address, which
125
+ * is the honest verdict for an address chant cannot dial.
126
+ */
127
+ export type EngineConnect = (endpoint: BehaviourEngineEndpoint, env: Record<string, string | undefined>) => BehaviourEngine | undefined;
128
+ /**
129
+ * A {@link BehaviourEngine} over a contract transport: render the request
130
+ * canonically, send it, parse what came back. The one bridge between the
131
+ * transport level and the engine level, so the parse runs on every wire and
132
+ * no transport gets its own.
133
+ */
134
+ export declare function transportEngine(transport: BehaviourTransport, endpoint: BehaviourEngineEndpoint): BehaviourEngine;
135
+ /**
136
+ * A `command on PATH` address, run as a subprocess: the contract's
137
+ * `BehaviourTransport` for the third kind of address it names.
138
+ *
139
+ * The address is split on whitespace into a program and its arguments, which
140
+ * is the shape `CHANT_BEHAVIOUR_ENGINE="my-engine --model tiny"` produces.
141
+ * No shell: a shell would make the address a code-execution surface for
142
+ * whatever set the variable, and every argument the address needs can be
143
+ * written without one.
144
+ */
145
+ export declare function commandTransport(endpoint: BehaviourEngineEndpoint): BehaviourTransport;
146
+ /** {@link commandTransport}, bridged to the engine level. */
147
+ export declare function commandEngine(endpoint: BehaviourEngineEndpoint): BehaviourEngine;
148
+ /**
149
+ * Everything wrong with one figure, as readable field paths. Empty means the
150
+ * figure is usable.
151
+ *
152
+ * Pure and exported so the shapes an engine can get wrong are testable without
153
+ * a subprocess. Every check here mirrors one `validateBehaviourBlock` applies
154
+ * downstream (`packages/core/src/behaviour.ts`) — the difference is *when*:
155
+ * that one throws, and a throw is the whole-lexicon failure `lexicon.ts`
156
+ * reserves for a live credential. A third party emitting one bad number is not
157
+ * that, and reporting it as that is how an operator goes looking for a leak.
158
+ */
159
+ export declare function figureProblems(name: string, figure: unknown): string[];
160
+ /**
161
+ * What the parse of an engine's text comes to: the answer, or a detail
162
+ * naming what was wrong with it. Not a refusal yet — {@link transportEngine}
163
+ * builds that, with the endpoint the parse does not need to know.
164
+ */
165
+ export type ParsedEngineAnswer = {
166
+ ok: true;
167
+ answer: EngineAnswer;
168
+ } | {
169
+ ok: false;
170
+ detail: string;
171
+ };
172
+ /**
173
+ * Parse an engine's text into an {@link EngineAnswer}.
174
+ *
175
+ * A malformed answer is `engine-unreachable` and not a report full of holes.
176
+ * The alternative — taking the fields that parsed and reporting the rest as
177
+ * unpredicted — would turn an engine emitting garbage into an estate that
178
+ * looks partially free, which is the failure the refusal arm exists to
179
+ * prevent, one level down.
180
+ *
181
+ * **Every figure is validated here, not only the envelope.** The first version
182
+ * of this function checked `engine`/`version`/`tolerance`/`basis` and that
183
+ * `figures` was an object, and then handed each figure's contents straight to
184
+ * `block()` in `./predict-behaviour.ts`, which dereferenced
185
+ * `figure.resilience.failure`. Seven malformed shapes were executed against
186
+ * it and all seven threw: three as bare `TypeError`s with no chant message,
187
+ * four through `validateBehaviourBlock` after the fact. A throw is the
188
+ * whole-lexicon failure `lexicon.ts` reserves for a live credential in the
189
+ * request, so a third-party engine emitting one bad number was indistinguishable
190
+ * from a leak — and the doc above claimed the opposite was happening.
191
+ *
192
+ * The detail names the entity and the field, because "the engine sent
193
+ * something wrong" is not a thing anybody can act on and "figures.web.headroom
194
+ * carries neither cpu nor latency" is. It flows into
195
+ * `unreachableBehaviourEngineRefusal`, which runs it through
196
+ * `scrubEngineDetail` and bounds it.
197
+ */
198
+ export declare function parseEngineAnswer(text: string): ParsedEngineAnswer;
199
+ /**
200
+ * The transport chooser, with what the HTTP transport needs injectable.
201
+ *
202
+ * A `http://` or `https://` address gets core's `httpBehaviourTransport`,
203
+ * with its token read from `env` — the same `env` the address was read from,
204
+ * so a test drives both chains from one object. A bare address gets
205
+ * {@link commandTransport}. Any other scheme (`grpc://`, `unix://`) gets
206
+ * `undefined`, and the caller refuses naming the address: no transport here
207
+ * speaks it, and saying so beats a guess.
208
+ */
209
+ export declare function connectWith(deps?: HttpBehaviourTransportDeps): EngineConnect;
210
+ /** The chooser the shipped plugin uses: the process's own `fetch`, the default deadline. */
211
+ export declare const defaultConnect: EngineConnect;
212
+ //# sourceMappingURL=behaviour-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behaviour-engine.d.ts","sourceRoot":"","sources":["../src/behaviour-engine.ts"],"names":[],"mappings":"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"}
@@ -68,7 +68,7 @@ export interface HttpBehaviourTransportDeps {
68
68
  /** How long the engine has before it is treated as unreachable. */
69
69
  timeoutMs?: number;
70
70
  }
71
- /** The default deadline: the same one augur's command transport gives a child. */
71
+ /** The default deadline: the same one the command transport gives a child. */
72
72
  export declare const HTTP_BEHAVIOUR_TIMEOUT_MS = 30000;
73
73
  /** True when an address is one this adapter dials. `grpc://` and friends are not. */
74
74
  export declare function isHttpBehaviourAddress(value: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"behaviour-http.d.ts","sourceRoot":"","sources":["../src/behaviour-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,EAKL,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EAExB,MAAM,aAAa,CAAC;AAGrB,sFAAsF;AACtF,MAAM,WAAW,0BAA0B;IACzC,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAKhD,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,GAClB,sBAAsB,GAAG,SAAS,CAgCpC;AAQD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,uBAAuB,EACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,IAAI,GAAE,0BAA+B,GACpC,kBAAkB,CAoDpB"}
1
+ {"version":3,"file":"behaviour-http.d.ts","sourceRoot":"","sources":["../src/behaviour-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,EAKL,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EAExB,MAAM,aAAa,CAAC;AAGrB,sFAAsF;AACtF,MAAM,WAAW,0BAA0B;IACzC,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAKhD,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,GAClB,sBAAsB,GAAG,SAAS,CAgCpC;AAQD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,uBAAuB,EACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,IAAI,GAAE,0BAA+B,GACpC,kBAAkB,CAoDpB"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * What an engine is asked to price, and who gets to say so (#2382).
3
+ *
4
+ * `./behaviour.ts` says what a prediction may mean and `./behaviour-http.ts`
5
+ * carries one to an engine. Between them sits a question neither answers: for
6
+ * a given entity in a project's graph, what *kind* of thing is it, and is it
7
+ * something an engine prices at all? #2357 answered it with one table in one
8
+ * lexicon, keyed by every other lexicon's entity types. That table described
9
+ * 117 types it did not own, and the capability rode on whether an optional
10
+ * package happened to be installed — which gave a consumer a fourth
11
+ * outcome this contract never named: no overlay, no refusal, and no reason.
12
+ *
13
+ * So the resolution lives here, where every other part of the feature already
14
+ * does, and the *rows* are contributed by the lexicon that owns the substrate.
15
+ * Core learns that an entity has an engine kind. It never learns which.
16
+ *
17
+ * ## Why rows are safe to make optional when the capability is not
18
+ *
19
+ * A lexicon that is not installed declared no entities of its types, so it has
20
+ * no rows to contribute and nothing goes missing: the estate simply contains
21
+ * nothing of that substrate. That is the opposite of the capability itself
22
+ * being optional, where an uninstalled package means an estate full of
23
+ * entities nobody will say anything about.
24
+ *
25
+ * ## The three things a lexicon can say about its own types
26
+ *
27
+ * - **mapped** — this type is a `compute`/`database`/`queue`, and its size
28
+ * is read from this declared property, in the provider's own vocabulary.
29
+ * - **declared unmapped** — this type is real and carries no rate an engine
30
+ * can quote, with the sentence saying why. A grant is not a resource; a
31
+ * boundary is not a node; a meter chant cannot read is not a figure.
32
+ * - **nothing priced** — nothing this lexicon declares is an estate an
33
+ * engine prices, once, for the whole substrate. A CI workflow is not an
34
+ * estate, and a policy language has nothing to saturate.
35
+ *
36
+ * A type a contributor claims and has no row for is `unknown-type`: the one
37
+ * verdict that is a defect rather than a decision, and the reason the three
38
+ * above are distinct states instead of one absent row.
39
+ */
40
+ /**
41
+ * The categories a cost-and-saturation model actually distinguishes, not a
42
+ * taxonomy of every product a cloud sells.
43
+ *
44
+ * A kind is here when an engine would price it differently or when its
45
+ * saturation axis differs. `cache` is separate from `database` because a cache
46
+ * saturates on memory and a database on IO; `serverless` is separate from
47
+ * `compute` because one is priced per invocation and the other per hour of
48
+ * existence, and this contract's output shape is per hour. `control-plane` is
49
+ * separate from everything because a managed control plane is a flat hourly
50
+ * fee that does not move with the estate's traffic at all — pricing one as
51
+ * `compute` would make it look like something a right-size suggestion could
52
+ * shrink.
53
+ */
54
+ export type EngineKind = "compute" | "serverless" | "control-plane" | "database" | "cache" | "queue" | "object-store" | "block-store" | "load-balancer" | "cdn";
55
+ /**
56
+ * Every legal {@link EngineKind}, derived from a total witness rather than
57
+ * written out by hand — the construction `behaviour.ts` uses for
58
+ * `BEHAVIOUR_BASES`, for the same reason: a hand-written array is checked for
59
+ * having legal members and never for having all of them.
60
+ */
61
+ export declare const ENGINE_KINDS: readonly EngineKind[];
62
+ /** True when `value` is a legal {@link EngineKind}. */
63
+ export declare function isEngineKind(value: unknown): value is EngineKind;
64
+ /** One row of a lexicon's coverage: what one of its entity types becomes on the wire. */
65
+ export interface EngineKindMapping {
66
+ /** The engine-side kind. */
67
+ kind: EngineKind;
68
+ /**
69
+ * The substrate, as the engine names it — `aws`, `kubernetes`. A plain
70
+ * string rather than a closed union: the contributing lexicon names its own
71
+ * substrate, and core adding a member here for every lexicon that ships
72
+ * would be core holding the list it exists to stop holding.
73
+ */
74
+ provider: string;
75
+ /**
76
+ * The declared property whose value is the entity's size, in the provider's
77
+ * own vocabulary — `InstanceType`, not a parsed vCPU count. Absent where the
78
+ * type has no size a single `size` string can carry.
79
+ */
80
+ sizeProp?: string;
81
+ /**
82
+ * Which type {@link sizeProp} holds. A value of the other type is absent
83
+ * rather than coerced: a number rendered into a size field an engine matches
84
+ * against a price table of strings is worse than no size at all.
85
+ */
86
+ sizeType?: "string" | "number";
87
+ /**
88
+ * The declared property naming the region or zone this entity sits in, where
89
+ * the type states one of its own. Most do not, and inherit the caller's.
90
+ */
91
+ regionProp?: string;
92
+ }
93
+ /**
94
+ * One lexicon's answer for its own entity types, contributed through the
95
+ * plugin's `behaviourKinds` field.
96
+ *
97
+ * Every field except {@link prefixes} is optional, and a lexicon that supplies
98
+ * only {@link nothingPriced} has said something complete: that none of what it
99
+ * declares is an estate an engine prices.
100
+ */
101
+ export interface BehaviourKinds {
102
+ /** The substrate, as the engine names it. Every mapped row inherits it. */
103
+ provider: string;
104
+ /**
105
+ * The entity-type prefixes this lexicon owns — `["AWS::"]`, `["K8s::"]`.
106
+ * Ownership is what makes a missing row a defect rather than silence: a type
107
+ * nobody claims is nobody's mistake, and a type its own lexicon claims and
108
+ * cannot classify is a row somebody forgot to write.
109
+ */
110
+ prefixes: readonly string[];
111
+ /** Types this lexicon prices, keyed by entity type — or by resolved type where {@link resolveType} is given. */
112
+ mapped?: Readonly<Record<string, Omit<EngineKindMapping, "provider"> & {
113
+ provider?: string;
114
+ }>>;
115
+ /** Types that are real and carry no rate, keyed the same way, with the sentence saying why. */
116
+ unmapped?: Readonly<Record<string, string>>;
117
+ /**
118
+ * Nothing this lexicon declares is priced, and this says why — checked
119
+ * before the tables, so a lexicon that states it needs no rows at all.
120
+ */
121
+ nothingPriced?: string;
122
+ /**
123
+ * For a lexicon whose entities do not key rows by their entity type, the
124
+ * key to look rows up on instead. terraform's every `resource` block arrives
125
+ * as `Terraform::Resource`, and what an engine would price is the type its
126
+ * address carries.
127
+ *
128
+ * It is handed the entity type as well as the props, because a lexicon
129
+ * usually redirects only *some* of its types: terraform's root blocks
130
+ * (`Terraform::Variable`, `Terraform::Output`) key by entity type like
131
+ * everyone else, and only `Terraform::Resource` reads its key out of the
132
+ * props. Returning the entity type unchanged is how a contributor says "this
133
+ * one is ordinary". Returning `undefined` is "nothing to look up", the same
134
+ * no-opinion an unseen type gets.
135
+ */
136
+ resolveType?: (entityType: string, props: Record<string, unknown> | undefined) => string | undefined;
137
+ /**
138
+ * A family of this lexicon's types that belongs to a substrate nothing here
139
+ * models, named as the substrate — or `undefined` to let the type carry on
140
+ * to {@link unmappedWhen}. terraform is the case this exists for: one
141
+ * lexicon's `resource` blocks span every provider there is, so `google_`
142
+ * and `azurerm_` and `null_` are each a boundary of their own, while an
143
+ * `aws_` type with no row stays the defect it is. A contributor whose whole
144
+ * substrate is unpriced uses {@link nothingPriced} instead; this is for the
145
+ * lexicon that models part of what it declares.
146
+ */
147
+ notModelledWhen?: (type: string) => string | undefined;
148
+ /**
149
+ * A last word on a type this lexicon claims and has no row for: the reason
150
+ * it is unmapped, or `undefined` to leave it a defect. CloudFormation's
151
+ * property types are the case this exists for — hundreds of nested blocks
152
+ * that became entities of their own, never separately priced, and
153
+ * enumerating them one row at a time would bury the table's real decisions.
154
+ */
155
+ unmappedWhen?: (type: string) => string | undefined;
156
+ }
157
+ /** What one entity type resolves to. Total: every type reaches exactly one of these. */
158
+ export type CoverageVerdict = {
159
+ status: "mapped";
160
+ mapping: EngineKindMapping;
161
+ } | {
162
+ status: "declared-unmapped";
163
+ reason: string;
164
+ } | {
165
+ status: "provider-not-modelled";
166
+ substrate: string;
167
+ } | {
168
+ status: "unknown-type";
169
+ };
170
+ /**
171
+ * Resolve one entity type against the contributed rows.
172
+ *
173
+ * Total by construction: every type reaches one of the four states and there
174
+ * is no fifth arm for "dropped". That is the property the request builder
175
+ * relies on to guarantee every entity it was asked about lands in `entities`
176
+ * or in `unpredicted`.
177
+ *
178
+ * `hasOwnProperty` rather than a truthiness check, because these are plain
179
+ * object literals and an entity named after a prototype member would otherwise
180
+ * resolve to a mapping that does not exist.
181
+ */
182
+ export declare function coverageFor(contributors: readonly BehaviourKinds[], entityType: string, props?: Record<string, unknown>): CoverageVerdict;
183
+ /**
184
+ * The contributor that owns an entity type, or `undefined` when nobody claims
185
+ * it. Exported because a caller that has already resolved a verdict often
186
+ * needs the owner too — to name it in a detail, or to label the entity the way
187
+ * its own lexicon would.
188
+ */
189
+ export declare function ownerOf(contributors: readonly BehaviourKinds[], entityType: string): BehaviourKinds | undefined;
190
+ /**
191
+ * How a detail names an entity's kind. The chant entity type, except where the
192
+ * owning lexicon keys its rows on something else: `Terraform::Resource` names
193
+ * nothing a reader can act on, and the provider type is the kind, so the label
194
+ * is `aws_vpc (Terraform::Resource)`.
195
+ */
196
+ export declare function coverageLabel(contributors: readonly BehaviourKinds[], entityType: string, props?: Record<string, unknown>): string;
197
+ /**
198
+ * The `detail` an `unpredicted` entry carries, naming the kind in every case.
199
+ *
200
+ * The kind is named here, in the per-entity decline, because that is the only
201
+ * place in this contract with a per-entity axis: a `BehaviourRefusalReport` is
202
+ * a statement about the whole run and has no `entities` key by design, so a
203
+ * report-level refusal for one unmapped bucket would take the other nineteen
204
+ * entities' figures down with it.
205
+ *
206
+ * The `unknown-type` arm names the lexicon that owns the type rather than a
207
+ * file path, because since #2382 the row belongs to whichever lexicon defines
208
+ * the type, and that is the thing a reader needs to be told.
209
+ */
210
+ export declare function unmappedDetail(label: string, verdict: CoverageVerdict, owner?: BehaviourKinds): string;
211
+ /**
212
+ * Compare two strings by UTF-16 code unit.
213
+ *
214
+ * Not `localeCompare`, which reads the ambient locale: under `sv-SE` and
215
+ * `et-EE` it orders these type names differently from `en-US`, which would
216
+ * make the request's bytes — and therefore any golden fixture, and therefore
217
+ * any declared-versus-live delta — a function of the machine that built them.
218
+ */
219
+ export declare const byCodeUnit: (a: string, b: string) => number;
220
+ //# sourceMappingURL=behaviour-kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behaviour-kinds.d.ts","sourceRoot":"","sources":["../src/behaviour-kinds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `predictBehaviour()` — the fourth
3
+ * observation method (#2357, contract #2356).
4
+ *
5
+ * The whole method is four moves, in this order, and the order is the contract:
6
+ *
7
+ * 1. **Screen the request.** `screenBehaviourRequest` and nothing else. It is
8
+ * the only entry point, and calling `assertNoCredentialInOptions` instead
9
+ * applies one rule of three — that was review finding F1 on #2365, where a
10
+ * `ghp_…` past the walk's depth budget and an `awsSecretAccessKey` in
11
+ * `props` both went out with the request.
12
+ * 2. **Resolve the engine.** `behaviourEngineFrom`, walking
13
+ * `CHANT_BEHAVIOUR_ENGINE_BEHAVIOUR_SCOPE` → `CHANT_BEHAVIOUR_ENGINE` →
14
+ * `BEHAVIOUR_ENGINE`. Nothing named means `noBehaviourEngineRefusal`, and
15
+ * it happens **before** anything is priced: a lexicon that prices first and
16
+ * checks the engine afterwards has already decided what zero means.
17
+ * 3. **Build the request.** `buildEngineRequest`, offline and pure
18
+ * (`./request.ts`).
19
+ * 4. **Ask, and translate the answer.** One {@link EngineOutcome} onto the
20
+ * contract's builders. A refusal comes back from the transport already
21
+ * built — the contract's transport (#2373) names the condition and the
22
+ * variable where the wire was seen — and is returned as it stands; this
23
+ * file holds no switch over causes. No figure is ever computed here either:
24
+ * this file has no arithmetic on money in it at all, and that is not an
25
+ * accident. The epic's first rule is that a missing engine produces a
26
+ * refusal and "never a locally faked number", and the surest way to keep
27
+ * that true is for the code that would have faked it not to exist.
28
+ *
29
+ * ## Every entity lands somewhere
30
+ *
31
+ * `behaviourReport` refuses a report that leaves a name in neither map, so the
32
+ * loop below has no `continue` that drops one. Four ways an entity can end up
33
+ * unpredicted, and each names a different thing:
34
+ *
35
+ * | Outcome | Reason | Means |
36
+ * |---|---|---|
37
+ * | the coverage table declares it unmapped | `unsupported-kind` | chant looked, and this kind has no rate |
38
+ * | the coverage table has never seen it | `unsupported-kind` | chant has not looked; the detail says so and where to add the row |
39
+ * | the engine declined it | `read-failed` | chant asked and the engine could not answer |
40
+ * | the engine answered about neither | `read-failed` | the engine lost it, and this says so rather than hiding it |
41
+ *
42
+ * The last row is the one that would otherwise be silent. An engine that
43
+ * returns figures for eleven of twelve nodes and mentions the twelfth nowhere
44
+ * is a bug in the engine, and an estate that quietly renders eleven nodes is
45
+ * how it stays a bug.
46
+ */
47
+ import { type BehaviourResult, type PredictBehaviourOptions } from "./behaviour.js";
48
+ import type { BehaviourKinds } from "./behaviour-kinds.js";
49
+ import { BEHAVIOUR_SCOPE, type EngineConnect } from "./behaviour-engine.js";
50
+ export { BEHAVIOUR_SCOPE };
51
+ /** What {@link createBehaviourPredict} needs that the method's own options do not carry. */
52
+ export interface BehaviourPredictDeps {
53
+ /**
54
+ * The coverage rows to resolve entity types against — every configured
55
+ * lexicon's `behaviourKinds`, in the order the plugins were loaded.
56
+ *
57
+ * An empty list is legal and means every declared entity is withheld as
58
+ * `unknown-type`: nothing has said what any of them are. That is a report
59
+ * about an estate nobody has rows for, not a refusal, because the engine was
60
+ * reachable and answered; the distinction is the whole point of the refusal
61
+ * arm.
62
+ */
63
+ kinds?: readonly BehaviourKinds[];
64
+ /** The environment the engine address is resolved from. Defaults to the process's. */
65
+ env?: Record<string, string | undefined>;
66
+ /**
67
+ * How an address becomes an engine. Defaults to `./behaviour-engine.ts`'s chooser,
68
+ * which dials a URL through core's HTTP transport and a bare address as a
69
+ * command on PATH.
70
+ */
71
+ connect?: EngineConnect;
72
+ }
73
+ /**
74
+ * Build core's `predictBehaviour`, with the environment and the
75
+ * transport injected.
76
+ *
77
+ * Injected rather than read from module scope so a test can drive the whole
78
+ * method — screen, resolve, serialize, translate — against a fixture engine
79
+ * without touching `process.env`, which is what the shared conformance suite's
80
+ * probes need in order to ask the same request twice and compare.
81
+ */
82
+ export declare function createBehaviourPredict(deps?: BehaviourPredictDeps): (options: PredictBehaviourOptions) => Promise<BehaviourResult>;
83
+ //# sourceMappingURL=behaviour-predict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behaviour-predict.d.ts","sourceRoot":"","sources":["../src/behaviour-predict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAG7B,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAkB,KAAK,aAAa,EAAqB,MAAM,oBAAoB,CAAC;AAE5G,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,4FAA4F;AAC5F,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,GAAE,oBAAyB,GAC9B,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,eAAe,CAAC,CA0FhE"}