@afokapu/atdd-bun 0.10.55 → 0.10.56
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/detectors/bun_interlocking_coverage/_shared/interlocking.mjs +59 -10
- package/detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs +1 -1
- package/detectors/bun_interlocking_coverage/checks/interlocking_smoke_coverage.mjs +9 -3
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/e2e/smoke/resolve-match.smoke.test.ts +12 -0
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/plan/_trains/_interlockings/match-resolution.yaml +8 -0
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/src/server.ts +13 -0
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/e2e/smoke/resolve-match.smoke.test.ts +12 -0
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/plan/_trains/_interlockings/match-resolution.yaml +8 -0
- package/detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/src/server.ts +9 -0
- package/integrity.json +10 -4
- package/package.json +1 -1
|
@@ -364,7 +364,7 @@ export function productionExecutionProofs(text, file, croot) {
|
|
|
364
364
|
const named = [...receivers].some((name) => receiver.trim().endsWith(name));
|
|
365
365
|
if (!direct && !named) continue;
|
|
366
366
|
const after = text.slice((m.index ?? 0) + whole.length);
|
|
367
|
-
const execute = new RegExp(`\\b(?:const|let|var)\\s+(${ident})(?:\\s*:\\s*[^=;\\n]+)?\\s*=\\s*new\\s+${PROD_TRAIN}\\s*\\(\\s*${escaped(resolution)}\\.(?:trainId|selectedTrainId)\\s*\\)\\s*\\.execute\\s*\\(`).exec(after);
|
|
367
|
+
const execute = new RegExp(`\\b(?:const|let|var)\\s+(${ident})(?:\\s*:\\s*[^=;\\n]+)?\\s*=\\s*(?:await\\s+)?new\\s+${PROD_TRAIN}\\s*\\(\\s*${escaped(resolution)}\\.(?:trainId|selectedTrainId)\\s*\\)\\s*\\.execute\\s*\\(`).exec(after);
|
|
368
368
|
const anonymous = new RegExp(`new\\s+${PROD_TRAIN}\\s*\\(\\s*${escaped(resolution)}\\.(?:trainId|selectedTrainId)\\s*\\)\\s*\\.execute\\s*\\(`).test(after);
|
|
369
369
|
if (execute || anonymous) proofs.push({ resolution, execution: execute?.[1] ?? null });
|
|
370
370
|
}
|
|
@@ -420,15 +420,64 @@ export function stationMasterExecutionProof(text, action) {
|
|
|
420
420
|
return seen.has(PROD_INTERLOCKING) && seen.has(PROD_TRAIN) && assertsExpression(text, `${escaped(result)}(?:\\.[A-Za-z_$][\\w$]*)?`);
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
423
|
+
function balancedBlock(text, open) {
|
|
424
|
+
let depth = 0, quote = null;
|
|
425
|
+
for (let i = open; i < text.length; i++) {
|
|
426
|
+
const c = text[i], next = text[i + 1];
|
|
427
|
+
if (quote) {
|
|
428
|
+
if (c === "\\") { i++; continue; }
|
|
429
|
+
if (c === quote) quote = null;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (c === "'" || c === '"' || c === "`") { quote = c; continue; }
|
|
433
|
+
if (c === "/" && next === "/") { i = text.indexOf("\n", i + 2); if (i < 0) return null; continue; }
|
|
434
|
+
if (c === "/" && next === "*") { const end = text.indexOf("*/", i + 2); if (end < 0) return null; i = end + 1; continue; }
|
|
435
|
+
if (c === "{") depth++;
|
|
436
|
+
if (c === "}" && --depth === 0) return text.slice(open + 1, i);
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function exportedActionBody(text, name) {
|
|
442
|
+
const n = escaped(name);
|
|
443
|
+
const headers = [
|
|
444
|
+
new RegExp(`\\bexport\\s+(?:async\\s+)?function\\s+${n}\\s*\\([^)]*\\)\\s*\\{`, "g"),
|
|
445
|
+
new RegExp(`\\bexport\\s+(?:const|let|var)\\s+${n}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*=>\\s*\\{`, "g"),
|
|
446
|
+
];
|
|
447
|
+
for (const header of headers) {
|
|
448
|
+
const match = header.exec(text);
|
|
449
|
+
if (match) return balancedBlock(text, (match.index ?? 0) + match[0].lastIndexOf("{"));
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function exportedActionReturnsProductionExecution(stationText, action) {
|
|
455
|
+
const body = exportedActionBody(stationText, action);
|
|
456
|
+
if (!body) return false;
|
|
457
|
+
const receivers = productionReceivers(stationText);
|
|
458
|
+
const resolutions = new RegExp(`\\b(?:const|let|var)\\s+(${ident})(?:\\s*:\\s*[^=;\\n]+)?\\s*=\\s*([^\\n]{0,240}?)\\.resolveTrain\\s*\\(`, "g");
|
|
459
|
+
for (const m of body.matchAll(resolutions)) {
|
|
460
|
+
const [whole, resolution, receiver] = m;
|
|
461
|
+
const direct = new RegExp(`new\\s+${PROD_INTERLOCKING}\\b`).test(receiver);
|
|
462
|
+
const named = [...receivers].some((name) => receiver.trim().endsWith(name));
|
|
463
|
+
if (!direct && !named) continue;
|
|
464
|
+
const after = body.slice((m.index ?? 0) + whole.length);
|
|
465
|
+
const directReturn = new RegExp(`\\breturn\\s+(?:await\\s+)?new\\s+${PROD_TRAIN}\\s*\\(\\s*${escaped(resolution)}\\.(?:trainId|selectedTrainId)\\s*\\)\\s*\\.execute\\s*\\(`).test(after);
|
|
466
|
+
if (directReturn) return true;
|
|
467
|
+
const execution = new RegExp(`\\b(?:const|let|var)\\s+(${ident})(?:\\s*:\\s*[^=;\\n]+)?\\s*=\\s*(?:await\\s+)?new\\s+${PROD_TRAIN}\\s*\\(\\s*${escaped(resolution)}\\.(?:trainId|selectedTrainId)\\s*\\)\\s*\\.execute\\s*\\(`).exec(after)?.[1];
|
|
468
|
+
if (execution && new RegExp(`\\breturn\\s+(?:await\\s+)?${escaped(execution)}\\s*;?`).test(after)) return true;
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// HTTP/module entrypoints do not expose a StationMaster instance to the test. The asserted result
|
|
474
|
+
// must therefore come from the SAME exported action the test invokes, and that action's own return
|
|
475
|
+
// path must resolve then execute the selected train. Module-wide runner tokens are not evidence.
|
|
476
|
+
export function stationModuleExecutionProof(text, stationText, action) {
|
|
477
|
+
if (!stationText) return false;
|
|
478
|
+
const call = new RegExp(`\\b(?:const|let|var)\\s+(${ident})(?:\\s*:\\s*[^=;\\n]+)?\\s*=\\s*await\\s+(?:${ident}\\.)?(dispatch|handleAction|executeAction)\\s*\\(\\s*["']${escaped(action)}["']`).exec(text);
|
|
479
|
+
return Boolean(call && assertsExpression(text, `${escaped(call[1])}(?:\\.[A-Za-z_$][\\w$]*)?`) &&
|
|
480
|
+
exportedActionReturnsProductionExecution(stationText, call[2]));
|
|
432
481
|
}
|
|
433
482
|
|
|
434
483
|
// Bun's native `expect` vocabulary provides a compact mutation witness: exact ordered equality
|
|
@@ -72,7 +72,7 @@ for (const scanRoot of roots) {
|
|
|
72
72
|
|
|
73
73
|
const semantic = hasProductionExecutionProof(text, file, croot) ||
|
|
74
74
|
records.some((rec) => rec.actions.some((action) => stationMasterExecutionProof(text, action))) ||
|
|
75
|
-
(drivesStationMaster(text) && stationModuleExecutionProof(text, station));
|
|
75
|
+
(drivesStationMaster(text) && records.some((rec) => rec.actions.some((action) => stationModuleExecutionProof(text, station, action))));
|
|
76
76
|
if (!semantic) {
|
|
77
77
|
violations.push(
|
|
78
78
|
mk(
|
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
PROD_INTERLOCKING,
|
|
22
22
|
PROD_TRAIN,
|
|
23
23
|
stationMasterExecutionProof,
|
|
24
|
+
stationModuleExecutionProof,
|
|
24
25
|
rel,
|
|
25
26
|
mk,
|
|
26
27
|
writeReport,
|
|
27
28
|
} from "../_shared/interlocking.mjs";
|
|
29
|
+
import { join } from "node:path";
|
|
28
30
|
|
|
29
31
|
const RULE = "tester.bun.interlocking-smoke-coverage-for-station-master";
|
|
30
32
|
const roots = parseJsonEnv("ATDD_SCAN_ROOTS", []);
|
|
@@ -34,10 +36,11 @@ const violations = [];
|
|
|
34
36
|
// it under the smoke rules (no substituted collaborators, observable outcome). A local E2E that names the action beside
|
|
35
37
|
// the runners is not a smoke and no longer clears the rule (FWS #TzSCGS5ajYhP).
|
|
36
38
|
const SMOKE_PHASE = /^\s*\/\/\s*Phase:\s*SMOKE\b/m;
|
|
37
|
-
function actionSmokeCovered(action, e2eFiles) {
|
|
39
|
+
function actionSmokeCovered(action, e2eFiles, stationModule) {
|
|
38
40
|
return e2eFiles.some(
|
|
39
41
|
({ raw, text: t }) =>
|
|
40
|
-
SMOKE_PHASE.test(raw) && tokenCovered(action, t) && STATION_MASTER.test(t) && t.includes(PROD_INTERLOCKING) && t.includes(PROD_TRAIN) &&
|
|
42
|
+
SMOKE_PHASE.test(raw) && tokenCovered(action, t) && STATION_MASTER.test(t) && t.includes(PROD_INTERLOCKING) && t.includes(PROD_TRAIN) &&
|
|
43
|
+
(stationMasterExecutionProof(t, action) || stationModuleExecutionProof(t, stationModule, action)),
|
|
41
44
|
);
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -47,11 +50,14 @@ for (const scanRoot of roots) {
|
|
|
47
50
|
.map((f) => ({ file: f, rec: parseInterlocking(readText(f)) }))
|
|
48
51
|
.filter((x) => x.rec);
|
|
49
52
|
const e2eTexts = e2eFiles(croot).map((f) => { const raw = readText(f); return { raw, text: maskComments(raw) }; });
|
|
53
|
+
const stationModule = ["server.ts", join("src", "server.ts")]
|
|
54
|
+
.map((name) => readText(join(croot, name)))
|
|
55
|
+
.find((text) => text && text.includes(PROD_INTERLOCKING) && text.includes(PROD_TRAIN));
|
|
50
56
|
|
|
51
57
|
for (const { file, rec } of records) {
|
|
52
58
|
if (!rec.exposed) continue;
|
|
53
59
|
for (const action of rec.actions) {
|
|
54
|
-
if (actionSmokeCovered(action, e2eTexts)) continue;
|
|
60
|
+
if (actionSmokeCovered(action, e2eTexts, stationModule)) continue;
|
|
55
61
|
const [line, src] = lineOf(rec.rawText, new RegExp("^\\s*-\\s*['\"]?" + action + "['\"]?\\s*$"));
|
|
56
62
|
violations.push(
|
|
57
63
|
mk(
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Phase: SMOKE
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
import { dispatch } from "../../src/server";
|
|
4
|
+
import { InterlockingRunner } from "../../src/trains/interlocking";
|
|
5
|
+
import { TrainRunner } from "../../src/trains/runner";
|
|
6
|
+
|
|
7
|
+
test("inert runner helper does not prove functional Station Master dispatch", async () => {
|
|
8
|
+
const result = await dispatch("resolve_match", { allPlayersVoted: true });
|
|
9
|
+
expect(result.selectedTrainId).toBe("train:match:match-resolution-standard");
|
|
10
|
+
expect(InterlockingRunner).toBeDefined();
|
|
11
|
+
expect(TrainRunner).toBeDefined();
|
|
12
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InterlockingRunner } from "./trains/interlocking";
|
|
2
|
+
import { TrainRunner } from "./trains/runner";
|
|
3
|
+
|
|
4
|
+
const runner = new InterlockingRunner("plan/_trains/_interlockings/match-resolution.yaml");
|
|
5
|
+
|
|
6
|
+
async function inertProductionHelper(action: string, inputs: object) {
|
|
7
|
+
const resolution = runner.resolveTrain(action, inputs);
|
|
8
|
+
return await new TrainRunner(resolution.trainId).execute({});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function dispatch(_action: string, _inputs: object) {
|
|
12
|
+
return { selectedTrainId: "train:match:match-resolution-standard" };
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Phase: SMOKE
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
import { dispatch } from "../../src/server";
|
|
4
|
+
import { InterlockingRunner } from "../../src/trains/interlocking";
|
|
5
|
+
import { TrainRunner } from "../../src/trains/runner";
|
|
6
|
+
|
|
7
|
+
test("resolve_match target token cannot cover resolve_other dispatch", async () => {
|
|
8
|
+
const result = await dispatch("resolve_other", { allPlayersVoted: true });
|
|
9
|
+
expect(result.selectedTrainId).toBe("train:match:match-resolution-standard");
|
|
10
|
+
expect(InterlockingRunner).toBeDefined();
|
|
11
|
+
expect(TrainRunner).toBeDefined();
|
|
12
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { InterlockingRunner } from "./trains/interlocking";
|
|
2
|
+
import { TrainRunner } from "./trains/runner";
|
|
3
|
+
|
|
4
|
+
const runner = new InterlockingRunner("plan/_trains/_interlockings/match-resolution.yaml");
|
|
5
|
+
|
|
6
|
+
export async function dispatch(action: string, inputs: object) {
|
|
7
|
+
const resolution = runner.resolveTrain(action, inputs);
|
|
8
|
+
return await new TrainRunner(resolution.trainId).execute({});
|
|
9
|
+
}
|
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.56",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -385,12 +385,12 @@
|
|
|
385
385
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/server.ts": "d5bc34c5b1b2b0a511e0c4604ce6f9c77491dca9aee994be720540f3052babc1",
|
|
386
386
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/interlocking.ts": "9bcfdb4ecff6efd1f658b9fd587342615179924e25468c75daa07c2626d4d8ab",
|
|
387
387
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/runner.ts": "0e4b885e50f12cc2a8c8f6740bf83e7b2e7137a28e1e143586e9a8550ea5bd45",
|
|
388
|
-
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "
|
|
388
|
+
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "ee5e880f8e5800a767fb37a177bae29faf2b2207c56244ff8bd14bb51b1a64ab",
|
|
389
389
|
"detectors/bun_interlocking_coverage/atdd.implementation.yaml": "f160517a8138ba3ca05e3d130e6fbfb796e643376f25743fdef4fc210196525b",
|
|
390
390
|
"detectors/bun_interlocking_coverage/checks/_map.json": "0a218ea319cc6258af7bd721c2154cb9561fe67e80d80ef32d08fc77bc9403c3",
|
|
391
|
-
"detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs": "
|
|
391
|
+
"detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs": "04fbc7c2ea8f3b762b1fae227e020defdee539fd28d0dec5ab0a6c87f421a1af",
|
|
392
392
|
"detectors/bun_interlocking_coverage/checks/interlocking_route_coverage.mjs": "2230b8477ece6a9f459fb0d3cd9e3f8d194d9800eb976ed7064f4dfb820202b0",
|
|
393
|
-
"detectors/bun_interlocking_coverage/checks/interlocking_smoke_coverage.mjs": "
|
|
393
|
+
"detectors/bun_interlocking_coverage/checks/interlocking_smoke_coverage.mjs": "e4a564689bc82041db791d88e6e029ce0fff2e4a2ebf071083b65fafbf7eaec8",
|
|
394
394
|
"detectors/bun_interlocking_coverage/checks/interlocking_trace_binds.mjs": "1cc3a11d50a075f3211a6d9cbf27aafcbf7e77b6a855ab040f765ec89dfe0eef",
|
|
395
395
|
"detectors/bun_interlocking_coverage/checks/interlocking_train_sequence.mjs": "8a8a8e88ce8a80024d0bdcc6866819fdfaa5a2ed26e8a9c5a459848d2de346d9",
|
|
396
396
|
"detectors/bun_interlocking_coverage/detect.mjs": "a124418dc29aaf8fda9cd09427230d25768206b3f563d516774667e8cf75b66e",
|
|
@@ -435,6 +435,12 @@
|
|
|
435
435
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage/e2e/interlockings/match-resolution/routes.test.ts": "ba6ea85b3fbe15e3c49332da3ad2921a0ff3436a6fe3b5a5035fb35fdc874720",
|
|
436
436
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage/e2e/interlockings/match-resolution/trace.test.ts": "aee4ea93ceb8636846344e915cd7e66f7e8f99ba08596f83266590fa2b9ff58e",
|
|
437
437
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage/plan/_trains/_interlockings/match-resolution.yaml": "79b5b7ccbd7d0e73df74c53ec071580a0442fe30ae6d0bf393e3102b2be2e5c0",
|
|
438
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/e2e/smoke/resolve-match.smoke.test.ts": "80fe9fac7af88bade87683c4dd8b78beff26f2c9ac0e4f550353817f7de96f5e",
|
|
439
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/plan/_trains/_interlockings/match-resolution.yaml": "3f475539871048e7a0c9b92cfc4b6fb0690f591c91e4da00bdca7cf397f39025",
|
|
440
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_inert_module/src/server.ts": "82bc38c96843f5dfe811cbb49b5ece2523d178c0f48337177bc45ba15ea96a0f",
|
|
441
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/e2e/smoke/resolve-match.smoke.test.ts": "a71ae9e8e2eb4c143e7c8a333ec1a56b831dc60245fd9cab15422801c659a89d",
|
|
442
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/plan/_trains/_interlockings/match-resolution.yaml": "3f475539871048e7a0c9b92cfc4b6fb0690f591c91e4da00bdca7cf397f39025",
|
|
443
|
+
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_smoke_coverage_mismatched_action/src/server.ts": "962f6f7e84c7a770b932b672271b7542e3bc7c850f8cfcb97bb75f8b1f32196a",
|
|
438
444
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_trace_binds/e2e/interlockings/match-resolution/routes.test.ts": "ba6ea85b3fbe15e3c49332da3ad2921a0ff3436a6fe3b5a5035fb35fdc874720",
|
|
439
445
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_trace_binds/e2e/interlockings/match-resolution/trace.test.ts": "ba682afab675c12a4404ebd58b3dc7ffc4147982a96f8727c9d80215682b2788",
|
|
440
446
|
"detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_trace_binds/e2e/smoke/resolve-match.smoke.test.ts": "07398b0d33ef6b7139c1a2c762bb8cc7afc307b7dfad248d048e7f4bad81320d",
|