@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.
- package/dist/behaviour-engine.d.ts +212 -0
- package/dist/behaviour-engine.d.ts.map +1 -0
- package/dist/behaviour-http.d.ts +1 -1
- package/dist/behaviour-http.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts +220 -0
- package/dist/behaviour-kinds.d.ts.map +1 -0
- package/dist/behaviour-predict.d.ts +83 -0
- package/dist/behaviour-predict.d.ts.map +1 -0
- package/dist/behaviour-request.d.ts +141 -0
- package/dist/behaviour-request.d.ts.map +1 -0
- package/dist/behaviour.d.ts +7 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +19 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/predict-behaviour.d.ts +12 -5
- package/dist/op/activities/predict-behaviour.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/behaviour-delta.test.ts +8 -8
- package/src/behaviour-engine.ts +478 -0
- package/src/behaviour-http.ts +1 -1
- package/src/behaviour-kinds.test.ts +235 -0
- package/src/behaviour-kinds.ts +324 -0
- package/src/behaviour-predict.ts +253 -0
- package/src/behaviour-request.ts +294 -0
- package/src/behaviour.ts +7 -7
- package/src/cli/handlers/scenario.test.ts +1 -1
- package/src/cli/handlers/scenario.ts +1 -1
- package/src/index.ts +1 -0
- package/src/lexicon.ts +20 -0
- package/src/lifecycle/scenario-cost.test.ts +2 -2
- package/src/op/activities/index.ts +1 -2
- package/src/op/activities/predict-behaviour.test.ts +20 -7
- package/src/op/activities/predict-behaviour.ts +32 -30
|
@@ -105,12 +105,12 @@ describe("evaluateScenario — cost against the fixture's recorded prediction",
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
test("a fixture whose prediction is a refusal fails with the refusal's reason, never a pass on nothing", () => {
|
|
108
|
-
const verdict = evaluateScenario(EMPTY, { cost: { maxPerHour: 1e9, currency: "USD" } }, { result: noBehaviourEngineRefusal("
|
|
108
|
+
const verdict = evaluateScenario(EMPTY, { cost: { maxPerHour: 1e9, currency: "USD" } }, { result: noBehaviourEngineRefusal("chant") });
|
|
109
109
|
expect(verdict.pass).toBe(false);
|
|
110
110
|
expect(cost(verdict).detail).toContain("the fixture's prediction is a refusal (no-engine)");
|
|
111
111
|
expect(cost(verdict).detail).toContain("Set CHANT_BEHAVIOUR_ENGINE to the engine's address.");
|
|
112
112
|
|
|
113
|
-
const broke = outOfCreditBehaviourEngineRefusal("
|
|
113
|
+
const broke = outOfCreditBehaviourEngineRefusal("chant", { value: "engine", source: "CHANT_BEHAVIOUR_ENGINE" }, "balance 0");
|
|
114
114
|
expect(cost(evaluateScenario(EMPTY, { cost: { maxPerHour: 1e9, currency: "USD" } }, { result: broke })).detail).toContain("engine-out-of-credit");
|
|
115
115
|
});
|
|
116
116
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { describe, expect, test, vi } from "vitest";
|
|
8
|
+
import { coverageFor, type BehaviourKinds } from "../../behaviour-kinds";
|
|
8
9
|
import {
|
|
9
10
|
behaviourReport,
|
|
10
11
|
noBehaviourEngineRefusal,
|
|
@@ -15,14 +16,13 @@ import {
|
|
|
15
16
|
} from "../../behaviour";
|
|
16
17
|
import { commentMarker, issueMarker, type ReconcilePrArgs, type ReconcileResult } from "./reconcile";
|
|
17
18
|
import {
|
|
18
|
-
ambiguousPredictorMessage,
|
|
19
19
|
baseRefFrom,
|
|
20
20
|
behaviourFindingMarker,
|
|
21
21
|
createBehaviourFinding,
|
|
22
22
|
declaredEdgeCoverage,
|
|
23
23
|
headRefFrom,
|
|
24
24
|
noBaseRefMessage,
|
|
25
|
-
|
|
25
|
+
noContributedRowsMessage,
|
|
26
26
|
type BaseCheckout,
|
|
27
27
|
} from "./predict-behaviour";
|
|
28
28
|
|
|
@@ -97,12 +97,25 @@ describe("declaredEdgeCoverage — never complete on the declared path", () => {
|
|
|
97
97
|
|
|
98
98
|
describe("the refusals for a project that cannot predict", () => {
|
|
99
99
|
test("no predicting lexicon names the configured ones and what to add", () => {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// Not an error any more: with rows contributed per lexicon there is
|
|
101
|
+
// nothing to choose between, and an estate whose lexicons contribute no
|
|
102
|
+
// rows is a real state with a real answer rather than a broken command
|
|
103
|
+
// (#2382).
|
|
104
|
+
expect(noContributedRowsMessage(["aws", "k8s"])).toMatch(/\(aws, k8s\).*behaviourKinds.*CHANT_BEHAVIOUR_ENGINE/s);
|
|
105
|
+
expect(noContributedRowsMessage([])).toContain("(none)");
|
|
106
|
+
expect(noContributedRowsMessage(["aws"])).toContain("unknown-type");
|
|
102
107
|
});
|
|
103
108
|
|
|
104
|
-
test("two
|
|
105
|
-
|
|
109
|
+
test("two lexicons contributing rows is ordinary, not a conflict", () => {
|
|
110
|
+
// The old shape refused here, because two lexicons implementing
|
|
111
|
+
// predictBehaviour() meant two engines pricing one estate. Rows do not
|
|
112
|
+
// have that problem: each lexicon speaks only about its own entity types,
|
|
113
|
+
// so contributions are additive and there is nothing to merge.
|
|
114
|
+
const aws: BehaviourKinds = { provider: "aws", prefixes: ["AWS::"], mapped: { "AWS::S3::Bucket": { kind: "object-store" } } };
|
|
115
|
+
const k8s: BehaviourKinds = { provider: "kubernetes", prefixes: ["K8s::"], mapped: { "K8s::Core::Pod": { kind: "compute" } } };
|
|
116
|
+
expect(coverageFor([aws, k8s], "AWS::S3::Bucket").status).toBe("mapped");
|
|
117
|
+
expect(coverageFor([aws, k8s], "K8s::Core::Pod").status).toBe("mapped");
|
|
118
|
+
expect(coverageFor([k8s, aws], "AWS::S3::Bucket").status).toBe("mapped");
|
|
106
119
|
});
|
|
107
120
|
});
|
|
108
121
|
|
|
@@ -198,7 +211,7 @@ describe("behaviourFinding — predicts both sides, differences them, and posts
|
|
|
198
211
|
});
|
|
199
212
|
|
|
200
213
|
test("a refusal on the base side posts a finding that says no prediction, and flags the run", async () => {
|
|
201
|
-
const { run, posted } = harness({ base: noBehaviourEngineRefusal("
|
|
214
|
+
const { run, posted } = harness({ base: noBehaviourEngineRefusal("chant"), head: report({ db: figure(0.1) }) });
|
|
202
215
|
const result = await run({ environment: "prod", traffic: TRAFFIC, op: "pr-behaviour" });
|
|
203
216
|
expect(result.refused).toBe(true);
|
|
204
217
|
expect(result.finding.kind).toBe("no-prediction");
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* `partial` either, because `partial` has to name a gap as `dangling` or
|
|
56
56
|
* `unresolvedKinds`, and the gap here is neither — every reference resolved,
|
|
57
57
|
* and the generic builder has no vocabulary for "this kind is a boundary
|
|
58
|
-
* whose containment is missing" (
|
|
58
|
+
* whose containment is missing" (the request fixture names its boundary
|
|
59
59
|
* kinds by hand, which is lexicon knowledge core does not have). An earlier
|
|
60
60
|
* draft named every kind no reference touched, and named a queue nobody
|
|
61
61
|
* references as "unresolved", which it is not. So the claim is `unknown`,
|
|
@@ -81,6 +81,8 @@ import {
|
|
|
81
81
|
type BehaviourDelta,
|
|
82
82
|
} from "../../behaviour-delta";
|
|
83
83
|
import type { LexiconPlugin } from "../../lexicon";
|
|
84
|
+
import type { BehaviourKinds } from "../../behaviour-kinds";
|
|
85
|
+
import { createBehaviourPredict } from "../../behaviour-predict";
|
|
84
86
|
import type { SerializerResult } from "../../serializer";
|
|
85
87
|
import { markerSlug, reconcilePr, suppliedMarker, type ReconcileResult } from "./reconcile";
|
|
86
88
|
|
|
@@ -107,22 +109,23 @@ export interface PredictBehaviourArgs {
|
|
|
107
109
|
owned?: boolean;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
+
/**
|
|
113
|
+
* What the activity says when nothing has contributed coverage rows.
|
|
114
|
+
*
|
|
115
|
+
* Not an error and not a refusal: the prediction runs, every declared entity
|
|
116
|
+
* is withheld as `unknown-type`, and this is what the report's own detail says
|
|
117
|
+
* one level down. Kept as a message rather than a throw because an estate
|
|
118
|
+
* whose lexicons contribute no rows is a real state with a real answer — "no
|
|
119
|
+
* row says what any of these are" — and a throw would turn it into a broken
|
|
120
|
+
* command (#2382).
|
|
121
|
+
*/
|
|
122
|
+
export function noContributedRowsMessage(lexicons: readonly string[]): string {
|
|
112
123
|
const configured = lexicons.length > 0 ? lexicons.join(", ") : "(none)";
|
|
113
124
|
return (
|
|
114
|
-
"predictBehaviour has the estate to predict and no
|
|
115
|
-
`lexicons (${configured})
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** What the activity says when more than one configured lexicon predicts. */
|
|
121
|
-
export function ambiguousPredictorMessage(names: readonly string[]): string {
|
|
122
|
-
return (
|
|
123
|
-
`predictBehaviour found ${names.length} configured lexicons that implement predictBehaviour() (${names.join(", ")}) ` +
|
|
124
|
-
"and predicts with one. Two engines pricing one estate is a merge this activity does not do, because an entity " +
|
|
125
|
-
"priced twice has two provenances and one row; configure one predicting lexicon."
|
|
125
|
+
"predictBehaviour has the estate to predict and no coverage rows to read it with: none of the " +
|
|
126
|
+
`configured lexicons (${configured}) contributes \`behaviourKinds\`. Every entity will be reported ` +
|
|
127
|
+
"unpredicted as `unknown-type`. Set CHANT_BEHAVIOUR_ENGINE to the engine's address, and add rows " +
|
|
128
|
+
"in the lexicon that owns the entity types you expect priced."
|
|
126
129
|
);
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -157,10 +160,10 @@ export async function predictDeclared(projectPath: string, args: PredictBehaviou
|
|
|
157
160
|
const { config } = await loadChantConfigUpward(projectPath);
|
|
158
161
|
const lexicons = await resolveProjectLexicons(projectPath);
|
|
159
162
|
const plugins = (await loadPlugins(lexicons)) as LexiconPlugin[];
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
163
|
+
// Rows, not a predictor. Since #2382 core predicts and each lexicon says
|
|
164
|
+
// only what its own entity types mean, so there is nothing to choose between
|
|
165
|
+
// and nothing to refuse when two lexicons both answer — they are additive.
|
|
166
|
+
const kinds = plugins.map((p) => p.behaviourKinds).filter((k): k is BehaviourKinds => k !== undefined);
|
|
164
167
|
|
|
165
168
|
const sourceDir = resolve(projectPath, config.sourceDir ?? ".");
|
|
166
169
|
const result = await build(sourceDir, plugins.map((p) => p.serializer));
|
|
@@ -169,11 +172,10 @@ export async function predictDeclared(projectPath: string, args: PredictBehaviou
|
|
|
169
172
|
throw new Error(`predictBehaviour: the project under ${projectPath} did not build: ${messages.join("; ")}`);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
// Every declared entity with a type
|
|
173
|
-
//
|
|
174
|
-
// what
|
|
175
|
-
//
|
|
176
|
-
// coverage table exists to name.
|
|
175
|
+
// Every declared entity with a type. The contributed rows decide what
|
|
176
|
+
// reaches the engine and what is declared unmapped, and they can only decide
|
|
177
|
+
// about what they are handed: filtering here to "resources" would silently
|
|
178
|
+
// drop the kinds the coverage rows exist to name.
|
|
177
179
|
const entities = new Map<string, { entityType: string; props: Record<string, unknown> }>();
|
|
178
180
|
for (const [name, entity] of result.entities) {
|
|
179
181
|
const declarable = entity as { entityType?: unknown; props?: unknown };
|
|
@@ -185,11 +187,11 @@ export async function predictDeclared(projectPath: string, args: PredictBehaviou
|
|
|
185
187
|
}
|
|
186
188
|
const edges = buildGraphIr(result.entities, sourceDir).edges;
|
|
187
189
|
|
|
188
|
-
// The
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// the
|
|
192
|
-
const raw = result.outputs.
|
|
190
|
+
// The build output the other three reads are handed — a string, not a path
|
|
191
|
+
// (cli/handlers/lifecycle.ts). Nothing on this path reads it; the mirror is
|
|
192
|
+
// kept so the four methods take the same shape. With no predicting lexicon
|
|
193
|
+
// to take it from, it is the first output the build produced, or empty.
|
|
194
|
+
const raw = [...result.outputs.values()][0];
|
|
193
195
|
const buildOutput = raw === undefined ? "" : typeof raw === "string" ? raw : (raw as SerializerResult).primary;
|
|
194
196
|
|
|
195
197
|
const options: PredictBehaviourOptions = {
|
|
@@ -204,7 +206,7 @@ export async function predictDeclared(projectPath: string, args: PredictBehaviou
|
|
|
204
206
|
edges,
|
|
205
207
|
edgeCoverage: declaredEdgeCoverage(),
|
|
206
208
|
};
|
|
207
|
-
const answer = await
|
|
209
|
+
const answer = await createBehaviourPredict({ kinds })(options);
|
|
208
210
|
// On arrival, with the names that were asked — `behaviourReport` checks the
|
|
209
211
|
// ordinary route and says a consumer that needs the guarantee checks again.
|
|
210
212
|
return validateBehaviourResult(answer, options.entityNames);
|