statelyai 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -21
- package/dist/bin.mjs +1 -1
- package/dist/{cli-CE7KhcOJ.mjs → cli-BBG--n3O.mjs} +10556 -10334
- package/dist/coverageWorker.d.mts +1 -0
- package/dist/coverageWorker.mjs +178 -0
- package/dist/index.d.mts +65 -17
- package/dist/index.mjs +2 -2
- package/dist/skills/setup-behavior-ci/SKILL.md +25 -0
- package/dist/skills/setup-behavior-ci/agents/openai.yaml +3 -0
- package/dist/skills/setup-behavior-ci/assets/campaign-adapter.mjs +203 -0
- package/dist/skills/setup-behavior-ci/assets/campaign-producer.mjs +63 -0
- package/dist/skills/setup-behavior-ci/assets/config.json +39 -0
- package/dist/skills/setup-behavior-ci/assets/estimated-model.json +12 -0
- package/dist/skills/setup-behavior-ci/assets/executable-config.json +42 -0
- package/dist/skills/setup-behavior-ci/assets/minimal.campaign.mjs +50 -0
- package/dist/skills/setup-behavior-ci/assets/plan.json +86 -0
- package/dist/skills/setup-behavior-ci/assets/vitest-adapter.mjs +47 -0
- package/dist/skills/setup-behavior-ci/assets/workflow.yml +28 -0
- package/dist/skills/setup-behavior-ci/references/contract.md +31 -0
- package/dist/skills/setup-behavior-ci/references/executable.md +41 -0
- package/dist/testing.d.mts +1 -0
- package/dist/testing.mjs +3 -0
- package/package.json +13 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createRequire, registerHooks } from "node:module";
|
|
2
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { register } from "tsx/esm/api";
|
|
8
|
+
import { createXStateSourceCoverageGraph, extractXStateSourceCoverage, getXStateSourceCoverageEventCases, instrumentXStateSourceCoverage } from "@statelyai/sdk/source-coverage";
|
|
9
|
+
|
|
10
|
+
//#region src/sourceCoverageRunner.ts
|
|
11
|
+
/** Executes trusted source with ordinary local permissions; called in a worker. */
|
|
12
|
+
async function runSourceCoverageFile(options) {
|
|
13
|
+
const fileName = await realpath(resolve(options.sourceFile));
|
|
14
|
+
const source = await readFile(fileName, "utf8");
|
|
15
|
+
const manifest = await extractXStateSourceCoverage(source, { fileName });
|
|
16
|
+
if (!options.execute) return {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
mode: "extract",
|
|
19
|
+
status: manifest.complete ? "complete" : "incomplete",
|
|
20
|
+
manifest,
|
|
21
|
+
exitCode: manifest.complete ? 0 : 2
|
|
22
|
+
};
|
|
23
|
+
if (!manifest.complete) throw Object.assign(/* @__PURE__ */ new Error("Source extraction is incomplete."), { diagnostics: manifest.diagnostics });
|
|
24
|
+
const projectRequire = createRequire(fileName);
|
|
25
|
+
const load = async (name) => import(pathToFileURL(projectRequire.resolve(name)).href);
|
|
26
|
+
const graph = await load("@statelyai/graph");
|
|
27
|
+
const testing = await load("@xstate/test");
|
|
28
|
+
if (typeof graph.getSourceCoverageWitnesses !== "function" || typeof testing.createSourceCoverageSession !== "function") throw new Error("Source coverage requires @statelyai/graph with source-coverage planning and @xstate/test with createSourceCoverageSession. Upgrade these project dependencies.");
|
|
29
|
+
const runtimeName = options.runtime ?? "xstate";
|
|
30
|
+
const runtime = await load(runtimeName);
|
|
31
|
+
const runtimePackage = projectRequire(`${runtimeName}/package.json`);
|
|
32
|
+
const runtimeId = `${runtimePackage.name}@${runtimePackage.version}`;
|
|
33
|
+
if (typeof runtime.getMicrosteps !== "function") throw new Error("The selected runtime does not expose microstep execution.");
|
|
34
|
+
const instrumented = await instrumentXStateSourceCoverage(source, { fileName });
|
|
35
|
+
const transpiled = ts.transpileModule(instrumented.source, {
|
|
36
|
+
fileName,
|
|
37
|
+
compilerOptions: {
|
|
38
|
+
target: ts.ScriptTarget.ES2022,
|
|
39
|
+
module: ts.ModuleKind.ESNext
|
|
40
|
+
},
|
|
41
|
+
reportDiagnostics: true
|
|
42
|
+
});
|
|
43
|
+
if (transpiled.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) throw new Error("The instrumented module could not be compiled.");
|
|
44
|
+
const sourceUrl = pathToFileURL(fileName).href;
|
|
45
|
+
const unregister = register({ tsconfig: ts.findConfigFile(dirname(fileName), ts.sys.fileExists) ?? false });
|
|
46
|
+
const hooks = registerHooks({ load(url, context, nextLoad) {
|
|
47
|
+
if (url === sourceUrl) return {
|
|
48
|
+
format: "module",
|
|
49
|
+
source: transpiled.outputText,
|
|
50
|
+
shortCircuit: true
|
|
51
|
+
};
|
|
52
|
+
return nextLoad(url, context);
|
|
53
|
+
} });
|
|
54
|
+
try {
|
|
55
|
+
const module = await import(sourceUrl);
|
|
56
|
+
const selected = module[options.exportName ?? "machine"];
|
|
57
|
+
const control = module[instrumented.controlExport];
|
|
58
|
+
if (!selected || !control) throw new Error("Expected the selected machine export and source coverage control.");
|
|
59
|
+
const input = options.inputFile ? JSON.parse(await readFile(options.inputFile, "utf8")) : void 0;
|
|
60
|
+
const machineIndex = options.machineIndex ?? 0;
|
|
61
|
+
if (!Number.isSafeInteger(machineIndex) || machineIndex < 0 || machineIndex >= manifest.machineCount) throw new Error("machineIndex is outside the extracted machine range.");
|
|
62
|
+
const selectedManifest = {
|
|
63
|
+
...manifest,
|
|
64
|
+
obligations: manifest.obligations.filter((obligation) => obligation.machineIndex === machineIndex)
|
|
65
|
+
};
|
|
66
|
+
const createSession = (input) => {
|
|
67
|
+
const machine = typeof selected === "function" ? selected(input) : selected;
|
|
68
|
+
if (!machine.eventSchema || !machine.sources) throw new Error("Source coverage execution currently requires an XState v6 machine.");
|
|
69
|
+
return testing.createSourceCoverageSession(machine, control, {
|
|
70
|
+
input,
|
|
71
|
+
runtimeId,
|
|
72
|
+
validateEvent: async (event, snapshot) => {
|
|
73
|
+
const candidate = event;
|
|
74
|
+
if (typeof candidate.type === "string" && candidate.type.startsWith("xstate.") && typeof runtime.getNextTransitions === "function" && runtime.getNextTransitions(snapshot).some((transition) => transition.eventType === candidate.type && Object.entries(transition.matches ?? {}).every(([key, value]) => Object.is(candidate[key], value)))) return;
|
|
75
|
+
const validation = await machine.eventSchema["~standard"].validate(event);
|
|
76
|
+
if (validation.issues) throw new Error(`Event validation failed: ${JSON.stringify(validation.issues)}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
let result;
|
|
81
|
+
if (options.replayFile) {
|
|
82
|
+
const saved = JSON.parse(await readFile(options.replayFile, "utf8"));
|
|
83
|
+
const witnesses = Array.isArray(saved) ? saved : saved.witnesses;
|
|
84
|
+
if (!Array.isArray(witnesses)) throw new Error("Replay file must contain a witness array or a report with witnesses.");
|
|
85
|
+
const actual = [];
|
|
86
|
+
for (const witness of witnesses) actual.push((await graph.getSourceCoverageReplay(manifest, witness, createSession)).witness);
|
|
87
|
+
result = {
|
|
88
|
+
report: graph.getSourceCoverageReport(selectedManifest, actual),
|
|
89
|
+
witnesses: actual,
|
|
90
|
+
exploration: { replayed: actual.length }
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
if (!options.casesFile) throw new Error("Execution requires --cases or --replay.");
|
|
94
|
+
const cases = JSON.parse(await readFile(options.casesFile, "utf8"));
|
|
95
|
+
if (!Array.isArray(cases) || cases.some((event) => !event || typeof event.type !== "string")) throw new Error("Cases must be an array of event objects with string types.");
|
|
96
|
+
const suggest = options.suggestFile ? (await import(pathToFileURL(resolve(options.suggestFile)).href)).default : void 0;
|
|
97
|
+
if (options.suggestFile && typeof suggest !== "function") throw new Error("Suggestion module must default-export a function.");
|
|
98
|
+
result = await graph.getSourceCoverageWitnesses({
|
|
99
|
+
manifest,
|
|
100
|
+
machineIndex,
|
|
101
|
+
input,
|
|
102
|
+
createSession,
|
|
103
|
+
events: cases,
|
|
104
|
+
maxStates: options.maxStates,
|
|
105
|
+
maxDepth: options.maxDepth,
|
|
106
|
+
maxExecutions: options.maxExecutions,
|
|
107
|
+
suggestEvents: async (args) => {
|
|
108
|
+
const boundaries = getXStateSourceCoverageEventCases({
|
|
109
|
+
...selectedManifest,
|
|
110
|
+
obligations: args.uncovered
|
|
111
|
+
}, args.state.snapshot, cases);
|
|
112
|
+
const suggestions = await suggest?.({
|
|
113
|
+
...args,
|
|
114
|
+
sourceHash: manifest.source.hash
|
|
115
|
+
}) ?? [];
|
|
116
|
+
if (!Array.isArray(suggestions) || suggestions.some((event) => !event || typeof event.type !== "string")) throw new Error("Suggested cases must be event objects.");
|
|
117
|
+
return [...boundaries, ...suggestions];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (options.shrink !== false) {
|
|
121
|
+
const minimized = [];
|
|
122
|
+
for (const witness of result.witnesses) minimized.push((await graph.getMinimizedSourceCoverageWitness(manifest, witness, createSession, { maxExecutions: 100 })).witness);
|
|
123
|
+
result.witnesses = graph.getReducedSourceCoverageWitnesses(selectedManifest, minimized);
|
|
124
|
+
}
|
|
125
|
+
const verified = [];
|
|
126
|
+
for (const witness of result.witnesses) verified.push((await graph.getSourceCoverageReplay(manifest, witness, createSession)).witness);
|
|
127
|
+
result.witnesses = verified;
|
|
128
|
+
result.report = graph.getSourceCoverageReport(selectedManifest, verified);
|
|
129
|
+
}
|
|
130
|
+
const projection = await createXStateSourceCoverageGraph(source, result.report, {
|
|
131
|
+
fileName,
|
|
132
|
+
machineIndex,
|
|
133
|
+
profile: "xstate-v6-alpha"
|
|
134
|
+
});
|
|
135
|
+
const machine = typeof selected === "function" ? selected(input) : selected;
|
|
136
|
+
const schemas = Object.entries(machine.schemas?.events ?? {});
|
|
137
|
+
const typeOnlyEvents = schemas.filter(([, schema]) => schema?.["~standard"]?.vendor === "xstate.types").map(([type]) => type);
|
|
138
|
+
const runtimeSchemaEvents = schemas.filter(([, schema]) => schema?.["~standard"]?.vendor && schema["~standard"].vendor !== "xstate.types").map(([type]) => type);
|
|
139
|
+
const unconstrainedEvents = [...new Set(result.witnesses.flatMap((witness) => witness.events.map((event) => event.type)))].filter((type) => !schemas.some(([key]) => key === type));
|
|
140
|
+
return {
|
|
141
|
+
schemaVersion: 1,
|
|
142
|
+
mode: options.replayFile ? "replay" : "execute",
|
|
143
|
+
status: result.report.complete ? "complete" : "incomplete",
|
|
144
|
+
machineIndex,
|
|
145
|
+
runtimeId,
|
|
146
|
+
effectsExecuted: false,
|
|
147
|
+
payloadValidation: {
|
|
148
|
+
typeOnlyEvents,
|
|
149
|
+
runtimeSchemaEvents,
|
|
150
|
+
unconstrainedEvents,
|
|
151
|
+
modeledRuntimeEvents: result.witnesses.flatMap((witness) => witness.events.filter((event) => event.type.startsWith("xstate.")))
|
|
152
|
+
},
|
|
153
|
+
manifest: selectedManifest,
|
|
154
|
+
...result,
|
|
155
|
+
graph: projection.graph,
|
|
156
|
+
exitCode: result.report.complete ? 0 : 1
|
|
157
|
+
};
|
|
158
|
+
} finally {
|
|
159
|
+
hooks.deregister();
|
|
160
|
+
await unregister();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/coverageWorker.ts
|
|
166
|
+
if (parentPort) runSourceCoverageFile(workerData).then((result) => parentPort.postMessage({
|
|
167
|
+
ok: true,
|
|
168
|
+
result
|
|
169
|
+
}), (error) => parentPort.postMessage({
|
|
170
|
+
ok: false,
|
|
171
|
+
error: {
|
|
172
|
+
message: error instanceof Error ? error.message : String(error),
|
|
173
|
+
diagnostics: error?.diagnostics
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
export { };
|
package/dist/index.d.mts
CHANGED
|
@@ -69,10 +69,10 @@ declare const behaviorCiResultsSchema: z.ZodArray<z.ZodObject<{
|
|
|
69
69
|
file: z.ZodString;
|
|
70
70
|
fullName: z.ZodString;
|
|
71
71
|
outcome: z.ZodEnum<{
|
|
72
|
-
error: "error";
|
|
73
72
|
passed: "passed";
|
|
74
73
|
failed: "failed";
|
|
75
74
|
skipped: "skipped";
|
|
75
|
+
error: "error";
|
|
76
76
|
}>;
|
|
77
77
|
detail: z.ZodString;
|
|
78
78
|
}, z.core.$strict>>;
|
|
@@ -129,6 +129,53 @@ declare class BehaviorCalibrateCommand extends Command {
|
|
|
129
129
|
run(): Promise<void>;
|
|
130
130
|
}
|
|
131
131
|
//#endregion
|
|
132
|
+
//#region src/sourceCoverageRunner.d.ts
|
|
133
|
+
interface SourceCoverageRunOptions {
|
|
134
|
+
sourceFile: string;
|
|
135
|
+
execute?: boolean;
|
|
136
|
+
casesFile?: string;
|
|
137
|
+
inputFile?: string;
|
|
138
|
+
replayFile?: string;
|
|
139
|
+
runtime?: string;
|
|
140
|
+
exportName?: string;
|
|
141
|
+
machineIndex?: number;
|
|
142
|
+
maxStates?: number;
|
|
143
|
+
maxDepth?: number;
|
|
144
|
+
maxExecutions?: number;
|
|
145
|
+
shrink?: boolean;
|
|
146
|
+
/** Trusted callback module supplying additional event cases from coverage gaps. */
|
|
147
|
+
suggestFile?: string;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/sourceCoverageCommand.d.ts
|
|
151
|
+
declare function runSourceCoverageWorker(options: SourceCoverageRunOptions, timeoutMs?: number, workerUrl?: URL): Promise<any>;
|
|
152
|
+
declare class SourceCoverageCommand extends Command {
|
|
153
|
+
static enableJsonFlag: boolean;
|
|
154
|
+
static summary: string;
|
|
155
|
+
static description: string;
|
|
156
|
+
static args: {
|
|
157
|
+
source: _oclif_core_interfaces0.Arg<string, Record<string, unknown>>;
|
|
158
|
+
};
|
|
159
|
+
static flags: {
|
|
160
|
+
execute: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
161
|
+
cases: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
162
|
+
input: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
163
|
+
replay: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
164
|
+
runtime: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
|
|
165
|
+
export: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
|
|
166
|
+
"machine-index": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
|
|
167
|
+
"max-states": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
|
|
168
|
+
"max-depth": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
|
|
169
|
+
"max-executions": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
|
|
170
|
+
timeout: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
|
|
171
|
+
shrink: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
172
|
+
suggest: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
173
|
+
report: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
|
|
174
|
+
graph: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
175
|
+
};
|
|
176
|
+
run(): Promise<void>;
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
132
179
|
//#region src/testCommand.d.ts
|
|
133
180
|
declare class TestCommand extends Command {
|
|
134
181
|
static enableJsonFlag: boolean;
|
|
@@ -321,7 +368,7 @@ declare function createStatelyProjectConfig(options: {
|
|
|
321
368
|
defaultXStateVersion?: number;
|
|
322
369
|
}): StatelyProjectConfig;
|
|
323
370
|
//#endregion
|
|
324
|
-
//#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.
|
|
371
|
+
//#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.5.0_cytoscape@3.34.0_d3-force@3.0.0_d3-hiera_b5a11f1847a44c5e54d10b16dab0c7b9/node_modules/@statelyai/graph/dist/types-BAEQTwK_.d.mts
|
|
325
372
|
//#region src/types.d.ts
|
|
326
373
|
/**
|
|
327
374
|
* Directedness of a graph or an individual edge.
|
|
@@ -885,6 +932,7 @@ declare const COMMANDS: {
|
|
|
885
932
|
'behavior:check': typeof BehaviorCheckCommand;
|
|
886
933
|
'behavior:adopt': typeof BehaviorAdoptCommand;
|
|
887
934
|
test: typeof TestCommand;
|
|
935
|
+
coverage: typeof SourceCoverageCommand;
|
|
888
936
|
verify: typeof VerifyCommand;
|
|
889
937
|
plan: typeof PlanCommand;
|
|
890
938
|
diff: typeof DiffCommand;
|
|
@@ -910,8 +958,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
910
958
|
version: z.ZodLiteral<1>;
|
|
911
959
|
suite: z.ZodString;
|
|
912
960
|
status: z.ZodEnum<{
|
|
913
|
-
failed: "failed";
|
|
914
961
|
passed: "passed";
|
|
962
|
+
failed: "failed";
|
|
915
963
|
}>;
|
|
916
964
|
verdict: z.ZodEnum<{
|
|
917
965
|
inconclusive: "inconclusive";
|
|
@@ -1016,8 +1064,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1016
1064
|
type: z.ZodLiteral<"path-result">;
|
|
1017
1065
|
id: z.ZodString;
|
|
1018
1066
|
status: z.ZodEnum<{
|
|
1019
|
-
failed: "failed";
|
|
1020
1067
|
passed: "passed";
|
|
1068
|
+
failed: "failed";
|
|
1021
1069
|
}>;
|
|
1022
1070
|
witnessedStates: z.ZodArray<z.ZodString>;
|
|
1023
1071
|
witnessedTransitions: z.ZodArray<z.ZodString>;
|
|
@@ -1028,8 +1076,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1028
1076
|
state: z.ZodString;
|
|
1029
1077
|
event: z.ZodString;
|
|
1030
1078
|
status: z.ZodEnum<{
|
|
1031
|
-
failed: "failed";
|
|
1032
1079
|
passed: "passed";
|
|
1080
|
+
failed: "failed";
|
|
1033
1081
|
}>;
|
|
1034
1082
|
}, z.core.$strip>>>;
|
|
1035
1083
|
divergences: z.ZodArray<z.ZodObject<{
|
|
@@ -1073,8 +1121,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1073
1121
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1074
1122
|
state: z.ZodString;
|
|
1075
1123
|
status: z.ZodEnum<{
|
|
1076
|
-
failed: "failed";
|
|
1077
1124
|
passed: "passed";
|
|
1125
|
+
failed: "failed";
|
|
1078
1126
|
}>;
|
|
1079
1127
|
signals: z.ZodArray<z.ZodString>;
|
|
1080
1128
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1088,8 +1136,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1088
1136
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1089
1137
|
state: z.ZodString;
|
|
1090
1138
|
status: z.ZodEnum<{
|
|
1091
|
-
failed: "failed";
|
|
1092
1139
|
passed: "passed";
|
|
1140
|
+
failed: "failed";
|
|
1093
1141
|
}>;
|
|
1094
1142
|
signals: z.ZodArray<z.ZodString>;
|
|
1095
1143
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1159,8 +1207,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1159
1207
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1160
1208
|
state: z.ZodString;
|
|
1161
1209
|
status: z.ZodEnum<{
|
|
1162
|
-
failed: "failed";
|
|
1163
1210
|
passed: "passed";
|
|
1211
|
+
failed: "failed";
|
|
1164
1212
|
}>;
|
|
1165
1213
|
signals: z.ZodArray<z.ZodString>;
|
|
1166
1214
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1174,8 +1222,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
|
|
|
1174
1222
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1175
1223
|
state: z.ZodString;
|
|
1176
1224
|
status: z.ZodEnum<{
|
|
1177
|
-
failed: "failed";
|
|
1178
1225
|
passed: "passed";
|
|
1226
|
+
failed: "failed";
|
|
1179
1227
|
}>;
|
|
1180
1228
|
signals: z.ZodArray<z.ZodString>;
|
|
1181
1229
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1229,8 +1277,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1229
1277
|
version: z.ZodLiteral<1>;
|
|
1230
1278
|
suite: z.ZodString;
|
|
1231
1279
|
status: z.ZodEnum<{
|
|
1232
|
-
failed: "failed";
|
|
1233
1280
|
passed: "passed";
|
|
1281
|
+
failed: "failed";
|
|
1234
1282
|
}>;
|
|
1235
1283
|
verdict: z.ZodEnum<{
|
|
1236
1284
|
inconclusive: "inconclusive";
|
|
@@ -1335,8 +1383,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1335
1383
|
type: z.ZodLiteral<"path-result">;
|
|
1336
1384
|
id: z.ZodString;
|
|
1337
1385
|
status: z.ZodEnum<{
|
|
1338
|
-
failed: "failed";
|
|
1339
1386
|
passed: "passed";
|
|
1387
|
+
failed: "failed";
|
|
1340
1388
|
}>;
|
|
1341
1389
|
witnessedStates: z.ZodArray<z.ZodString>;
|
|
1342
1390
|
witnessedTransitions: z.ZodArray<z.ZodString>;
|
|
@@ -1347,8 +1395,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1347
1395
|
state: z.ZodString;
|
|
1348
1396
|
event: z.ZodString;
|
|
1349
1397
|
status: z.ZodEnum<{
|
|
1350
|
-
failed: "failed";
|
|
1351
1398
|
passed: "passed";
|
|
1399
|
+
failed: "failed";
|
|
1352
1400
|
}>;
|
|
1353
1401
|
}, z.core.$strip>>>;
|
|
1354
1402
|
divergences: z.ZodArray<z.ZodObject<{
|
|
@@ -1392,8 +1440,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1392
1440
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1393
1441
|
state: z.ZodString;
|
|
1394
1442
|
status: z.ZodEnum<{
|
|
1395
|
-
failed: "failed";
|
|
1396
1443
|
passed: "passed";
|
|
1444
|
+
failed: "failed";
|
|
1397
1445
|
}>;
|
|
1398
1446
|
signals: z.ZodArray<z.ZodString>;
|
|
1399
1447
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1407,8 +1455,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1407
1455
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1408
1456
|
state: z.ZodString;
|
|
1409
1457
|
status: z.ZodEnum<{
|
|
1410
|
-
failed: "failed";
|
|
1411
1458
|
passed: "passed";
|
|
1459
|
+
failed: "failed";
|
|
1412
1460
|
}>;
|
|
1413
1461
|
signals: z.ZodArray<z.ZodString>;
|
|
1414
1462
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1478,8 +1526,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1478
1526
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1479
1527
|
state: z.ZodString;
|
|
1480
1528
|
status: z.ZodEnum<{
|
|
1481
|
-
failed: "failed";
|
|
1482
1529
|
passed: "passed";
|
|
1530
|
+
failed: "failed";
|
|
1483
1531
|
}>;
|
|
1484
1532
|
signals: z.ZodArray<z.ZodString>;
|
|
1485
1533
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1493,8 +1541,8 @@ declare const verificationRequestSchema: z.ZodObject<{
|
|
|
1493
1541
|
predicateResults: z.ZodArray<z.ZodObject<{
|
|
1494
1542
|
state: z.ZodString;
|
|
1495
1543
|
status: z.ZodEnum<{
|
|
1496
|
-
failed: "failed";
|
|
1497
1544
|
passed: "passed";
|
|
1545
|
+
failed: "failed";
|
|
1498
1546
|
}>;
|
|
1499
1547
|
signals: z.ZodArray<z.ZodString>;
|
|
1500
1548
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -1822,4 +1870,4 @@ declare function verifyProject(options?: VerifyProjectOptions): Promise<{
|
|
|
1822
1870
|
/** One case per recorded path/probe. Unattributed divergences remain separate cases. */
|
|
1823
1871
|
declare function formatVerificationJUnit(report: VerificationRunReport): string;
|
|
1824
1872
|
//#endregion
|
|
1825
|
-
export { ApiKeyResolution, type BehaviorCiAdapter, type BehaviorCiConfig, type BehaviorCiResult, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, type LinkProjectOptions, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, type VerificationExecutor, type VerificationRequest, type VerificationRunReport, type VerifyProjectOptions, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
|
|
1873
|
+
export { ApiKeyResolution, type BehaviorCiAdapter, type BehaviorCiConfig, type BehaviorCiResult, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, type LinkProjectOptions, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, SourceCoverageCommand, type SourceCoverageRunOptions, type VerificationExecutor, type VerificationRequest, type VerificationRunReport, type VerifyProjectOptions, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, runSourceCoverageWorker, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as formatVerificationJUnit, B as
|
|
1
|
+
import { A as formatVerificationJUnit, B as behaviorCiConfigSchema, C as run, D as resourceId, E as linkProject, F as readGraphFile, H as calibrateBehavior, I as verifyReport, L as SourceCoverageCommand, M as briefReport, N as changesReport, O as resolveVerificationRequest, P as pendingReport, R as runSourceCoverageWorker, S as resolveProjectStatus, T as scanProjectSources, U as checkBehavior, V as behaviorCiResultsSchema, _ as isFileGitDirty, a as discoverMachinesInSource, b as resolveCredentialStatus, c as formatDiscoveredMachines, d as formatProjectStatus, f as formatStoredCredentialSource, g as initProject, h as inferInitProjectName, i as discoverLinkedPullTargets, j as createStatelyProjectConfig, k as verifyProject, l as formatMissingNewMachineDirMessage, m as getEnvCredential, n as buildOAuthLoginStart, o as discoverOAuthLogin, p as getEnvApiKey, r as classifyPushCandidates, s as discoverRemoteProjectMachineTargets, t as COMMANDS, u as formatPlanSummary, v as resolveApiKey, w as scanProjectMachines, x as resolveOpenRootDir, y as resolveConfiguredPullTargets, z as adoptBehavior } from "./cli-BBG--n3O.mjs";
|
|
2
2
|
|
|
3
|
-
export { COMMANDS, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
|
|
3
|
+
export { COMMANDS, SourceCoverageCommand, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, runSourceCoverageWorker, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-behavior-ci
|
|
3
|
+
description: Set up deterministic Stately behavior CI from existing Vitest tests or authored executable model-based campaigns, with reviewed requirements, calibrated mappings, real application adapters and immutable evidence history.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Set up the selected feature from real source and authoritative intent. CI runs the existing CLI and tests without an LLM. Author the model and bindings for the user; do not ask them to maintain hashes.
|
|
7
|
+
|
|
8
|
+
Choose the mode before setup. Preserve the existing selected-Vitest mode when connecting current tests to an estimated model. For an executable model-based campaign, read [executable mode](references/executable.md), author the model, generators, real adapter and requirement assertions, then apply the shared calibration/adoption rules below. Do not treat estimated graph coverage as executed requirement evidence.
|
|
9
|
+
|
|
10
|
+
This skill ships with `statelyai` at `dist/skills/setup-behavior-ci/SKILL.md`. Resolve its exported `statelyai/skills/setup-behavior-ci/SKILL.md` path from a local install; copy the complete skill directory into the target agent's skill location when installation is requested. Its relative assets/references remain usable outside this monorepo. Repository `.agents/skills/setup-behavior-ci` is the sole maintained source; the package build copies it deterministically.
|
|
11
|
+
|
|
12
|
+
For selected-Vitest mode:
|
|
13
|
+
|
|
14
|
+
1. Discover the feature's existing test files, native runner command/configuration, source dependencies and authoritative requirements. Run the actual bounded Vitest selection once with `--reporter=json --outputFile=<fresh temporary file>`. Read native `testResults[].name` and `assertionResults[].fullName`; copy exact titles into mappings. Map every case in the selected native command, including existing public tests. Compare the complete `(file, fullName)` inventory against config mappings and require exact equality before proceeding. Label ancillary observed smoke checks as observations rather than invented requirements. Preserve existing behavioral assertions. Don't substitute toy tests or invent reporter titles.
|
|
15
|
+
2. Read [the contract](references/contract.md). Generate a short `spec.json` with meaningful stable requirement/check IDs, acceptance criteria, source authority/citations, and `retire: []`. Requirements inferred from implementation must be labeled inference. Estimate the behavior as XGraph JSON in `model.json`; describe states/transitions that matter, including retained edge cases. Entity mappings aid investigation; they do not prove coverage. Review generated meaning against source/spec and existing assertions. The model is an estimate, not executable test truth.
|
|
16
|
+
3. Independently challenge the estimated model with external inputs. Inventory all seven categories in [plan](assets/plan.json): user, host-update, async-result, cancellation, timer, server-event, lifecycle. Trace each relevant state owner and ingress in source, not just the selected tests. Combine operations across owners, such as host replacement after selection, cancellation before completion, disposal during work, and timer expiry during retry. Map discovered obligations to stable requirement/check/entity IDs, or record a cited gap or reasoned not-applicable entry. Keep cited intent, observations, inferences and assumptions distinct. Existing passing tests do not settle requirement conflicts.
|
|
17
|
+
4. Copy/adapt [config](assets/config.json) and the self-contained [Vitest adapter](assets/vitest-adapter.mjs). Select bounded source, test producer, runner configuration, and meaningful dependency files. Paths are repository-relative, including check file paths; command cwd may be a package. Include project/package manifests and lockfiles for the selected dependency scope. The CLI automatically includes root and command-cwd manifests/locks, config/spec/model/adapter/mapped tests and exact baseline files. Materialize external/user intent in checked requirements documents and list authoritative intent files in `plan.authorityInputs`; edits to them must invalidate calibration. Put runner/producer configuration modules in `plan.runnerInputs` so changes require recalibration. Pin CI Node and package-manager versions to the calibrated versions; original and current OS/architecture remain evidence. Limit all selected files together to 200. No broad workspace directory or `node_modules` selection. Keep state/output outside inputs.
|
|
18
|
+
5. Adapt [workflow](assets/workflow.yml) to the repository's package manager, lockfile, installation, CLI pin/build, selected command and paths. Use hosted runners by default, `contents: read`, unconditional artifact upload including hidden files and job summary. Keep `GITHUB_SHA` tied to actual checkout. Never run an LLM, auto-adopt, push, or add auth credentials in CI. Include the actual workflow path and Vitest config in config inputs before staging and checking. Existing trusted self-hosted runner workflows require same-repository trusted branch triggers, not arbitrary fork code.
|
|
19
|
+
6. Stage generated input files with `git add` before the first local run. `baseline: null` bootstraps a public FileStore model and behavior record. Run `statelyai behavior calibrate --config <config> --output <fresh ignored directory>` against the unchanged implementation first. The CLI executes the real native runner and generates a self-contained calibration receipt. Inspect every mismatch against authoritative requirements. Correct a wrong model/test assumption or fix the implementation, then recalibrate. If a known implementation defect must remain red, add a cited, reasoned `implementation-defect` disposition to the plan before recalibration. Unresolved calibration mismatches block adoption. Never copy observed bugs into the requirements automatically. Inspect summary, native report, argv/exit/logs, manifest, model bindings and current gaps. A passing run is exit 0; mapped test failures/missing/skipped checks are 1; invalid evidence is 2. Fix setup errors; do not shrink obligations to make red pass.
|
|
20
|
+
7. Explicitly run `statelyai behavior adopt <inspected output> --config <config>`. Adopt red as well as green records before the next evolution so failures remain in history. Commit generated config/spec/model/adapter/plan, the installed calibration receipt and the complete stateRoot `.statelyai` tree. Adoption updates baseline; no manual hashes. For dirty local runs, bound bytes must still match when adopting. `git add -f` may be needed when repository ignores `.statelyai`. Never commit output logs/runstore.
|
|
21
|
+
8. Verify the generated setup with the built/installed CLI and real tests. Check a deliberate assertion failure in a selected existing test produces exit 1, a missing required mapping cannot pass, and a restored run passes while history still shows failure. Any unmapped selected case, including a passing case, is ERROR and cannot be adopted; fix the mapping inventory before application repair. Never turn that ERROR into a claimed behavioral FAIL. Keep experimental changes isolated. Remote execution is a separate verification step requiring actual run/log/artifact evidence. On a remote red or green run, download and inspect the artifact, adopt it against its exact checked parent, then commit the advanced state before the next run.
|
|
22
|
+
|
|
23
|
+
Normal CI runs `behavior check` with the checked receipt. Changed model/spec/plan/mappings/adapter/producer or runner identity requires explicit native recalibration and inspected adoption. Implementation changes run against the stable calibrated contract. Calibration binds its original revision and files; it does not pin all future app revisions. Schema 1 remains readable but has no setup assurance claim.
|
|
24
|
+
|
|
25
|
+
On later requirement changes, update spec and model estimate, preserve IDs for the same obligation, and use an explicit reasoned retirement to remove one. Omitted requirements/checks remain active. Refresh mappings from actual reporter output when tests change. Adopt refuses divergent ancestry; inspect both refs and rerun from the chosen checked baseline rather than rewriting artifact history.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const statuses = new Set(["passed", "discrepancy", "inconclusive"]);
|
|
2
|
+
const nonempty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
3
|
+
const count = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
4
|
+
const fail = (message) => {
|
|
5
|
+
throw new Error(`Invalid campaign evidence: ${message}`);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const executionFullName = "campaign.execution";
|
|
9
|
+
export const assertionFullName = (requirementId, checkId) =>
|
|
10
|
+
`campaign.assertion ${JSON.stringify([requirementId, checkId])}`;
|
|
11
|
+
|
|
12
|
+
export default function normalize(envelope) {
|
|
13
|
+
const { file, report } = envelope ?? {};
|
|
14
|
+
if (
|
|
15
|
+
!nonempty(file) ||
|
|
16
|
+
file.startsWith("/") ||
|
|
17
|
+
file.includes("\\") ||
|
|
18
|
+
file.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
19
|
+
!file.endsWith(".mjs")
|
|
20
|
+
)
|
|
21
|
+
fail("file must be a repository-relative campaign .mjs path");
|
|
22
|
+
if (report?.formatVersion !== 1 || report.mode !== "campaign" || !statuses.has(report.status))
|
|
23
|
+
fail("expected a native full-campaign report");
|
|
24
|
+
if (
|
|
25
|
+
!Array.isArray(report.phases) ||
|
|
26
|
+
report.phases.length !== 2 ||
|
|
27
|
+
report.phases[0]?.phase !== "paths" ||
|
|
28
|
+
report.phases[1]?.phase !== "property" ||
|
|
29
|
+
report.phases.some((phase) => !statuses.has(phase.status))
|
|
30
|
+
)
|
|
31
|
+
fail("expected paths and property phases in order");
|
|
32
|
+
const status = report.phases.some((phase) => phase.status === "inconclusive")
|
|
33
|
+
? "inconclusive"
|
|
34
|
+
: report.phases.some((phase) => phase.status === "discrepancy")
|
|
35
|
+
? "discrepancy"
|
|
36
|
+
: "passed";
|
|
37
|
+
if (status !== report.status) fail("native verdict conflicts with phases");
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
!nonempty(report.campaign?.id) ||
|
|
41
|
+
!nonempty(report.campaign.intent) ||
|
|
42
|
+
!nonempty(report.campaign.scope) ||
|
|
43
|
+
!nonempty(report.campaign.modelVersion) ||
|
|
44
|
+
!Array.isArray(report.campaign.assumptions) ||
|
|
45
|
+
!report.campaign.assumptions.every(nonempty) ||
|
|
46
|
+
!Array.isArray(report.campaign.evidence) ||
|
|
47
|
+
!report.campaign.evidence.every(nonempty) ||
|
|
48
|
+
!/^[a-f0-9]{64}$/.test(report.provenance?.entrySha256 ?? "") ||
|
|
49
|
+
report.provenance?.hashScope !== "entry-module-only" ||
|
|
50
|
+
!nonempty(report.provenance.node) ||
|
|
51
|
+
!["runner", "xstate", "xstateTest", "fastCheck"].every((key) =>
|
|
52
|
+
nonempty(report.provenance.versions?.[key]),
|
|
53
|
+
) ||
|
|
54
|
+
!Number.isSafeInteger(report.budget?.seed) ||
|
|
55
|
+
!["numRuns", "maxCommands", "pathLimit", "timeoutMs"].every(
|
|
56
|
+
(key) => count(report.budget?.[key]) && report.budget[key] > 0,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
fail("native campaign metadata, provenance or budget missing");
|
|
60
|
+
for (const phase of report.phases) {
|
|
61
|
+
if (phase.status !== "passed" && !nonempty(phase.message)) fail("native diagnostic missing");
|
|
62
|
+
if (phase.status !== "passed" && phase.requirementGaps === undefined) continue;
|
|
63
|
+
const coverage = phase.coverage;
|
|
64
|
+
if (
|
|
65
|
+
!coverage ||
|
|
66
|
+
!count(coverage.totals?.runs) ||
|
|
67
|
+
coverage.totals.runs === 0 ||
|
|
68
|
+
!coverage.exploration ||
|
|
69
|
+
typeof coverage.exploration.truncated !== "boolean" ||
|
|
70
|
+
!Array.isArray(coverage.exploration.truncationReasons) ||
|
|
71
|
+
(coverage.exploration.truncated &&
|
|
72
|
+
(coverage.exploration.truncationReasons.length === 0 ||
|
|
73
|
+
coverage.exploration.truncationReasons.some(
|
|
74
|
+
(reason) => phase.phase !== "property" || reason !== "maximum sequence length reached",
|
|
75
|
+
))) ||
|
|
76
|
+
!Array.isArray(coverage.temporal?.failed) ||
|
|
77
|
+
coverage.temporal.failed.length > 0 ||
|
|
78
|
+
!Array.isArray(coverage.temporal?.inconclusive) ||
|
|
79
|
+
coverage.temporal.inconclusive.length > 0
|
|
80
|
+
)
|
|
81
|
+
fail("passing or coverage-gap native phase lacks completed execution evidence");
|
|
82
|
+
const assertions = [
|
|
83
|
+
"sutComparisons",
|
|
84
|
+
"oracleComparisons",
|
|
85
|
+
"invariantChecks",
|
|
86
|
+
"temporalChecks",
|
|
87
|
+
].map((key) => coverage.totals[key]);
|
|
88
|
+
assertions.push(phase.additionalAssertions ?? 0);
|
|
89
|
+
if (!assertions.every(count) || assertions.reduce((total, value) => total + value, 0) === 0)
|
|
90
|
+
fail("passing or coverage-gap native phase lacks observable assertions");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const declarations = report.campaign?.requirements;
|
|
94
|
+
if (!Array.isArray(declarations) || declarations.length === 0)
|
|
95
|
+
fail("nonempty campaign.requirements declarations required");
|
|
96
|
+
const requirements = new Map();
|
|
97
|
+
const checks = new Set();
|
|
98
|
+
for (const requirement of declarations) {
|
|
99
|
+
if (
|
|
100
|
+
!nonempty(requirement?.id) ||
|
|
101
|
+
requirements.has(requirement.id) ||
|
|
102
|
+
!Array.isArray(requirement.checks) ||
|
|
103
|
+
requirement.checks.length === 0
|
|
104
|
+
)
|
|
105
|
+
fail("duplicate or malformed requirement declaration");
|
|
106
|
+
const entries = new Map();
|
|
107
|
+
for (const id of requirement.checks) {
|
|
108
|
+
if (!nonempty(id) || checks.has(id)) fail("duplicate or malformed assertion declaration");
|
|
109
|
+
checks.add(id);
|
|
110
|
+
entries.set(id, { passed: 0, failed: 0 });
|
|
111
|
+
}
|
|
112
|
+
requirements.set(requirement.id, entries);
|
|
113
|
+
}
|
|
114
|
+
for (const phase of report.phases) {
|
|
115
|
+
if (!Array.isArray(phase.requirements) || phase.requirements.length !== requirements.size)
|
|
116
|
+
fail(`missing requirement inventory in ${phase.phase}`);
|
|
117
|
+
const seenRequirements = new Set();
|
|
118
|
+
for (const requirement of phase.requirements) {
|
|
119
|
+
const expected = requirements.get(requirement?.id);
|
|
120
|
+
if (
|
|
121
|
+
!expected ||
|
|
122
|
+
seenRequirements.has(requirement.id) ||
|
|
123
|
+
!Array.isArray(requirement.checks) ||
|
|
124
|
+
requirement.checks.length !== expected.size
|
|
125
|
+
)
|
|
126
|
+
fail(`requirement inventory mismatch in ${phase.phase}`);
|
|
127
|
+
seenRequirements.add(requirement.id);
|
|
128
|
+
const seenChecks = new Set();
|
|
129
|
+
for (const check of requirement.checks) {
|
|
130
|
+
const total = expected.get(check?.id);
|
|
131
|
+
if (!total || seenChecks.has(check.id) || !count(check.passed) || !count(check.failed))
|
|
132
|
+
fail(`assertion inventory or counts invalid in ${phase.phase}`);
|
|
133
|
+
if (phase.status === "passed" && check.failed > 0)
|
|
134
|
+
fail("passed phase contains failed assertions");
|
|
135
|
+
seenChecks.add(check.id);
|
|
136
|
+
total.passed += check.passed;
|
|
137
|
+
total.failed += check.failed;
|
|
138
|
+
if (!count(total.passed) || !count(total.failed)) fail("assertion counts overflow");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const results = [];
|
|
143
|
+
for (const [requirementId, entries] of requirements) {
|
|
144
|
+
for (const [checkId, counts] of entries) {
|
|
145
|
+
results.push({
|
|
146
|
+
file,
|
|
147
|
+
fullName: assertionFullName(requirementId, checkId),
|
|
148
|
+
outcome: counts.failed ? "failed" : counts.passed ? "passed" : "skipped",
|
|
149
|
+
detail:
|
|
150
|
+
`Assertion ${checkId}: ${counts.passed} passed, ${counts.failed} failed invocations across paths and property, including shrinking.`.slice(
|
|
151
|
+
0,
|
|
152
|
+
4000,
|
|
153
|
+
),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const missing = results.filter((result) => result.outcome === "skipped");
|
|
158
|
+
const inconclusive = report.phases.filter((phase) => phase.status === "inconclusive");
|
|
159
|
+
const discrepancy = report.phases.some((phase) => phase.status === "discrepancy");
|
|
160
|
+
const missingIds = [...requirements.values()].flatMap((entries) =>
|
|
161
|
+
[...entries].filter(([, counts]) => counts.passed + counts.failed === 0).map(([id]) => id),
|
|
162
|
+
);
|
|
163
|
+
for (const phase of report.phases) {
|
|
164
|
+
if (
|
|
165
|
+
phase.requirementGaps !== undefined &&
|
|
166
|
+
(discrepancy ||
|
|
167
|
+
phase.phase !== "property" ||
|
|
168
|
+
phase.status !== "inconclusive" ||
|
|
169
|
+
!Array.isArray(phase.requirementGaps) ||
|
|
170
|
+
phase.requirementGaps.length === 0 ||
|
|
171
|
+
results.some((result) => result.outcome === "failed") ||
|
|
172
|
+
new Set(phase.requirementGaps).size !== phase.requirementGaps.length ||
|
|
173
|
+
JSON.stringify([...phase.requirementGaps].sort()) !==
|
|
174
|
+
JSON.stringify([...missingIds].sort()))
|
|
175
|
+
)
|
|
176
|
+
fail("requirement gap marker conflicts with unevaluated assertions");
|
|
177
|
+
}
|
|
178
|
+
const coverageOnly =
|
|
179
|
+
inconclusive.length > 0 && inconclusive.every((phase) => phase.requirementGaps !== undefined);
|
|
180
|
+
const detail = report.phases
|
|
181
|
+
.filter((phase) => phase.status !== "passed")
|
|
182
|
+
.map((phase) => `${phase.phase}: ${phase.message}`)
|
|
183
|
+
.join("\n");
|
|
184
|
+
results.push({
|
|
185
|
+
file,
|
|
186
|
+
fullName: executionFullName,
|
|
187
|
+
outcome:
|
|
188
|
+
inconclusive.length && !coverageOnly
|
|
189
|
+
? "error"
|
|
190
|
+
: discrepancy
|
|
191
|
+
? "failed"
|
|
192
|
+
: missing.length
|
|
193
|
+
? "skipped"
|
|
194
|
+
: "passed",
|
|
195
|
+
detail: (
|
|
196
|
+
detail ||
|
|
197
|
+
(missing.length
|
|
198
|
+
? "Required assertions were not executed."
|
|
199
|
+
: "Native paths and property execution passed.")
|
|
200
|
+
).slice(0, 4000),
|
|
201
|
+
});
|
|
202
|
+
return results;
|
|
203
|
+
}
|