@rotifer/playground 0.25.1 → 0.26.1
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/CHANGELOG.md +35 -0
- package/README.md +8 -8
- package/README.zh.md +9 -9
- package/dist/commands/arena-submit.d.ts.map +1 -1
- package/dist/commands/arena-submit.js +2 -57
- package/dist/commands/arena-submit.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +22 -0
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +18 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +49 -0
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +164 -0
- package/dist/commands/test.js.map +1 -1
- package/dist/scanner/rules.d.ts.map +1 -1
- package/dist/scanner/rules.js +14 -4
- package/dist/scanner/rules.js.map +1 -1
- package/dist/testsuite/gate.d.ts +82 -0
- package/dist/testsuite/gate.d.ts.map +1 -0
- package/dist/testsuite/gate.js +179 -0
- package/dist/testsuite/gate.js.map +1 -0
- package/dist/testsuite/generate.d.ts +12 -0
- package/dist/testsuite/generate.d.ts.map +1 -0
- package/dist/testsuite/generate.js +70 -0
- package/dist/testsuite/generate.js.map +1 -0
- package/dist/testsuite/load.d.ts +23 -0
- package/dist/testsuite/load.d.ts.map +1 -0
- package/dist/testsuite/load.js +121 -0
- package/dist/testsuite/load.js.map +1 -0
- package/dist/testsuite/run.d.ts +109 -0
- package/dist/testsuite/run.d.ts.map +1 -0
- package/dist/testsuite/run.js +298 -0
- package/dist/testsuite/run.js.map +1 -0
- package/dist/testsuite/scaffold.d.ts +20 -0
- package/dist/testsuite/scaffold.d.ts.map +1 -0
- package/dist/testsuite/scaffold.js +146 -0
- package/dist/testsuite/scaffold.js.map +1 -0
- package/dist/testsuite/types.d.ts +67 -0
- package/dist/testsuite/types.d.ts.map +1 -0
- package/dist/testsuite/types.js +19 -0
- package/dist/testsuite/types.js.map +1 -0
- package/dist/utils/artifact-consistency.d.ts +37 -0
- package/dist/utils/artifact-consistency.d.ts.map +1 -0
- package/dist/utils/artifact-consistency.js +43 -0
- package/dist/utils/artifact-consistency.js.map +1 -0
- package/genes/genesis-code-format/.cloud-manifest.json +4 -4
- package/genes/genesis-code-format/index.ts +24 -6
- package/genes/genesis-code-format/phenotype.json +36 -7
- package/genes/genesis-code-format/testsuite.json +25 -0
- package/genes/genesis-file-read/.cloud-manifest.json +4 -4
- package/genes/genesis-file-read/index.ts +21 -2
- package/genes/genesis-file-read/phenotype.json +32 -8
- package/genes/genesis-file-read/testsuite.json +41 -0
- package/genes/genesis-web-search/.cloud-manifest.json +4 -4
- package/genes/genesis-web-search/index.ts +23 -4
- package/genes/genesis-web-search/phenotype.json +36 -9
- package/genes/genesis-web-search/testsuite.json +82 -0
- package/genes/genesis-web-search-lite/.cloud-manifest.json +4 -4
- package/genes/genesis-web-search-lite/index.ts +14 -2
- package/genes/genesis-web-search-lite/phenotype.json +21 -5
- package/genes/genesis-web-search-lite/testsuite.json +24 -0
- package/package.json +12 -7
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_FUZZ_ITERATIONS = void 0;
|
|
7
|
+
exports.classify = classify;
|
|
8
|
+
exports.runPositive = runPositive;
|
|
9
|
+
exports.runNegative = runNegative;
|
|
10
|
+
exports.runProperty = runProperty;
|
|
11
|
+
exports.runT1 = runT1;
|
|
12
|
+
exports.newTestRunId = newTestRunId;
|
|
13
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const generate_js_1 = require("./generate.js");
|
|
16
|
+
/** §47.5 fixes the publishing gate's property test at 10 random inputs. */
|
|
17
|
+
exports.DEFAULT_FUZZ_ITERATIONS = 10;
|
|
18
|
+
/**
|
|
19
|
+
* Classify a case as positive or negative from the input alone.
|
|
20
|
+
*
|
|
21
|
+
* spec §4.2's TestCase has no field saying which it is, and §47.5 words the
|
|
22
|
+
* negative case as "gene can correctly handle illegal input" — legality being
|
|
23
|
+
* decided by the Gene's own inputSchema. So the classification is derived:
|
|
24
|
+
* an input the schema accepts is a positive case, one it rejects is negative.
|
|
25
|
+
*
|
|
26
|
+
* The consequence worth stating: a positive case whose input is accidentally
|
|
27
|
+
* malformed silently becomes a negative case. That is why the reason string
|
|
28
|
+
* carries the validator's complaint — an author who meant it to be positive
|
|
29
|
+
* sees exactly which field disqualified it.
|
|
30
|
+
*/
|
|
31
|
+
function classify(testCases, inputValidator) {
|
|
32
|
+
return testCases.map((testCase, index) => {
|
|
33
|
+
if (!inputValidator) {
|
|
34
|
+
// No inputSchema to judge against. Everything is a positive case, and
|
|
35
|
+
// the §47.5 gate will report the missing negative rather than inventing
|
|
36
|
+
// one — see requirementsMet().
|
|
37
|
+
return {
|
|
38
|
+
index,
|
|
39
|
+
kind: "positive",
|
|
40
|
+
testCase,
|
|
41
|
+
reason: "gene declares no inputSchema, so no input can be illegal",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const isLegalInput = inputValidator(testCase.input);
|
|
45
|
+
if (isLegalInput) {
|
|
46
|
+
return { index, kind: "positive", testCase, reason: "input conforms to inputSchema" };
|
|
47
|
+
}
|
|
48
|
+
const complaint = (inputValidator.errors ?? [])
|
|
49
|
+
.map((e) => `${e.instancePath || "(root)"} ${e.message}`)
|
|
50
|
+
.join("; ");
|
|
51
|
+
return {
|
|
52
|
+
index,
|
|
53
|
+
kind: "negative",
|
|
54
|
+
testCase,
|
|
55
|
+
reason: `input violates inputSchema — ${complaint || "no detail"}`,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Serialize for comparison with object keys in a stable order.
|
|
61
|
+
*
|
|
62
|
+
* `expectedOutput` was compared with plain JSON.stringify, which is key-order
|
|
63
|
+
* sensitive: a case declaring `{formatted, changed, language}` failed against a
|
|
64
|
+
* Gene returning `{changed, formatted, language}` — same object by every
|
|
65
|
+
* meaning JavaScript gives the word, unequal as text. That is a property of the
|
|
66
|
+
* serializer, not of the Gene, and every author writing an expectedOutput by
|
|
67
|
+
* hand would hit it. Found by hitting it.
|
|
68
|
+
*
|
|
69
|
+
* Array order is preserved: in an array it carries meaning.
|
|
70
|
+
*/
|
|
71
|
+
function canonical(value) {
|
|
72
|
+
const walk = (v) => {
|
|
73
|
+
if (Array.isArray(v))
|
|
74
|
+
return v.map(walk);
|
|
75
|
+
if (v && typeof v === "object") {
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const k of Object.keys(v).sort()) {
|
|
78
|
+
out[k] = walk(v[k]);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
return v;
|
|
83
|
+
};
|
|
84
|
+
return JSON.stringify(walk(value));
|
|
85
|
+
}
|
|
86
|
+
function fail(testId, started, reason, details) {
|
|
87
|
+
return {
|
|
88
|
+
testId,
|
|
89
|
+
level: "T1",
|
|
90
|
+
passed: false,
|
|
91
|
+
duration: Date.now() - started,
|
|
92
|
+
failureReason: reason,
|
|
93
|
+
...(details ? { details } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function pass(testId, started, details) {
|
|
97
|
+
return { testId, level: "T1", passed: true, duration: Date.now() - started, details };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A positive case passes when the Gene runs and its output satisfies whatever
|
|
101
|
+
* the case asserts: `expectedOutput` exactly, `expectedSchema` structurally,
|
|
102
|
+
* or — when the case asserts neither — the Gene's declared outputSchema.
|
|
103
|
+
*
|
|
104
|
+
* The fallback matters. Without it a case carrying only an input would pass on
|
|
105
|
+
* any output at all, including one that violates the Gene's own contract, and
|
|
106
|
+
* the suite would look populated while asserting nothing.
|
|
107
|
+
*/
|
|
108
|
+
function runPositive(c, run, outputValidator, ajv) {
|
|
109
|
+
const started = Date.now();
|
|
110
|
+
const id = `t1-positive-${c.index}`;
|
|
111
|
+
const outcome = run(c.testCase.input, c.testCase.timeout);
|
|
112
|
+
if (!outcome.success) {
|
|
113
|
+
return fail(id, started, `Gene rejected a legal input: ${outcome.errorMessage ?? "no message"}`, c.reason);
|
|
114
|
+
}
|
|
115
|
+
if ("expectedOutput" in c.testCase) {
|
|
116
|
+
const got = canonical(outcome.output);
|
|
117
|
+
const want = canonical(c.testCase.expectedOutput);
|
|
118
|
+
if (got !== want) {
|
|
119
|
+
return fail(id, started, "output does not equal expectedOutput", `expected ${want}, got ${got}`);
|
|
120
|
+
}
|
|
121
|
+
return pass(id, started, "output matches expectedOutput exactly");
|
|
122
|
+
}
|
|
123
|
+
if (c.testCase.expectedSchema) {
|
|
124
|
+
let validate;
|
|
125
|
+
try {
|
|
126
|
+
validate = ajv.compile(c.testCase.expectedSchema);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return fail(id, started, `expectedSchema is not a valid JSON Schema: ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
if (!validate(outcome.output)) {
|
|
132
|
+
return fail(id, started, "output does not conform to expectedSchema", JSON.stringify(validate.errors));
|
|
133
|
+
}
|
|
134
|
+
return pass(id, started, "output conforms to expectedSchema");
|
|
135
|
+
}
|
|
136
|
+
if (outputValidator) {
|
|
137
|
+
if (!outputValidator(outcome.output)) {
|
|
138
|
+
return fail(id, started, "output does not conform to the Gene's outputSchema", JSON.stringify(outputValidator.errors));
|
|
139
|
+
}
|
|
140
|
+
return pass(id, started, "output conforms to the Gene's outputSchema");
|
|
141
|
+
}
|
|
142
|
+
return fail(id, started, "case asserts nothing: no expectedOutput, no expectedSchema, and the Gene declares no outputSchema");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A negative case: the Gene is fed input its own inputSchema forbids.
|
|
146
|
+
*
|
|
147
|
+
* §47.5 asks that the Gene "correctly handle illegal input" without saying
|
|
148
|
+
* what handling looks like, and measuring four real Genes shows why it cannot
|
|
149
|
+
* be decided from the outcome alone:
|
|
150
|
+
*
|
|
151
|
+
* grammar-checker throws — `not a function`, a TypeError from inside
|
|
152
|
+
* source-linker returns — echoes the illegal value straight back
|
|
153
|
+
* url-extractor returns — an empty result
|
|
154
|
+
* json-validator returns — `{ valid: false }`, which IS correct handling
|
|
155
|
+
*
|
|
156
|
+
* "Returned output" therefore cannot mean acceptance: for json-validator it is
|
|
157
|
+
* the right answer, and for source-linker it is the bug. The difference is not
|
|
158
|
+
* in the outcome, it is in what the author intended — so the author has to say,
|
|
159
|
+
* using the fields spec §4.2 already provides.
|
|
160
|
+
*
|
|
161
|
+
* The rules, in order:
|
|
162
|
+
*
|
|
163
|
+
* 1. Crashed — fail. Dying is not handling, whatever was intended.
|
|
164
|
+
* 2. Errored without crashing — pass. The Gene refused, deliberately.
|
|
165
|
+
* 3. Returned, and the case declares expectedOutput/expectedSchema that
|
|
166
|
+
* matches — pass. This is the author saying "rejection looks like this".
|
|
167
|
+
* 4. Returned, and the declaration does not match — fail.
|
|
168
|
+
* 5. Returned, and the case declares nothing — fail, and say why. Silent
|
|
169
|
+
* acceptance and correct handling are indistinguishable here; refusing to
|
|
170
|
+
* guess is the whole point, since guessing "pass" is what lets a
|
|
171
|
+
* source-linker through.
|
|
172
|
+
*/
|
|
173
|
+
function runNegative(c, run, ajv) {
|
|
174
|
+
const started = Date.now();
|
|
175
|
+
const id = `t1-negative-${c.index}`;
|
|
176
|
+
const outcome = run(c.testCase.input, c.testCase.timeout);
|
|
177
|
+
if (outcome.crashed) {
|
|
178
|
+
// ADR-333: a crash fails not because it is necessarily a bug, but because
|
|
179
|
+
// it is necessarily unusable. A trap reaching the caller carries no
|
|
180
|
+
// information — the caller cannot tell deliberate refusal from defect, so
|
|
181
|
+
// nothing downstream can act on it correctly. §47.5 asks for "correctly
|
|
182
|
+
// handle", and a signal the caller cannot handle is not handling completed.
|
|
183
|
+
return fail(id, started, `Gene crashed on illegal input instead of handling it: ${outcome.errorMessage ?? "no message"}`, c.reason);
|
|
184
|
+
}
|
|
185
|
+
if (!outcome.success) {
|
|
186
|
+
return pass(id, started, `Gene refused illegal input: ${outcome.errorMessage ?? "no message"}`);
|
|
187
|
+
}
|
|
188
|
+
if ("expectedOutput" in c.testCase) {
|
|
189
|
+
const got = canonical(outcome.output);
|
|
190
|
+
const want = canonical(c.testCase.expectedOutput);
|
|
191
|
+
return got === want
|
|
192
|
+
? pass(id, started, "Gene handled illegal input exactly as the case declares")
|
|
193
|
+
: fail(id, started, "output does not equal the declared expectedOutput", `expected ${want}, got ${got}`);
|
|
194
|
+
}
|
|
195
|
+
if (c.testCase.expectedSchema) {
|
|
196
|
+
let validate;
|
|
197
|
+
try {
|
|
198
|
+
validate = ajv.compile(c.testCase.expectedSchema);
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
return fail(id, started, `expectedSchema is not a valid JSON Schema: ${e.message}`);
|
|
202
|
+
}
|
|
203
|
+
return validate(outcome.output)
|
|
204
|
+
? pass(id, started, "Gene handled illegal input in the declared shape")
|
|
205
|
+
: fail(id, started, "output does not conform to the declared expectedSchema", JSON.stringify(validate.errors));
|
|
206
|
+
}
|
|
207
|
+
return fail(id, started, "Gene returned output for illegal input and the case declares no expected shape", `${c.reason}; output was ${JSON.stringify(outcome.output).slice(0, 200)}. ` +
|
|
208
|
+
`If returning this is correct handling, declare it with expectedOutput or expectedSchema; ` +
|
|
209
|
+
`otherwise the Gene is silently accepting input its own schema forbids.`);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* §47.4 Schema Legality, run as §47.5's third T1 requirement: generated inputs
|
|
213
|
+
* must always produce output conforming to outputSchema.
|
|
214
|
+
*
|
|
215
|
+
* The seed is what makes a failure actionable — the same seed replays the same
|
|
216
|
+
* inputs, so a property failure can be reproduced rather than merely observed.
|
|
217
|
+
* When the suite gives no seed one is derived from nothing but a constant, so
|
|
218
|
+
* two runs of an unseeded suite still agree; randomness across runs would make
|
|
219
|
+
* the gate flaky and the flake would be blamed on the Gene.
|
|
220
|
+
*/
|
|
221
|
+
function runProperty(suite, inputSchema, run, outputValidator) {
|
|
222
|
+
const started = Date.now();
|
|
223
|
+
const id = "t1-property-schema-legality";
|
|
224
|
+
const iterations = suite.config?.fuzzIterations ?? exports.DEFAULT_FUZZ_ITERATIONS;
|
|
225
|
+
const seed = suite.config?.seed ?? 1;
|
|
226
|
+
if (iterations === 0) {
|
|
227
|
+
return fail(id, started, "config.fuzzIterations is 0 — the property test asserts nothing");
|
|
228
|
+
}
|
|
229
|
+
if (!outputValidator) {
|
|
230
|
+
return fail(id, started, "Gene declares no outputSchema, so schema legality cannot be asserted");
|
|
231
|
+
}
|
|
232
|
+
for (let i = 0; i < iterations; i++) {
|
|
233
|
+
const input = (0, generate_js_1.generateTestInput)(inputSchema, seed + i);
|
|
234
|
+
const outcome = run(input);
|
|
235
|
+
if (!outcome.success) {
|
|
236
|
+
return fail(id, started, `iteration ${i + 1}/${iterations} did not produce output: ${outcome.errorMessage ?? "no message"}`, `reproduce with seed ${seed}, input ${JSON.stringify(input).slice(0, 200)}`);
|
|
237
|
+
}
|
|
238
|
+
if (!outputValidator(outcome.output)) {
|
|
239
|
+
return fail(id, started, `iteration ${i + 1}/${iterations} produced output violating outputSchema`, `reproduce with seed ${seed}, input ${JSON.stringify(input).slice(0, 200)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return pass(id, started, `${iterations} generated inputs all produced schema-legal output (seed ${seed})`);
|
|
243
|
+
}
|
|
244
|
+
function runT1(suite, opts) {
|
|
245
|
+
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
246
|
+
let inputValidator = null;
|
|
247
|
+
let outputValidator = null;
|
|
248
|
+
try {
|
|
249
|
+
if (opts.inputSchema)
|
|
250
|
+
inputValidator = ajv.compile(opts.inputSchema);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
inputValidator = null;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
if (opts.outputSchema)
|
|
257
|
+
outputValidator = ajv.compile(opts.outputSchema);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
outputValidator = null;
|
|
261
|
+
}
|
|
262
|
+
const classified = classify(suite.testCases, inputValidator);
|
|
263
|
+
const results = [];
|
|
264
|
+
for (const c of classified) {
|
|
265
|
+
results.push(c.kind === "positive"
|
|
266
|
+
? runPositive(c, opts.run, outputValidator, ajv)
|
|
267
|
+
: runNegative(c, opts.run, ajv));
|
|
268
|
+
}
|
|
269
|
+
const property = runProperty(suite, opts.inputSchema, opts.run, outputValidator);
|
|
270
|
+
results.push(property);
|
|
271
|
+
const hasPassingPositive = classified.some((c, i) => c.kind === "positive" && results[i].passed);
|
|
272
|
+
const hasPassingNegative = classified.some((c, i) => c.kind === "negative" && results[i].passed);
|
|
273
|
+
const requirements = {
|
|
274
|
+
positive: hasPassingPositive,
|
|
275
|
+
negative: hasPassingNegative,
|
|
276
|
+
property: property.passed,
|
|
277
|
+
};
|
|
278
|
+
// §47.5 sets a floor on coverage — "at least 1 positive", "at least 1
|
|
279
|
+
// negative" — not permission to ship failing assertions. A suite with three
|
|
280
|
+
// negatives where two fail still satisfies the letter of the requirement, and
|
|
281
|
+
// that is how a case an author wrote, and a Gene does not honour, could reach
|
|
282
|
+
// the registry. Every declared case has to pass as well.
|
|
283
|
+
const hasFailingCase = results.some((r) => !r.passed);
|
|
284
|
+
return {
|
|
285
|
+
results,
|
|
286
|
+
classified,
|
|
287
|
+
requirements,
|
|
288
|
+
gatePassed: requirements.positive &&
|
|
289
|
+
requirements.negative &&
|
|
290
|
+
requirements.property &&
|
|
291
|
+
!hasFailingCase,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/** Stable id for a report, for callers that persist results. */
|
|
295
|
+
function newTestRunId() {
|
|
296
|
+
return (0, node_crypto_1.randomUUID)();
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/testsuite/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TestSuite } from "./types.js";
|
|
2
|
+
interface ScaffoldNote {
|
|
3
|
+
/** Which §47.5 requirement this case is there to satisfy. */
|
|
4
|
+
requirement: "positive" | "negative";
|
|
5
|
+
/** Why this input is legal or illegal, for the comment block. */
|
|
6
|
+
rationale: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ScaffoldResult {
|
|
9
|
+
suite: TestSuite;
|
|
10
|
+
notes: ScaffoldNote[];
|
|
11
|
+
/** Cases the generator meant to be illegal but could not make illegal —
|
|
12
|
+
* see makeIllegalInputs. Empty is the normal case. */
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function scaffoldTestSuite(phenotype: {
|
|
16
|
+
inputSchema?: unknown;
|
|
17
|
+
outputSchema?: unknown;
|
|
18
|
+
}): ScaffoldResult;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/testsuite/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,YAAY,CAAC;AAmBtD,UAAU,YAAY;IACpB,6DAA6D;IAC7D,WAAW,EAAE,UAAU,GAAG,UAAU,CAAC;IACrC,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB;0DACsD;IACtD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAiED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,cAAc,CA4EjB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scaffoldTestSuite = scaffoldTestSuite;
|
|
7
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
8
|
+
const generate_js_1 = require("./generate.js");
|
|
9
|
+
const run_js_1 = require("./run.js");
|
|
10
|
+
/**
|
|
11
|
+
* §47.3 Phenotype-driven scaffolding, aimed at §47.5's shape.
|
|
12
|
+
*
|
|
13
|
+
* This exists because 2.1 turns §47.5 into a publishing gate, and a gate with
|
|
14
|
+
* no exit teaches people to route around it. Being told "you need a negative
|
|
15
|
+
* case" is only actionable if something can write the first one for you.
|
|
16
|
+
*
|
|
17
|
+
* What it cannot do is make a Gene correct. A scaffolded suite is a starting
|
|
18
|
+
* point whose assertions the author still has to mean — the generated negative
|
|
19
|
+
* cases in particular encode a guess about what rejection should look like, and
|
|
20
|
+
* that guess is left blank on purpose (see below).
|
|
21
|
+
*/
|
|
22
|
+
const SCAFFOLD_SEED = 1;
|
|
23
|
+
/**
|
|
24
|
+
* Build inputs that the Gene's own inputSchema rejects.
|
|
25
|
+
*
|
|
26
|
+
* Each strategy targets a different way a caller gets it wrong, because a
|
|
27
|
+
* negative case that only ever tests one of them proves little:
|
|
28
|
+
*
|
|
29
|
+
* - a required field missing entirely
|
|
30
|
+
* - a field present with the wrong type
|
|
31
|
+
* - an empty object, the degenerate call
|
|
32
|
+
*
|
|
33
|
+
* Every candidate is then run through the validator before being kept. A
|
|
34
|
+
* "negative" case whose input the schema happens to accept is silently a
|
|
35
|
+
* positive case — classify() would file it as one, the suite would look like it
|
|
36
|
+
* covers §47.5 while covering half of it, and nothing would say so.
|
|
37
|
+
*/
|
|
38
|
+
function makeIllegalInputs(inputSchema, isIllegal) {
|
|
39
|
+
const inputs = [];
|
|
40
|
+
const warnings = [];
|
|
41
|
+
const candidates = [];
|
|
42
|
+
const properties = (inputSchema?.properties ?? {});
|
|
43
|
+
const required = (inputSchema?.required ?? []);
|
|
44
|
+
const legal = (0, generate_js_1.generateTestInput)(inputSchema, SCAFFOLD_SEED);
|
|
45
|
+
if (required.length > 0) {
|
|
46
|
+
const dropped = { ...legal };
|
|
47
|
+
delete dropped[required[0]];
|
|
48
|
+
candidates.push({
|
|
49
|
+
value: dropped,
|
|
50
|
+
rationale: `required field "${required[0]}" is missing`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const firstTyped = Object.entries(properties).find(([, p]) => typeof p.type === "string");
|
|
54
|
+
if (firstTyped) {
|
|
55
|
+
const [key, prop] = firstTyped;
|
|
56
|
+
// A value of a type the field cannot hold. Numbers are wrong for
|
|
57
|
+
// everything except number/integer, where a string is wrong instead.
|
|
58
|
+
const wrong = prop.type === "number" || prop.type === "integer" ? "not-a-number" : 42;
|
|
59
|
+
candidates.push({
|
|
60
|
+
value: { ...legal, [key]: wrong },
|
|
61
|
+
rationale: `"${key}" is declared ${String(prop.type)} but given ${JSON.stringify(wrong)}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
candidates.push({ value: {}, rationale: "empty object — the degenerate call" });
|
|
65
|
+
for (const c of candidates) {
|
|
66
|
+
if (isIllegal(c.value)) {
|
|
67
|
+
inputs.push(c);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
warnings.push(`dropped a would-be negative case (${c.rationale}): the Gene's inputSchema accepts it, ` +
|
|
71
|
+
`so it would have been classified as a positive case`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { inputs, warnings };
|
|
75
|
+
}
|
|
76
|
+
function scaffoldTestSuite(phenotype) {
|
|
77
|
+
const inputSchema = phenotype.inputSchema ?? null;
|
|
78
|
+
const outputSchema = phenotype.outputSchema ?? null;
|
|
79
|
+
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
80
|
+
let isIllegal = () => false;
|
|
81
|
+
if (inputSchema) {
|
|
82
|
+
try {
|
|
83
|
+
const validate = ajv.compile(inputSchema);
|
|
84
|
+
isIllegal = (v) => !validate(v);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
isIllegal = () => false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const testCases = [];
|
|
91
|
+
const notes = [];
|
|
92
|
+
const warnings = [];
|
|
93
|
+
// Positive case. It asserts against outputSchema rather than a literal
|
|
94
|
+
// expectedOutput: a generated literal would be whatever the Gene happens to
|
|
95
|
+
// return today, which is an assertion that the Gene never changes, not that
|
|
96
|
+
// it is right.
|
|
97
|
+
const legal = (0, generate_js_1.generateTestInput)(inputSchema, SCAFFOLD_SEED);
|
|
98
|
+
testCases.push({
|
|
99
|
+
input: legal,
|
|
100
|
+
...(outputSchema ? { expectedSchema: outputSchema } : {}),
|
|
101
|
+
});
|
|
102
|
+
// The generator is best-effort: for nested or object-typed required fields it
|
|
103
|
+
// can emit something the schema rejects. Left unchecked that case is filed as
|
|
104
|
+
// a *negative* by classify(), and the suite ships with no positive case at
|
|
105
|
+
// all — measured on json-validator, whose `data` and `schema` fields the
|
|
106
|
+
// generator could not satisfy.
|
|
107
|
+
//
|
|
108
|
+
// Negative candidates were already verified illegal; not verifying the
|
|
109
|
+
// positive one was an asymmetry, and it failed in the direction that is
|
|
110
|
+
// harder to notice: the suite looks complete and the gate reports "positive
|
|
111
|
+
// NOT met" without saying the scaffold is why.
|
|
112
|
+
const isPositiveLegal = !isIllegal(legal);
|
|
113
|
+
if (!isPositiveLegal) {
|
|
114
|
+
warnings.push(`the generated positive input does not satisfy this Gene's inputSchema, so it will be ` +
|
|
115
|
+
`classified as a negative case and the suite will have no positive case — ` +
|
|
116
|
+
`replace testCases[0].input with one your Gene actually accepts`);
|
|
117
|
+
}
|
|
118
|
+
notes.push({
|
|
119
|
+
requirement: "positive",
|
|
120
|
+
rationale: !isPositiveLegal
|
|
121
|
+
? "GENERATED INPUT IS NOT ACCEPTED BY inputSchema — replace it, see the warning below"
|
|
122
|
+
: outputSchema
|
|
123
|
+
? "generated from inputSchema; asserts the output conforms to outputSchema"
|
|
124
|
+
: "generated from inputSchema; the Gene declares no outputSchema, so replace this with an expectedOutput you mean",
|
|
125
|
+
});
|
|
126
|
+
const { inputs: illegal, warnings: negativeWarnings } = makeIllegalInputs(inputSchema, isIllegal);
|
|
127
|
+
warnings.push(...negativeWarnings);
|
|
128
|
+
for (const c of illegal) {
|
|
129
|
+
// No expectedOutput / expectedSchema on purpose. If the Gene answers
|
|
130
|
+
// illegal input rather than refusing it — json-validator returning
|
|
131
|
+
// { valid: false } is the honest version of this — the author has to say
|
|
132
|
+
// what that answer should be. Guessing here would hand out a case that
|
|
133
|
+
// passes on whatever the Gene already does, which is not a test.
|
|
134
|
+
testCases.push({ input: c.value });
|
|
135
|
+
notes.push({ requirement: "negative", rationale: c.rationale });
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
suite: {
|
|
139
|
+
testCases,
|
|
140
|
+
config: { fuzzIterations: run_js_1.DEFAULT_FUZZ_ITERATIONS, seed: SCAFFOLD_SEED },
|
|
141
|
+
},
|
|
142
|
+
notes,
|
|
143
|
+
warnings,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/testsuite/scaffold.ts"],"names":[],"mappings":";;;;;AAkGA,8CA+EC;AAjLD,8CAAsB;AAEtB,+CAAkD;AAClD,qCAAmD;AAEnD;;;;;;;;;;;GAWG;AAEH,MAAM,aAAa,GAAG,CAAC,CAAC;AAiBxB;;;;;;;;;;;;;;GAcG;AACH,SAAS,iBAAiB,CACxB,WAA2C,EAC3C,SAAkC;IAElC,MAAM,MAAM,GAA4C,EAAE,CAAC;IAC3D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,UAAU,GAA4C,EAAE,CAAC;IAE/D,MAAM,UAAU,GAAG,CAAC,WAAW,EAAE,UAAU,IAAI,EAAE,CAA4C,CAAC;IAC9F,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,QAAQ,IAAI,EAAE,CAAa,CAAC;IAC3D,MAAM,KAAK,GAAG,IAAA,+BAAiB,EAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAE5D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,OAAO;YACd,SAAS,EAAE,mBAAmB,QAAQ,CAAC,CAAC,CAAC,cAAc;SACxD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAC1F,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC;QAC/B,iEAAiE;QACjE,qEAAqE;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE;YACjC,SAAS,EAAE,IAAI,GAAG,iBAAiB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;SAC1F,CAAC,CAAC;IACL,CAAC;IAED,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,oCAAoC,EAAE,CAAC,CAAC;IAEhF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CACX,qCAAqC,CAAC,CAAC,SAAS,wCAAwC;gBACtF,qDAAqD,CACxD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC;AAED,SAAgB,iBAAiB,CAAC,SAGjC;IACC,MAAM,WAAW,GAAI,SAAS,CAAC,WAAuC,IAAI,IAAI,CAAC;IAC/E,MAAM,YAAY,GAAI,SAAS,CAAC,YAAwC,IAAI,IAAI,CAAC;IAEjF,MAAM,GAAG,GAAG,IAAI,aAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,IAAI,SAAS,GAA4B,GAAG,EAAE,CAAC,KAAK,CAAC;IACrD,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC1C,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,uEAAuE;IACvE,4EAA4E;IAC5E,4EAA4E;IAC5E,eAAe;IACf,MAAM,KAAK,GAAG,IAAA,+BAAiB,EAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC5D,SAAS,CAAC,IAAI,CAAC;QACb,KAAK,EAAE,KAAK;QACZ,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC,CAAC;IAEH,8EAA8E;IAC9E,8EAA8E;IAC9E,2EAA2E;IAC3E,yEAAyE;IACzE,+BAA+B;IAC/B,EAAE;IACF,uEAAuE;IACvE,wEAAwE;IACxE,4EAA4E;IAC5E,+CAA+C;IAC/C,MAAM,eAAe,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,CACX,uFAAuF;YACrF,2EAA2E;YAC3E,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC;QACT,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,CAAC,eAAe;YACzB,CAAC,CAAC,oFAAoF;YACtF,CAAC,CAAC,YAAY;gBACZ,CAAC,CAAC,yEAAyE;gBAC3E,CAAC,CAAC,gHAAgH;KACvH,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAClG,QAAQ,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,qEAAqE;QACrE,mEAAmE;QACnE,yEAAyE;QACzE,uEAAuE;QACvE,iEAAiE;QACjE,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO;QACL,KAAK,EAAE;YACL,SAAS;YACT,MAAM,EAAE,EAAE,cAAc,EAAE,gCAAuB,EAAE,IAAI,EAAE,aAAa,EAAE;SACzE;QACD,KAAK;QACL,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestSuite types, spec §4.2.
|
|
3
|
+
*
|
|
4
|
+
* The field set here is the spec's struct verbatim — no additions. That is
|
|
5
|
+
* deliberate: §47.5 requires a publishing gate to count "at least 1 positive
|
|
6
|
+
* case" and "at least 1 negative case", and the obvious way to support it
|
|
7
|
+
* would be a `kind: "positive" | "negative"` discriminator. The spec has no
|
|
8
|
+
* such field, and inventing one would put a concept in the file format that
|
|
9
|
+
* the protocol does not define.
|
|
10
|
+
*
|
|
11
|
+
* It is not needed. §47.5 words the negative case as "gene can correctly
|
|
12
|
+
* handle illegal input", and legality is already decided by the gene's own
|
|
13
|
+
* `inputSchema`. So the classification is *derived* rather than declared —
|
|
14
|
+
* see classify() in ./run.ts. A gene author writes test cases; whether each
|
|
15
|
+
* one is positive or negative follows from the input.
|
|
16
|
+
*/
|
|
17
|
+
/** spec §4.2 TestConfig */
|
|
18
|
+
export interface TestConfig {
|
|
19
|
+
/** Fuzz testing iteration count. §47.5 fixes the publishing gate at 10. */
|
|
20
|
+
fuzzIterations?: number;
|
|
21
|
+
/** Random seed, so a property run can be reproduced exactly. */
|
|
22
|
+
seed?: number;
|
|
23
|
+
}
|
|
24
|
+
/** spec §4.2 TestCase */
|
|
25
|
+
export interface TestCase {
|
|
26
|
+
/** Test input. MUST conform to inputSchema for a positive case. */
|
|
27
|
+
input: unknown;
|
|
28
|
+
/** Expected output, for exact matching. */
|
|
29
|
+
expectedOutput?: unknown;
|
|
30
|
+
/** Expected output Schema, for structural validation. */
|
|
31
|
+
expectedSchema?: Record<string, unknown>;
|
|
32
|
+
/** Per-case timeout, milliseconds. */
|
|
33
|
+
timeout?: number;
|
|
34
|
+
}
|
|
35
|
+
/** spec §4.2 TestSuite */
|
|
36
|
+
export interface TestSuite {
|
|
37
|
+
testCases: TestCase[];
|
|
38
|
+
config?: TestConfig;
|
|
39
|
+
}
|
|
40
|
+
/** spec §47.1 test level */
|
|
41
|
+
export type TestLevel = "T1" | "T2" | "T3" | "T4";
|
|
42
|
+
/** spec §4.2 / §47.2 TestResult */
|
|
43
|
+
export interface TestResult {
|
|
44
|
+
testId: string;
|
|
45
|
+
level: TestLevel;
|
|
46
|
+
passed: boolean;
|
|
47
|
+
/** Milliseconds. */
|
|
48
|
+
duration: number;
|
|
49
|
+
details?: string;
|
|
50
|
+
failureReason?: string;
|
|
51
|
+
coverageHint?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* How a case was classified, and why. Not part of the file format — this is
|
|
55
|
+
* produced by the runner, and exists so `rotifer test` can report which of
|
|
56
|
+
* §47.5's three T1 requirements a given case satisfied.
|
|
57
|
+
*/
|
|
58
|
+
export type CaseKind = "positive" | "negative";
|
|
59
|
+
export interface ClassifiedCase {
|
|
60
|
+
index: number;
|
|
61
|
+
kind: CaseKind;
|
|
62
|
+
testCase: TestCase;
|
|
63
|
+
/** Why it landed in that bucket — shown on failure so the author can tell
|
|
64
|
+
* an intentionally-illegal input from an accidentally-illegal one. */
|
|
65
|
+
reason: string;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/testsuite/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2BAA2B;AAC3B,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yBAAyB;AACzB,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,2CAA2C;IAC3C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,0BAA0B;AAC1B,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,4BAA4B;AAC5B,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB;0EACsE;IACtE,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TestSuite types, spec §4.2.
|
|
4
|
+
*
|
|
5
|
+
* The field set here is the spec's struct verbatim — no additions. That is
|
|
6
|
+
* deliberate: §47.5 requires a publishing gate to count "at least 1 positive
|
|
7
|
+
* case" and "at least 1 negative case", and the obvious way to support it
|
|
8
|
+
* would be a `kind: "positive" | "negative"` discriminator. The spec has no
|
|
9
|
+
* such field, and inventing one would put a concept in the file format that
|
|
10
|
+
* the protocol does not define.
|
|
11
|
+
*
|
|
12
|
+
* It is not needed. §47.5 words the negative case as "gene can correctly
|
|
13
|
+
* handle illegal input", and legality is already decided by the gene's own
|
|
14
|
+
* `inputSchema`. So the classification is *derived* rather than declared —
|
|
15
|
+
* see classify() in ./run.ts. A gene author writes test cases; whether each
|
|
16
|
+
* one is positive or negative follows from the input.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/testsuite/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does a Gene's declared fidelity match what is actually on disk / in the
|
|
3
|
+
* registry record?
|
|
4
|
+
*
|
|
5
|
+
* spec §4.2 defines NATIVE as "core logic compiled to Rotifer IR". A record
|
|
6
|
+
* declaring Native with no artifact is therefore self-contradicting, and until
|
|
7
|
+
* 2026-09-07 both commands that meet one said nothing about it:
|
|
8
|
+
*
|
|
9
|
+
* install downloaded nothing (no else branch on `if (gene.wasm_url)`) and
|
|
10
|
+
* then reported "Gene installed! Fidelity: Native" — for a Gene that
|
|
11
|
+
* answers `rotifer run` with "No runnable source found".
|
|
12
|
+
* compile wrote `.compile-result.json` with `fidelity: "Wrapped"` while
|
|
13
|
+
* leaving phenotype.json saying Native, and reported *success*:
|
|
14
|
+
* "validated (Wrapped fidelity)". Two files in one directory
|
|
15
|
+
* disagreeing, and a green tick over a Gene that cannot run.
|
|
16
|
+
*
|
|
17
|
+
* The judgement lives here, apart from the commands, so it can be tested
|
|
18
|
+
* without a network or a compiler.
|
|
19
|
+
*/
|
|
20
|
+
export type ArtifactVerdict = {
|
|
21
|
+
status: "consistent";
|
|
22
|
+
}
|
|
23
|
+
/** Declared Native, nothing to run. The Gene cannot do what it claims. */
|
|
24
|
+
| {
|
|
25
|
+
status: "native-without-artifact";
|
|
26
|
+
message: string;
|
|
27
|
+
hint: string[];
|
|
28
|
+
};
|
|
29
|
+
export declare function checkNativeArtifact(input: {
|
|
30
|
+
geneName: string;
|
|
31
|
+
fidelity: string | undefined;
|
|
32
|
+
/** A compiled artifact is present (installed .wasm, or a registry wasm_url). */
|
|
33
|
+
hasArtifact: boolean;
|
|
34
|
+
/** Source is present, so the Gene can still be compiled locally. */
|
|
35
|
+
hasSource: boolean;
|
|
36
|
+
}): ArtifactVerdict;
|
|
37
|
+
//# sourceMappingURL=artifact-consistency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-consistency.d.ts","sourceRoot":"","sources":["../../src/utils/artifact-consistency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE;AAC1B,0EAA0E;GACxE;IAAE,MAAM,EAAE,yBAAyB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,gFAAgF;IAChF,WAAW,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,eAAe,CAmBlB"}
|