@afokapu/atdd-bun 0.10.81 → 0.10.83
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.
|
@@ -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.83",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -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",
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afokapu/atdd-bun",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.83",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/afokapu/atdd-bun.git"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"bun": ">=1.3.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@afokapu/atdd-flow": "^0.3.
|
|
38
|
+
"@afokapu/atdd-flow": "^0.3.38",
|
|
39
39
|
"ajv": "^8.17.1",
|
|
40
40
|
"ajv-formats": "^3.0.1",
|
|
41
41
|
"typescript": "6.0.3"
|
|
@@ -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;
|