@afokapu/atdd-bun 0.10.80 → 0.10.82
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/checks/interlocking_production_runner.mjs +4 -1
- package/detectors/bun_interlocking_infrastructure/checks/wagon_contract_honoured.mjs +48 -9
- package/integrity.json +6 -6
- package/lib/journey-chain.mjs +75 -0
- package/package.json +1 -1
- package/planner-schemas/wagon.schema.json +2 -2
- package/src/planner-validators.ts +3 -2
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
mk,
|
|
30
30
|
writeReport,
|
|
31
31
|
} from "../_shared/interlocking.mjs";
|
|
32
|
+
import { assertedJourneyExecution } from "../../../lib/journey-chain.mjs";
|
|
32
33
|
|
|
33
34
|
const RULE = "tester.bun.interlocking-production-runner-used";
|
|
34
35
|
const roots = parseJsonEnv("ATDD_SCAN_ROOTS", []);
|
|
@@ -72,7 +73,9 @@ for (const scanRoot of roots) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
// A journey test drives the runners through the production JourneyRunner: its asserted result counts
|
|
77
|
+
// when the journey-mediated chain is proven on the AST (lib/journey-chain.mjs), as for the Station Master.
|
|
78
|
+
const semantic = hasProductionExecutionProof(text, file, croot) || assertedJourneyExecution(raw, file, croot) ||
|
|
76
79
|
records.some((rec) => rec.actions.some((action) => stationMasterExecutionProof(text, action))) ||
|
|
77
80
|
(drivesStationMaster(text) && records.some((rec) => rec.actions.some((action) =>
|
|
78
81
|
stationModuleExecutionProof(text, station.text, action, station.file, croot),
|
|
@@ -70,24 +70,36 @@ function consumerRoots(root, excludes) {
|
|
|
70
70
|
return roots;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Items under `produce:` / `consume:` in a wagon document: each artifact `name` with its
|
|
74
|
+
// declared consume source (`from:`) or produce destination (`to:`). Read line-wise
|
|
74
75
|
// rather than with a YAML parser so this check carries no dependency the rest of the
|
|
75
76
|
// family does not already have; the shape it reads is the one core's schema requires.
|
|
76
77
|
export function wagonContract(text) {
|
|
77
78
|
if (!/^wagon:\s*\S/m.test(text)) return null;
|
|
78
79
|
const out = { produce: [], consume: [] };
|
|
79
|
-
let section = null;
|
|
80
|
+
let section = null, item = null, itemIndent = -1;
|
|
80
81
|
for (const line of text.split(/\r?\n/)) {
|
|
81
82
|
const top = line.match(/^(\w+):\s*$/);
|
|
82
|
-
if (top) { section = top[1] === "produce" || top[1] === "consume" ? top[1] : null; continue; }
|
|
83
|
-
if (/^\S/.test(line)) { section = null; continue; }
|
|
83
|
+
if (top) { section = top[1] === "produce" || top[1] === "consume" ? top[1] : null; item = null; itemIndent = -1; continue; }
|
|
84
|
+
if (/^\S/.test(line)) { section = null; item = null; continue; }
|
|
84
85
|
if (!section) continue;
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
86
|
+
const dash = line.match(/^(\s*)-(?:\s|$)/);
|
|
87
|
+
if (dash && (itemIndent < 0 || dash[1].length === itemIndent)) {
|
|
88
|
+
itemIndent = dash[1].length;
|
|
89
|
+
item = { name: null, from: null, to: null };
|
|
90
|
+
out[section].push(item);
|
|
91
|
+
}
|
|
92
|
+
const field = line.match(/^\s*(?:-\s*)?(name|from|to):\s*["']?([^"'#\s]+)["']?/);
|
|
93
|
+
if (field && item && item[field[1]] === null) item[field[1]] = field[2];
|
|
87
94
|
}
|
|
95
|
+
for (const kind of ["produce", "consume"]) out[kind] = out[kind].filter((entry) => entry.name);
|
|
88
96
|
return out.produce.length || out.consume.length ? out : null;
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
// A declared platform-service side: a consume `from: system:<service>` or a produce
|
|
100
|
+
// `to: system:<service>`. That side is storage, not Cargo, so it carries no Cargo move.
|
|
101
|
+
const systemSide = (end) => typeof end === "string" && /^system:[a-z][a-z0-9-]*$/.test(end);
|
|
102
|
+
|
|
91
103
|
// Blank out COMMENTS, keeping string literals intact.
|
|
92
104
|
//
|
|
93
105
|
// The artifact name legitimately appears as a literal — `cargo.put("orders:confirmed-order")`
|
|
@@ -213,6 +225,14 @@ for (const root of parseJsonEnv("ATDD_SCAN_ROOTS", [])) {
|
|
|
213
225
|
for (const step of doc.sequence) for (const end of [step?.from, step?.to]) if (typeof end === "string" && end.startsWith("wagon:")) carried.add(end.slice(6).trim());
|
|
214
226
|
}
|
|
215
227
|
if (!contracts.length) continue; // no declared contract
|
|
228
|
+
// Wagons that put an artifact into Cargo (any produce not written to system:) or take one
|
|
229
|
+
// from Cargo (any consume not read from system:). A system: label on the other side of such
|
|
230
|
+
// an artifact cannot relabel it: that artifact travels as Cargo.
|
|
231
|
+
const cargoProducers = new Map(), cargoConsumers = new Map();
|
|
232
|
+
for (const c of contracts) {
|
|
233
|
+
for (const p of c.produce) if (!systemSide(p.to)) cargoProducers.set(p.name, [...(cargoProducers.get(p.name) ?? []), c.wagon]);
|
|
234
|
+
for (const q of c.consume) if (!systemSide(q.from)) cargoConsumers.set(q.name, [...(cargoConsumers.get(q.name) ?? []), c.wagon]);
|
|
235
|
+
}
|
|
216
236
|
|
|
217
237
|
const sources = walk(croot, (p) => TS.test(p) && !TEST.test(p), excludes)
|
|
218
238
|
.map((file) => ({ file, text: maskComments(read(file)) }));
|
|
@@ -234,11 +254,30 @@ for (const root of parseJsonEnv("ATDD_SCAN_ROOTS", [])) {
|
|
|
234
254
|
for (const artifact of moves.reads) reads.add(artifact);
|
|
235
255
|
for (const artifact of moves.writes) writes.add(artifact);
|
|
236
256
|
}
|
|
237
|
-
|
|
238
|
-
|
|
257
|
+
const rel = c.file.startsWith(croot + sep) ? c.file.slice(croot.length + 1) : c.file;
|
|
258
|
+
for (const [kind, items] of [["produce", c.produce], ["consume", c.consume]]) {
|
|
259
|
+
for (const { name, from, to } of items) {
|
|
260
|
+
const side = kind === "produce" ? to : from;
|
|
261
|
+
if (systemSide(side)) {
|
|
262
|
+
const others = ((kind === "consume" ? cargoProducers : cargoConsumers).get(name) ?? []).filter((w) => w !== c.wagon);
|
|
263
|
+
if (others.length) {
|
|
264
|
+
const wagons = others.map((w) => `wagon:${w}`).join(", ");
|
|
265
|
+
violations.push({
|
|
266
|
+
rule_id: RULE, file: rel, line: 1, col: 0,
|
|
267
|
+
evidence: kind === "consume"
|
|
268
|
+
? `the wagon declares it consumes "${name}" from ${side}, but ${wagons} produces it into Cargo; ` +
|
|
269
|
+
`a system: source cannot relabel Cargo traffic, so declare its wagon: source and read it through Cargo`
|
|
270
|
+
: `the wagon declares it produces "${name}" to ${side}, but ${wagons} consumes it through Cargo; ` +
|
|
271
|
+
`a system: destination cannot relabel Cargo traffic, so drop the system: destination and write it through Cargo`,
|
|
272
|
+
source_line: "",
|
|
273
|
+
});
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// Storage, not Cargo: no Cargo move is owed, but the wagon must still be bound to its train step.
|
|
277
|
+
if (handlers.length) continue;
|
|
278
|
+
}
|
|
239
279
|
const moved = kind === "produce" ? writes : reads;
|
|
240
280
|
if (moved.has(name)) continue;
|
|
241
|
-
const rel = c.file.startsWith(croot + sep) ? c.file.slice(croot.length + 1) : c.file;
|
|
242
281
|
violations.push({
|
|
243
282
|
rule_id: RULE, file: rel, line: 1, col: 0,
|
|
244
283
|
evidence:
|
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.82",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "8a90490daaaa6eecf4c971fb569da38156b45969d48755dbcf355175dde0b079",
|
|
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": "97224d46f0c4c3259f15e41f90bb7f9f5a4e651d4facd41f1e037cac919be500",
|
|
392
392
|
"detectors/bun_interlocking_coverage/checks/interlocking_route_coverage.mjs": "a2cff94a6bf9e11fe8e370332551742f9c7db317dcd57a88d08dc6c74f868f49",
|
|
393
393
|
"detectors/bun_interlocking_coverage/checks/interlocking_smoke_coverage.mjs": "28e016e1a4cd394291640679876b16c7d6a725530c7fbfa4bd1da8a354ecff86",
|
|
394
394
|
"detectors/bun_interlocking_coverage/checks/interlocking_trace_binds.mjs": "1cc3a11d50a075f3211a6d9cbf27aafcbf7e77b6a855ab040f765ec89dfe0eef",
|
|
@@ -492,7 +492,7 @@
|
|
|
492
492
|
"detectors/bun_interlocking_infrastructure/checks/interlocking_station_routing.mjs": "f3870fb3bacee46a6c3e10238284ef4d359182030d40d4f33b0f0ad465f7a284",
|
|
493
493
|
"detectors/bun_interlocking_infrastructure/checks/journey_runner_boundary.mjs": "6284a8532722e5a6bff7e5184e24c7f273c1e63f1331204502e0e3704db5c86b",
|
|
494
494
|
"detectors/bun_interlocking_infrastructure/checks/journey_station_routing.mjs": "66c09cdc17b099a55fa0ca894bd85a44706742bdd29caa5347f10e6487b4e724",
|
|
495
|
-
"detectors/bun_interlocking_infrastructure/checks/wagon_contract_honoured.mjs": "
|
|
495
|
+
"detectors/bun_interlocking_infrastructure/checks/wagon_contract_honoured.mjs": "f6a7f8ef2e1d403934253b0491d3cb15451156ad7fa2b5b6ba313c0522a359b2",
|
|
496
496
|
"detectors/bun_interlocking_infrastructure/detect.mjs": "6494579ec7b9d4680099401346337d73f4a52368ffb6b9d3b0fbca5dce2cde15",
|
|
497
497
|
"detectors/bun_interlocking_infrastructure/fixtures/clean/interlocking_delegates/server.ts": "36913357fdd3c5e375829a0554c605d70f5c05e2e83349f47505741598f6eb15",
|
|
498
498
|
"detectors/bun_interlocking_infrastructure/fixtures/clean/interlocking_delegates/src/match/wagon.ts": "ea202fa15fdf9d1391b1ae23343c9d1b09fcf8ef14f1dccfb8114ffaedea97c1",
|
|
@@ -1034,7 +1034,7 @@
|
|
|
1034
1034
|
"lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
|
|
1035
1035
|
"lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
|
|
1036
1036
|
"lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
|
|
1037
|
-
"lib/journey-chain.mjs": "
|
|
1037
|
+
"lib/journey-chain.mjs": "bdafc9cc3da4ade644aebacc6e1015e29285b764465588d764b7c581bade9025",
|
|
1038
1038
|
"lib/scan.mjs": "02b0fa864cab24157a8f27b50a87a2bb66a875f90cf6632c753c4627d61456ef",
|
|
1039
1039
|
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
|
|
1040
1040
|
"planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
|
|
@@ -1253,7 +1253,7 @@
|
|
|
1253
1253
|
"planner-schemas/train-interlocking-registry.schema.json": "fd741b5a3909d54cef225fffde80b04359416690a26aca5718b7d91027f3934b",
|
|
1254
1254
|
"planner-schemas/train-interlocking.schema.json": "67914979977f23652298b5272adf3df298576ecafbff3e5084574848be8c33d8",
|
|
1255
1255
|
"planner-schemas/train.schema.json": "d6fbac9067c1bffeb84eb79a71228441055af6e6e7ba141dd4e0bcb079345ec0",
|
|
1256
|
-
"planner-schemas/wagon.schema.json": "
|
|
1256
|
+
"planner-schemas/wagon.schema.json": "12353e46d2216d2deef754615b629a696bd8a7bb35326379e7ef2b221a5ece3a",
|
|
1257
1257
|
"planner-schemas/wmbt.schema.json": "7a3a5693bfcf01340667c2be4a3c48e74f34b1af7e8dc13372c0de06b7f4fe12",
|
|
1258
1258
|
"relationships.yaml": "5860a308ebc3ee7fa8172fc2c6633984000ab3003f0ae4b6fa28db48879eda22",
|
|
1259
1259
|
"src/agent.ts": "6d6c6e75908af8fbab13432f0001b0374bb25cf3dee46ba218dfd67e0090cb56",
|
|
@@ -1267,7 +1267,7 @@
|
|
|
1267
1267
|
"src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
|
|
1268
1268
|
"src/planner-kernel.ts": "5d3f5305fb59becb2cc97d8a03aca6b104f385805d91cc0410856e289dd6fbb1",
|
|
1269
1269
|
"src/planner-schema-validator.ts": "ce529c0936171075e93c7539b3655fe895a98dd2973ebda5775958e6ebc316d9",
|
|
1270
|
-
"src/planner-validators.ts": "
|
|
1270
|
+
"src/planner-validators.ts": "bb300a5ca6ed6487359d680f9512359aa75c48205110dd46c33067db7b5a18a2",
|
|
1271
1271
|
"src/profile-registry.ts": "5a7761d324181ae27deb51f198d63648a4c99e858b4de98b54d35e6cf8a39fe7",
|
|
1272
1272
|
"src/ratchet.ts": "909b47124863287cbf6b02237b70fd50d82522d5029b2ae900c371d2ea2287a2",
|
|
1273
1273
|
"src/register.ts": "6a2f1c6b8df0dc9c8f45df8ec9fac2be29044e048b08c8ffe423410e94dadedd",
|
package/lib/journey-chain.mjs
CHANGED
|
@@ -647,6 +647,81 @@ export function provenJourneyAction(stationText, stationFile, croot, name) {
|
|
|
647
647
|
everyModule(modules, "JourneyRunner", journeyExecutes(croot));
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
+
// The tester assertion vocabulary (shared tester layer `assertsExpression`), positive and under `.not`.
|
|
651
|
+
const MATCHERS = new Set(["toBe", "toBeDefined", "toEqual", "toStrictEqual", "toContain", "toContainEqual", "toMatchObject", "toMatch", "toBeTruthy"]);
|
|
652
|
+
const NEGATED_MATCHERS = new Set([...MATCHERS].filter((name) => name !== "toBeTruthy"));
|
|
653
|
+
|
|
654
|
+
// The journey declaration at `path` (relative to the consumer root, where its tests run) declares
|
|
655
|
+
// `action` among its entrypoint actions.
|
|
656
|
+
function declaresAction(croot, path, action) {
|
|
657
|
+
const file = resolve(croot, path);
|
|
658
|
+
if (!file.startsWith(resolve(croot) + sep) || !existsSync(file) || !statSync(file).isFile() || !globalThis.Bun?.YAML) return false;
|
|
659
|
+
try {
|
|
660
|
+
const actions = Bun.YAML.parse(readFileSync(file, "utf8"))?.entrypoint?.actions;
|
|
661
|
+
return Array.isArray(actions) && actions.includes(action);
|
|
662
|
+
} catch {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// A test module asserts a production JourneyRunner execution result, proven like provenJourneyAction:
|
|
668
|
+
// - a live `const` bound to `await <receiver>.execute("<action>", ...)`, the receiver being
|
|
669
|
+
// `new JourneyRunner("<journey declaration>")` or a const bound to one;
|
|
670
|
+
// - JourneyRunner binds to a local production import, and the declaration it is constructed from
|
|
671
|
+
// exists under the consumer root and declares that action;
|
|
672
|
+
// - the JourneyRunner module reaches InterlockingRunner -> TrainRunner (journeyExecutes);
|
|
673
|
+
// - a live `expect(<result or a member chain of it>).<matcher>(...)` whose argument binds to that
|
|
674
|
+
// const by checker symbol, not by spelling.
|
|
675
|
+
// The override contract covers the test module and every local module it reaches.
|
|
676
|
+
export function assertedJourneyExecution(testText, testFile, croot) {
|
|
677
|
+
if (!testText || !testFile || !croot) return false;
|
|
678
|
+
const ctx = analyze(testFile, testText);
|
|
679
|
+
if (tampered(ctx) || [...localImportClosure(testFile, croot)].some((module) => tampered(analyze(module)))) return false;
|
|
680
|
+
const results = new Set();
|
|
681
|
+
walk(ctx.source, (node) => {
|
|
682
|
+
if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name) || !node.initializer ||
|
|
683
|
+
!(ts.isVariableDeclarationList(node.parent) && node.parent.flags & ts.NodeFlags.Const)) return;
|
|
684
|
+
const awaited = ts.isAwaitExpression(node.initializer) ? unwrap(node.initializer) : null;
|
|
685
|
+
const action = awaited && ts.isCallExpression(awaited) && awaited.arguments[0];
|
|
686
|
+
if (!action || !ts.isStringLiteralLike(action) || dead(node, ctx.source) || insideCatch(node, ctx.source)) return;
|
|
687
|
+
const fromDeclaration = (construction) => {
|
|
688
|
+
const path = construction.arguments?.[0];
|
|
689
|
+
return Boolean(path && ts.isStringLiteralLike(path) && declaresAction(croot, path.text, action.text));
|
|
690
|
+
};
|
|
691
|
+
const modules = new Set();
|
|
692
|
+
if (executesImported(ctx, "JourneyRunner", croot, modules, fromDeclaration)(awaited) &&
|
|
693
|
+
everyModule(modules, "JourneyRunner", journeyExecutes(croot))) results.add(symbolOf(ctx, node.name));
|
|
694
|
+
});
|
|
695
|
+
if (!results.size) return false;
|
|
696
|
+
const boundToResult = (node) => {
|
|
697
|
+
node = unwrap(node);
|
|
698
|
+
while (node && ts.isPropertyAccessExpression(node)) node = unwrap(node.expression);
|
|
699
|
+
return Boolean(node) && ts.isIdentifier(node) && results.has(symbolOf(ctx, node));
|
|
700
|
+
};
|
|
701
|
+
const isExpect = (node) => {
|
|
702
|
+
if (!ts.isIdentifier(node) || node.text !== "expect") return false;
|
|
703
|
+
const declaration = declarationOf(ctx, node);
|
|
704
|
+
if (!declaration) return true;
|
|
705
|
+
const statement = ts.isImportSpecifier(declaration) ? declaration.parent.parent.parent : null;
|
|
706
|
+
return Boolean(statement && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "bun:test" &&
|
|
707
|
+
(declaration.propertyName ?? declaration.name).text === "expect");
|
|
708
|
+
};
|
|
709
|
+
let asserted = false;
|
|
710
|
+
walk(ctx.source, (node) => {
|
|
711
|
+
if (asserted) return false;
|
|
712
|
+
if (!ts.isCallExpression(node) || !isExpect(node.expression) || !boundToResult(node.arguments[0]) || dead(node, ctx.source)) return;
|
|
713
|
+
let access = node.parent, matchers = MATCHERS;
|
|
714
|
+
if (!ts.isPropertyAccessExpression(access) || access.expression !== node) return;
|
|
715
|
+
if (access.name.text === "not") {
|
|
716
|
+
access = access.parent;
|
|
717
|
+
matchers = NEGATED_MATCHERS;
|
|
718
|
+
}
|
|
719
|
+
asserted = ts.isPropertyAccessExpression(access) && matchers.has(access.name.text) &&
|
|
720
|
+
ts.isCallExpression(access.parent) && access.parent.expression === access;
|
|
721
|
+
});
|
|
722
|
+
return asserted;
|
|
723
|
+
}
|
|
724
|
+
|
|
650
725
|
// Names of every exported Station Master action proven by provenJourneyAction.
|
|
651
726
|
export function provenJourneyActions(stationText, stationFile, croot) {
|
|
652
727
|
if (!stationText || !stationFile || !croot) return [];
|
package/package.json
CHANGED
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
},
|
|
86
86
|
"to": {
|
|
87
87
|
"type": "string",
|
|
88
|
-
"
|
|
88
|
+
"pattern": "^(internal|external|system:[a-z][a-z0-9-]*)$",
|
|
89
89
|
"default": "external",
|
|
90
|
-
"description": "Optional
|
|
90
|
+
"description": "Optional destination: internal (private to this wagon), external (public to all wagons including self), or system:service (written to a platform service, symmetric with consume from: system:service). Defaults to external when omitted."
|
|
91
91
|
},
|
|
92
92
|
"version": {
|
|
93
93
|
"type": "string",
|
|
@@ -116,7 +116,8 @@ function contractRegistryFindings(root: string, wagons: PlanArtifact[], registry
|
|
|
116
116
|
const name = text(produced.name), contract = text(produced.contract), producer = text(wagon.data.wagon);
|
|
117
117
|
const consumers = (byName.get(name) ?? []).filter(consumer => consumer && consumer !== producer);
|
|
118
118
|
if (consumers.length && !contract) findings.push(finding("planner.contract.registry-coherence", wagon.file, `produce ${name} has contract null but is consumed cross-wagon by ${consumers.sort().join(", ")}`));
|
|
119
|
-
|
|
119
|
+
const to = text(produced.to || "external");
|
|
120
|
+
if (contract && !consumers.length && to !== "external" && !/^system:[a-z][a-z0-9-]*$/.test(to)) findings.push(finding("planner.contract.registry-coherence", wagon.file, `produce ${name} declares ${contract} but has no cross-wagon consumer and is not marked to: external`));
|
|
120
121
|
}
|
|
121
122
|
return findings;
|
|
122
123
|
}
|
|
@@ -297,7 +298,7 @@ export async function validateStaticPlannerConventions(root = process.cwd()): Pr
|
|
|
297
298
|
for (const item of produced) {
|
|
298
299
|
const contract = text(item.contract), telemetry = item.telemetry; if (contract) contractOwners.set(contract, [...(contractOwners.get(contract) ?? []), slug]);
|
|
299
300
|
for (const value of Array.isArray(telemetry) ? telemetry.map(text) : [text(telemetry)]) if (value) telemetryOwners.set(value, [...(telemetryOwners.get(value) ?? []), slug]);
|
|
300
|
-
const to = text(item.to || "external"); if (to !== "external" && to !== "internal" && (!to.startsWith("wagon:") || !wagonSlugs.has(to.slice(6)))) findings.push(finding("planner.wagon.produce-consume-artifacts", wagon.file, `${expected} produces to invalid destination ${to}`));
|
|
301
|
+
const to = text(item.to || "external"); if (to !== "external" && to !== "internal" && !/^system:[a-z][a-z0-9-]*$/.test(to) && (!to.startsWith("wagon:") || !wagonSlugs.has(to.slice(6)))) findings.push(finding("planner.wagon.produce-consume-artifacts", wagon.file, `${expected} produces to invalid destination ${to}`));
|
|
301
302
|
}
|
|
302
303
|
for (const item of consumed) {
|
|
303
304
|
const from = text(item.from); if (!from) continue;
|