@bldrs-ai/conway 1.476.1409 → 1.484.1410
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/compiled/examples/browser-bundled.cjs +1 -1
- package/compiled/examples/cli-bundled.cjs +1 -1
- package/compiled/examples/cli-step-bundled.cjs +1 -1
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/ifc/ifc_regression_batch_main.js +114 -42
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
|
|
|
30
30
|
var readline = __toESM(require("node:readline"), 1);
|
|
31
31
|
|
|
32
32
|
// compiled/src/version/version.js
|
|
33
|
-
var versionString = "Conway v1.
|
|
33
|
+
var versionString = "Conway v1.484.1410";
|
|
34
34
|
|
|
35
35
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
36
36
|
var wasmType = "";
|
|
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
|
|
|
14965
14965
|
var import_process = require("process");
|
|
14966
14966
|
|
|
14967
14967
|
// compiled/src/version/version.js
|
|
14968
|
-
var versionString = "Conway v1.
|
|
14968
|
+
var versionString = "Conway v1.484.1410";
|
|
14969
14969
|
|
|
14970
14970
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
14971
14971
|
function pThreadsAllowed() {
|
|
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
|
|
|
15943
15943
|
};
|
|
15944
15944
|
|
|
15945
15945
|
// compiled/src/version/version.js
|
|
15946
|
-
var versionString = "Conway v1.
|
|
15946
|
+
var versionString = "Conway v1.484.1410";
|
|
15947
15947
|
|
|
15948
15948
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
15949
15949
|
function pThreadsAllowed() {
|
|
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
|
|
|
944
944
|
var entity_types_ifc_gen_default = EntityTypesIfc;
|
|
945
945
|
|
|
946
946
|
// compiled/src/version/version.js
|
|
947
|
-
var versionString = "Conway v1.
|
|
947
|
+
var versionString = "Conway v1.484.1410";
|
|
948
948
|
|
|
949
949
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
950
950
|
var wasmType = "";
|
|
@@ -88,6 +88,39 @@ function csvSafeString(from) {
|
|
|
88
88
|
}
|
|
89
89
|
return from;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Read back the first field of a line written with csvSafeString.
|
|
93
|
+
*
|
|
94
|
+
* Naive `line.split(',')[0]` is wrong for exactly the inputs csvSafeString
|
|
95
|
+
* exists for: a model filename containing a comma comes back quoted, and
|
|
96
|
+
* splitting on ',' truncates it mid-name. The zero-geometry collision check
|
|
97
|
+
* keys on this field, so a truncated name silently changes which stems it
|
|
98
|
+
* thinks collide.
|
|
99
|
+
*
|
|
100
|
+
* @param line A CSV line whose first field was written by csvSafeString.
|
|
101
|
+
* @return {string} The unescaped first field.
|
|
102
|
+
*/
|
|
103
|
+
function csvFirstField(line) {
|
|
104
|
+
const trimmed = line.replace(/[\r\n]+$/, '');
|
|
105
|
+
if (!trimmed.startsWith('"')) {
|
|
106
|
+
return trimmed.split(',')[0];
|
|
107
|
+
}
|
|
108
|
+
let result = '';
|
|
109
|
+
for (let cursor = 1; cursor < trimmed.length; ++cursor) {
|
|
110
|
+
if (trimmed[cursor] !== '"') {
|
|
111
|
+
result += trimmed[cursor];
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// A doubled quote is a literal one; a lone quote closes the field.
|
|
115
|
+
if (trimmed[cursor + 1] === '"') {
|
|
116
|
+
result += '"';
|
|
117
|
+
++cursor;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
91
124
|
/**
|
|
92
125
|
* Encapsulates a string in a CSV safe way, taking
|
|
93
126
|
* file paths (assumed by directory characters / and \,
|
|
@@ -206,6 +239,15 @@ async function runForFile(filePath, outputPath, maxTimeout, perfPath) {
|
|
|
206
239
|
'./compiled/src/ifc/ifc_regression_main.js';
|
|
207
240
|
const safeExecCommand = `node --experimental-specifier-resolution=node ${childScript} -d${perfFlag} "${filePath}" "${outputPath}"`;
|
|
208
241
|
console.log(`Current File: ${filePath}`);
|
|
242
|
+
// Remove any stale digest before the child runs. Without this, a child that
|
|
243
|
+
// dies without writing one leaves the committed baseline CSV in place, the
|
|
244
|
+
// batch hashes THAT, and the model reports an unchanged hash with no failure
|
|
245
|
+
// and no zero-geometry row - green, while extraction is completely broken.
|
|
246
|
+
// That is precisely the case this gate exists to catch.
|
|
247
|
+
const staleDigest = `${outputPath}.csv`;
|
|
248
|
+
if (fs.existsSync(staleDigest)) {
|
|
249
|
+
await fsPromises.rm(staleDigest, { force: true });
|
|
250
|
+
}
|
|
209
251
|
// Use safeExecWithCancellation, will kill the process if it takes longer than MAX_TIMEOUT_MS.
|
|
210
252
|
const process = await safeExecWithCancellation(safeExecCommand, MAX_TIMEOUT_MS);
|
|
211
253
|
totalTime += Date.now() - startTime;
|
|
@@ -228,54 +270,41 @@ async function runForFile(filePath, outputPath, maxTimeout, perfPath) {
|
|
|
228
270
|
}
|
|
229
271
|
const outputFile = path.basename(outputPath);
|
|
230
272
|
let fileHash;
|
|
273
|
+
// Absent digest counts as zero geometry: the stale file was removed before
|
|
274
|
+
// the child ran, so nothing here can be left over from the committed
|
|
275
|
+
// baseline. A child that dies without writing one is exactly the case this
|
|
276
|
+
// gate exists to catch, and hashing the old file would report it as green.
|
|
277
|
+
let producedNoGeometry = true;
|
|
231
278
|
const outputCSV = `${outputPath}.csv`;
|
|
232
279
|
if (fs.existsSync(outputCSV)) {
|
|
280
|
+
const digest = await fsPromises.readFile(outputCSV);
|
|
233
281
|
fileHash = crypto
|
|
234
282
|
.createHash('sha1')
|
|
235
|
-
.update(
|
|
283
|
+
.update(digest)
|
|
236
284
|
.digest('hex');
|
|
285
|
+
// A digest with nothing after its header means the model loaded and
|
|
286
|
+
// produced no geometry at all. The child still exits 0, so this is
|
|
287
|
+
// invisible to every other signal the harness has — see #477 / #478.
|
|
288
|
+
//
|
|
289
|
+
// Known limitation: a digest row is any hashed entity, and that includes
|
|
290
|
+
// non-renderable ones (IFCSURFACESTYLE, IFCPOLYLINE, IFCCENTERLINEPROFILEDEF
|
|
291
|
+
// …), so "has rows" is weaker than "has renderable geometry". A model that
|
|
292
|
+
// emits only styles and profiles reads as fine here. That is a deliberate
|
|
293
|
+
// floor, not an oversight: this catches the total-blank case with zero
|
|
294
|
+
// false positives, which is what an unconditional CI gate needs. Tightening
|
|
295
|
+
// it to a mesh/vertex count means teaching the digest which types are
|
|
296
|
+
// renderable and re-blessing every baseline; tracked on #478.
|
|
297
|
+
producedNoGeometry =
|
|
298
|
+
digest.toString('utf8').split('\n').filter((line) => line.trim().length > 0).length <= 1;
|
|
237
299
|
}
|
|
238
300
|
return {
|
|
239
301
|
type: 'Run',
|
|
240
302
|
errorLines: errorLines.length > 0 ? errorLines : undefined,
|
|
241
303
|
outputFile,
|
|
242
304
|
hash: fileHash,
|
|
305
|
+
producedNoGeometry,
|
|
243
306
|
};
|
|
244
307
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Run a file's digest, retrying ONCE if the first attempt times out.
|
|
247
|
-
*
|
|
248
|
-
* A per-model timeout is not a deterministic parse/geometry failure — it
|
|
249
|
-
* surfaces as a failed.csv row with an empty code AND empty signal (the
|
|
250
|
-
* TimeoutError path sets neither) and, historically, has come from transient
|
|
251
|
-
* core oversubscription on the CI runner rather than the model itself.
|
|
252
|
-
* ISSUE_159_kleine_Wohnung_R22.ifc (a geometry-dense ~18.5k-item model) has
|
|
253
|
-
* reddened the rc-regression gate this way twice: first co-scheduled at
|
|
254
|
-
* --concurrency 2, then again running ALONE at --concurrency 1, so serializing
|
|
255
|
-
* the batch was not enough to make it reliable.
|
|
256
|
-
*
|
|
257
|
-
* Digest regeneration is idempotent, so a second attempt is safe: a transient
|
|
258
|
-
* timeout clears on the retry, while a genuine hang times out both times and
|
|
259
|
-
* still lands in failed.csv — the gate keeps its protective value against a
|
|
260
|
-
* model that truly never loads. Only timeouts are retried; a real non-zero
|
|
261
|
-
* exit or signal is returned immediately (those ARE deterministic).
|
|
262
|
-
*
|
|
263
|
-
* @param filePath Model file to digest.
|
|
264
|
-
* @param outputPath Digest output path (without extension).
|
|
265
|
-
* @param maxTimeout Per-attempt timeout in ms.
|
|
266
|
-
* @param perfPath Optional path the child writes its one-row perf CSV to.
|
|
267
|
-
* @return The first attempt's result, or the retry's result on a timeout.
|
|
268
|
-
*/
|
|
269
|
-
async function runForFileWithTimeoutRetry(filePath, outputPath, maxTimeout, perfPath) {
|
|
270
|
-
const timedOut = (r) => r.type === 'Failed' && r.message === 'Execution timed out';
|
|
271
|
-
const first = await runForFile(filePath, outputPath, maxTimeout, perfPath);
|
|
272
|
-
if (!timedOut(first)) {
|
|
273
|
-
return first;
|
|
274
|
-
}
|
|
275
|
-
console.log(`"${path.basename(filePath)}" timed out; retrying once ` +
|
|
276
|
-
`(a transient timeout clears, a true hang times out again).`);
|
|
277
|
-
return runForFile(filePath, outputPath, maxTimeout, perfPath);
|
|
278
|
-
}
|
|
279
308
|
// Model files the regression harness understands: IFC plus STEP AP214.
|
|
280
309
|
const SUPPORTED_MODEL_EXTENSIONS = ['.ifc', '.stp', '.step'];
|
|
281
310
|
/**
|
|
@@ -355,12 +384,13 @@ function getSystemMemoryUsagePercent() {
|
|
|
355
384
|
* @param errorLines
|
|
356
385
|
* @param fileLines
|
|
357
386
|
* @param failedLines
|
|
387
|
+
* @param zeroGeometryLines
|
|
358
388
|
* @param memUtilization
|
|
359
389
|
* @param maxTimeout
|
|
360
390
|
* @param concurrency Max children processed at once (>= 1).
|
|
361
391
|
* @param perfDir If set, the child writes its perf CSV here as <basename>.perf.csv.
|
|
362
392
|
*/
|
|
363
|
-
async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileLines, failedLines, memUtilization, maxTimeout, concurrency, perfDir) {
|
|
393
|
+
async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileLines, failedLines, zeroGeometryLines, memUtilization, maxTimeout, concurrency, perfDir) {
|
|
364
394
|
const concurrencyLimit = Math.max(1, concurrency);
|
|
365
395
|
console.log(`Concurrency: ${concurrencyLimit} children - Max Timeout: ${maxTimeout} ms`);
|
|
366
396
|
const limit = pLimit(concurrencyLimit);
|
|
@@ -378,7 +408,7 @@ async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileL
|
|
|
378
408
|
const perfChildPath = perfDir ?
|
|
379
409
|
path.join(perfDir, `${path.parse(ifcPath).name}.perf.csv`) :
|
|
380
410
|
undefined;
|
|
381
|
-
const fileResults = await
|
|
411
|
+
const fileResults = await runForFile(ifcPath, path.join(outputPath, path.parse(ifcPath).name), maxTimeout, perfChildPath);
|
|
382
412
|
activeTasks--;
|
|
383
413
|
console.log(`Completed task for "${path.basename(ifcPath)}". Active tasks: ${activeTasks}`);
|
|
384
414
|
return { ifcPath, fileResults };
|
|
@@ -394,6 +424,9 @@ async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileL
|
|
|
394
424
|
fileLines.push(`${csvSafeString(path.basename(ifcPath))},` +
|
|
395
425
|
`${csvSafeString(fileResults.hash ?? '')},` +
|
|
396
426
|
`${fileResults.errorLines?.length ?? 0}\n`);
|
|
427
|
+
if (fileResults.producedNoGeometry) {
|
|
428
|
+
zeroGeometryLines.push(`${csvSafeString(path.basename(ifcPath))}\n`);
|
|
429
|
+
}
|
|
397
430
|
}
|
|
398
431
|
else {
|
|
399
432
|
// it's 'Failed'
|
|
@@ -412,10 +445,11 @@ async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileL
|
|
|
412
445
|
* @param errorLines
|
|
413
446
|
* @param fileLines
|
|
414
447
|
* @param failedLines
|
|
448
|
+
* @param zeroGeometryLines
|
|
415
449
|
* @param maxTimeout
|
|
416
450
|
* @param perfDir If set, the child writes its perf CSV here as <basename>.perf.csv.
|
|
417
451
|
*/
|
|
418
|
-
async function recursiveWalk(parentPath, excludeRegex, outputPath, errorLines, fileLines, failedLines, maxTimeout, perfDir) {
|
|
452
|
+
async function recursiveWalk(parentPath, excludeRegex, outputPath, errorLines, fileLines, failedLines, zeroGeometryLines, maxTimeout, perfDir) {
|
|
419
453
|
const items = await fsPromises.readdir(parentPath, { withFileTypes: true });
|
|
420
454
|
items.sort((a, b) => (a.name > b.name ? 1 : -1));
|
|
421
455
|
for (const item of items) {
|
|
@@ -424,18 +458,21 @@ async function recursiveWalk(parentPath, excludeRegex, outputPath, errorLines, f
|
|
|
424
458
|
continue;
|
|
425
459
|
}
|
|
426
460
|
if (item.isDirectory()) {
|
|
427
|
-
await recursiveWalk(resolved, excludeRegex, outputPath, errorLines, fileLines, failedLines, maxTimeout, perfDir);
|
|
461
|
+
await recursiveWalk(resolved, excludeRegex, outputPath, errorLines, fileLines, failedLines, zeroGeometryLines, maxTimeout, perfDir);
|
|
428
462
|
}
|
|
429
463
|
else if (isSupportedModelFile(resolved)) {
|
|
430
464
|
const perfChildPath = perfDir ?
|
|
431
465
|
path.join(perfDir, `${path.parse(resolved).name}.perf.csv`) :
|
|
432
466
|
undefined;
|
|
433
|
-
const fileResults = await
|
|
467
|
+
const fileResults = await runForFile(resolved, path.join(outputPath, path.parse(resolved).name), maxTimeout, perfChildPath);
|
|
434
468
|
if (fileResults.type === 'Run') {
|
|
435
469
|
if (fileResults.errorLines !== void 0) {
|
|
436
470
|
errorLines.push(...fileResults.errorLines);
|
|
437
471
|
}
|
|
438
472
|
fileLines.push(`${csvSafeString(path.basename(resolved))},${csvSafeString(fileResults.hash ?? '')},${fileResults.errorLines?.length ?? 0}\n`);
|
|
473
|
+
if (fileResults.producedNoGeometry) {
|
|
474
|
+
zeroGeometryLines.push(`${csvSafeString(path.basename(resolved))}\n`);
|
|
475
|
+
}
|
|
439
476
|
}
|
|
440
477
|
else {
|
|
441
478
|
failedLines.push(`${csvSafeString(path.basename(resolved))},${csvSafeString(fileResults.code?.toString() ?? '')},${csvSafeString(fileResults.signal ?? '')}\n`);
|
|
@@ -557,25 +594,60 @@ const args = yargs(process.argv.slice(SKIP_PARAMS))
|
|
|
557
594
|
const mainPath = path.join(outputPath, 'main.csv');
|
|
558
595
|
const errorPath = path.join(outputPath, 'errors.csv');
|
|
559
596
|
const failedPath = path.join(outputPath, 'failed.csv');
|
|
597
|
+
const zeroGeometryPath = path.join(outputPath, 'zero_geometry.csv');
|
|
560
598
|
const errorLines = [];
|
|
561
599
|
const fileLines = [];
|
|
562
600
|
const failedLines = [];
|
|
601
|
+
const zeroGeometryLines = [];
|
|
563
602
|
const excludeRegex = excludeFilter.length > 0 ? new RegExp(excludeFilter) : undefined;
|
|
564
603
|
if (doParallel) {
|
|
565
604
|
console.log('Processing in parallel mode...');
|
|
566
605
|
// 1) Collect all IFC files first
|
|
567
606
|
const allIFCFiles = await collectIFCFiles(ifcFolder, excludeRegex);
|
|
568
607
|
// 2) Process them in parallel
|
|
569
|
-
await processIFCFilesInParallel(allIFCFiles, outputPath, errorLines, fileLines, failedLines, memUtilization, maxTimeout, concurrency, perfTmpDir);
|
|
608
|
+
await processIFCFilesInParallel(allIFCFiles, outputPath, errorLines, fileLines, failedLines, zeroGeometryLines, memUtilization, maxTimeout, concurrency, perfTmpDir);
|
|
570
609
|
}
|
|
571
610
|
else {
|
|
572
611
|
console.log('Processing in serial mode...');
|
|
573
|
-
await recursiveWalk(ifcFolder, excludeRegex, outputPath, errorLines, fileLines, failedLines, maxTimeout, perfTmpDir);
|
|
612
|
+
await recursiveWalk(ifcFolder, excludeRegex, outputPath, errorLines, fileLines, failedLines, zeroGeometryLines, maxTimeout, perfTmpDir);
|
|
574
613
|
}
|
|
575
614
|
// Write out results
|
|
576
615
|
await fsPromises.writeFile(mainPath, `file,hash,errors\n${fileLines.join('')}`);
|
|
577
616
|
await fsPromises.writeFile(errorPath, `${errorCSVHeader}\n${errorLines.join('')}`);
|
|
578
617
|
await fsPromises.writeFile(failedPath, `file,code,signal\n${failedLines.join('')}`);
|
|
618
|
+
// Models that loaded and produced no geometry. Written as its own
|
|
619
|
+
// artifact rather than folded into failed.csv, because the two need
|
|
620
|
+
// different gates: failed.csv is empty and stays empty, while a
|
|
621
|
+
// handful of zero-geometry models are known and tracked (#477), so
|
|
622
|
+
// CI compares this against an explicit allowlist instead.
|
|
623
|
+
// Models whose digest stem collides cannot be judged: two models
|
|
624
|
+
// sharing a basename write the SAME digest file, so in --parallel one
|
|
625
|
+
// can be read between another's truncate and its first row. That
|
|
626
|
+
// collision is a pre-existing harness bug (it also means the blessed
|
|
627
|
+
// digest is whichever model ran last) - `ifc/index.ifc` and
|
|
628
|
+
// `ifc/bldrs/index.ifc` are a live example, both in the smoke subset.
|
|
629
|
+
// Suppress rather than guess, so it cannot red an unrelated PR, and
|
|
630
|
+
// say so loudly.
|
|
631
|
+
const stemCounts = new Map();
|
|
632
|
+
for (const line of fileLines) {
|
|
633
|
+
const stem = path.parse(csvFirstField(line)).name;
|
|
634
|
+
stemCounts.set(stem, (stemCounts.get(stem) ?? 0) + 1);
|
|
635
|
+
}
|
|
636
|
+
const collidingStems = new Set([...stemCounts].filter(([, count]) => count > 1).map(([stem]) => stem));
|
|
637
|
+
if (collidingStems.size > 0) {
|
|
638
|
+
console.warn(`WARNING: ${collidingStems.size} digest stem(s) are written by more ` +
|
|
639
|
+
`than one model, so their digests overwrite each other and their ` +
|
|
640
|
+
`zero-geometry status cannot be determined: ${[...collidingStems].join(', ')}`);
|
|
641
|
+
}
|
|
642
|
+
const reportableZeroGeometry = zeroGeometryLines.filter((line) => !collidingStems.has(path.parse(csvFirstField(line)).name));
|
|
643
|
+
await fsPromises.writeFile(zeroGeometryPath, `file\n${reportableZeroGeometry.join('')}`);
|
|
644
|
+
if (reportableZeroGeometry.length > 0) {
|
|
645
|
+
console.log(`\n${reportableZeroGeometry.length} model(s) loaded but produced NO geometry:`);
|
|
646
|
+
for (const line of reportableZeroGeometry) {
|
|
647
|
+
console.log(` ${line.trim()}`);
|
|
648
|
+
}
|
|
649
|
+
console.log('');
|
|
650
|
+
}
|
|
579
651
|
// Aggregate per-child perf rows (if requested) before runDiff so the
|
|
580
652
|
// run completes deterministically even when the git diff step is
|
|
581
653
|
// skipped or fails.
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
// only the first segment (major) is meaningful and is the one CI carries forward.
|
|
6
6
|
// Must stay in `vN.N.N` shape: the CI stamp regex, scripts/updateVersion.mjs, and
|
|
7
7
|
// statistics.ts all match `v\d+\.\d+\.\d+`.
|
|
8
|
-
const versionString = 'Conway v1.
|
|
8
|
+
const versionString = 'Conway v1.484.1410';
|
|
9
9
|
export { versionString };
|